Channels & Subscriptions
AHP organises all push-based communication into channels. A channel is a URI-identified resource that a client subscribes to in order to receive updates. Channels MAY have state (root, sessions, terminals, changesets) or be stateless (future: logging, MCP relay, LSP relay). The subscription mechanism — subscribe, unsubscribe, and per-channel notifications — is uniform across channel types.
Every message carries channel
The channel concept is woven into every wire message. Every command and every notification has a top-level channel: URI field on its params. This invariant lets servers, clients, and intermediate proxies dispatch any incoming message by inspecting (method, params.channel) without per-method knowledge of the rest of the payload.
| Direction | Methods | channel value |
|---|---|---|
| Client → Server commands (channel-scoped) | subscribe, unsubscribe, createSession, disposeSession, createTerminal, disposeTerminal, fetchTurns, completions, invokeChangesetOperation | The target channel's URI (e.g. ahp-session:/<uuid>). |
| Client → Server commands (connection-level) | initialize, ping, reconnect, listSessions, authenticate, resolveSessionConfig, sessionConfigCompletions, resourceRead, resourceWrite, resourceList, resourceCopy, resourceDelete, resourceMove, resourceResolve, resourceMkdir, resourceRequest, createResourceWatch | Literal 'ahp-root://'. |
Server → Client commands (bidirectional resource* family) | The same nine resource* request methods plus createResourceWatch may also be initiated by the server. Used for host-driven per-session filesystem providers and for fetching client-published URIs (e.g. virtual://my-client/... plugins). | Literal 'ahp-root://'. |
Client → Server dispatchAction | The channel the action targets. | |
Server → Client action | The channel that owns the action envelope. | |
| Server → Client protocol notifications | root/sessionAdded, root/sessionRemoved, root/sessionSummaryChanged, auth/required, otlp/exportLogs, otlp/exportTraces, otlp/exportMetrics | The channel the notification scopes to (the root channel for root/*; the channel the auth requirement targets for auth/required; the host-defined ahp-otlp: channel URI for otlp/*). |
The constraint is encoded in the TypeScript types: every entry in CommandMap and the notification maps has params assignable to BaseParams (or, for notifications, structurally { channel: URI }). The compile-time check in types/version/message-checks.ts fails if any new method omits the field.
The rest of this page details the URI scheme and the lifecycle of a subscription. The mechanics of action delivery and protocol notifications are described under each channel page (Root, Session, Terminal).
URI Scheme
| URI | State type | Description |
|---|---|---|
ahp-root:// | RootState | Global state (agents, terminals, host config). Always present. |
ahp-session:/<uuid> | SessionState | Per-session state. The session's provider is carried on SessionSummary.provider, not in the URI scheme. |
ahp-terminal:/<id> | TerminalState | Per-terminal state. Server-defined id. |
ahp-changeset:/<id> | ChangesetState | Per-changeset state. URI is obtained by expanding a Changeset.uriTemplate advertised on a session; the id is server-defined. |
ahp-otlp: (authority/path host-defined) | stateless | OpenTelemetry signal channels (logs, traces, metrics). Concrete URIs are advertised on InitializeResult.telemetry; clients MUST treat them as opaque. See Telemetry Channel. |
ahp-resource-watch:/<id> | ResourceWatchState | Per-watch channel returned by createResourceWatch. Delivers resourceWatch/changed actions for file/directory changes under the watched URI. The id is caller-chosen. |
Future channel types (LSP relay, MCP relay, …) introduce their own URI schemes. Clients MUST NOT subscribe to a scheme they do not understand.
Subscribe (Request)
subscribe is a JSON-RPC request. The result includes a snapshot for state-bearing channels and omits it for stateless ones.
// Client → Server
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscribe",
"params": {
"channel": "ahp-session:/<uuid>",
"delivery": { "maxLatencyMs": 100 },
"view": { "turns": 30 }
}
}
// Server → Client (state-bearing channel)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"snapshot": {
"resource": "ahp-session:/<uuid>",
"state": {
"summary": { "resource": "ahp-session:/<uuid>", "title": "New Session", ... },
"lifecycle": "creating",
"turns": [],
"activeTurn": null
},
"fromSeq": 5
}
}
}
// Server → Client (stateless channel)
{
"jsonrpc": "2.0",
"id": 1,
"result": {}
}After subscribing, the client receives all messages scoped to that channel — both action envelopes (for state channels) and any channel-specific notifications.
Delivery preferences
Clients MAY include delivery.maxLatencyMs on subscribe to request an upper bound, in milliseconds, on intentional server-side buffering for that subscription. Servers MAY use that budget to coalesce high-frequency updates while preserving the same reduced state a client would observe from immediate delivery. A value of 0 requests immediate delivery with no intentional coalescing. Omitting delivery uses the server's default delivery behavior.
Snapshot views
Clients MAY include view on subscribe to ask the server to shape the returned snapshot. View preferences are advisory and additive: a server that does not understand a requested view ignores it and returns its default snapshot, and clients MUST tolerate receiving more state than requested.
For chat channels, view.turns asks the server to expose approximately that many most-recent completed turns in the snapshot. The value is advisory: the server MAY return more or fewer turns than requested. If view.turns is omitted, the server MUST return all retained turns. If older retained turns remain available, the returned ChatState includes turnsNextCursor; the client can pass that cursor to fetchTurns to ask the host to page older turns into the same reduced state.
Unsubscribe (Notification)
unsubscribe is a fire-and-forget client → server notification. Like every wire message, its params carry the channel URI being released.
{
"jsonrpc": "2.0",
"method": "unsubscribe",
"params": { "channel": "ahp-session:/<uuid>" }
}After unsubscribing, the client stops receiving messages for that channel.
Action Delivery (action)
State channels deliver mutations via the action server notification. The params are an ActionEnvelope — flat, with channel identifying the channel and a single action payload:
{
"jsonrpc": "2.0",
"method": "action",
"params": {
"channel": "ahp-session:/<uuid>",
"action": { "type": "session/delta", "turnId": "t1", "partId": "p1", "content": "Hello" },
"serverSeq": 6,
"origin": { "clientId": "client-1", "clientSeq": 1 }
}
}- Root actions go to all clients subscribed to
ahp-root://. - Session actions go to all clients subscribed to that session's URI.
- Terminal actions go to all clients subscribed to that terminal's URI.
Action payloads (the inner action object) carry only fields intrinsic to the action — the channel comes from the envelope. Individual actions do NOT carry a session: URI or terminal: URI field of their own.
The client → server dispatch path uses a different method, dispatchAction, with params { channel, clientSeq, action }:
{
"jsonrpc": "2.0",
"method": "dispatchAction",
"params": {
"channel": "ahp-session:/<uuid>",
"clientSeq": 1,
"action": { "type": "session/turnStarted", "turnId": "t1", "message": { "text": "Hi", "origin": { "kind": "user" } } }
}
}See Actions for the full list of client-dispatchable actions.
Initial Subscriptions
During the handshake, clients MAY include initialSubscriptions in initialize to subscribe to channels in the same round-trip:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"channel": "ahp-root://",
"protocolVersions": ["0.3.0"],
"clientId": "client-abc",
"initialSubscriptions": ["ahp-root://", "ahp-session:/<prev-session>"]
}
}The server includes a snapshot for each state-bearing channel in the initialize response.
Protocol Notifications
Beyond action, the server pushes per-channel protocol notifications for ephemeral events. Each one is its own top-level JSON-RPC method (e.g. root/sessionAdded, auth/required) — there is no notification wrapper.
{
"jsonrpc": "2.0",
"method": "root/sessionAdded",
"params": {
"channel": "ahp-root://",
"summary": { "resource": "ahp-session:/<uuid>", "title": "New Session", ... }
}
}For partial updates to an existing session's summary, the server broadcasts root/sessionSummaryChanged:
{
"jsonrpc": "2.0",
"method": "root/sessionSummaryChanged",
"params": {
"channel": "ahp-root://",
"session": "ahp-session:/<uuid>",
"changes": { "title": "Refactor auth middleware", "status": 8 }
}
}Protocol notifications go only to clients subscribed to the channel they target. They are not stored in state and are not replayed on reconnection.
Stateless Channels
A channel MAY be stateless — i.e. carry no Snapshot. Subscribing returns an empty result {}, and subsequent traffic flows via channel-specific methods rather than action envelopes. The subscription/unsubscription mechanism is identical to state channels. Stateless channels are not replayed across reconnection — clients re-subscribe and resume from the live edge.