Skip to content

feat: replay recorded subagent conversations - #684

Merged
benbrandt merged 3 commits into
mainfrom
feat/subagent-transcript-replay
Oct 26, 2026
Merged

benbrandt merged 3 commits into
mainfrom
feat/subagent-transcript-replay

Conversation

@benbrandt

@benbrandt benbrandt commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

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. Official TypeScript SDK c7e07388feed7a530f8211d0dae4951cb6d26d09 / published 2.0.2 is runtime evidence, not the normative contract.

@benbrandt
benbrandt merged commit bbe14f6 into main Oct 26, 2026
6 of 7 checks passed
@benbrandt
benbrandt deleted the feat/subagent-transcript-replay branch October 26, 2026 13:44
benbrandt added a commit that referenced this pull request Oct 26, 2026
## Summary

Step 7 of the incremental extraction tracked in #655, directly against
current `main` after #684. This is read-only configuration reporting,
not historical work outcomes or child controls.

- Extend standard child configuration from model-only reports to
complete reported model/reasoning-effort snapshots. Every select offers
only its observed current value; neither parent values nor requested
spawn arguments supply child values.
- Preserve the existing model-source precedence and model labels. Newer
observations remove unset/unavailable effort, including empty snapshots
when no fields remain reportable. A model change cannot carry the
previous model's effort.
- Keep recorded configuration historical. Reattachment drops cached
confirmation; fresh observations and verified, loaded provider metadata
establish current configuration after the actual response write,
including whole JSON-RPC batches.
- Run optional metadata reads outside the notification lane and rejoin
that lane only to publish. Newer observations, unload/close, or another
attachment invalidate late results. Reads verify the physical child ID
and immediate parent without loading or changing the child.
- Ignore foreign runtime starts for history-only children; copied
associations do not establish current configuration or ownership.

V1 standard opt-in, v2 defaults, empty child capabilities, mutation
restrictions, and AIR's existing contract remain unchanged. AIR's only
source change is exporting the existing source-priority constant for
reuse. Dependencies and generated app-server types are untouched.
Existing AIR and parent-session snapshots have no changes.

## Review size

9 files, +653/-36 lines. About four fifths of the additions are
tests/snapshots. Runtime changes stay within the standard router, its
server wiring, and a small shared snapshot builder used by live and
historical reporting.

## Validation

- `npm run typecheck`
- `npm test`: **2,028 passed**, 39 skipped. Includes **33 new v1/v2 wire
regressions**, with one v2-only batch case skipped on v1.
- `npm run build`
- `npm run test:integration`: **15 passed** against real Codex 0.164.0
and a local mock Responses API. Native child tests now compare reported
effort with the child's actual Responses request, both initially and
after parent reattachment.
- `npm run bundle:all`: all six platform targets compiled.
- `npm run codex-test -- -p 'Reply with exactly child-configuration-ok.
Do not read files or use tools.' -c <isolated-scratch-directory> -o
summary`: live-model smoke completed with `end_turn`.
- `git diff --check`

Wire tests cover complete singleton snapshots,
effort-only/unknown/cleared values, model changes, source precedence and
pre-announcement buffering, no-op mutation rejection, changed and
unchanged reattachment snapshots, full and no-replay attachment, delayed
batches, pending reads without blocked output, newer native settings,
late reads across attachments/unload/close, untrusted metadata, and
historical-only configuration.

The full live-model E2E suite was not run. Cross-platform bundles were
compiled, not executed on each target OS.

## Protocol / reference check

- Specification `main` at `53620d0b325d6815b3cff1ca7e909351d488fdf0`:
[RFD child
configuration](https://github.com/agentclientprotocol/agent-client-protocol/blob/53620d0b325d6815b3cff1ca7e909351d488fdf0/docs/rfds/subagents.mdx#L252-L285)
supplies the proposed child reporting, singleton-choice, and
reattachment semantics. Existing draft configuration notifications
require complete state in
[v1](https://github.com/agentclientprotocol/agent-client-protocol/blob/53620d0b325d6815b3cff1ca7e909351d488fdf0/docs/protocol/v1/draft/session-config-options.mdx#L337-L368)
and
[v2](https://github.com/agentclientprotocol/agent-client-protocol/blob/53620d0b325d6815b3cff1ca7e909351d488fdf0/docs/protocol/v2/draft/session-config-options.mdx#L333-L382).
- TypeScript SDK `main` at `c7e07388feed7a530f8211d0dae4951cb6d26d09`,
with the installed SDK **2.0.2** used for real wire decoding/schema
checks. SDK runtime evidence is separate from the specification; the
existing actual-write response boundary and ordered session-transition
helper are reused, not replaced.
- Codex's generated `Thread` metadata documents model/effort as current
configured values when loaded, or persisted values otherwise. Readback
accepts only loaded, correctly parented children; persisted metadata
alone does not confirm current configuration.

Historical work outcomes remain the next extraction. This PR does not
merge or replace #655 wholesale.
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