Skip to content

fix(server): a delegated child's finished follow-up turn wakes its parent - #213

Merged
lukemaj merged 3 commits into
mainfrom
fix/212-followup-wake
Oct 9, 2026
Merged

lukemaj merged 3 commits into
mainfrom
fix/212-followup-wake

Conversation

@lukemaj

@lukemaj lukemaj commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What: When a parent sends more work to a delegated child and the child finishes that turn, the parent is woken with the result, like the first result.
Why: finalizeAppOwnedSubagent returned once a child had any subagent_result transfer, so follow-up turns only showed up by polling task_status (pingdotgg/t3code#13490).
So what: Reviewed twice (pass); merge when CI is green. The stop-while-running case is tracked in #214. Drop this carried edit and take upstream's when #13490 is fixed there.

Problem

After a parent continues a delegated job with t3_thread_send, the finished follow-up turn never wakes the parent. Upstream: pingdotgg/t3code#13490 (open). #203 deliberately left this out and told agents to wait instead.

Change

  • Orchestrator.ts: dedupe the result transfer per child run instead of per child thread. A follow-up keeps the task's first result (shown as latestTerminal* in task_status), reopens a wake the parent already acknowledged or was delivered, and adds no new parent node or turn item. A cancelled (disposed) task stays quiet. Monitor wakes stay excluded (fix(server): a Monitor wake no longer holds a delegated task's result #211).
  • Review round 1 fixes: a follow-up reopens a cohort the user stopped and is delivered like an async result even for a mode: "wait" task whose delegating turn still runs. The orchestration guidance no longer suggests t3_thread_wait, which would add a second wake.
  • Agent instructions (T3OrchestrationInstructions.ts, toolInstructions.ts, t3_thread_send description) no longer say follow-ups do not wake the parent.
  • Carried edit recorded as child-follow-up-wake in docs/fork-features.md and scripts/fork-upstream-edits.txt, retired when upstream fixes #13490.

Scope and approval

Closes #212. The user decided to fix #13490 in Chromeria now. Upstream PR pingdotgg/t3code#14030 (closed, unmerged) also reopened the task to running; that is left out here.

Review

Verification

  • New test delegated task follow-up turns > wakes the parent once per finished follow-up turn, not for a Monitor wake in DelegatedCompletionDelivery.test.ts. Without the Orchestrator.ts change it fails: expected [ 'run:follow-up-child:1' ] to deeply equal [ 'run:follow-up-child:1', …(1) ]. With it, it passes and also checks: wake reclaimed (claimed), replay of the same run adds no second result, Monitor wake adds no result and no wake.
  • Two more tests (delegated task follow-up turns after the delegating turn) fail without the round-1 fix (expected 'disposed' to equal 'claimed', expected 'acknowledged' to equal 'claimed') and pass with it.
  • OrchestratorMcpToolkit.integration.test.ts wait-mode site asserted zero continuation offers; the completed follow-up now offers one, so it asserts exactly one and resets the counter for the later absence checks.
  • OrchestratorMcpToolkit.integration.test.ts asserted the old contract (follow-up latestTerminalResultContextTransferId: null). It now asserts the new requirement: a non-null transfer distinct from the first result.
  • vp test run on DelegatedCompletionDelivery, prism Recovery integration, OrchestratorMcpToolkit integration, delegatedTaskContinuation, T3OrchestrationInstructions: 5 files, 29 tests passed. Server typecheck: 0 errors. node scripts/fork-features.mjs check: OK. Lint on changed files: only a pre-existing warning (layerUnavailable unused).
  • Not checked: a live run against a real provider.

Elon record

Requirements and who asked: User: wake the parent for every finished follow-up turn, no duplicates, no Monitor-only notices.
Deleted: Reopening task status to running and startup recovery of missed follow-ups (upstream #14030's extra scope).
Bottleneck: The single per-thread dedupe check in finalizeAppOwnedSubagent.
Checked myself: Ran the new test red without the fix and green with it; read the dedupe, acknowledge handler and task_status per-run transfer lookup.

Model and harness: Claude Opus 5.5 in Claude Code (T3 Code).

🤖 Generated with Claude Code

lukemaj and others added 2 commits October 9, 2026 22:35
…rent

finalizeAppOwnedSubagent skipped every turn after a child's first result,
so work sent with t3_thread_send never woke the parent
(pingdotgg/t3code#13490). Each finished child run now transfers once and
reclaims the parent wake; Monitor wakes and cancelled tasks stay quiet.

Closes #212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Oct 9, 2026
@lukemaj

lukemaj commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Independent review of 3b40d6c (effort: high)

Verdict: pass, no blocking findings. The per-run dedupe, the Monitor exclusion, the disposed guard and the task_status shape all match #212. Two non-blocking gaps follow.

Checked

  • Per-run dedupe: contextSourcePointForRun always sets runId, so new transfers are keyed by run. Legacy transfers without runId keep the old once-per-child rule. Every caller holds the parent lock, so the read-then-write cannot race. Replaying the same run is covered by the new test.
  • Monitor-only runs: delegatedTaskProgress drops Monitor runs, so resultRun stays on the run that already has a transfer and dedupe returns early. holdsFinalization excludes Monitor runs the same way (fix(server): a Monitor wake no longer holds a delegated task's result #211 behavior kept).
  • updatedTask: acknowledged/delivered are stripped so the planner can claim a new wake. claimed/pending pass through unchanged and merge into the live delivery. disposed short-circuits in the planner, so there is no wake. summary, the node and the turn item keep the first result.
  • Startup flood: none. The subagent-results recovery query (SQL and needsRecovery) still skips any child that has a result transfer, and the terminal-run listener starts after latestSequence(). The only way an old follow-up gets a transfer after deploy is a pending restart-continuation effect (recoverDelegatedTask), and that is bounded to one per effect.
  • The integration test edit asserts a real requirement: the follow-up run has its own transfer, distinct from the first.
  • Ran from apps/server: pnpm exec vp test run on DelegatedCompletionDelivery, OrchestratorMcpToolkit.integration, Recovery.integration and delegatedTaskContinuation tests: 4 files, 21 passed. node scripts/fork-features.mjs check: OK (26 features).

Findings (non-blocking)

  1. Medium: a stopped cohort turns a follow-up into disposed with no wake (apps/server/src/orchestration-v2/Orchestrator.ts:9848, together with planDelegatedCompletionDelivery at :9560).
    • The planner keys the wake to the spawning run's cohort. Interrupting the spawning run, or the wake run that delivered the first result, sets that cohort to disposition: "stopped" for good (:8671, :8857). disposeDelegatedCompletionCohort leaves the already-acknowledged task alone.
    • Scenario: an async task's result wakes the parent and the parent acknowledges it. The user then stops that wake turn. Later the parent sends a follow-up from a new turn. When the follow-up finishes, updatedTask drops acknowledged and the planner hits cohort.disposition !== "open", so the task is rewritten to disposed and the parent is never woken. That is the stall Delegated child follow-up turns wake the parent #212 targets, and the task row now looks cancelled.
    • Suggested fix: treat a non-open cohort on a follow-up as "keep observedDelivery, no offer", or anchor follow-up wakes to an open cohort. Either way, add a test.
  2. Low: the new instruction text overpromises in two cases (apps/server/src/provider/T3OrchestrationInstructions.ts:12, mcp/toolInstructions.ts:4, the t3_thread_send description).
    • (a) For a mode: "wait" task (settled_only), a follow-up that finishes while the spawning run is still live gets no offer, and its delivery is cleared to undefined. Nothing re-offers it later, so "otherwise the finished follow-up wakes you" is false in that case.
    • (b) For always tasks, t3_thread_wait does not acknowledge the task, so a parent that waits still gets a second wake for a result it has already seen.
    • Either document both cases or acknowledge the task on t3_thread_wait.

Not verified: a failing-first run against the base (no scratch checkout was used). From the code, the old early return on any prior transfer makes the new test's second results assertion fail.

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.9 KiB 20.9 KiB 0 B (0.0%) 29.3 KiB ✅
Codex Live turn messages 2 2 0 (0.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB −24 B (−0.5%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB −24 B (−2.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.2 KiB −41 B (−0.2%) 29.3 KiB ✅
Claude Live turn messages 2 1 −1 (−50.0%) 8 ✅

Baseline: f3ac272 · PR result: 6163680 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Review of #213 found two follow-up turns that still never woke the parent:
one finished after the user stopped the delegating turn (it was marked
disposed), and one of a wait-mode task that finished while the
delegating turn ran. A follow-up now reopens a stopped cohort and is
delivered like an async result. The orchestration guidance no longer
suggests t3_thread_wait, which would add a second wake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj

lukemaj commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Independent review, round 2 (head 6163680d04728dd0e08e4fc47bb52144c4da8bf4, focus 3b40d6c..6163680)

Verdict: pass. No blocking finding. One medium gap remains in the round-1 "stop" fix.

Focused tests at this SHA: pnpm exec vp test run src/orchestration-v2/DelegatedCompletionDelivery.test.ts src/mcp/OrchestratorMcpToolkit.integration.test.ts src/prism/Recovery.integration.test.ts gave 3 files, 22 tests passed.

Findings

  1. Medium: the round-1 stop fix only covers tasks acknowledged before the stop. apps/server/src/orchestration-v2/Orchestrator.ts:9849-9871 with disposeDelegatedCompletionCohort (Orchestrator.ts:2193-2219). On run.interrupt/thread.stop (Orchestrator.ts:8666, 8852), the stop path marks every task of the delegating run disposed, including tasks that are still running. Later finalizations keep disposed: the first-result branch spreads ...task, and the follow-up branch passes task unchanged. The planner then returns early at Orchestrator.ts:9548-9559. Acknowledge does not clear disposed either (Orchestrator.ts:2013-2016). Reopening the cohort never runs for these tasks.
    Scenario: the parent delegates async, and the user stops the parent turn while the child is still running. The child finishes, so the task is now disposed and no wake fires (that part is intended). In a later turn the parent reads task_status, then sends a follow-up with t3_thread_send. The follow-up finishes and the parent is never woken. The new test passes only because it acknowledges before the stop. Per the issue, only task_cancel should keep a task quiet. A stop-disposal and a task_cancel disposal both end up as the same disposed state, so the planner can't tell them apart. Fix: let a follow-up reopen delivery unless the task was cancelled. Either record why it was disposed, or check the child or task for a cancel. Not a regression against main, where follow-ups never wake at all.

  2. Low: a follow-up that finishes while the server is down or crashing is never recovered. The subagent-results recovery query (apps/server/src/orchestration-v2/ProjectionStore.ts:3559-3580, mirrored at :552-566) excludes any child that already has a subagent_result transfer. So startup recovery only handles first results. A follow-up whose terminal event landed without a run of finalizeAppOwnedSubagent gets no transfer and no wake after restart. The flip side is good: deploying this branch does not replay wakes for old follow-ups. If recovery is extended, match it to a missing transfer for the latest result run, not to any transfer at all.

  3. Low / informational: the reopened cohort is persisted, not just used for planning. The planner spreads the overridden cohort into nextCohort (Orchestrator.ts:9596-9603, 9640-9646, 9703-9711), so the parent run's delegatedCompletion.disposition is written back as "open". That also covers a cohort the user disposed with queued-run.cancel (Orchestrator.ts:7714-7721). I checked the effects. Siblings disposed by that cancel or a stop stay quiet, because their task rows are disposed. A live queued delivery for siblings is merged, not overwritten, and its generation is kept. Archived or deleted parents still dispose through the thread check. So I found no clobbering, and waking for new work the parent explicitly sent after a cancelled wake seems acceptable. One side effect: the run-level "disposed" no longer sticks once a follow-up reopens it (Orchestrator.ts:2159). If that matters, plan with the open cohort but persist the original disposition.

Questions from the brief

  • Upstream integration test edit (OrchestratorMcpToolkit.integration.test.ts:1939-1946) protects a real requirement and does not hide a regression. The count runs from the reset at line 1409. So 1 still proves the wait-mode first result offered nothing, while the completed childFollowup offered one. expectOffersToStay(1) also proves the activeChildFollowup that task_cancel interrupted (task disposed) did not offer. Resetting afterwards leaves the later absence checks unchanged.
  • Monitor-only wake runs: unchanged by this round. The result-run selection still goes through delegatedTaskProgress, as in fix(server): agents continue a stopped delegated task in its own thread #204/fix(server): a Monitor wake no longer holds a delegated task's result #211.
  • Duplicate notices for the same run: still guarded by the per-run transfer check at Orchestrator.ts:9819-9826.
  • Instruction change: removing the t3_thread_wait hint resolves round-1 item 2(b). Item 2(a) is fixed by completionWake: "always" for follow-ups and is covered by the new wait-mode test.

@lukemaj
lukemaj merged commit 93dddfb into main Oct 9, 2026
28 checks passed
lukemaj added a commit that referenced this pull request Oct 10, 2026
With #17144 an end the agent never reads before settle no longer holds
the turn; Grok's report arrives in a continuation run instead. That
continuation was named a generic background task, so a delegated child's
Monitor wake counted as new work (#211) and could replace its result or
wake the parent twice (#213). finalizeTurn now names the continuation
after the unread ends' own reports, so a Monitor wake stays one.

Found in review of #216.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lukemaj added a commit that referenced this pull request Oct 10, 2026
…and reasoning effort (#216)

* test(server): replay Grok 1.0.46 background commands that end in the turn

Three hand-ported replay fixtures for the Grok shapes that leave a run
running forever: a TaskOutput poll on a call-* task id and a MultiResult
poll, a KillTask kill, and a task_completed with no poll at all.

Refs #15888

* fix(server): Grok runs finish when background commands end in the turn

Grok 1.0.46 reports background command ends in shapes the adapter missed,
so deferred finalize held the run open forever:

- task_completed before the prompt settles was dropped. It now finishes the
  command row, without marking it read, so an unread end still gets its
  continuation.
- TaskOutput results for call-<uuid>-<n> task ids were ignored.
- TaskOutput MultiResult was not read.
- KillTask kills were not recognized. A kill now ends only the tasks its
  result reports as killed or already exited.

A row with a genuine end no longer reopens when normalization reads its kept
start acknowledgement as running, so a kill or poll with no output also ends
it. A poll of a command task_completed already finished no longer appends its
output a second time.

Fixes #15888

* fix(server): a Grok poll fills a command task_completed ended without output

A task_completed without task_snapshot.output ends the row with only its
start acknowledgement or partial stdout. The agent's later TaskOutput poll
now replaces that with the full output instead of being dropped; a poll that
matches what the row already shows still changes nothing.

* fix(server): Grok ACP threads run at the selected reasoning effort

The Grok flavor's applyModelSelection called applyGrokAcpModelSelection
without the stored reasoning effort, so session/set_model carried only
the model id and a same-model selection was skipped. The generic option
loop could not apply it either: the composer stores the effort under
the capability id reasoningEffort while the Grok session exposes it as
the thought_level option reasoning_effort.

Pass the effort the way text generation already does. On protocol 1 it
rides on session/set_model metadata, with the current effort read from
the session setup; on newer protocols the wrapper writes the session's
thought_level option through setConfigOption and only calls setModel
when the model itself changes.

Fixes #15251

* fix(server): Grok protocol 1 effort baseline follows the last applied selection

Same-runtime switches re-run the flavor against the stored session setup,
which only keeps the model id current, so changing the effort away and back
on protocol 1 compared against the setup-time value and was skipped as a
no-op. Hand the flavor the selection the session was last configured with
and baseline protocol 1 on it; activation still starts from the setup alone.

Also narrows the newer-protocol baseline to a string: the previous commit
left `string | boolean` reaching `currentReasoningEffort` and failed the
server typecheck.

* fix(server): Grok effort write survives model switches and effort-free selections

On newer protocols a model switch can change which efforts the live
`thought_level` option offers; writing a value it no longer advertises, or
one the agent rejects, failed the session open and with it the turn. Re-read
the live option after the switch and, like the generic option loop, skip the
value with a warning so the agent's default applies.

On protocol 1 an effort-free selection between two effort changes made the
baseline fall back to stale setup metadata, so a return to the setup-time
effort was skipped. Once a selection has run on the session, baseline on the
effort it carried; an effort-free selection leaves the live value unknown and
the next requested effort is sent.

* test(server): fork proof for the carried Grok background command fix

Regression test from the stuck Grok child frames (pingdotgg/t3code#15888):
a killed UUID task and a polled call-id task that both end before the
prompt settles no longer hold the finished turn. It fails on 93dddfb
('held') and passes with #17144.

#17144 deliberately finishes a row on a pre-settle task_completed, so the
old 'settle holds for the injected monitor report' test asserted the
removed hold. It now asserts the requirement it protected: one wake, no
second continuation for the late report.

#15319's new test used upstream's testLayer; the fork names it layerTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(fork): record the carried Grok fixes #17144 and #15319

Refs #215

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(server): the regression snapshot uses only known task_snapshot fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(server): keep #17144's fixture prompts module-local for knip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): an unread pre-settle Grok end keeps its report kind

With #17144 an end the agent never reads before settle no longer holds
the turn; Grok's report arrives in a continuation run instead. That
continuation was named a generic background task, so a delegated child's
Monitor wake counted as new work (#211) and could replace its result or
wake the parent twice (#213). finalizeTurn now names the continuation
after the unread ends' own reports, so a Monitor wake stays one.

Found in review of #216.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Henrique Caiano <5713877+hcaiano@users.noreply.github.com>
Co-authored-by: abderbj <115119179+abderbj@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@lukemaj
lukemaj deleted the fix/212-followup-wake branch October 14, 2026 06:24
lukemaj added a commit that referenced this pull request Oct 14, 2026
…rent (#213)

* fix(server): a delegated child's finished follow-up turn wakes its parent

finalizeAppOwnedSubagent skipped every turn after a child's first result,
so work sent with t3_thread_send never woke the parent
(pingdotgg/t3code#13490). Each finished child run now transfers once and
reclaims the parent wake; Monitor wakes and cancelled tasks stay quiet.

Closes #212

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(fork): link child-follow-up-wake to #213

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): a follow-up wakes the parent after a stop or in wait mode

Review of #213 found two follow-up turns that still never woke the parent:
one finished after the user stopped the delegating turn (it was marked
disposed), and one of a wait-mode task that finished while the
delegating turn ran. A follow-up now reopens a stopped cohort and is
delivered like an async result. The orchestration guidance no longer
suggests t3_thread_wait, which would add a second wake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lukemaj added a commit that referenced this pull request Oct 14, 2026
…and reasoning effort (#216)

* test(server): replay Grok 1.0.46 background commands that end in the turn

Three hand-ported replay fixtures for the Grok shapes that leave a run
running forever: a TaskOutput poll on a call-* task id and a MultiResult
poll, a KillTask kill, and a task_completed with no poll at all.

Refs #15888

* fix(server): Grok runs finish when background commands end in the turn

Grok 1.0.46 reports background command ends in shapes the adapter missed,
so deferred finalize held the run open forever:

- task_completed before the prompt settles was dropped. It now finishes the
  command row, without marking it read, so an unread end still gets its
  continuation.
- TaskOutput results for call-<uuid>-<n> task ids were ignored.
- TaskOutput MultiResult was not read.
- KillTask kills were not recognized. A kill now ends only the tasks its
  result reports as killed or already exited.

A row with a genuine end no longer reopens when normalization reads its kept
start acknowledgement as running, so a kill or poll with no output also ends
it. A poll of a command task_completed already finished no longer appends its
output a second time.

Fixes #15888

* fix(server): a Grok poll fills a command task_completed ended without output

A task_completed without task_snapshot.output ends the row with only its
start acknowledgement or partial stdout. The agent's later TaskOutput poll
now replaces that with the full output instead of being dropped; a poll that
matches what the row already shows still changes nothing.

* fix(server): Grok ACP threads run at the selected reasoning effort

The Grok flavor's applyModelSelection called applyGrokAcpModelSelection
without the stored reasoning effort, so session/set_model carried only
the model id and a same-model selection was skipped. The generic option
loop could not apply it either: the composer stores the effort under
the capability id reasoningEffort while the Grok session exposes it as
the thought_level option reasoning_effort.

Pass the effort the way text generation already does. On protocol 1 it
rides on session/set_model metadata, with the current effort read from
the session setup; on newer protocols the wrapper writes the session's
thought_level option through setConfigOption and only calls setModel
when the model itself changes.

Fixes #15251

* fix(server): Grok protocol 1 effort baseline follows the last applied selection

Same-runtime switches re-run the flavor against the stored session setup,
which only keeps the model id current, so changing the effort away and back
on protocol 1 compared against the setup-time value and was skipped as a
no-op. Hand the flavor the selection the session was last configured with
and baseline protocol 1 on it; activation still starts from the setup alone.

Also narrows the newer-protocol baseline to a string: the previous commit
left `string | boolean` reaching `currentReasoningEffort` and failed the
server typecheck.

* fix(server): Grok effort write survives model switches and effort-free selections

On newer protocols a model switch can change which efforts the live
`thought_level` option offers; writing a value it no longer advertises, or
one the agent rejects, failed the session open and with it the turn. Re-read
the live option after the switch and, like the generic option loop, skip the
value with a warning so the agent's default applies.

On protocol 1 an effort-free selection between two effort changes made the
baseline fall back to stale setup metadata, so a return to the setup-time
effort was skipped. Once a selection has run on the session, baseline on the
effort it carried; an effort-free selection leaves the live value unknown and
the next requested effort is sent.

* test(server): fork proof for the carried Grok background command fix

Regression test from the stuck Grok child frames (pingdotgg/t3code#15888):
a killed UUID task and a polled call-id task that both end before the
prompt settles no longer hold the finished turn. It fails on 93dddfb
('held') and passes with #17144.

old 'settle holds for the injected monitor report' test asserted the
removed hold. It now asserts the requirement it protected: one wake, no
second continuation for the late report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(fork): record the carried Grok fixes #17144 and #15319

Refs #215

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(server): the regression snapshot uses only known task_snapshot fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(server): keep #17144's fixture prompts module-local for knip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(server): an unread pre-settle Grok end keeps its report kind

With #17144 an end the agent never reads before settle no longer holds
the turn; Grok's report arrives in a continuation run instead. That
continuation was named a generic background task, so a delegated child's
Monitor wake counted as new work (#211) and could replace its result or
wake the parent twice (#213). finalizeTurn now names the continuation
after the unread ends' own reports, so a Monitor wake stays one.

Found in review of #216.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Henrique Caiano <5713877+hcaiano@users.noreply.github.com>
Co-authored-by: abderbj <115119179+abderbj@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lukemaj added a commit that referenced this pull request Oct 26, 2026
…rk contracts

Upstream's newer Stop, queue and follow-up behavior collided with retained
fork contracts and left 35 focused tests failing.

- A usage-limit continuation the user re-armed after Stop now resumes that
  thread through the retirement guard, which rechecks the arm at commit.
- A failed Codex root still settles at once, but stays reachable so Stop and
  cut-in reach its live child and background processes (fork #332).
- Tests now model the fork's same-task follow-ups (#213/#428), parked
  completions (#431), per-Stop retirement tokens and upstream's message-led
  queue release, instead of upstream's fresh-task and dispose-on-Stop shapes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 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.

Delegated child follow-up turns wake the parent

1 participant