Skip to content

refactor: replay historical child outcomes - #686

Merged
benbrandt merged 1 commit into
mainfrom
feat/subagent-historical-outcomes
Oct 26, 2026
Merged

benbrandt merged 1 commit into
mainfrom
feat/subagent-historical-outcomes

Conversation

@benbrandt

Copy link
Copy Markdown
Member

Summary

Final planned extraction (step 8) from #655, directly against current main after #685.

  • Replay the latest recorded child turn's normal completion, cancellation, or failure as historical idle before the load/resume response, using the same stop-reason/error mapping as live turn endings.
  • Emit no outcome for missing or still-running turn metadata. Do not substitute an older completed turn for a latest running turn, or infer endings from activity-item kind, missing content, or runtime loss.
  • Reuse the legacy transcript's turn payload without a second full read. Paginated history adds one optional latest-turn summary read (limit: 1, descending, no items); an unavailable summary retains available transcript content and uses the existing history-gap advisory.
  • Publish historical state directly, without changing the live foreground cache, ending current interactions, establishing runtime ownership, or enabling mutations. Parent closure fences late summaries.

V1 opt-in gating, v2 defaults, AIR behavior, current configuration reporting, child identities, ancestry, and empty capabilities remain unchanged. Dependencies and generated app-server types are untouched. Existing AIR and parent-session golden transcripts are unchanged. Two existing v2 child-replay snapshots gain the intended historical state_update.

Review size

15 files, +422/-22 lines. Most additions are tests/snapshots; runtime changes touch only the history reader, standard replay dispatcher, and standard child router. The router shares one explicit terminal-status mapping between live and historical reporting, but historical publication bypasses the live state cache.

Validation

  • npm run typecheck
  • npm test: 2,064 passed, 39 skipped, including 36 new v1/v2 outcome regressions.
  • npm run build
  • npm run test:integration: 15 passed with real Codex 0.164.0 and a local mock Responses API. The v1/v2 completion cases now close the parent and cold-load it before checking the recorded ending, avoiding a cached live ending as a false positive.
  • npm run bundle:all: all six targets compiled.
  • npm run codex-test -- -p 'Reply with exactly historical-outcomes-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 regressions cover both history stores, completion/cancellation/failure, unavailable or running final turns, response ordering, no mutation/interrupt authority, independent paginated summary failures, latest-turn selection, a subsequent non-replaying resume, newer live permissions and actual work completion, nested ancestry and repeated spawn records, late summaries after close, and failures without details.

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

Specification / reference check

  • Current specification main, 5a5ab96ea8e0f6d3d5c5d7de3a7d29a87c79ddd2: the subagent RFD replay rules and freshness boundary distinguish historical pre-response state from confirmed current activity and controls, and prohibit inventing outcomes from missing history.
  • The explicit replayed-idle clarification is still on the pending rfd-replayed-subagent-outcomes branch, not specification main. It permits keeping a recorded idle outcome visible without treating it as current activity or authorization. This implementation is compatible with both versions and requires no new wire fields.
  • TypeScript SDK main, c7e07388feed7a530f8211d0dae4951cb6d26d09, checked separately as reference/type evidence. Installed SDK 2.0.2 performs real wire decoding in the regression tests; this change reuses the existing v1/v2 state publication and actual response-write freshness boundary.

This finishes the eight planned extraction scopes once merged; it does not merge the old combined #655 wholesale. Child mutation controls and directed-message extensions remain outside this work.

@benbrandt benbrandt changed the title feat: replay historical child outcomes refactor: replay historical child outcomes Oct 26, 2026
@benbrandt
benbrandt merged commit 9091b73 into main Oct 26, 2026
7 checks passed
@benbrandt
benbrandt deleted the feat/subagent-historical-outcomes branch October 26, 2026 15:29
benbrandt added a commit that referenced this pull request Oct 26, 2026
## Summary

Small replay omission found while auditing the completed #655 extraction
after #686 merged. All eight planned slices are already on `main`; this
is a narrowly scoped correctness follow-up, not another feature tranche.

A legacy `spawnAgent` record can establish a coordinator's identity and
parent without an agent path. Later replay may supply that coordinator's
path and newly recorded nested child activity. Previously
`announceHistory` returned immediately for the known coordinator,
discarding the available path. The nested activity then could not
resolve its immediate parent, so valid descendant history was omitted
with a gap advisory.

Fill a known child's **missing** normalized path from recorded activity
before descending into its history. Do not replace an already observed
path with older history. Identities, established parent links, titles,
runtime ownership, capabilities, AIR behavior, and v1/v2 negotiation
remain unchanged.

**2 files, +39/-1 lines. No snapshot changes.**

## Regression evidence

- Four v1/v2 warm/cold replay cases failed on current `main` before the
fix: a live or restored pathless coordinator prevented newly recovered
descendant text from replaying.
- Those four cases now replay the text under the correct immediate
parent without a false gap.
- Two additional v1/v2 cases preserve an already observed parent path
when an older spawn record disagrees.

## Validation

- `npm run typecheck`
- `npm test`: **2,070 passed**, 39 skipped
- `npm run build`
- `npm run test:integration`: **15 passed** against real Codex 0.164.0
with a local mock Responses API
- `npm run bundle:all`: all six targets compiled
- `git diff --check`

The full live-model E2E suite and an additional live-model smoke test
were not run for this fix. Cross-platform bundles were compiled, not
executed on each target OS.

## Audit context

The remaining old #655 diff largely reflects replaced implementations,
differently organized tests/snapshots, and intervening trunk work such
as native model reporting. In particular its older queue-completion
helper and model implementation must not replace the corrected trunk
versions. The combined PR should not be merged wholesale.

Checked current specification revision
`c0897908cb2305e89203218c348d69ab4acf9d61`. This fix retains the [RFD's
truthful identity, parentage, and parent-before-child replay
ordering](https://github.com/agentclientprotocol/agent-client-protocol/blob/c0897908cb2305e89203218c348d69ab4acf9d61/docs/rfds/subagents.mdx#L724-L755);
it adds no wire fields or mutation authority.
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