Skip to content

Session Channel

A session channel carries session-level state and acts as the coordination scope for one or more chats. The session tracks lifecycle, customizations, per-session configuration, changesets, and the catalog of chats that belong to the session. The per-conversation state — turns, streaming responses, tool calls, pending messages, and input requests — lives on the chat channel.

ahp-session:/<uuid>

The path is a server-unique identifier (typically a UUID) chosen by the client at creation time. The session’s provider (e.g. "copilot") is not encoded in the URI scheme — it is carried on SessionSummary.provider. This decoupling lets the same scheme address sessions backed by any agent.

Multiple session channels may be active simultaneously. Clients subscribe to each one whose state they want to track.

Subscribers receive a SessionState snapshot containing the session metadata (title, status, provider, activity, working directory, …) inlined directly, the lifecycle phase, the catalog of chats belonging to this session, the optional defaultChat routing hint, active-client state, customizations, changesets, the inputNeeded aggregate, and per-session configuration. Per-conversation state (turns, streaming, tool calls, pending messages, input requests) lives on the chat channel. Refer to the State Model guide for a structural overview.

1. Client picks a session URI (e.g. ahp-session:/<new-uuid>)
2. Client sends createSession(uri, config) command
3. Client sends subscribe(uri) — MAY be batched with the command
4. Server creates session with lifecycle: 'creating', returns the snapshot
5. Server asynchronously initialises the agent backend
6. On success: server dispatches session/ready
7. On failure: server dispatches session/creationFailed
8. Server broadcasts root/sessionAdded to clients subscribed to ahp-root://

createSession is a JSON-RPC request. The client picks the URI; the server allocates session state and begins backend initialisation. If the URI is already in use the server returns SessionAlreadyExists (-32003).

Once a session reaches lifecycle: 'ready', clients may create chats on it with createChat. Each chat is independently subscribable at its own ahp-chat:/<cid> URI; see the Chat Channel specification for the per-chat lifecycle, turn flow, tool calls, and input request handling.

Session-scoped actions dispatched on this channel are limited to:

  • Catalog mutations — session/chatAdded, session/chatRemoved, session/chatUpdated, and session/defaultChatChanged.
  • Session-wide configuration — active-client tracking, customizations, changesets, lifecycle transitions.

All actions dispatched on this channel travel on ActionEnvelopes whose channel is the session URI. Action payloads do NOT carry their own session URI — the channel comes from the envelope.

Three discrete actions keep SessionState.chats in sync as chats come and go. Sessions with a single chat trivially round-trip a session/chatAdded once at creation; multi-chat sessions exercise all three:

Action Payload Reducer behavior
session/chatAdded summary: ChatSummary Upsert by summary.resource. Appends when no entry has the same URI; otherwise replaces the existing entry. Mirrors root/sessionAdded.
session/chatRemoved chat: URI Removes the matching entry. No-op when no entry matches. If state.defaultChat referenced the removed URI, the reducer clears it. Mirrors root/sessionRemoved.
session/chatUpdated chat: URI, changes: Partial<ChatSummary> Merges the non-identity fields of changes onto the matching entry. No-op when no entry matches; clients SHOULD then wait for a session/chatAdded. Identity fields (resource) MUST NOT be carried in changes. Mirrors root/sessionSummaryChanged.

The producer of the chat’s own ChatState is responsible for emitting matching session/chatUpdated actions so the catalog and the per-chat channel stay consistent.

SessionSummary carries session-wide identity (resource, provider, createdAt, workingDirectories) but several of its mutable fields are aggregates derived from the session’s chats. Producers SHOULD apply these rules so clients that only consume the session summary (a session list, for example) still see meaningful state:

Field Derivation rule
status Take the activity bits (Idle / InProgress / InputNeeded / Error) from the defaultChat when set, else from the most recently modified chat. Promote InputNeeded if any chat needs input. Promote Error if any chat is in an error state. The orthogonal IsRead / IsArchived flags remain session-scoped and pass through unchanged.
activity Mirror the activity string of the chat that contributes the activity bits — usually the default chat, but the chat that raised InputNeeded / Error when a non-default chat wins the promotion.
modifiedAt The maximum of every chat’s modifiedAt.
workingDirectories The session-level set. Individual chats MAY restrict to a subset via ChatSummary.workingDirectories; aggregating per-chat subsets up is meaningless and SHOULD NOT be attempted.
changes Optional roll-up. Producers MAY sum per-chat changeset stats or report the most expensive chat’s stats — whichever is cheaper to compute.

Sessions with a single chat satisfy all of the above trivially (the chat’s values pass through). The rules only matter once a session carries multiple chats.

A chat blocks on user input (an elicitation) or on a tool confirmation deep inside its turn state. Discovering those blocks would normally require subscribing to every chat channel — impractical for a mobile app or a tool-providing client that only watches the session.

SessionState.inputNeeded is a session-level roll-up of every outstanding block across all chats. The host upserts entries with session/inputNeededSet and removes them with session/inputNeededRemoved as the underlying chat-level requests appear and resolve. Whenever the list is non-empty the session’s status carries the InputNeeded bit.

Each entry is a SessionInputRequest — a discriminated union over kind:

kind Carries Respond by dispatching…
chatInput the mirrored ChatInputRequest chat/inputCompleted (or chat/inputAnswerChanged)
toolConfirmation a ToolCallConfirmationState plus turnId chat/toolCallConfirmed or chat/toolCallResultConfirmed
toolClientExecution a ToolCallState in running status plus turnId and the owning clientId chat/toolCallComplete (optionally chat/toolCallContentChanged)
toolAuthentication a ToolCallAuthRequiredState plus turnId (see below) authenticate

Every entry carries the owning chat URI plus the identifiers (request.id, or turnId + toolCall.toolCallId) needed to construct the response. A client therefore answers by dispatching the ordinary chat/* action to that chat’s channel — it does not need to have subscribed to the chat first. inputNeeded is a read/respond convenience surface, not a separate response protocol: the chat channel remains the source of truth and the host removes the aggregate entry once the chat-level request resolves.

toolAuthentication is the one exception to the “respond via chat/* action” pattern: the client resolves it by calling the connection-level authenticate command with the resource from toolCall.auth.resource (see Authentication), not by dispatching an action to the chat. The host dispatches chat/toolCallAuthResolved once the token is accepted and removes the session/inputNeeded entry at that point.

// Client → Server (request)
{
"jsonrpc": "2.0",
"id": 5,
"method": "disposeSession",
"params": { "channel": "ahp-session:/<uuid>" },
}

The server tears down the session backend, drops associated subscriptions, and broadcasts root/sessionRemoved to clients subscribed to ahp-root://.

This section lists wire methods that are interpreted in the context of a session URI (ahp-session:/<uuid>).

Commands (params.channel = "ahp-session:/<uuid>")

Section titled “Commands (params.channel = "ahp-session:/<uuid>")”
Method Kind Purpose
createSession request Create a session at the chosen URI.
disposeSession request Dispose this session and its backend resources (cascades to every chat in the session’s catalog).

Notifications (params.channel = "ahp-session:/<uuid>")

Section titled “Notifications (params.channel = "ahp-session:/<uuid>")”
Method Kind Meaning
action server → client notification Session action envelope (session/* action payloads — catalog updates, lifecycle, customizations, changesets).
dispatchAction client → server notification Dispatch client actions on this session (session/titleChanged, session/defaultChatChanged, …).
unsubscribe client → server notification Stop receiving messages for this session channel.

auth/required may also target a session URI when auth is required for an operation scoped to that session; see Authentication.

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 following validation rules apply:

Action Condition Server Behavior
Any action referencing a non-existent session Channel URI not found Server MUST silently ignore the action (no echo)
session/defaultChatChanged defaultChat URI does not match an entry in the session’s chat catalog Server MUST reject the action

Turn-, tool-call-, input-request-, and pending-message-level validation lives on the Chat Channel.

Refer to the Session Channel Reference for the full per-action reference. All session-scoped action envelopes carry channel: "ahp-session:/<uuid>".

Session catalogue events (creation, disposal, summary mutations) are emitted on the Root Channel, not on the session channel itself. This lets clients track the session list without subscribing to every session.