Skip to content

feat: support standard ACP subagent sessions - #655

Open
benbrandt wants to merge 5 commits into
mainfrom
feat/standard-subagent-associations
Open

benbrandt wants to merge 5 commits into
mainfrom
feat/standard-subagent-associations

Conversation

@benbrandt

@benbrandt benbrandt commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Summary

Implement the first coherent slice of the unstable standard ACP subagent contract, independently of AIR's existing private contract.

  • Use standard associations for opted-in non-AIR v1 clients and non-AIR v2 clients. Preserve AIR's updates, generation-based identities, and opt-out fallback without emitting both contracts.
  • Keep one opaque child session identity across delegations, idle work, and parent replay. Announce immediate parents before children, buffer unresolved ancestry instead of flattening it, and namespace copied fork histories separately.
  • Report child work independently of conversation lifetime and the parent's foreground prompt. Observed endings become idle; lost observability becomes unknown, not a fabricated terminal failure. V2 also gets child-scoped native state updates.
  • Route permissions and elicitation under the child ID. Keep request scopes alive through parent replay, withdraw them at actual work boundaries (even under output backpressure), and reject late replies locally even if a client ignores cancellation.
  • Advertise empty child capabilities and reject unadvertised session mutations, including private extensions and raw-provider-ID bypasses. Direct child cancellation is ignored; parent cancellation cascades to observed live descendants.
  • Replay each child conversation once, deduplicate overlapping completed items, and publish fresh work snapshots after the replay response. Serialize standard same-parent attachment operations, then release fresh child traffic after the latest setup response is written, including batched responses. Keep close/cancel independent and fence queued setups after close.
  • Keep child titles, plans, and unfinished tools separate from the parent. Child Markdown plans use authoritative completed snapshots. Agent-owned child input is not misreported as ordinary user input.

Review corrections

  • Child approval, elicitation, and accepted-URL completion state now belong to the child conversation, not the parent's current prompt or resume handler.
  • Replay release observes successful SDK wire writes rather than the next event-loop turn. Delayed/failed batch members cannot cause pre-response fresh snapshots, and two same-parent resumes in one batch do not deadlock.
  • Existing nested-child metadata uses the same ancestor-announcement gate as child events. The regression fails on both protocol versions without the gate.
  • The root Vitest configuration is JavaScript ESM, fixing the application TypeScript root-directory failure while retaining owned-test discovery.

Second review: lifecycle corrections

  • Restored child delivery validates current provider parentage through the ancestor chain. Loaded owned runtimes are rejoined without execution-context overrides. Owned inactive children get routes without being loaded; copied foreign fork history cannot steal the original runtime's events or authorize interrupts.
  • Child item-rendering state now survives parent handler changes, including terminals, streamed-output bookkeeping, message phases, and item presentation tracking.
  • Standard child admission precedes tool/terminal cleanup as well as normal rendering. Setup transitions share the session notification lane; replay snapshots share live state-publication ordering.
  • Replayed tool snapshots reconcile persistent tracking before duplicate live completions are suppressed. Outstanding-tool cleanup is turn-scoped so an old completion cannot fail newer work.
  • Both recorded spawn formats use the same child-history traversal.
  • Live child requests retain foreground/action tracking during replay, including a new native turn arriving while presentation remains buffered.

26 permanent lifecycle regressions cover these paths on both protocol versions, plus real-Codex terminal-lifetime tests. Existing AIR golden transcripts are unchanged. The terminal fixture uses full-access mode only for its fixed local script in an isolated workspace, avoiding CI hosts that cannot create sandbox network namespaces; production sandbox policy is unchanged.

CodeQL correction

Unexpected mock response-handler exceptions are logged server-side and return only a fixed HTTP 500 message. Regression tests cover synchronous exceptions, rejected stack-trace strings, and recovery on the next request. No production adapter behavior is changed.

Deliberate limits / next package

  • No child mutation controls are advertised yet.
  • Dedicated session_message / directed-message reporting remains follow-up work; ordinary runtime operation reports remain available.
  • Child history and missing ancestry remain best-effort. Copied history alone never authorizes interrupting the original runtime.
  • The full live-model E2E suite was not run, and cross-platform bundles were compiled, not executed on every target OS.

Validation

  • npm run typecheck
  • npm test: 1,946 passed, 39 skipped
  • npm run build
  • npm run test:integration: 15 passed, including actual Codex 0.160.1 active-child replay, fork identity isolation, cancellation, close, and child terminal completion across independent parent prompts through both ACP versions with a local mock model
  • npm run bundle:all: all six targets compile
  • npm run codex-test: live-model no-tool smoke test completed with end_turn
  • git diff --check

Includes 75 focused selection/wire/lifecycle tests (72 run; 3 v2-batch cases skipped on v1), four response-delivery tests, and a foreground-state regression, with golden transcripts for both protocol versions. Existing AIR snapshots are unchanged.

The parallel validation run also reproduced a test-discovery bug: Codex downloaded plugins containing tests into an integration fixture under tmp/. Vitest now discovers only the repository-owned src/__tests__/ and scripts/ suites, using its default filename patterns, instead of collecting generated third-party fixtures.

References checked

  • ACP specification checkout at b4f0b13d76b24c936862d35adfa2ca54d80b0897: unstable subagent association and routing design, replay freshness, and restricted operations.
  • TypeScript SDK checkout at c2b1a8633745dd859a2732b4e17d5ed61e869bb6: runtime response/cancellation behavior, alongside published SDK models and real wire decoding in the tests. The specification checkout is the protocol source of truth; the SDK is implementation evidence.

Comment thread src/__tests__/integration/mock-responses-server.ts Fixed

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants