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
18 changes: 18 additions & 0 deletions web/content/docs/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,24 @@ taskManager.registerAction({
});
```

## Node actions: spawn and release

The same action model places work onto [nodes](/docs/nodes). A node advertises **capabilities** — named
actions with a `kind` of `spawn` or `action` — and the engine routes an invocation to a node that provides
the capability. Spawning and releasing an agent are both actions:

- **Spawn** invokes the `spawn` (or `spawn:<harness>`) capability with input such as the harness/`cli`, a
`name`, an optional `target_node`, and optional `harnessConfig`. The engine places it on an eligible node,
which runs the harness its capability declares and registers the new agent through the node — so the agent
is born bound to that node and its messages route back there.
- **Release** invokes the `release` action on the node that owns the agent.

At the node-protocol layer these flow as `action.invoke` to the chosen node and `action.result
{ invocation_id, output | error }` back. That result is delivered to the calling agent as the
`action.completed` (or `action.failed`) event described above, so from the SDK both surfaces are the same
fire-and-forget action. See [Architecture](/docs/architecture) for the full spawn flow and
[Nodes](/docs/nodes) for placement, targeting, and capability declaration.

## MCP tool generation

The agent-relay MCP exposes each registered action as an explicit tool, with JSON Schema generated from the
Expand Down
13 changes: 13 additions & 0 deletions web/content/docs/agent-relay-mcp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Create a workspace first if you do not have a key:
agent-relay workspace create support-triage
```

Workspace keys use the `rk_live_*` prefix. The MCP server uses that key to bootstrap workspace access, then registered agents act with their own `at_live_*` tokens. See [Authentication](/docs/authentication).

## Messaging Tools

| Tool | Purpose |
Expand Down Expand Up @@ -65,6 +67,17 @@ An agent using MCP can coordinate without SDK imports:
5. Call a generated action tool (each registered action is exposed by name), or `invoke_action` / `list_actions`.
6. Call `reply_to_thread`, `add_reaction`, or `mark_message_read` as work progresses.

## Agent Registration And Nodes

`register_agent` creates or adopts an agent identity for the MCP caller. A directly connected MCP server is
routed through an implicit `direct_ws` node, so there is no separate node setup step for normal tool-based
agents.

`query_nodes` reads the workspace node roster. Use it to discover delivery hosts and capabilities before
calling `spawn` or before asking an operator to bind an app-server agent to a node. Nodes are delivery
routes, not a workspace feature flag: a roster entry can be a direct connection, a broker-controlled
WebSocket worker, an HTTP push endpoint, or a polling integration.

## Generated Action Tools

Each registered action becomes an explicit MCP tool.
Expand Down
137 changes: 137 additions & 0 deletions web/content/docs/architecture.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
title: 'Architecture: nodes, the broker, and action runners'
description: 'How Agent Relay routes reliable delivery through nodes, what the broker stack on a machine owns, and the vocabulary for brokers, the broker-pty-engine, the harness driver, and action runners.'
---

Agent Relay separates _what_ a message means from _where_ it is delivered. Messaging writes a
durable record; [delivery](/docs/delivery) gets that record into a session. This page describes the
delivery side end to end: the node model, the broker stack that runs on a machine, and the action
runners that spawn and drive agents.

## Everything is a node

A **node** is a delivery endpoint connected to the engine over `/v1/node/ws`. Every agent is owned by
a node, and that ownership is what tells the engine where to route the agent's future deliveries.

Realtime delivery is **node-only** and reliable: each agent has a per-agent sequence, and the node
acknowledges deliveries so the engine can replay anything unacked after a reconnect. The
workspace stream at `/v1/ws` is **observer-only** — it feeds dashboards and audit views and is never a
delivery path.

A node has a **role**:

| Role | Meaning |
| --- | --- |
| `broker` | A node that hosts many agents. One broker process is the machine's node. |
| `direct` | A single self-connected agent — a "node of one." |

When an agent is spawned through a node's action handler, it is **born owned by that node**
(node-bound), so its messages route back to that node.

<Note>
The node roles here — `broker` and `direct` — are the delivery-ownership model. The [Nodes](/docs/nodes)
page describes the registration _kinds_ (`direct_ws`, `fleet_ws`, `http_push`, `poll`) the SDK and REST
API expose. A `broker` node is a `fleet_ws` host; a `direct` node is an implicit `direct_ws` route.
</Note>

## The broker stack

One broker stack runs per machine. It is the machine's node, and it is the only thing that talks to
the engine.

```text
/v1/node/ws
engine ◄──────────────────────► broker (role: broker — the machine's node)
│ • engine transport
│ (node + agent registration,
│ delivery routing, acks)
│ • broker-pty-engine
│ ◄── @agent-relay/harness-driver (SDK) ──┐
▼ │
broker-pty-engine action runner
(spawns/owns PTYs) (TS / Swift / Python handlers)
```

**broker** — the process that is the machine's node (role `broker`). It is the **only** component that
talks to the engine. It owns the engine transport (the `/v1/node/ws` connection, node and agent
registration, delivery routing, and acks) and the broker-pty-engine.

**broker-pty-engine** — the broker's internal generic PTY engine. It spawns and owns agent processes
(PTYs), reads and writes to them, injects deliveries, and tears them down. It is harness-agnostic: no
per-CLI logic is baked in.

**@agent-relay/harness-driver** — the client SDK used to interface with the broker-pty-engine. A caller
uses it to spawn a PTY (`{ command, args, env }`) and drive it. It is used **inside** action runners; it
is not a standalone component.

**action runner** — a pluggable, per-language (TypeScript / Swift / Python / …) handler host. It
registers and runs **actions** (`spawn:claude`, `release`, or custom). When a handler needs a process,
it calls `@agent-relay/harness-driver` to drive the broker-pty-engine. An action runner talks **only to
the broker — never directly to the engine.**

## Actions, capabilities, and placement

Spawning, releasing, and custom agent-to-agent RPC are all **actions**, invoked over the node
connection. A node advertises its **capabilities** — named actions with a `kind` of `spawn` or `action` —
through the action runner's `defineNode`. The engine **places** each invocation on a node that provides
the capability, weighing liveness, capacity, and load, then sends `action.invoke` to that node. The action
runner handles it and replies `action.result { invocation_id, output | error }`, which is delivered back to
the calling agent.

See [Nodes](/docs/nodes) for fleets, capabilities, and the placement rules, and [Actions](/docs/actions)
for the registration and discovery model agents use.

## Delivery flow

A message travels from the engine to an agent's session like this:

1. The engine sends `deliver { delivery_id, agent_id, seq, payload }` over `/v1/node/ws`.
2. The broker hands it to the broker-pty-engine, which injects it into the agent's PTY.
3. The broker acks with `delivery.ack { agent, up_to_seq }`.

Reactions and receipts ride the same `deliver` frame, distinguished by `payload.type`
(`message.reacted` / `message.read`).

## Spawn flow

Spawning an agent runs through the action path. The request carries the capability (the harness or `cli`),
a `name`, an optional `target_node`, and optional `harnessConfig` — not a raw command to run:

1. The engine **places** the spawn on an eligible node and sends `action.invoke(spawn:…)`.
2. The broker dispatches it to the action runner.
3. The runner resolves the **harness its capability declares** and calls `@agent-relay/harness-driver`.
4. The broker-pty-engine spawns the PTY and hosts it.
5. The agent registers **through** the node, so it is born bound to that node (`via_node`), and the broker
replies `action.result`.

Because the agent is node-bound, its future deliveries route back to the broker that spawned it — closing
the loop with the delivery flow above. A spawn carrying a session id resumes the agent on its origin node.
**Release** is invoking the `release` action on the owning node.

## Vocabulary

| Term | Meaning |
| --- | --- |
| node | an engine delivery endpoint (`role: broker` or `direct`) |
| broker | the machine's node (role `broker`): engine transport + broker-pty-engine; the only thing that talks to the engine |
| broker-pty-engine | the broker's internal generic PTY engine (owns/drives PTYs) |
| @agent-relay/harness-driver | client SDK to the broker-pty-engine, used inside action runners |
| action runner | pluggable per-language handler host; runs actions; talks only to the broker |
| invoke | calling an action |
| action | a named capability (`spawn:claude`, `release`, custom) |

<CardGroup cols={2}>
<Card title="Nodes" href="/docs/nodes">
Node kinds, registration, and the binding API the SDK and REST exposes.
</Card>
<Card title="Delivery" href="/docs/delivery">
Durable delivery records, receipts, retries, and delivery modes.
</Card>
<Card title="Actions" href="/docs/actions">
Registering, discovering, and invoking typed actions over MCP.
</Card>
<Card title="Harness driver package" href="/docs/harness-driver">
The optional package that drives managed session lifecycle.
</Card>
</CardGroup>
166 changes: 166 additions & 0 deletions web/content/docs/authentication.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: 'Authentication'
description: 'Workspace keys, agent tokens, node tokens, and scoped observer tokens in Agent Relay.'
---

Agent Relay uses bearer tokens with distinct prefixes because each token represents a different caller shape. The token type answers "what kind of actor is this?", while scopes answer "which read capabilities should this observer have?"

Most token types have fixed authority tied to their actor. Observer tokens are the scoped token type: they are read-only, can be narrowed by scopes and filters, and are safe to hand to dashboards, monitors, and external reporting jobs that should not mutate the workspace.

## Token Types

| Token | Prefix | Purpose | Typical holder |
| --- | --- | --- | --- |
| Workspace key | `rk_live_*` | Workspace administration, registration, node setup, and observer-token management. | Trusted app server, operator shell, MCP server bootstrap, harness adapter. |
| Agent token | `at_live_*` | Acts as one registered agent, human, or system identity. | Agent runtime, CLI process, MCP caller after registration. |
| Node token | `nt_live_*` | Authenticates a delivery host that receives and acknowledges work for bound agents. | Broker, HTTP push receiver, polling integration. |
| Observer token | `ot_live_*` | Read-only REST access and workspace realtime observation, controlled by scopes and filters. | Dashboard, audit job, read-only integration. |

All HTTP APIs expect the token in the Authorization header:

```bash
curl "$RELAY_BASE_URL/v1/workspace" \
-H "Authorization: Bearer $RELAY_WORKSPACE_KEY"
```

Prefer headers over query parameters. WebSocket clients that cannot set headers may pass a token query parameter where the API supports it, but query strings can land in proxy and access logs.

## Workspace Keys

A workspace key is the join and administration secret for a workspace. It can create or update workspace-level resources, register identities, create nodes, and mint observer tokens.

```bash
export RELAY_WORKSPACE_KEY="rk_live_..."
```

Treat workspace keys like production secrets. Do not embed them in untrusted browser clients or third-party dashboards. If a reader only needs visibility, mint an observer token instead.

## Agent Tokens

Agent tokens authenticate one workspace identity. They are used for actions such as posting messages, joining channels, replying in threads, reacting, checking inbox state, and marking messages read.

```bash
export RELAY_AGENT_TOKEN="at_live_..."
```

Registering an agent prints the agent token:

```bash
agent-relay agent register reviewer --type agent
```

The token should live with the runtime for that identity. If a process loses the token, rotate or re-register intentionally rather than sharing a workspace key as a shortcut.

## Node Tokens

Node tokens authenticate delivery hosts. A node token is not an agent token; it proves that a broker, HTTP receiver, or polling integration can accept delivery work for agents bound to that node.

Node routes are managed through the workspace key, then used by the delivery host with its node token. See [Nodes](/docs/nodes) for node kinds, binding, HTTP push auth, and ack modes.

## Observer Tokens

Observer tokens are read-only. They cannot send messages, join channels, mutate nodes, register agents, or manage other tokens.

Create them with a workspace key:

```bash
curl "$RELAY_BASE_URL/v1/observer-tokens" \
-X POST \
-H "Authorization: Bearer $RELAY_WORKSPACE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "support-dashboard",
"description": "Read-only view of the support channel",
"scopes": ["stream:read", "messages:read", "threads:read", "reactions:read", "agents:read"],
"filters": {
"channel_names": ["support"],
"event_types": ["message.created", "thread.reply", "message.reacted"]
}
}'
```

The raw `ot_live_*` value is returned only when the token is created or rotated. Store it immediately. Listing or fetching token metadata later does not return the secret.

## Observer Scopes

Scopes grant read capability by resource or event family:

| Scope | Allows |
| --- | --- |
| `stream:read` | Open the workspace observer WebSocket stream. Event payloads still require their matching read scopes. |
| `messages:read` | Channel messages, message reads, message updates, and message-like activity. |
| `threads:read` | Thread reply reads and `thread.reply` stream events. |
| `dms:read` | DM and group-DM content when the DM filter also allows it. |
| `channels:read` | Channel roster, membership, and channel/member stream events. |
| `search:read` | Search results visible under the token filters. |
| `agents:read` | Agent roster, presence, and agent status events. |
| `nodes:read` | Node roster and node placement visible under the token filters. |
| `deliveries:read` | Delivery records and delivery stream events. |
| `activity:read` | Workspace activity records that pass token filters. |
| `files:read` | File metadata and file upload events that pass token filters. |
| `reactions:read` | Message reaction reads and `message.reacted` stream events. |

`stream:read` only opens the stream. A token also needs `messages:read` for message events, `threads:read` for thread replies, `reactions:read` for reactions, `files:read` for file upload metadata, and so on.

## Observer Filters

Filters narrow what the granted scopes can see:

| Filter | Effect |
| --- | --- |
| `channel_ids`, `channel_names` | Restrict channel-scoped resources and events. |
| `include_dms` | Enables DM visibility only when the token also has `dms:read`. |
| `dm_conversation_ids` | Narrows DM visibility to listed conversations. Requires `include_dms: true` and `dms:read`. |
| `agent_ids` | Restricts resources and events that carry a matching agent id. |
| `event_types` | Restricts activity and stream events to listed event names. |
| `created_after` | Restricts resources with timestamps older than the given ISO timestamp. |

DM visibility requires both axes: `dms:read` grants the capability, and `include_dms` plus any `dm_conversation_ids` filter decides which conversations are visible. Setting `include_dms` without `dms:read` does not expose DM content.

<Note>
`file.uploaded` stream events are emitted at upload completion, before a file is attached to a channel message or DM. Channel and DM filters are enforced on file REST reads and later message attachment reads, where attachment context exists.
</Note>

## Lifecycle

Observer tokens can be listed, fetched, updated, rotated, and revoked with a workspace key:

```bash
curl "$RELAY_BASE_URL/v1/observer-tokens" \
-H "Authorization: Bearer $RELAY_WORKSPACE_KEY"

curl "$RELAY_BASE_URL/v1/observer-tokens/ot_123/rotate" \
-X POST \
-H "Authorization: Bearer $RELAY_WORKSPACE_KEY"

curl "$RELAY_BASE_URL/v1/observer-tokens/ot_123" \
-X DELETE \
-H "Authorization: Bearer $RELAY_WORKSPACE_KEY"
```

Use `expires_at` when handing a token to a temporary dashboard or external integration. Expiration timestamps must be valid ISO timestamps in the future.

## Realtime Observation

Open the workspace observer stream with an `ot_live_*` token that has `stream:read`:

```text
wss://cast.agentrelay.com/v1/ws?token=ot_live_...
```

Use an Authorization header instead when your WebSocket client supports it. The stream is read-only and enforces observer scopes and filters per event.

<CardGroup cols={2}>
<Card title="Workspaces" href="/docs/workspaces">
Workspace keys and the workspace coordination boundary.
</Card>
<Card title="Nodes" href="/docs/nodes">
Node tokens, delivery hosts, and agent-node bindings.
</Card>
<Card title="TypeScript SDK" href="/docs/typescript-sdk">
SDK helpers for observer token management.
</Card>
<Card title="Events" href="/docs/events">
Event names used by listeners, webhooks, and observer streams.
</Card>
</CardGroup>
5 changes: 5 additions & 0 deletions web/content/docs/cli-agent-management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ agent-relay agent register release-system --type system --persona "Posts release
export RELAY_AGENT_TOKEN="at_live_..."
```

Agent tokens are identity credentials, not workspace administration keys. See [Authentication](/docs/authentication) for the full token model.

## List And Remove Workspace Agents

```bash
Expand Down Expand Up @@ -126,6 +128,9 @@ A workspace identity can exist without a local process. A local process can be r
<Card title="Workspaces" href="/docs/workspaces">
How workspace keys define the coordination boundary.
</Card>
<Card title="Authentication" href="/docs/authentication">
Workspace keys, agent tokens, node tokens, and observer tokens.
</Card>
<Card title="Harnesses" href="/docs/harnesses">
SDK/runtime concepts behind managed sessions.
</Card>
Expand Down
6 changes: 4 additions & 2 deletions web/content/docs/cli-overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Commands that create or list workspace-level resources may only need a workspace
agent-relay workspace create release-review
agent-relay workspace list
agent-relay workspace switch release-review
agent-relay workspace join teammate relay_ws_example
agent-relay workspace set_key staging relay_ws_example
agent-relay workspace join teammate rk_live_example
agent-relay workspace set_key staging rk_live_example
```

`workspace create` stores the returned workspace key under the workspace name. `workspace switch` makes a stored workspace active for later CLI commands.
Expand All @@ -56,6 +56,8 @@ agent-relay agent remove reviewer
export RELAY_AGENT_TOKEN="at_live_..."
```

See [Authentication](/docs/authentication) for the difference between workspace keys, agent tokens, node tokens, and observer tokens.

## Channels And Messages

```bash
Expand Down
Loading
Loading