Skip to content
Merged
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
51 changes: 30 additions & 21 deletions .claude/rules/docs-sync.md
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.
247 changes: 247 additions & 0 deletions web/content/docs/reference-broker-api.mdx
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>
```
Comment on lines +30 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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
-```
+```http
 X-API-Key: <token>
 Authorization: Bearer <token>

@@
- +text
ws://localhost:3888/ws?sinceSeq=12345

Also applies to: 154-156

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/content/docs/reference-broker-api.mdx` around lines 30 - 33, The MDX code
fences lack explicit fence languages; update the blocks containing "X-API-Key:
<token>\nAuthorization: Bearer <token>" to use a language like http (```http)
and the block with "ws://localhost:3888/ws?sinceSeq=12345" to use a neutral text
language (```text) so the doc linter and markdown source stay in sync; locate
these exact blocks in reference-broker-api.mdx and replace the opening triple
backticks with the appropriate fenced language tokens.


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.

@cubic-dev-ai cubic-dev-ai Bot May 16, 2026

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: /api/input/{name} is documented as returning 404 for missing agents, but the handler currently returns 400 (agent_not_found).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/content/docs/reference-broker-api.mdx, line 117:

<comment>`/api/input/{name}` is documented as returning 404 for missing agents, but the handler currently returns 400 (`agent_not_found`).</comment>

<file context>
@@ -0,0 +1,247 @@
+  -d '{"data":"hello\n"}'
+```
+
+Returns 404 if the agent isn't found.
+
+#### `POST /api/send`
</file context>
Suggested change
Returns 404 if the agent isn't found.
Returns 400 with code `agent_not_found` if the agent isn't found.
Fix with Cubic


#### `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 \

@cubic-dev-ai cubic-dev-ai Bot May 16, 2026

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: POST examples with JSON bodies are missing Content-Type: application/json, which can cause Axum to reject the requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/content/docs/reference-broker-api.mdx, line 207:

<comment>POST examples with JSON bodies are missing `Content-Type: application/json`, which can cause Axum to reject the requests.</comment>

<file context>
@@ -0,0 +1,247 @@
+agent-relay up --port 3888 &
+
+# 2. Spawn Alice running claude
+curl -sX POST localhost:3888/api/spawn \
+  -H "X-API-Key: $KEY" \
+  -d '{"name":"Alice","cli":"claude"}'
</file context>
Fix with Cubic

-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:

@cubic-dev-ai cubic-dev-ai Bot May 16, 2026

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: The docs claim a single consistent error envelope, but the API currently returns multiple error formats across endpoints.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/content/docs/reference-broker-api.mdx, line 233:

<comment>The docs claim a single consistent error envelope, but the API currently returns multiple error formats across endpoints.</comment>

<file context>
@@ -0,0 +1,247 @@
+
+## Error envelope
+
+Failed responses return a consistent envelope:
+
+```json
</file context>
Fix with Cubic


```json
{
Comment on lines +233 to +236

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 Badge Remove claim of a single failed-response envelope

This section says all failures use the nested error.code/message/statusCode envelope, but many protected endpoints actually return {"success": false, "error": "..."} instead (for example /api/send and /api/spawn paths in src/listen_api.rs). Consumers implementing error parsing from this doc will mis-handle common non-2xx responses unless the docs describe both formats (or scope this envelope to the endpoints that really use it).

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).
1 change: 1 addition & 0 deletions web/lib/docs-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const docsNav: NavGroup[] = [
{ title: 'Cloud commands', slug: 'cli-cloud-commands' },
{ title: 'On the relay', slug: 'cli-on-the-relay' },
{ title: 'CLI reference', slug: 'reference-cli' },
{ title: 'Broker HTTP / WS API', slug: 'reference-broker-api' },
],
},
{
Expand Down
Loading