Architecture: nodes, providers, and the agent runtime

How Agent Relay routes reliable delivery through nodes, how providers attach to a node and connect directly to the engine, and the vocabulary for nodes, providers, capacity, and the agent runtime.

Agent Relay separates what a message means from where it is delivered. Messaging writes a durable record; delivery gets that record into a session. This page describes the delivery side end to end: the node and provider model, the agent runtime that hosts sessions on a machine, and how spawn flows through the engine.

The engine is the hub

A node is an enrolled context in a workspace — usually a project directory, sometimes an application. Providers attach to a node: each is a process that connects directly to the engine over /v1/node/ws, shares the node's token, and registers a subset of the node's capabilities. The engine is the only hub; there is no local one.

Realtime delivery is node-only and reliable: each agent has a per-agent sequence, and the provider that owns an agent 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.

Providers on a node

A node's abilities are split across its providers, each connecting to the engine on its own:

engine (cast.agentrelay.com)
 ├─ /v1/node/ws ← agent runtime (Rust)      — spawn:*, release, agent delivery
 ├─ /v1/node/ws ← capability provider (TS)  — project agent-relay.ts actions
 ├─ /v1/node/ws ← capability provider (py)  — project agent-relay.py actions
 └─ /v1/node/ws ← app provider (Swift)      — its own node

agent runtime — the Rust provider that spawns and owns agent processes (PTYs). It registers the node's capacity (spawn:<harness>, release) and the agents it hosts, receives deliver frames for those agents, and heartbeats its own load. It owns a generic PTY engine that reads, writes, injects deliveries into, and tears down agent processes; it is harness-agnostic, with no per-CLI logic baked in.

capability provider — a process in TypeScript, Python, or Swift that registers actions: named handlers the engine dispatches to it. A capability that needs to spawn an agent calls the engine's placement through ctx.spawnAgent, which lands on an agent runtime — it never tunnels through a local process.

@agent-relay/harness-driver — the client SDK for the agent runtime's PTY engine, used to spawn a PTY ({ command, args, env }) and drive it.

Capabilities, capacity, and placement

Providers register two kinds of capability. An action is an invokable handler; the engine materializes it and dispatches invokes to the registering provider. Capacityspawn:<harness> and release — is what the node can run; the agent runtime registers it for placement and delegation, and it is never materialized as an action.

Invocation is node-addressed: POST /v1/nodes/:node/actions/:name/invoke resolves the capability to its provider and sends action.invoke down that socket. The provider replies action.result { invocation_id, output | error }, delivered back to the calling agent as the action's completion. See Nodes and providers for the model and Nodes for placement and the node API.

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 to the provider that owns the agent — its agent runtime.
  2. The agent runtime injects it into the agent's PTY.
  3. The runtime 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 capacity. The request carries 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 a node whose agent runtime advertises the harness and sends action.invoke(spawn:…).
  2. The agent runtime resolves the harness and spawns the PTY.
  3. The new agent registers through the node, so it is born bound to that node, and the runtime replies action.result.

Because the agent is node-bound, its future deliveries route back to the agent runtime 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 invokes the release capacity on the owning node.

A provider can register an action named spawn:<harness> that shadows native capacity: the engine routes spawn invokes for that harness to the handler, which mutates the spec and delegates via ctx.spawnAgent. The delegation addresses capacity directly, so a shadow never re-enters itself, and while it is registered the native capacity is reachable only through it.

Vocabulary

TermMeaning
nodean enrolled context in a workspace (a project directory or an application)
providera process attached to a node that connects directly to the engine and registers capabilities
agent runtimethe Rust provider that owns PTYs and registers the node's spawn/release capacity
capability providera per-language provider that registers invokable actions
@agent-relay/harness-driverclient SDK for the agent runtime's PTY engine
capacitywhat a node can run (spawn:<harness>, release); feeds placement, never invoked
actionan invokable capability with a handler hosted by a provider
invokecalling an action (POST /v1/nodes/:node/actions/:name/invoke)