Stale MultiAgentV2 branch in legacy completion watcher obscures the active completion path
Summary
The legacy subagent completion watcher still contains a MultiAgentV2 delivery branch, even though normal V2 spawn/resume paths intentionally do not start that watcher. The active V2 completion path now lives in the session terminal-turn lifecycle instead.
This leaves a semantically stale branch and a direct unit test that can make changes appear correct without exercising production V2 routing.
Current behavior on main
AgentControl::maybe_start_completion_watchercontains achild_uses_multi_agent_v2branch that sends anInterAgentCommunicationmessage.- Normal V2 spawn and resume paths skip
maybe_start_completion_watcher. - V2 child completion is actually forwarded by the session terminal-turn path (
maybe_notify_parent_of_terminal_turn/forward_child_completion_to_parent). multi_agent_v2_completion_queues_message_for_direct_parentcalls the private watcher directly, so it covers a synthetic path rather than the normal V2 lifecycle.
I confirmed the mismatch by changing only the watcher's V2 trigger_turn behavior: the direct watcher unit test passed, while an integration test that spawned a real V2 child and completed its turn did not observe the corresponding automatic parent turn. The integration path only changed behavior when the session terminal-turn forwarding path was changed.
The watcher itself is not dead code: V1 still uses it. The concern is specifically the V2 branch inside the legacy watcher and the misleading V2 unit coverage around it.
Why this matters
- It is easy to patch the wrong completion path when investigating V2 notification or wake-up bugs.
- The watcher and session implementations can drift in delivery semantics such as
trigger_turn. - A passing direct unit test can give false confidence while the production V2 path remains unchanged.
- This obscures work on #15723, where the desired wake-up behavior needs to be implemented on the active path.
Related history and issues
- #15570 introduced the custom MultiAgent V2 watcher.
- #16308 added one-shot end-of-turn forwarding in the session lifecycle, which is now the active V2 completion path.
- #15723 requests durable completion notification and wake-up behavior.
- #33061 reports the opposite failure mode: a late subagent completion causing an extra user-visible response even with
trigger_turn: false.
Proposed direction
- Make
maybe_start_completion_watcherexplicitly V1-only and remove its V2-specific branch, arguments, imports, and direct V2 watcher test where they are no longer needed. - Replace the synthetic V2 watcher test with integration coverage that exercises spawn -> child terminal turn -> parent delivery through the session path.
- If V1 and V2 need shared code, share construction of a bounded, idempotent completion receipt rather than duplicating lifecycle ownership.
- Treat wake-up policy as a separate decision from this cleanup. A possible policy is:
- enqueue one durable terminal receipt;
- wake only when the parent is idle and has expressed that it is waiting for child work;
- defer or coalesce while the parent is busy;
- avoid unconditional
trigger_turn: true, given the duplicate-response scenario in #33061.
Design questions
- Should completion wake-up depend on explicit parent wait/join state rather than a fixed
trigger_turnvalue? - Should V1 watcher delivery and V2 session delivery share a
CompletionReceiptabstraction while keeping their lifecycle triggers separate?
2 Comments
I reproduced this as an A/B path test on
c7f21a852cc35c66b7936963cab2c80d6041a09c.Static call-path evidence
maybe_start_completion_watcheronly when the effective version is not V2.maybe_notify_parent_of_terminal_turnandforward_child_completion_to_parent.Dynamic test setup
I added a local
codex-coreintegration test namedmulti_agent_v2_completion_starts_follow_up_turn_when_parent_idleusing the existing mock response helpers:spawn_agenttool.child doneafter a 300 ms delay./responsesrequest waits up to 6 seconds for anagent_messagecontaining the child'sFINAL_ANSWERenvelope.A/B result
| Experiment | Watcher
trigger_turn| Session terminal pathtrigger_turn| Result ||---|---:|---:|---|
| A: change only the legacy watcher and update its direct unit expectation |
true|false| direct watcher unit test PASS; real V2 integration test FAIL twice withautomatic follow-up request was not sent|| B: restore the watcher and change the session terminal path |
false|true| the same real V2 integration test PASS |Commands and observed summaries:
This demonstrates the routing/test-coverage mismatch. It does not establish that unconditional
trigger_turn: trueis the correct product policy; #33061 is still an important counterexample for that design decision. I can publish the integration-test PoC on a fork branch as a follow-up so the exact test can be fetched and run unchanged.Scope correction: the previously linked
completion-wakebranch belongs to the behavior discussion in #15723, not to this watcher-cleanup issue. The runnable PoCs are now split.#33468 cleanup PoC
StatPan/codex:poc/multi-agent-v2-watcher-cleanup7d67db6e91df92a51ea40a9096af04759fb25101openai/codex:main...StatPan:poc/multi-agent-v2-watcher-cleanup(ahead 1,behind 0when published)This branch contains no wake-policy or session-terminal-path change. It only:
MultiAgentVersion::V1;child_agent_pathplumbing;The last point captures a nuance found during cleanup: the stale branch is skipped by normal V2 spawn/resume, but it was still reachable when the watcher could not look up a child because
Nonedefaulted to “uses V2.” Since watcher attachment is already V1-only, that fallback could misclassify a missing legacy child and send the wrong envelope type.Validation:
All five executed test cases passed (the plaintext V2 test covers both completed and terminal-error scenarios), and
git diff --checkpassed. No upstream PR was opened.The separate wake experiment is tracked under #15723 and is intentionally not part of this branch.