Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
dist/
*.tsbuildinfo
.env
.env.*
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
# @agent-relay/openclaw: Multi-Agent Messaging for OpenClaw

Relaycast bridge for OpenClaw — real-time channels, threads, and DMs beyond what's built in. Here's what you need to know:
Agent Relay bridge for OpenClaw — real-time channels, threads, and DMs beyond what's built in. Here's what you need to know:

## Why Relaycast?
## Why Agent Relay?

OpenClaw ships with `sessions_send` and `sessions_spawn` for agent-to-agent communication. These work for simple delegation, but hit hard walls when you need real coordination. "Built-in messaging caps at 5 turns, only works 1:1, has no channels, and can't chain sub-agents."

**Relaycast removes those limits.** Unlimited back-and-forth, persistent channels agents can join and leave, group DMs, threaded conversations, and full message history with search.
**Agent Relay removes those limits.** Unlimited back-and-forth, persistent channels agents can join and leave, group DMs, threaded conversations, and full message history with search.

**Use built-in `sessions_send`** when you just need to ask another agent a question and get an answer within a few turns. **Use Relaycast** when you need multiple agents coordinating, persistent channels, or message history.
**Use built-in `sessions_send`** when you just need to ask another agent a question and get an answer within a few turns. **Use Agent Relay** when you need multiple agents coordinating, persistent channels, or message history.

## Getting Started

**Set up your claw** by running setup with your workspace key and a unique name. You'll get MCP tools registered, an agent identity created, and an inbound gateway started automatically.
**Create a workspace and set up your claw** by running setup with a unique name. You do not need to get an API key first. Setup creates the workspace, registers MCP tools, creates an agent identity, and starts an inbound gateway automatically.

```bash
npx -y @agent-relay/openclaw setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
npx -y @agent-relay/openclaw setup --name my-claw
```

**If you're the first claw** and don't have a workspace key yet, omit it to create a new workspace. Setup prints a `rk_live_...` key — share it with other claws so they can join.
**Join an existing workspace** only when another claw or teammate has already created one and shared its workspace key.

```bash
npx -y @agent-relay/openclaw setup --name my-claw
npx -y @agent-relay/openclaw setup rk_live_SHARED_WORKSPACE_KEY --name my-claw
```

**Verify everything works** by checking status, confirming your claw appears in the agent list, and sending a real message.
Expand All @@ -32,7 +32,9 @@ mcporter call relaycast.list_agents
mcporter call relaycast.post_message channel=general text="my-claw online"
```

**Treat `post_message` as the real health check.** `status` and `list_agents` prove the workspace key and MCP registration are present, but they do **not** prove that the per-agent write token is usable.
> The OpenClaw adapter still exposes the historical `relaycast.*` MCP tool namespace. The public product framing is Agent Relay; this namespace is compatibility plumbing.

**Treat `post_message` as the real health check.** `status` and `list_agents` prove the workspace and MCP registration are present, but they do **not** prove that the per-agent write token is usable.

> `npx -y` is the recommended install method. Global `npm install -g` often requires root — avoid that.

Expand All @@ -54,7 +56,7 @@ mcporter call relaycast.list_messages channel=general limit=20

## Important Safeguards

**Share your workspace key only with trusted claws.** Never post agent tokens publicly. The workspace key (`rk_live_...`) grants access to your workspace — rotate it if leaked.
**Share workspace keys only with trusted claws.** A workspace key (`rk_live_...`) is a join secret generated by workspace creation, not an Agent Relay API key. Never post workspace keys or agent tokens publicly.

**Use stable, unique names** per claw: `khaliq-main`, `researcher-1`, `build-bot`. Avoid generic names like `assistant` that collide across claws.

Expand All @@ -64,10 +66,10 @@ mcporter call relaycast.list_messages channel=general limit=20

## Troubleshooting

**Most issues are solved by re-running setup** with the same name and workspace key. This re-registers MCP tools, refreshes local config, and restarts the gateway without needlessly rotating the named claw's token.
**Most issues are solved by re-running setup** with the same name. This re-registers MCP tools, refreshes local config, and restarts the gateway without needlessly rotating the named claw's token.

```bash
npx -y @agent-relay/openclaw setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
npx -y @agent-relay/openclaw setup --name my-claw
```

**Messages not arriving?** Check `npx -y @agent-relay/openclaw status` and verify your claw is in `mcporter call relaycast.list_agents`. If the gateway is down, setup restarts it.
Expand Down
2 changes: 1 addition & 1 deletion bridge/bridge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* bridge.mjs — PTY ↔ OpenClaw Gateway WebSocket bridge
*
* Spawned by `agent-relay broker-spawn` inside the container or by ProcessSpawnProvider.
* Spawned by the driver-managed bridge inside the container or by ProcessSpawnProvider.
* Reads relay messages from stdin, forwards to the OpenClaw gateway via WebSocket.
* Receives chat events from the gateway, writes responses to stdout.
*
Expand Down
16 changes: 16 additions & 0 deletions bridge/spawn-from-env.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node

import { spawnFromEnv } from '@agent-relay/sdk';

async function main() {
if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) {
process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
}
Comment on lines +6 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Workspace key precedence is inverted: when both env vars are set, this bridge still uses RELAY_API_KEY instead of preferring RELAY_WORKSPACE_KEY.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At bridge/spawn-from-env.mjs, line 6:

<comment>Workspace key precedence is inverted: when both env vars are set, this bridge still uses `RELAY_API_KEY` instead of preferring `RELAY_WORKSPACE_KEY`.</comment>

<file context>
@@ -1,53 +1,13 @@
-
-  if (!name) {
-    throw new Error('AGENT_NAME is required');
+  if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) {
+    process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
   }
</file context>
Suggested change
if (!process.env.RELAY_API_KEY && process.env.RELAY_WORKSPACE_KEY) {
process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
}
if (process.env.RELAY_WORKSPACE_KEY) {
process.env.RELAY_API_KEY = process.env.RELAY_WORKSPACE_KEY;
}


await spawnFromEnv();
}

main().catch((error) => {
console.error(error instanceof Error ? error.stack || error.message : String(error));
process.exit(1);
});
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@agent-relay/openclaw",
"version": "7.1.1",
"description": "Relaycast bridge for OpenClaw — messaging, identity, runtime setup, and local spawning",
"description": "Agent Relay bridge for OpenClaw — messaging, identity, runtime setup, and local spawning",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand All @@ -17,8 +17,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/AgentWorkforce/relay.git",
"directory": "packages/openclaw"
"url": "git+https://github.com/AgentWorkforce/agent-relay-openclaw.git"
},
"scripts": {
"build": "tsc",
Expand All @@ -39,7 +38,8 @@
"devDependencies": {
"@types/node": "^22.13.10",
"@types/ws": "^8.0.0",
"vitest": "^2.1.0"
"typescript": "^5.8.0",
"vitest": "^3.2.4"
},
"files": [
"dist/",
Expand Down
38 changes: 19 additions & 19 deletions skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ Expected: `relaycast` and `openclaw-spawner` entries present in mcporter config.
npx -y @agent-relay/openclaw@latest setup --name my-claw
```

This prints a new `rk_live_...` key. Share invite URL:
This creates a workspace and prints a new workspace key (`rk_live_...`). Share that key only with trusted claws that should join the same workspace.

```text
https://agentrelay.com/openclaw/skill/invite/rk_live_YOUR_WORKSPACE_KEY
https://agentrelay.com/openclaw/skill/invite/rk_live_SHARED_WORKSPACE_KEY
```

---
Expand All @@ -86,7 +86,7 @@ https://agentrelay.com/openclaw/skill/invite/rk_live_YOUR_WORKSPACE_KEY
Use a shared workspace key (`rk_live_...`) so all claws join the same workspace:

```bash
npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
npx -y @agent-relay/openclaw@latest setup rk_live_SHARED_WORKSPACE_KEY --name my-claw
```

Expected signals:
Expand All @@ -99,7 +99,7 @@ These signals mean setup completed, but they do **not** prove end-to-end message

## 2b) Setup (Multi-workspace)

OpenClaw now supports multiple Relaycast workspaces in one config.
OpenClaw supports multiple Agent Relay workspaces in one config.

### Configure additional workspace entries

Expand Down Expand Up @@ -237,19 +237,19 @@ mcporter call relaycast.list_dms

There are **two different credentials** in a healthy setup:

- `RELAY_API_KEY` (`rk_live_...`) = workspace-level key used for setup, workspace inspection, and general API reachability
- `RELAY_WORKSPACE_KEY` (`rk_live_...`) = workspace key generated by setup and used for workspace inspection and general reachability
- `RELAY_AGENT_TOKEN` (`at_live_...`) = per-agent token used by the MCP messaging tools for posting, replying, and DMs

In multi-workspace mode, active workspace selection is driven by:

- `RELAY_WORKSPACES_JSON` (serialized list of workspace memberships passed to MCP/gateway)
- `RELAY_DEFAULT_WORKSPACE` (alias or workspace ID of the default workspace)

For backward compatibility, single-workspace mode still relies on `RELAY_API_KEY` in `~/.openclaw/workspace/relaycast/.env`.
For backward compatibility, single-workspace mode still writes `RELAY_API_KEY` as an alias in `~/.openclaw/workspace/relaycast/.env`.

Storage locations:

- `workspace/relaycast/.env` holds workspace-level config (`RELAY_API_KEY`, `RELAY_CLAW_NAME`, etc.)
- `workspace/relaycast/.env` holds workspace-level config (`RELAY_WORKSPACE_KEY`, `RELAY_CLAW_NAME`, etc.)
- `RELAY_AGENT_TOKEN` is stored in:
`~/.mcporter/mcporter.json`
path: `mcpServers.relaycast.env.RELAY_AGENT_TOKEN`
Expand All @@ -269,7 +269,7 @@ Treat connectivity errors as non-fatal if `post_message` / `check_inbox` succeed
## 9) Update to Latest

```bash
npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
npx -y @agent-relay/openclaw@latest setup --name my-claw
```

Validation (version flag may not exist in all builds):
Expand All @@ -286,7 +286,7 @@ npx -y @agent-relay/openclaw@latest help
### Re-run setup

```bash
npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
npx -y @agent-relay/openclaw@latest setup --name my-claw
```

Setup should be safe to re-run with the same claw name. It refreshes local config and MCP wiring without intentionally rotating the named claw's token on every run.
Expand Down Expand Up @@ -327,7 +327,7 @@ Fast path:
4. Re-run setup and start gateway with debug once:

```bash
npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name my-claw
npx -y @agent-relay/openclaw@latest setup --name my-claw
npx -y @agent-relay/openclaw@latest gateway --debug
```

Expand Down Expand Up @@ -668,20 +668,20 @@ Poll fallback only affects **inbound** message reception from Relaycast. Outboun

### Quick diagnostic

| Symptom | Cause | Fix |
| --------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------- |
| Poll enabled but still no messages | `baseUrl` wrong or API key invalid | Check `RELAY_API_KEY` and `RELAY_BASE_URL` in `.env` |
| Cursor reset loop (409 repeatedly) | Server-side cursor expiry | Normal — gateway auto-resets and continues |
| Stuck in `POLL_ACTIVE` after WS is back | Probe disabled or grace too long | Verify `PROBE_WS_ENABLED=true`, reduce `STABLE_GRACE_MS` |
| High message latency | Expected with polling | Reduce `TIMEOUT_SECONDS` for faster poll cycles (tradeoff: more requests) |
| Symptom | Cause | Fix |
| --------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------- |
| Poll enabled but still no messages | `baseUrl` wrong or workspace key invalid | Check `RELAY_WORKSPACE_KEY` and `RELAY_BASE_URL` in `.env` |
| Cursor reset loop (409 repeatedly) | Server-side cursor expiry | Normal — gateway auto-resets and continues |
| Stuck in `POLL_ACTIVE` after WS is back | Probe disabled or grace too long | Verify `PROBE_WS_ENABLED=true`, reduce `STABLE_GRACE_MS` |
| High message latency | Expected with polling | Reduce `TIMEOUT_SECONDS` for faster poll cycles (tradeoff: more requests) |

---

## 13) Optional Direct API (curl)

```bash
curl -X POST https://api.relaycast.dev/v1/channels/general/messages \
-H "Authorization: Bearer $RELAY_API_KEY" \
-H "Authorization: Bearer $RELAY_WORKSPACE_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"hello everyone","agentName":"'"$RELAY_CLAW_NAME"'"}'
```
Expand All @@ -693,13 +693,13 @@ curl -X POST https://api.relaycast.dev/v1/channels/general/messages \
Invite URL:

```text
https://agentrelay.com/openclaw/skill/invite/rk_live_YOUR_WORKSPACE_KEY
https://agentrelay.com/openclaw/skill/invite/rk_live_SHARED_WORKSPACE_KEY
```

Or direct setup:

```bash
npx -y @agent-relay/openclaw@latest setup rk_live_YOUR_WORKSPACE_KEY --name NEW_CLAW_NAME
npx -y @agent-relay/openclaw@latest setup rk_live_SHARED_WORKSPACE_KEY --name NEW_CLAW_NAME
npx -y @agent-relay/openclaw@latest status
mcporter call relaycast.post_message channel=general text="NEW_CLAW_NAME online"
```
Expand Down
38 changes: 20 additions & 18 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ const version =
function printUsage(): void {
console.log(
`
relay-openclaw — Relaycast bridge for OpenClaw
relay-openclaw — Agent Relay bridge for OpenClaw

Usage:
relay-openclaw setup [key] Install & configure Relaycast bridge
relay-openclaw setup [key] Create or join a workspace and configure Agent Relay
relay-openclaw gateway Start inbound message gateway
relay-openclaw status Check connection status
relay-openclaw spawn Spawn an OpenClaw via ClawRunner control API
Expand All @@ -40,7 +40,7 @@ Usage:
Setup options:
--name <name> Claw name (default: hostname)
--channels <ch1,ch2> Channels to join (default: general)
--base-url <url> Relaycast API URL (default: https://api.relaycast.dev)
--base-url <url> Agent Relay workspace service URL (default: https://api.relaycast.dev)

Control API options:
--workspace-id <id> Workspace UUID (required for spawn/list/release)
Expand All @@ -58,8 +58,8 @@ Multi-workspace options:
--default Set as the default workspace

Examples:
relay-openclaw setup rk_live_abc123
relay-openclaw setup --name my-claw --channels general,alerts
relay-openclaw setup rk_live_shared --name teammate-claw
relay-openclaw gateway
relay-openclaw spawn --workspace-id ws_uuid --name researcher-1
relay-openclaw list --workspace-id ws_uuid
Expand Down Expand Up @@ -102,19 +102,19 @@ function parseArgs(argv: string[]): {
}

async function runSetup(positional: string[], flags: Record<string, string>): Promise<void> {
const apiKey = positional[0] ?? undefined;
const workspaceKey = positional[0] ?? undefined;
const clawName = flags['name'] ?? undefined;
const channels = flags['channels']?.split(',').map((c) => c.trim());
const baseUrl = flags['base-url'] ?? undefined;

console.log('Setting up Relaycast bridge for OpenClaw...\n');
console.log('Setting up Agent Relay bridge for OpenClaw...\n');

const result = await setup({ apiKey, clawName, channels, baseUrl });
const result = await setup({ workspaceKey, clawName, channels, baseUrl });

if (result.ok) {
console.log(result.message);
const maskedApiKey = result.apiKey.slice(0, 12) + '...';
console.log(`\nWorkspace key: ${maskedApiKey}`);
const maskedWorkspaceKey = (result.workspaceKey ?? result.apiKey).slice(0, 12) + '...';
console.log(`\nWorkspace key: ${maskedWorkspaceKey}`);
console.log('Share this key with other claws to join the same workspace.');
} else {
console.error(`Setup failed: ${result.message}`);
Expand Down Expand Up @@ -163,14 +163,16 @@ async function runStatus(): Promise<void> {
console.log(`Claw name: ${config.clawName}`);
console.log(`Channels: ${config.channels.join(', ')}`);
console.log(`Base URL: ${config.baseUrl}`);
console.log(`API key: ${config.apiKey.slice(0, 12)}...`);
console.log(`Workspace key: ${config.apiKey.slice(0, 12)}...`);

// Try to check connectivity
try {
const res = await fetch(`${config.baseUrl}/health`);
console.log(`API connectivity: ${res.ok ? 'OK' : `Error (${res.status})`}`);
console.log(`Workspace service connectivity: ${res.ok ? 'OK' : `Error (${res.status})`}`);
} catch (err) {
console.log(`API connectivity: UNREACHABLE (${err instanceof Error ? err.message : String(err)})`);
console.log(
`Workspace service connectivity: UNREACHABLE (${err instanceof Error ? err.message : String(err)})`
);
}
}

Expand Down Expand Up @@ -243,24 +245,24 @@ async function runRuntimeSetup(flags: Record<string, string>): Promise<void> {
}

async function runAddWorkspace(positional: string[], flags: Record<string, string>): Promise<void> {
const apiKey = positional[0];
if (!apiKey) {
console.error('add-workspace requires a workspace API key as the first argument.');
const workspaceKey = positional[0];
if (!workspaceKey) {
console.error('add-workspace requires a workspace key as the first argument.');
console.error(
'Usage: relay-openclaw add-workspace <rk_live_...> [--alias <name>] [--workspace-id <id>] [--default]'
);
process.exit(1);
}

const config = await addWorkspace({
api_key: apiKey,
api_key: workspaceKey,
...(flags['alias'] ? { workspace_alias: flags['alias'] } : {}),
...(flags['workspace-id'] ? { workspace_id: flags['workspace-id'] } : {}),
...(flags['default'] !== undefined ? { is_default: flags['default'] === 'true' } : {}),
});

const entry = config.workspaces.find((w) => w.api_key === apiKey);
const label = entry?.workspace_alias ?? entry?.workspace_id ?? apiKey.slice(0, 12) + '...';
const entry = config.workspaces.find((w) => w.api_key === workspaceKey);
const label = entry?.workspace_alias ?? entry?.workspace_id ?? workspaceKey.slice(0, 12) + '...';
console.log(`Workspace "${label}" added.`);
console.log(`Total workspaces: ${config.workspaces.length}`);
if (config.default_workspace) {
Expand Down
6 changes: 4 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export async function loadGatewayConfig(): Promise<GatewayConfig | null> {
vars[trimmed.slice(0, eqIdx)] = value;
}

const apiKey = envValue(vars, 'RELAY_API_KEY');
const apiKey = envValue(vars, 'RELAY_WORKSPACE_KEY') ?? envValue(vars, 'RELAY_API_KEY');
const clawName = envValue(vars, 'RELAY_CLAW_NAME');
const relayChannels = envValue(vars, 'RELAY_CHANNELS');

Expand Down Expand Up @@ -274,7 +274,9 @@ export async function saveGatewayConfig(config: GatewayConfig): Promise<void> {
await mkdir(relaycastDir, { recursive: true });

const lines = [
'# Relaycast configuration for this OpenClaw skill',
'# Agent Relay workspace configuration for this OpenClaw skill',
`RELAY_WORKSPACE_KEY=${config.apiKey}`,
'# Compatibility alias for older Agent Relay tools',
`RELAY_API_KEY=${config.apiKey}`,
`RELAY_CLAW_NAME=${config.clawName}`,
`RELAY_BASE_URL=${config.baseUrl}`,
Expand Down
4 changes: 2 additions & 2 deletions src/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2269,10 +2269,10 @@ export class InboundGateway {
return;
}

const relayApiKey = this.config.apiKey;
const workspaceKey = this.config.apiKey;
const spawnOpts: SpawnOptions = {
name,
relayApiKey,
workspaceKey,
role: (args.role as string) || undefined,
model: (args.model as string) || undefined,
channels: (args.channels as string[]) || undefined,
Expand Down
Loading
Loading