-
Notifications
You must be signed in to change notification settings - Fork 59
docs: broker HTTP / WS API reference #865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,30 @@ | ||
| # Documentation Sync Rule | ||
|
|
||
| The docs exist in two locations that **must stay in sync**: | ||
|
|
||
| - `web/content/docs/*.mdx` — MDX source (used by the Next.js web app) | ||
| - `docs/*.md` — Plain markdown mirror (for LLMs, CLI users, GitHub readers) | ||
|
|
||
| ## Rules | ||
|
|
||
| 1. **Any change to an `.mdx` file must be mirrored to the corresponding `.md` file**, and vice versa. | ||
| 2. The markdown files should have the same content but with MDX components converted to plain markdown: | ||
| - `<CodeGroup>` / `</CodeGroup>` → remove (just keep the code blocks) | ||
| - `<Note>` → `> **Note:**` | ||
| - `<Warning>` → `> **Warning:**` | ||
| - `<Tabs>` / `<Tab>` → use headers or separate code blocks | ||
| - Frontmatter (`---` YAML block) → remove from `.md` files | ||
| 3. **File mapping** (flat structure, no subdirectories): | ||
| - `web/content/docs/{slug}.mdx` ↔ `docs/{slug}.md` | ||
| - e.g. `web/content/docs/reference-sdk.mdx` ↔ `docs/reference-sdk.md` | ||
| 4. If you add a new `.mdx` doc, create the corresponding `.md` mirror. | ||
| 5. If you update default values, API signatures, or examples — update **both** files. | ||
| # Documentation Rule | ||
|
|
||
| ## Public docs live in `web/content/docs/*.mdx` | ||
|
|
||
| This is the single source of truth — the Next.js web app under | ||
| `web/` reads this directory directly to build the published docs | ||
| site. New pages go here, period. | ||
|
|
||
| When you add a new page: | ||
|
|
||
| - Create `web/content/docs/{slug}.mdx` with the standard frontmatter | ||
| (`title:` and `description:`). | ||
| - Add an entry to the navigation in `web/lib/docs-nav.ts` under the | ||
| appropriate group, OR to the `ALL_SLUGS` "hidden but routable" | ||
| list if it shouldn't appear in the sidebar. | ||
|
|
||
| That's it. Don't create or update files in the top-level `docs/` | ||
| directory. | ||
|
|
||
| ## The top-level `docs/` directory is legacy | ||
|
|
||
| It contains a partial mirror of a handful of MDX pages converted to | ||
| plain markdown. It was originally maintained as an "LLMs / CLI users | ||
| / GitHub readers" alternate, but it drifted out of sync long ago | ||
| (5 pages survive vs 40+ in `web/content/docs/`) and is no longer | ||
| authoritative. Do not add new files to it, and do not "mirror" your | ||
| MDX changes into it. | ||
|
|
||
| Existing pages there will be cleaned up separately. Treat the | ||
| directory as read-only legacy until that happens. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,247 @@ | ||||||
| --- | ||||||
| title: 'Broker HTTP / WS API' | ||||||
| description: 'Reference for the listen API that the broker exposes for dashboards, the CLI, and custom integrations.' | ||||||
| --- | ||||||
|
|
||||||
| The broker daemon (`agent-relay-broker`) exposes an HTTP + WebSocket | ||||||
| listen API once it's running. This is the surface that the dashboard, | ||||||
| the `agent-relay` CLI, the SDKs, and any custom integration use to | ||||||
| spawn agents, inject input into PTYs, observe live events, and | ||||||
| shut the broker down. | ||||||
|
|
||||||
| ## Base URL and port | ||||||
|
|
||||||
| The listen API binds to the port you pass to `agent-relay up` (default | ||||||
| **3888**), on `127.0.0.1` by default. Bind to a non-loopback address | ||||||
| with `--api-bind` if you need remote access: | ||||||
|
|
||||||
| ```bash | ||||||
| agent-relay up --port 3888 # local only | ||||||
| agent-relay up --port 3888 --api-bind 0.0.0.0 # accept remote | ||||||
| ``` | ||||||
|
|
||||||
| All routes below live under `http://<host>:<port>`. | ||||||
|
|
||||||
| ## Authentication | ||||||
|
|
||||||
| The broker requires an API key on every protected route. Pass it as | ||||||
| either header: | ||||||
|
|
||||||
| ``` | ||||||
| X-API-Key: <token> | ||||||
| Authorization: Bearer <token> | ||||||
| ``` | ||||||
|
|
||||||
| The expected token is read from the `RELAY_BROKER_API_KEY` environment | ||||||
| variable. If that variable is unset, the broker runs **unauthenticated** | ||||||
| — protected routes accept any request. In production, always set it. | ||||||
|
|
||||||
| The only route exempt from auth is `GET /health`. | ||||||
|
|
||||||
| ## Routes | ||||||
|
|
||||||
| ### Health and configuration | ||||||
|
|
||||||
| | Method | Path | Purpose | | ||||||
| | ------ | ---- | ------- | | ||||||
| | `GET` | `/health` | Liveness probe + workspace/startup status. Unauthenticated. | | ||||||
| | `GET` | `/api/session` | Broker version, protocol version, persist/ephemeral mode, uptime. | | ||||||
| | `POST` | `/api/session/renew` | Renew the broker lease (persist mode only). | | ||||||
| | `GET` | `/api/config` | Relaycast workspace key and workspace memberships. | | ||||||
| | `GET` | `/api/metrics` | Broker metrics. Optional `?agent=<name>` filter. | | ||||||
| | `GET` | `/api/status` | Aggregate broker status. | | ||||||
| | `GET` | `/api/crash-insights` | Recent crash diagnostics. | | ||||||
| | `GET` | `/api/history/stats` | Stub message-history counters. | | ||||||
| | `POST` | `/api/preflight` | Check that each `{ name, cli }` agent can be spawned. | | ||||||
| | `POST` | `/api/shutdown` | Graceful broker shutdown. | | ||||||
|
|
||||||
| ### Agent lifecycle | ||||||
|
|
||||||
| | Method | Path | Purpose | | ||||||
| | ------ | ---- | ------- | | ||||||
| | `POST` | `/api/spawn` | Spawn an agent as a child of the broker. | | ||||||
| | `GET` | `/api/spawned` | List running agents. | | ||||||
| | `DELETE` | `/api/spawned/{name}` | Release / kill an agent. Optional body `{ "reason": "..." }`. | | ||||||
| | `POST` | `/api/spawned/{name}/model` | Change an agent's model. Body `{ "model": "...", "timeoutMs"?: number }`. | | ||||||
| | `POST` | `/api/spawned/{name}/subscribe` | Subscribe an agent to channels. Body `{ "channels": ["..."] }`. | | ||||||
| | `POST` | `/api/spawned/{name}/unsubscribe` | Unsubscribe an agent from channels. Body `{ "channels": ["..."] }`. | | ||||||
| | `POST` | `/api/agents/by-name/{name}/interrupt` | Interrupt an agent (not yet implemented — returns 501). | | ||||||
|
|
||||||
| #### `POST /api/spawn` | ||||||
|
|
||||||
| Body (fields accept both camelCase and snake_case): | ||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
| "name": "Alice", | ||||||
| "cli": "claude", | ||||||
| "model": "sonnet", | ||||||
| "args": ["--no-color"], | ||||||
| "task": "Read the README and summarize it", | ||||||
| "channels": ["#general"], | ||||||
| "cwd": "/Users/me/project", | ||||||
| "team": "demo", | ||||||
| "shadowOf": null, | ||||||
| "shadowMode": null, | ||||||
| "continueFrom": null, | ||||||
| "idleThresholdSecs": 0, | ||||||
| "skipRelayPrompt": false, | ||||||
| "restartPolicy": null, | ||||||
| "agentToken": null | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| `name` and `cli` are required. Returns `{ "success": true, ... }` on | ||||||
| success or `{ "success": false, "error": "..." }` with a non-2xx | ||||||
| status on failure. | ||||||
|
|
||||||
| ### PTY interaction | ||||||
|
|
||||||
| | Method | Path | Purpose | | ||||||
| | ------ | ---- | ------- | | ||||||
| | `POST` | `/api/input/{name}` | Send raw bytes to an agent's PTY stdin. Body `{ "data": "..." }`. | | ||||||
| | `POST` | `/api/resize/{name}` | Resize an agent's PTY. Body `{ "rows": <u16>, "cols": <u16> }`. | | ||||||
| | `POST` | `/api/send` | Inject a relay message into an agent. | | ||||||
|
|
||||||
| #### `POST /api/input/{name}` | ||||||
|
|
||||||
| This is the keystroke channel. The `data` string is written to the | ||||||
| target agent's stdin verbatim — escape characters are passed through. | ||||||
|
|
||||||
| ```bash | ||||||
| curl -X POST localhost:3888/api/input/Alice \ | ||||||
| -H "X-API-Key: $RELAY_BROKER_API_KEY" \ | ||||||
| -d '{"data":"hello\n"}' | ||||||
| ``` | ||||||
|
|
||||||
| Returns 404 if the agent isn't found. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents
Suggested change
|
||||||
|
|
||||||
| #### `POST /api/send` | ||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
| "to": "Alice", | ||||||
| "message": "Please review PR #837", | ||||||
| "from": "Bob", | ||||||
| "thread": "thr_abc", | ||||||
| "workspaceId": "ws_demo", | ||||||
| "mode": "wait" | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| The message text field accepts any of `message`, `text`, `body`, or | ||||||
| `content`. The `mode` field accepts `wait` (default — queue and | ||||||
| inject when the agent is idle) or `steer` (inject immediately, | ||||||
| even mid-response). Returns 504 on a 30s broker timeout; 404 if | ||||||
| the target agent isn't registered. | ||||||
|
|
||||||
| ### Event stream | ||||||
|
|
||||||
| | Method | Path | Purpose | | ||||||
| | ------ | ---- | ------- | | ||||||
| | `GET` | `/ws` | WebSocket: subscribe to broker events. | | ||||||
| | `GET` | `/api/events/replay` | HTTP-based replay of events since a sequence number. | | ||||||
|
|
||||||
| #### `GET /ws` | ||||||
|
|
||||||
| Upgrade to WebSocket and you'll receive every broker event as a JSON | ||||||
| text frame. The broker pings every 30s; respond with pong to stay | ||||||
| connected. | ||||||
|
|
||||||
| To resume after a disconnect without missing durable events, include | ||||||
| the last sequence number you saw: | ||||||
|
|
||||||
| ``` | ||||||
| ws://localhost:3888/ws?sinceSeq=12345 | ||||||
| ``` | ||||||
|
|
||||||
| If the requested sequence is older than the replay buffer's window, | ||||||
| the first frame you receive will be: | ||||||
|
|
||||||
| ```json | ||||||
| { "kind": "replay_gap", "requestedSinceSeq": 12345, "oldestAvailable": 14000, "seq": 14999 } | ||||||
| ``` | ||||||
|
|
||||||
| > **Note:** Two event kinds are **ephemeral** and never stored in the | ||||||
| > replay buffer: `worker_stream` (PTY output chunks — high frequency) | ||||||
| > and `delivery_active` (in-flight delivery progress). If you | ||||||
| > disconnect, you cannot replay them. | ||||||
|
|
||||||
| #### Event kinds emitted on `/ws` | ||||||
|
|
||||||
| Durable (replayable via `?sinceSeq=...`): | ||||||
|
|
||||||
| | `kind` | When it fires | | ||||||
| | ------ | ------------- | | ||||||
| | `agent_spawned` | An agent was successfully spawned. | | ||||||
| | `agent_exit` / `agent_exited` | Worker process exited. | | ||||||
| | `agent_idle` | Worker has been quiet past `idleThresholdSecs`. | | ||||||
| | `agent_restarting` / `agent_restarted` | Worker is being restarted under the restart policy. | | ||||||
| | `agent_released` | Worker was released by `/api/spawned/{name}` DELETE. | | ||||||
| | `agent_permanently_dead` | Worker exhausted restart attempts. | | ||||||
| | `worker_ready` | Worker finished startup and is ready for input. | | ||||||
| | `worker_error` | Worker emitted an error frame. | | ||||||
| | `relay_inbound` | Inbound relay message routed to an agent. | | ||||||
| | `delivery_ack` | Message delivery acknowledged. | | ||||||
| | `delivery_verified` | Echo verification confirmed the message was delivered. | | ||||||
| | `delivery_failed` | Message delivery failed. | | ||||||
| | `delivery_dropped` | Delivery was dropped (e.g. agent gone). | | ||||||
| | `delivery_retry` | Delivery is being retried. | | ||||||
|
|
||||||
| Ephemeral (broadcast only, no replay): | ||||||
|
|
||||||
| | `kind` | When it fires | | ||||||
| | ------ | ------------- | | ||||||
| | `worker_stream` | A chunk of stdout from a wrapped CLI. Payload contains `stream` (`"stdout"`) and `chunk` (the raw bytes — typically still ANSI-escaped). | | ||||||
| | `delivery_active` | High-frequency progress events for in-flight deliveries. | | ||||||
|
|
||||||
| ## Worked example: control a spawned agent end-to-end | ||||||
|
|
||||||
| ```bash | ||||||
| KEY="$RELAY_BROKER_API_KEY" | ||||||
|
|
||||||
| # 1. Start the broker | ||||||
| agent-relay up --port 3888 & | ||||||
|
|
||||||
| # 2. Spawn Alice running claude | ||||||
| curl -sX POST localhost:3888/api/spawn \ | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: POST examples with JSON bodies are missing Prompt for AI agents |
||||||
| -H "X-API-Key: $KEY" \ | ||||||
| -d '{"name":"Alice","cli":"claude"}' | ||||||
|
|
||||||
| # 3. Stream her PTY output (filter for her worker_stream frames) | ||||||
| websocat ws://localhost:3888/ws \ | ||||||
| -H "X-API-Key: $KEY" \ | ||||||
| | jq -r 'select(.kind=="worker_stream" and .name=="Alice") | .chunk' | ||||||
|
|
||||||
| # 4. Send a keystroke to Alice's CLI | ||||||
| curl -sX POST localhost:3888/api/input/Alice \ | ||||||
| -H "X-API-Key: $KEY" \ | ||||||
| -d '{"data":"hello\n"}' | ||||||
|
|
||||||
| # 5. Inject a relay message | ||||||
| curl -sX POST localhost:3888/api/send \ | ||||||
| -H "X-API-Key: $KEY" \ | ||||||
| -d '{"to":"Alice","from":"Bob","message":"please review #837"}' | ||||||
|
|
||||||
| # 6. Release her | ||||||
| curl -sX DELETE localhost:3888/api/spawned/Alice \ | ||||||
| -H "X-API-Key: $KEY" | ||||||
| ``` | ||||||
|
|
||||||
| ## Error envelope | ||||||
|
|
||||||
| Failed responses return a consistent envelope: | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The docs claim a single consistent error envelope, but the API currently returns multiple error formats across endpoints. Prompt for AI agents |
||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
|
Comment on lines
+233
to
+236
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This section says all failures use the nested Useful? React with 👍 / 👎. |
||||||
| "error": { | ||||||
| "code": "agent_not_found", | ||||||
| "message": "no agent named Alice", | ||||||
| "statusCode": 404 | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Common codes: `agent_not_found` (404), `invalid_request` (400), | ||||||
| `unsupported_operation` (400), `unauthorized` (401), `request_failed` | ||||||
| (400), `internal_error` (500). | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mirror file has the same missing fence-language issue.
These code fences also need explicit languages to keep the MDX page lint-clean and in sync with the markdown source.
Suggested patch
@@
-
+textws://localhost:3888/ws?sinceSeq=12345
Also applies to: 154-156
🤖 Prompt for AI Agents