Status: in progress (2026-10-30). pi-daemon pinned AHP 0.8.0
(@microsoft/agent-host-protocol) to match Culture. AHP
1.0.0 was released on 2026-10-02, with crates and npm packages for
1.0.0. Requirement INT-AHP-VERSION (bd-91acd4) asks that
hosts and clients upgrade together, with version-negotiation tests. This
is the risk assessment and the plan.
Every client still speaks 0.8.0
| Client | Library | Offers |
|---|---|---|
| Culture (Android, iOS, macOS, web, TUI) | Paratenic's Rust
ahp/ahp-types/ahp-ws
=0.8.0, via FFI and WASM |
["0.8.0"] (its PROTOCOL_VERSION) |
| Paratenic (host for Culture; its AHP-upstream connector dials pi-daemon) | the same crates =0.8.0 |
["0.8.0"] |
Cacophony (caco-daemon pi_daemon_ahp.rs,
caco-tui) |
ahp* =0.8.0 |
["0.8.0"] |
ag (agent-utils) |
ahp* =0.8.0 |
SUPPORTED_PROTOCOL_VERSIONS (0.8.0 down to 0.5.1) |
pi-client terminals (src/client/ahp-terminals.ts) |
this package | its PROTOCOL_VERSION |
Phones, desktop app installs and Cacophony nodes update at their own pace, so pi-daemon cannot drop 0.8. The host speaks both: a client that offers 1.0.x or 0.9.x gets 1.0 or 0.9; a client that offers only 0.8.0 gets 0.8 shapes, exactly as before.
A 0.8 Rust client cannot just be sent 1.0 messages. Its tagged enums
of actions and response parts keep unknown variants
(Unknown(Value)), and it ignores unknown struct fields (no
deny_unknown_fields). But its plain string enums, such as
SessionLifecycle, have no unknown variant, so a new value
fails to deserialize. Removed or reshaped fields also silently lose
meaning: a turn error that is no longer in turn.error, or a
terminal exit code that is no longer exitCode.
What changed between 0.8 and 1.0 that pi-daemon touches
| Area | 0.8 | 1.0 | Projection for 0.8 clients |
|---|---|---|---|
| Version negotiation | the host requires 0.8.0 in the offer |
baselines 1.0.0 and 0.9.0, caret ranges
(negotiateProtocolVersion) |
accept 0.8.0 as a legacy version |
| Failed session lifecycle | creationFailed |
failed |
failed → creationFailed in session
state |
| Turn errors | chat/error { error }; Turn.error |
chat/error { part: { kind: "error", error, resumable? } };
the error is the turn's last response part |
part.error → error; error parts →
turn.error |
| Terminals | TerminalState.exitCode,
TerminalInfo.exitCode?; session claims carry no chat |
lifecycle: { status: "running" | "exited", exitCode? };
TerminalSessionClaim.chat is required |
lifecycle → exitCode; a claim without a chat gets the
session's default chat |
| Session fork at create | createSession { fork } |
removed: fork a chat instead | keep accepting fork (pi-daemon's AHP-FORK); 1.0 clients
fork chats |
Error code ContentNotFound (-32006) |
defined | reserved, unassigned | use NotFound |
toolClientExecution requests |
toolCall: ToolCallState |
ToolCallRunningState (a subset) |
none (same JSON) |
FileEdit |
anonymous shapes | named models | none (same JSON) |
New actions and fields (chat read/archive, chat changesets,
background work, canvases, chat move, automations, turn resume,
SessionSummary.chats,
InitializeResult._meta) |
— | additive | actions newer than the client's version are not sent
(isActionKnownToVersion,
isNotificationKnownToVersion); unknown fields are left
(ignored) |
Every action pi-daemon emits today predates 0.8 (checked against
ACTION_INTRODUCED_IN). The breaking surface is therefore
the shapes above, not new names.
Risks and mitigations
- A 0.8 client breaks on a 1.0 shape (Culture or
Cacophony silently stop working). The projection runs at the one place
every outgoing message passes, per connection: action envelopes,
notifications, and the snapshots in
initialize,subscribeandreconnectresults. Tests drive the host as a 0.8 client and as a 1.0 client and compare the shapes ([bd-91acd4]). - A 1.0 client loses a feature pi-daemon serves only in a 0.8
shape (session fork at create). pi-daemon keeps
createSession { fork }for every version, and 1.0 chat forking stays what AHP-FORK already provides (forks as chats). - pi-client against older daemons. pi-client's
terminal client would offer only 1.0 and be refused by a 0.8-only
daemon. So it offers
[1.0.0, 0.9.0, 0.8.0]and reads either terminal shape. - New capabilities used before clients understand
them. pi-daemon adds 1.0-only behaviour (resumable turn errors,
chat read/archive, background work,
InitializeResult._meta) only behind the negotiated version. A 0.8 client sees what it saw before. - The bridge (TUI Pi sessions,
:7464/ahp) uses the same server code (tui-ahp.ts), so it gets the same negotiation and projection.
Order of rollout
- pi-daemon (this change): 1.0 internally; negotiates 1.0, 0.9 or 0.8; projects to 0.8. Nothing changes for today's clients.
- Clients, at their own pace, each offering
[1.0.0, 0.9.0, 0.8.0]and reading both shapes until every host they reach is upgraded:- Culture and Paratenic's upstream connector (Paratenic beads);
- Cacophony (Cacophony bead);
ag(agent-utils).
- Paratenic's host serves 1.0 with the same 0.8 fallback for older Culture builds.
- Drop 0.8 only when no client in the fleet offers it alone.
After this: what 1.0 lets pi-daemon say
- Turn errors become resumable error parts. Durable
can retry a turn, so
chat/turnResumemaps onto it. - Subagents and background shells appear as chat background work.
- Chat read and archive state.
- Host extensions are advertised in
InitializeResult._meta, for examplepi-daemon/ui, the session UI slots that let plugins (visual thoughts' backdrop, agent identity images, status chips) reach every client.
What /chord and pi-client do with the 1.0 additions that
have no /chord meaning (canvases, automations, chat moves)
is settled separately; see the CHORD-AUDIT follow-ups in PLAN.md.