Skip to content

fix(acp): support root session replacement in V2 - #1

Draft
nullStack65 wants to merge 1 commit into
orchestrator-v2-base-3d45b3056from
feat/acp-root-session-adoption-3d45b3056
Draft

nullStack65 wants to merge 1 commit into
orchestrator-v2-base-3d45b3056from
feat/acp-root-session-adoption-3d45b3056

Conversation

@nullStack65

Copy link
Copy Markdown
Owner

What this is

Fork-held slice for the V2 architecture (USE_GENERIC_ACP_V2_WITH_SMALL_GENERIC_FIXES). It adds the generic, provider-neutral root-session-replacement capability the V2 ACP runtime needs so an agent that swaps its root session behind the same connection (omp /fresh) does not go silent.

  • Upstream PR source: pingdotgg/t3code#2829
  • Exact feat(orchestrator): introduce new orchestrator pingdotgg/t3code#2829 head used: 3d45b305632c72cbb8fd8a6dd5077b94c25d7eb5
  • Base snapshot branch: orchestrator-v2-base-3d45b3056 (3d45b3056)
  • Feature branch/head: feat/acp-root-session-adoption-3d45b3056 (9ba3a54d83c0fc4f8317ae605bd6a6a528213396)
  • T3 main at completion: 4749035bda13b4b6260499caedbc0d69a2f60e6f
  • Frozen bespoke reference (unmodified): pingdotgg/t3code#11973 @ 4f9419b72bb92bebc6669dd95f345e1ccdf4bced
  • Not intended for upstream submission until the V2 architecture stabilizes.

Problem

An agent can replace the root session on the same ACP connection. Real omp 18.2.4 behavior, proven live:

  • durable ACP session = A (returned by session/new, addressable, replayable via session/load);
  • the user runs /fresh;
  • every later session/update notification arrives under a new id B, including the direct response to the /fresh prompt;
  • B is not independently addressable: prompts/cancel must keep using A, and session/list only ever shows A;
  • V2 dropped B as a foreign session, so the thread went silent after /fresh.

Design (generic, no provider branches)

AcpSessionRuntimeOptions.adoptRootSessionReplacement (default false) plus onRootSessionReplaced for diagnostics.

  • Durable vs live identity: the durable setup id is never rewritten. Prompts, cancellation, session/load, session/close, model/config requests, and assistant item identity all keep using the durable id.
  • Adoption rule: a new id can replace the live root only when it is first seen while a root prompt is in flight, and at most once per prompt. Foreign ids seen while idle are always rejected (child/background traffic cannot become root accidentally).
  • Projection: adopted notifications are projected back onto the durable id before either the runtime's internal consumer or the adapter sees them, so downstream session-id checks cannot reject the live root.
  • Durable stragglers accepted, stale replaced-live ids dropped: updates on the durable id remain root traffic; a previously adopted live id is dropped once replaced.
  • Wiring: the generic ACP Registry adapter exposes a per-instance opt-in through AcpRegistrySettings.rootSessionReplacement (default false, generic, usable by future agents). No global relaxation for every registry agent. No if agent === "oh-my-pi" anywhere.
  • The opt-in contract is explicit: enable only for agents whose connection publishes a single live root session id and never child/subagent session ids; child traffic must be normalized before it reaches the runtime.

Net change: 6 files, ~+609/-5.

Tests

apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts — new root-session-replacement block (all green, 47/47 total):

  • A strict default: replaced root rejected (text stays root before fresh)
  • B opt-in: A→B adopted, B updates continue, item ids stay in the durable namespace
  • C/E successive replacements A→B→C deterministic; onRootSessionReplaced reports each change once
  • D prompt/cancel requests keep the durable id after replacement
  • F idle cancel while a replaced-root turn hangs targets the durable id (and never B)
  • G durable stragglers accepted, stale replaced-live ids dropped
  • H foreign session while idle never adopted
  • I opt-in off = zero behavior change

Additional green:

  • AcpRegistryAdapterV2.test.ts 3/3 — includes a new end-to-end test through the real registry runtime with the instance opt-in.
  • AcpAdapterV2.test.ts 105/105 (V2 provider adapter runtime policy).
  • OrchestratorReplayFixtures.integration.test.ts 73/73 (V2 replay fixtures).
  • apps/server tsc --noEmit: clean. packages/contracts tsc --noEmit: clean.
  • Targeted vp lint on every changed file: clean. git diff --check: clean.
  • Test-fixture-only platform fix: the registry fixture did not declare a darwin-x86_64 distribution, so the pre-existing E2E test failed on this host. Same failure reproduces on the base snapshot 3d45b3056 (ACP Registry agent fixture-agent has no compatible distribution for darwin-x64), classified as a base/host fixture gap, now fixed in the fixture (no production code).

Live zero-inference proof (real omp 18.2.4, registry PR pingdotgg#613 metadata)

OMP updated to 18.2.4; local binary sha256 780a47a5... matches the exact darwin-x86_64 sha256 in agentclientprotocol/registry#613 (eed36bcaa677749b0bdf24010dfc3883834612c9). Registry JSON served from an injected HttpClient using the exact oh-my-pi/agent.json metadata; commandPath pointed at the local omp; authMethodId: "agent". No model prompt; only local commands (/context, /fresh, /rename).

Through the patched V2 registry adapter (rootSessionReplacement: true):

Direct AcpSessionRuntime with adoptRootSessionReplacement: true:

  • start id A = 01a0b151-102d-...; session/list contains A
  • /freshonRootSessionReplaced { previousSessionId: A, sessionId: 01a0b151-12be-... } (live identity observed without overwriting A)
  • idle session/cancel succeeds on A
  • /context after /fresh succeeds
  • session/load(A) returns A; session/list after load still contains A

Dependencies / not covered here

  • Registry metadata remains agentclientprotocol/registry#613 (oh-my-pi 18.2.4). This branch does not vendor registry metadata.
  • Inference-gated items (model prompt round-trips, thinking/model config application, usage) were intentionally not exercised; this proof stops at zero-inference behavior.
  • Separate follow-ups (not in this slice): ANSI cleanup, thread-title projection, subagent presentation, usage UI, onboarding.

An agent can replace the root session behind the same ACP connection: omp's
/fresh starts a new provider session and publishes every later session/update
under a new id while the original id stays the one session/load replays. The
V2 runtime treated those updates as a foreign child session, so the thread
went silent for the rest of the turn.

Add a generic, default-off runtime capability
(AcpSessionRuntimeOptions.adoptRootSessionReplacement) that adopts a new live
root session id first seen while a root prompt is in flight, at most once per
prompt, and projects adopted notifications back onto the durable setup id.
Prompts, cancellation, session loading, and item identity keep using the
durable session; foreign ids seen while idle stay rejected. The ACP Registry
adapter exposes the opt-in per instance through AcpRegistrySettings.

The frozen bespoke reference is PR pingdotgg#11973; this slice carries only the
provider-neutral adoption mechanism.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant