Skip to content

feat: add live standard ACP subagent sessions - #682

Merged
benbrandt merged 1 commit into
mainfrom
feat/standard-subagent-live-core
Oct 26, 2026
Merged

benbrandt merged 1 commit into
mainfrom
feat/standard-subagent-live-core

Conversation

@benbrandt

Copy link
Copy Markdown
Member

Scope: standalone live core

Fifth incremental extraction from #655, on current main (091f9b2), after #676, #677, #680, and #681 landed. This is the larger atomic feature slice discussed in the split plan: 43 files, +2,898/-181, including 1,573 added test/snapshot lines and 1,301 added runtime lines. No dependency or generated app-server files change.

Included

  • Standard associations for opted-in non-AIR v1 clients and non-AIR v2 clients. AIR retains its existing opt-ins, private updates, generation identities, and fallback.
  • One opaque conversation ID across assignments, idle work, and handler replacement. Announce immediate parents before children; buffer unresolved ancestry rather than flattening it.
  • Child-owned streamed-item/tool state, plans, titles, permissions, and elicitation. Accepted URL completion state survives parent attachment changes; late approval replies are rejected locally when actual work ends.
  • Empty child capabilities and an allowlist boundary for all client mutations, including private extensions and raw provider IDs. Known raw IDs are restricted even while announcement publication is backpressured. Parent cancellation cascades to observed live children; an actual ending produces idle, observability loss produces unknown.
  • Association-only replay and safe reattachment: current provider parentage must match through known ancestors, inactive owned children receive future events without loading them, and copied forks cannot steal the original runtime. Parent history still replays normally; an explicit advisory says recorded child transcripts are not included.
  • Current child snapshots follow the real successful wire write, including complete JSON-RPC batches. Same-parent setup execution is serialized without waiting on each other's shared batch delivery. Closing a parent fences queued reopen attempts.
  • Preserve the model reporting already landed in feat: report the model of each native subagent session #678: reuse its source priority and read-only model option helper. Model observations are not guessed from spawn arguments or parent values; buffered older settings cannot replace newer observations.

Explicitly deferred

No recursive child transcript replay, history/live item reconciliation, recorded historical work outcomes, new configuration readback/effort reporting, directed-session messages, or child mutation controls. Those remain separate changes after this live core is reviewed and landed.

The current RFD explicitly allows live support without complete child history. Recoverable associations are replayed truthfully; omitted transcripts are not silently presented as complete.

Review order

  1. Contract selection, connection-owned identities/restrictions, and ACP mutation dispatch.
  2. Live router admission, child-owned rendering/work/request state, and existing model reporting.
  3. Provider subscriptions and ownership verification; no parent cwd/MCP/instruction overrides when rejoining a child.
  4. Conversation-owned interaction handlers and request cancellation scopes.
  5. Session setup lane, batch-write freshness boundary, and association-only replay.
  6. Behavior-focused tests and wire snapshots for both versions.

The async queue-completion gap found while planning the split is fixed here: a session transition waits for async callbacks and propagates rejection without breaking the notification lane. A regression reproduces both ordering and rejection behavior. The old #655 reference helper must not be copied back over this implementation.

Validation

Final validation used main's latest lockfile (including #679):

  • npm run typecheck
  • npm run build
  • npm test: 1,959 passed, 38 skipped
  • Focused live/wire/queue suites: 44 passed, 2 v2-only cases skipped on v1
  • npm run test:integration: 15 passed — eight new real-Codex 0.164.0 cases cover active-child reattachment, fork identity/ownership isolation, parent cancel/close, and child terminal output/exit across later parent prompts, on v1 and v2, using a local scripted model
  • npm run bundle:all: all six targets compile
  • Real live-model no-tool smoke completed with end_turn
  • git diff --check

Existing AIR and parent-session golden files are unchanged. The fixed-command terminal fixture alone uses full-access mode to avoid CI hosts without sandbox namespace support; production sandbox settings are unchanged. The full live-model E2E suite and execution of every OS bundle were not run.

References

@benbrandt
benbrandt merged commit fe3ae92 into main Oct 26, 2026
6 checks passed
@benbrandt
benbrandt deleted the feat/standard-subagent-live-core branch October 26, 2026 13:01
benbrandt added a commit that referenced this pull request Oct 26, 2026
## Scope

Sixth standalone extraction from #655, directly on current `main`
(`fe3ae92`) after the live core #682 landed. **16 files, +864/-50**,
mostly direct tests and snapshots. No negotiation, capability,
dependency, or generated app-server changes.

Enhances only the existing standard-subagent path: non-AIR v1 still
requires client opt-in; non-AIR v2 uses it by default. AIR retains its
separate replay and identity rules.

## Changes

- Follow both recorded `subAgentActivity` and collab spawn formats,
replaying each conversation once, recursively under its immediate
parent. Resolve recoverable reversed ancestry, bound cycles, and never
guess or change parentage.
- Read legacy or paginated child items lazily. Current-provider
ownership verification and live subscription restoration happen before
item reads. A retained transcript remains displayable without granting
ownership or current work state.
- Send all selected history before the load/resume response, preserving
the live core's actual batch-write freshness boundary.
- Reconcile tool/rendering bookkeeping before suppressing duplicate
presentation. Replayed completed tools cannot be failed by later cleanup
or resurrected by stale running snapshots. Live completion bookkeeping
still runs, and completed runnable items do not leave a live permission
incorrectly reported as running.
- Restore in-progress command classification without resetting
streamed-byte tracking. Duplicate live starts do not reset the reporter,
and completion does not resend already streamed output.
- Report known gaps once at the parent while continuing available
sibling replay. Reject mismatched thread identities. Empty history alone
invents no gap, ending, failure, or runtime.

### Conservative limits

Agent-owned input is not reinterpreted as human input. Incomplete text
snapshots have no safe delta cursor and are omitted rather than
duplicated over newer buffered deltas; unfinished command replay carries
metadata rather than previously streamed bytes. Only renderers with a
safe partial form are used for unfinished items. Open-ended provider
status strings (such as image generation status) are not treated as
completion enums: those items use recorded completion metadata instead,
with legacy and paginated regressions. These omissions produce an
advisory. Complete retained content is replayed normally.

Historical work-state outcomes, directed-session messages, new child
controls, and broader configuration reporting remain deferred. Existing
provider-parentage checks still prevent copied fork history from
stealing the original runtime.

## Coverage

**36 new v1/v2 cases** cover both spawn formats, one visit per
conversation, nested/reversed ancestry, paginated item ordering,
missing/mismatched snapshots, completed-tool reconciliation,
running-command restoration, incomplete-text overlap, stale running
history, live permissions, cyclic records, close-during-read, and
no-replay behavior. Wire assertions verify recorded content precedes the
response; no recorded permission or elicitation request is reissued.

Real-Codex integration cases now reload finished children and assert
that their retained answers replay under the same child IDs, on v1 and
v2. The existing fork/cancel/close/terminal-lifetime checks remain in
place.

## Validation

- `npm run typecheck`
- `npm run build`
- `npm test`: **1,995 passed**, 38 skipped
- Focused live/replay suites: **77 passed**, 2 v2-only cases skipped on
v1
- `npm run test:integration`: **15 passed**, with the real Codex 0.164.0
app-server and a local scripted model
- `npm run bundle:all`: all six targets compile
- Live-model no-tool smoke completed with `end_turn`
- `git diff --check`

Existing golden files are unchanged. One live-core assertion changes
intentionally: an empty retained transcript no longer produces the
blanket “child transcripts are not included” advisory. New replay cases
verify advisories for actual failures and incomplete/omitted content.

The full live-model E2E suite and execution of every OS bundle were not
run. Native integration uses the default provider history store;
explicit paginated-branch coverage uses SDK-level fixtures.

## Contract references

ACP specification `53620d0b325d6815b3cff1ca7e909351d488fdf0`:
[best-effort replay, per-session identity/order, gaps, and
freshness](https://github.com/agentclientprotocol/agent-client-protocol/blob/53620d0b325d6815b3cff1ca7e909351d488fdf0/docs/rfds/subagents.mdx#L713-L836).
Official TypeScript SDK `c7e07388feed7a530f8211d0dae4951cb6d26d09` /
published 2.0.2 is runtime evidence, not the normative contract.
SeanLF added a commit to SeanLF/claude-code-subagent-harnesses that referenced this pull request Oct 28, 2026
… own name and end

2.3.0 sends subagents in a draft form ACP's schema rejects, so a nested agent
was named "subagent" and stayed open until the via agent's run ended. The
standard subagent_update landed 14 minutes after 2.3.0 was cut
(agentclientprotocol/codex-acp#682); preview.9 has it, with codex 0.165.0.

Sean's call: preview.9 is inside the one-day release-age cutoff (24 minutes
old at the pin). Checked live through the relay: a subagent's standard
announcement, running and idle; a steer; an interrupt; a resumed session.
The lockfile changes only codex-acp and codex's own packages, all hashed, no
install scripts. Back to a release when 2.3.1 ships; Dependabot opens it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0131zPk82o4yhGkjvhSWGU4o
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.

1 participant