Channels
This content is for the 0.1 version. Switch to the latest version for up-to-date documentation.
A channel is a URI you subscribe to.
Every command and notification carries one, so a host, a proxy, or this client
can route any message by looking at (method, params.channel) without
understanding the payload.
The channels
Section titled “The channels”| URI | State | What it holds |
|---|---|---|
ahp-root:// |
RootState |
Agent catalogue, terminals, host config. Always present. |
ahp-session:/<uuid> |
SessionState |
Session metadata and its chat list. |
ahp-chat:/<cid> |
ChatState |
Turns, streaming, tool calls, input requests. |
ahp-terminal:/<id> |
TerminalState |
Pty state. |
ahp-changeset:/<id> |
ChangesetState |
Proposed file edits. |
ahp-session:/<uuid>/annotations |
AnnotationsState |
Inline annotations on a session. |
ahp-resource-watch:/<id> |
ResourceWatchState |
File and directory change events. |
ahp-otlp: also exists for telemetry, but it is stateless and never mirrored.
Annotations are not a scheme
Section titled “Annotations are not a scheme”Look again at the annotations row. It is a path suffix on the session scheme, not a scheme of its own — which means classifying a channel by scheme alone is wrong:
channelKindOf(Uri.parse('ahp-session:/s1')); // sessionchannelKindOf(Uri.parse('ahp-session:/s1/annotations')); // annotationsBoth are ahp-session.
A startsWith('ahp-session:') check swallows the second and feeds annotation
envelopes to the session reducer, silently corrupting session state.
Subscribing
Section titled “Subscribing”Use AhpRuntime.acquire rather than subscribing by hand.
It refcounts, so several parts of an app can watch the same channel without
coordinating, and only the first claim reaches the wire:
final lease = runtime.acquire(Uri.parse('ahp-chat:/$chatId'));Releasing the last lease starts a short grace window before unsubscribing. Without it, a navigation push-and-pop, or a list recycling rows, would churn the wire and throw away a warm store on every scroll.
Delivery hints
Section titled “Delivery hints”Chat and terminal channels subscribe with maxLatencyMs: 16 by default, which
asks the host to coalesce envelopes into roughly one frame’s worth.
This matters more than any client-side optimisation. Without it a chat channel delivers one envelope per streamed token, and every consumer pays for that regardless of how carefully it narrows rebuilds. Batching server-side is strictly cheaper than anything you can do after the fact.
runtime.acquire( chatUri, options: const SubscribeOptions(maxLatencyMs: 16, turns: 50),);Terminals never reconcile
Section titled “Terminals never reconcile”Every channel applies your actions optimistically — except terminals.
A pty is a stateful, mutable process.
Predicting its output, or echoing input into the scrollback before the pty
replies, produces state that is simply wrong.
The protocol splits terminal/input from terminal/data for exactly this
reason.
The type system enforces it rather than a runtime check: terminal channels vend
a MirrorStore, which has no applyLocal at all, and mirror.reconciling()
refuses them outright.
runtime.dispatch(terminalUri, action); // throws ArgumentError