pi-daemon1.3

Reference

AHP 1.0 with a 0.8 fallback

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

  1. 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, subscribe and reconnect results. Tests drive the host as a 0.8 client and as a 1.0 client and compare the shapes ([bd-91acd4]).
  2. 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).
  3. 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.
  4. 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.
  5. 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

  1. 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.
  2. 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).
  3. Paratenic's host serves 1.0 with the same 0.8 fallback for older Culture builds.
  4. 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/turnResume maps onto it.
  • Subagents and background shells appear as chat background work.
  • Chat read and archive state.
  • Host extensions are advertised in InitializeResult._meta, for example pi-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.