Repository navigation
fix(orchestration): deliver the bootstrap prompt to a child created after the deadline (#1370) - #1375
Conversation
…ion child (#1370) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fter the deadline (#1370) A healthy provider can take longer than the bridge's 30 s create deadline to start (the bundled OpenCode took 43.1 s under load). The caller got "outcome unknown - do not repeat", the tool handler returned without delivering the prompt, and the late answer was adopted as an idle child with no brief and nothing telling the parent. - The tool handler hands the bridge a late-create continuation; the bridge keeps it for the outstanding request and runs it from adoptLateResponse (never for an in-time or failed answer). A throw is recorded as a prompt_delivery_failed incident. - The handler's bootstrap delivery is one function shared by the punctual and late paths (deliver, armPromptWhenReady, mark delivered). - The outcome-unknown reply says a late child receives its prompt automatically and must not be sent it again. Fail-first: bridge and real-MCP-handler tests are red on origin/main. Mutations killed: adoption skipping the continuation, the handler not registering it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts successor q85: the mark is the last step of a delivery that can land long after the create began (a prompt waiting for the composer, or #1370's late adoption). It was addressed with the parent id the tool call captured; after a replacement that id has no window and owns no children, so the mark was lost. Resolved through the alias chain; the combined late-create + replacement path was reproduced against #1375 and passes with this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dy; skip it when the parent is gone (#1375 review a) Review a: (1) on a provider with no readiness gate a late delivery refused as not-ready left the child idle although the parent was told delivery was automatic; the late path now retries on a bounded backoff (about a minute), stops as soon as any prompt lands on the child, and records never-ready; the timeout reply now tells the parent to check orchestration_read_agent before sending. (2) the late continuation could start the brief after the parent closed mid-spawn; adoptLateResponse now checks the parent's lease and skips with an incident. Red at the previous head; the no-landed-check and no-parent-check mutants are red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review disposition (round 1: a, codex at
|
| Finding | Verdict | Disposition |
|---|---|---|
| a1, major: a late delivery refused as not-ready (no readiness gate: OpenCode, Grok) left the child idle despite "delivered automatically" | valid | Fixed: bounded late retries (about a minute), stopping once any prompt has landed on the child, with a never-ready incident. The timeout reply now says to check orchestration_read_agent before sending. Three new system tests, red at the previous head; the no-landed-check mutant is red. |
| a2, major: the late brief could start after the parent closed mid-spawn | valid | Fixed: the parent's lease is checked before the late delivery, which is skipped with an incident. The child is left for the user. Red at the previous head; the mutant is red. |
a survivor: supersedesPendingPrompt |
noted | a judges false correct; the late race it would decide is covered by the landed-prompt check. |
Every orchestration suite passes (bridge 15, runtime 5, timeout 14, bootstrap-pending 20, duplicate-create 16, late-bootstrap 4), and npx tsc -b prints 0 lines. W2's late-warning patch (temp/q85) still applies after #1369 merges.
b (codex at cacd7ec0): FIX-BEFORE-MERGE, fixed at 5f54f8c0:
| Finding | Verdict | Disposition |
|---|---|---|
| b1, major: a parent closing during a retry delay still got its brief delivered | valid | Fixed: isParentAttached is checked before every late attempt, with an incident. Red with the check removed. |
b2, minor: the FAILED fixtures lacked type, masking the !response.ok guard |
valid | Fixed in both fixtures; removing the guard is red. |
b survivor: removing every lateCreates.delete passed |
valid | Pinned: the map empties in time and late; the mutant is red. |
c (codex at cacd7ec0): FIX-BEFORE-MERGE, fixed at ed86637b:
| Finding | Verdict | Disposition |
|---|---|---|
| c1, major: a parent closing during a retry delay | valid | Already fixed in 5f54f8c0 (the same as b1). |
| c2, minor: the timeout reply overpromised delivery | valid | Fixed: it says delivery is attempted if the renderer later confirms the child, names how it can fail, and tells the parent how to check before sending. |
| c survivor: the last retry delay could shrink to 1 ms | valid | Pinned: every wait is checked exactly; the first-delay and last-delay mutants are red. |
Round 1 (a, b, c) is complete.
Round 2 (a, b, c codex at ed86637b): all FIX-BEFORE-MERGE, the final round. Fixed at 24066860.
The round-1 fixes are verified: every targeted mutant is red (the per-attempt parent check, the adoption check, the landed-prompt stop, the late retry, the exact delays).
| Finding | Verdict | Disposition |
|---|---|---|
| a + b, major: a late Claude/Codex readiness waiter could deliver after the parent detached | valid | Fixed: deliverPromptWhenReady takes a shouldDeliver guard, asked when the gate opens, before any write; the late path arms it with the parent-lease check. Red at ed86637b. |
| c, major: a parent send landing BEFORE the late create answer was erased by adoption, so a second brief followed | valid | Fixed: adoption keeps an existing record. The test goes through the real send_prompt; red at ed86637b. |
| c, minor: a reservation collision ended the late loop | valid | Fixed: it retries, re-checking the landed count and the parent. Red at ed86637b. |
b, minor: the stop test bypassed send_prompt's own bookkeeping |
valid | Fixed: it uses the real tool; the bookkeeping mutant is red. |
Every orchestration suite passes, sessionManager.pendingPrompt passes 17/17, and npx tsc -b prints 0 lines. ## After #1369 merged (q85 / q86): 7956d889
- Main merged for a real dependency (
36830732). TheadoptLateResponseconflict keeps this PR's record-preserving set beside fix(orchestration): closed children follow their parent's replacement #1369'snoteCreatedChild. - W2's late-warning patch applied:
orchestration.bootstrap_mark_failedon the late path, and the late create + parent-replacement case. isParentAttachednow resolves through fix(orchestration): closed children follow their parent's replacement #1369's alias chain, so a parent replaced during the late window keeps its brief; only a parent with no live successor is gone. Removing the resolution fails W2's replaced-parent test.
Every orchestration suite passes, and npx tsc -b prints 0 lines. Both review rounds are spent: NEED-MANAGER (manager-verify a/b/c of 7956d889; the round-2 fixes are in 24066860, and the q85 integration is new since).
Manager verification and READY
B6 verified the code at 7956d889 (manager-verify a/b/c, MERGE-READY): a1/b1 (the shouldDeliver guard before the write), c1, c2, the b2 survivor and q85 are fixed, with 3+ mutants killed; 28/28 pass. The body is rewritten: the removed "delivered automatically: do not send it again" quote is replaced by the attempt wording, and the retry, parent-lease, alias-chain and q85 behaviour are described.
merge-gate.sh agent-code 1375 --member --dry: GATE PASS (7956d889, 0 behind main, checks green). READY.
…ttempt; pin the late-answer guard and continuation cleanup (#1375 review b) Review b: the parent was checked once at adoption, so a parent that closed during a retry delay still had its brief delivered; OrchestrationBridge now exposes isParentAttached and the late loop checks it before every attempt. The FAILED late-answer fixtures lacked type, masking the !response.ok guard; and removing every lateCreates.delete survived. All three mutants are red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…very; pin every late retry delay (#1375 review c) Review c: its major (a parent closing during a retry) was already fixed in 5f54f8c. The reply now says delivery is attempted only if the renderer later confirms the child, and names how to check before sending; each late backoff wait is pinned exactly, so shrinking any delay is red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; keep a submission recorded before adoption; retry after a reservation collision (#1375 review round 2) Round 2: (a, b) a late Claude/Codex child's readiness waiter could deliver after the parent detached; deliverPromptWhenReady takes a shouldDeliver guard asked when the gate opens, and the late path passes the parent-lease check. (c) the renderer files the child before its create answer reaches main, so a parent's own send could land first and adoption reset the record, causing a second brief; adoption now keeps an existing record. (c) a reservation collision ended the late loop; it now retries. (b) the stop test goes through the real orchestration_send_prompt. All red at ed86637; the send_prompt bookkeeping mutant is red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-bootstrap # Conflicts: # src/main/orchestration/OrchestrationBridge.ts
…esolve the parent lease through its alias chain W2's patch (temp/q85): orchestration.bootstrap_mark_failed on the late path only, and the combined late-create + parent-replacement case. isParentAttached now resolves the parent through #1369's currentParentId, so a parent replaced during the late window keeps its brief flowing to the successor; only a parent with no live successor is gone. Removing the alias resolution fails the replaced-parent test. Orchestration suites green; tsc 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2c41ab6
into
integration/batch-2026-09-27-p
Closes #1370.
Problem
orchestration_create_agentwaits at most 30 s for the renderer'screate-agentanswer, and the renderer answers only after the child's provider has started. A healthy but slow start exceeds that: the bundled OpenCode took 16.7, 18.3 and 43.1 s under CPU contention (#1367 review a).When it does:
OrchestrationOutcomeUnknownError("do not repeat");bootstrapPromptDelivered: false.The parent ends up with an idle child that never got its brief.
Change (the issue's option a; final head
7956d889)The bridge runs the create's bootstrap delivery when a late answer is adopted.
createAgentaccepts anonLateCreatecontinuation, kept main-side (a function cannot cross into the renderer) while the request is outstanding.adoptLateResponseruns it once, with the enriched child. An in-time answer or a failed late answer never runs it.prompt_delivery_failedincident (create_agent_late_bootstrap), and the adoption incident recordsbootstrapFollows.promptDeliveriesrecord that already exists, so a submission recorded before adoption is not lost.One delivery for both paths. The handler's bootstrap block is one function (
deliverBootstrap) that both the punctual and late paths call, so they cannot drift. That block delivers, armsarmPromptWhenReadywhen the composer is not ready, and marks the bootstrap delivered.Bounded retry while the late child is merely not ready (
deliverLateBootstrap). The retry delays are 2, 4, 8, 16 and 30 s, about a minute in all. Before every attempt it checks two things:promptSubmissionCount > 0, it stops. A parent that checked and sent the brief itself never gets a second copy.isParentAttachedis false, it stops and records acreate_agent_late_bootstrap_parent_goneincident. A parent that closed during a delay must not have its brief start an ownerless child.A reservation collision on the late path also counts as "not ready" and is retried. When the retries run out, it records a
create_agent_late_bootstrap_never_readyincident.The late readiness waiter is guarded too.
SessionManager.deliverPromptWhenReadytakes ashouldDeliver()check that runs right before the write. The late path passes the parent-lease check througharmPromptWhenReady. A refusal writes nothing and returnsdo-not-retry.The parent lease follows the alias chain.
OrchestrationBridge.isParentAttachedresolves the parent through fix(orchestration): closed children follow their parent's replacement #1369's replacement chain (currentParentId), so a parent that was reloaded or replaced still counts as attached.q85 (W2's patch, applied after fix(orchestration): closed children follow their parent's replacement #1369 merged). If marking the late bootstrap delivered fails, an
orchestration.bootstrap_mark_failedincident is recorded, because the journal is the late path's only reader. A test covers the replaced-parent case.The timeout reply promises an attempt, not a delivery. When a prompt was given, the outcome-unknown message now says: "If the renderer later confirms the child, Agent Code tries to deliver its bootstrap prompt automatically, retrying for about a minute while it starts. That can fail (the child never answers, is never ready, or you close first), so check orchestration_list_agents and orchestration_read_agent (promptSubmitted) before sending the brief yourself, or it may arrive twice."
Rejected: option b (a longer create deadline). MCP clients have their own tool-call timeouts, and a Codex parent's are shorter than 120 s.
Plan:
docs/plans/2026-09-27-orchestration-late-bootstrap.md.Tests
src/main/orchestration/timeoutOutcome.test.ts:src/mcp/runtime/orchestrationLateBootstrap.system.test.ts(the real MCP server, tool handler and bridge):send_promptfrom the parent stops the retry;src/main/sessionManager.pendingPrompt.test.ts: a refusedshouldDeliverwrites nothing and returnsdo-not-retry.origin/main, and each review fix was red at the head before it. Killed mutations include:npx tsc -bprints 0 lines. Review rounds 1 and 2 (a/b/c) are fixed, and the manager verified the code MERGE-READY at7956d889.🤖 Generated with Claude Code