Chat Channel
A chat channel carries the full state of a single conversation thread: turns, streaming responses, tool calls, pending messages, and input requests. A chat always belongs to a session; a session may contain one or many chats. Chats are independently subscribable so a client can observe a subset of activity without paying the bandwidth cost of every chat in the session.
URI
ahp-chat:/<uuid>The path is a server-unique identifier (typically a UUID) allocated by the server when the chat is created. The owning session URI is not encoded in the chat URI — the relationship is expressed via the session's chats catalog and each chat's origin.
Multiple chat channels may be active simultaneously. Clients subscribe to each chat whose state they want to track.
State
Subscribers receive a ChatState snapshot. ChatState denormalizes the ChatSummary fields directly onto itself (resource, title, status, activity, modifiedAt, model, agent, origin, workingDirectory) and adds the conversation contents (history of completed turns, the active turn if any, pending messages, outstanding input requests). Producers MUST keep the chat's ChatSummary in the session catalog consistent with these inlined fields — typically by dispatching a matching session/chatUpdated whenever any summary field on the chat changes. Refer to the State Model guide for a structural overview.
Per-chat working directory
ChatState.workingDirectory (and its mirror on ChatSummary) is optional. When absent, the chat inherits the session's workingDirectory. Hosts MAY set a per-chat working directory to give individual chats their own filesystem context — for example, allocating a separate git worktree per chat so multiple chats in the same session can make independent edits that the orchestrating chat later merges back. The session-level workingDirectory is then the default/primary location for chats that do not override it.
Relationship to the session channel
- A chat's
ChatSummaryappears in the session'sSessionState.chatscatalog. The session reducer keeps that catalog in sync with the underlying chat lifecycle. - The session may also expose
defaultChatas a UI routing hint for input that is addressed to the session as a whole. This is advisory only — chats remain equal peers at the protocol level. - Session-level fields such as
status,activity, andmodifiedAtare aggregates derived from the session's chats. See the Session Channel specification for the derivation rules.
Lifecycle
1. Client subscribes to the owning session URI (ahp-session:/<sid>)
2. Client (or the server, via a tool call or fork) creates a chat with createChat
3. Server allocates a chat URI (ahp-chat:/<cid>) and mutates the session's chats catalog
4. Client subscribes to the chat URI to receive its ChatState snapshot
5. Server streams chat actions over the chat channel until the chat (or its session) is disposedCreation
createChat is a JSON-RPC request. Callers identify the owning session via the request's channel parameter (ahp-session:/<sid>) and MAY supply:
- an
initialMessageto start the first turn immediately, - per-chat
agent/model/configoverrides that win over the session defaults, and - a
sourceof typeChatForkSourceto fork from an existing chat at a specific turn.
The server allocates the chat URI and adds the chat to the session's catalog (session/chatAdded on the session channel) before returning.
Origin
Each chat advertises how it came into existence via ChatOrigin:
| Kind | Meaning |
|---|---|
user | User created the chat explicitly (e.g. via the host UI). |
fork | Forked from an existing chat at a specific turn — payload references the source chat URI and turn id. |
tool | Spawned by a tool call running in another chat — payload references the source chat URI and tool call id (e.g. a sub-agent delegation). |
Clients MAY use the origin to render contextual UI (parent indicators, fork markers, "spawned by tool" badges), but origin is not a hierarchy — every chat is equally addressable.
A tool-spawned worker is described from both ends of the same edge. The worker chat carries the canonical record via its tool origin (the spawning chat URI and tool call id). The spawning tool call surfaces the same relationship forward through a ToolResultSubagentContent block in its result, whose resource is the worker chat URI (ahp-chat:/<cid>, not a session URI). The tool call that emits that block is the one named by the worker chat's origin.toolCallId; hosts MUST keep the two consistent.
Ancestry and nesting depth
A fork or tool origin names only the chat's immediate source chat (by URI), together with the turn or tool call that produced it. A chat's ancestry is therefore not stored directly; it is the chain you reconstruct by following origin.chat from one chat to the next. Because a tool-spawned chat can itself run tools that spawn further chats, these chains can be arbitrarily deep.
- No protocol-imposed depth limit. AHP does not cap nesting depth or fan-out, and the wire carries no depth counter or maximum-depth field. Any bound is a host policy decision that the protocol neither enforces nor advertises; hosts SHOULD guard against runaway recursion or unbounded fan-out on their side.
- Ancestry is advisory and may be incomplete. Every chat is a flat, equally-addressable peer in the session's
chatscatalog —originis a rendering hint, not a structural parent link. A source chat MAY be pruned (session/chatRemoved) while a chat it spawned lives on, so anorigin.chatURI is not guaranteed to resolve. Clients reconstructing ancestry MUST tolerate missing references and SHOULD guard against cycles and unbounded depth (for example, by capping how deep they walk or render).
Active chat
Once a chat exists and its session is lifecycle: 'ready', the chat accepts turns. The wire shape mirrors the legacy single-chat session shape:
- The client dispatches
chat/turnStartedto begin a turn. - The server streams
chat/delta,chat/responsePart,chat/toolCallStart,chat/toolCallReady, and related actions. - The client dispatches
chat/toolCallConfirmed/chat/toolCallResultConfirmedto approve or deny tool calls, orchat/turnCancelledto abort. - The server dispatches
chat/turnCompleteorchat/errorwhen the turn ends. - The server MAY dispatch
chat/inputRequestedwhile a turn is active. Clients sync answer drafts withchat/inputAnswerChangedand finish the request withchat/inputCompleted.
All actions dispatched on this channel travel on ActionEnvelopes whose channel is the chat URI. Action payloads do NOT carry their own chat URI — the channel comes from the envelope.
Disposal
A chat is implicitly disposed when its owning session is disposed. The protocol does not currently expose a disposeChat command; chats live for the life of their session unless the server prunes them. When a chat is removed (whether explicitly or because its session was torn down), the server MUST update the session's chats catalog via session/chatRemoved so subscribers can release their per-chat subscriptions.
Methods and events on this channel
This section lists wire methods that are interpreted in the context of a chat URI (ahp-chat:/<uuid>).
Commands (params.channel = "ahp-chat:/<uuid>")
| Method | Kind | Purpose |
|---|---|---|
fetchTurns | request | Page historical turns for this chat. |
completions | request | Chat-scoped inline completions (e.g. user-message mentions). |
createChat is dispatched against the owning session URI (params.channel = "ahp-session:/<sid>").
Notifications (params.channel = "ahp-chat:/<uuid>")
| Method | Kind | Meaning |
|---|---|---|
action | server → client notification | Chat action envelope (chat/* action payloads). |
dispatchAction | client → server notification | Dispatch client actions on this chat (chat/turnStarted, chat/toolCallConfirmed, ...). |
unsubscribe | client → server notification | Stop receiving messages for this chat channel. |
Server Validation of Client Actions
When the server receives a client-dispatched action on this channel, it MUST validate it before applying. Invalid actions MUST be echoed back with a rejectionReason on the ActionEnvelope. The validation rules mirror the legacy session validation table — substitute chat/* for session/*:
| Action | Condition | Server Behavior |
|---|---|---|
| Any action referencing a non-existent chat | Channel URI not found | Server MUST silently ignore the action (no echo) |
chat/toolCallConfirmed | Tool call not in pending-confirmation state | Server MUST reject the action |
chat/turnCancelled | No active turn | Server MUST reject the action |
chat/inputAnswerChanged | No input request with matching requestId | Server SHOULD reject the action |
chat/inputAnswerChanged | answer.state requires a value but answer.value is absent, or answer.value.kind is missing the matching payload field | Server SHOULD reject the action |
chat/inputCompleted | No input request with matching requestId | Server SHOULD reject the action |
chat/inputCompleted | response is 'accept' but required questions do not have submitted answers | Server SHOULD reject the action |
chat/pendingMessageRemoved | No pending message with matching id and kind | Server SHOULD reject the action |
Pending Message Consumption
Pending messages live on the chat, not the session. The consumption rules mirror the legacy session behavior:
Queued Messages
When a turn completes and queuedMessages is non-empty, the server SHOULD:
- Dispatch
chat/pendingMessageRemovedwithkind: 'queued'for the first queued message. - Dispatch
chat/turnStartedwith the queued message'smessageandqueuedMessageIdset to the message'sid.
When a queued message is added while the chat is idle (no active turn), the server SHOULD immediately consume it using the same two-step sequence.
Steering Messages
When a turn is active and steeringMessages is non-empty, the server MAY consume steering messages at its discretion. To consume a steering message, the server:
- Dispatches
chat/pendingMessageRemovedwithkind: 'steering'. - Injects the message content into the model context (the injection mechanism is opaque to the protocol).
Steering messages added while idle are silently stored and consumed when a turn becomes active.
Actions
Refer to the Chat Channel Reference for the full per-action reference. All chat-scoped action envelopes carry channel: "ahp-chat:/<uuid>".