Regression in muli-agent spawning in 0.113.0

Open 💬 5 comments Opened Mar 11, 2026 by randall-8080
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

codex-cli 0.113.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.4

What platform is your computer?

Linux 6.19.6-2-cachyos x86_64 unknown

What terminal emulator and version are you using (if applicable)?

Ghostty

What issue are you seeing?

0.113.0 introduced a regression in multi-agent handing. Previously, if an agent's developer prompt contained instructions to spawn sub-agents, it would do so. Now what I see happening is that an "Orchestrater" agent is spawned of the correct type, when then spawns another agent of the same type, which then spawns the desired sub-agents.

What steps can reproduce the bug?

Uploaded thread: 019cdaa2-05c5-7c51-97ce-79c3528a2017

  1. Spawn an agent that spawns specialized sub-agents
  2. Codex then spawns an "Orchestrater" (A0) agent that then spawns the same agent (A1) again
  3. Agent (A1) then finally spawns the specialized sub-agents

What is the expected behavior?

The spawned top-level agent uses the developer_prompt as written and then spawns the specialized sub-agents.

Additional information

_No response_

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 5 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #13491

Powered by Codex Action

SproutSeeds · 5 months ago

I have a strong local root-cause candidate for this one, but I’d frame it narrowly.

My current read is that the issue is likely in forked sub-agent handoff rather than generic multi-agent planning.

What seems to be happening:

  • spawn_agent(..., fork_context=true) inherits parent rollout history into the child
  • the child also gets a synthetic handoff telling it to treat the next user message as its task
  • but the parent’s most recent live user directive can still remain in inherited history
  • if that parent directive is itself about delegation/orchestration, the child can reinterpret it as its own instruction and recurse

This also seems consistent with the older related report in #13491.

Locally, I patched the forked-spawn path to:

  • truncate inherited history before the parent’s last user message
  • preserve the parent spawn_agent function-call marker when needed
  • keep the synthetic fork handoff output

That gives the child completed background context without the parent’s active directive.

I added targeted regression coverage for:

  • unit-level forked spawn history shaping
  • rollout truncation before the last user message
  • integration coverage showing the child still receives earlier parent context, but not the current parent turn prompt

Those targeted codex-core tests passed locally.

I also ran a full workspace cargo test. It was not fully green on this machine, but the failures I hit were in unrelated EMFILE rollback-reconstruction tests and local seatbelt sandbox tests, not in the multi-agent fork path.

jif-oai contributor · 5 months ago

This is due to model behaviour. We will study this and see what's the best way to fix.
Truncating in the parent history might actually be sub-optimal...

randall-8080 · 5 months ago

Created a new log file for a slightly different behavior that I just observed where the top-level agent spawned the three specialized agents. Each of the specialized agent spawn three additional specialized agents, for a total of ten agents.

Log: 019cf8a6-81a4-7a22-bf56-98dcc64c270e

SproutSeeds · 5 months ago

Model behavior can still affect whether the child chooses to delegate again.
The forked child history is also leaking a product-side instruction boundary.

  • spawn_agent(..., fork_context=true) snapshots parent rollout into the child
  • the child already receives its own task framing from the synthetic fork handoff and synthetic spawn_agent output
  • on clean upstream main, the second-generation fork test still fails at assert!(!body_contains(&grandchild_request, TURN_1_PROMPT)), so the grandchild request still contains the parent's live "spawn a child and continue" directive instead of only its own spawned task
  • in the targeted local tests, trimming inherited rollout before the newest surviving user-turn segment kept earlier completed context and spawn markers but removed the conflicting live delegation turn
  • that also removes the leaked parent TurnStarted scaffolding instead of cutting only at the raw user-message item
  • re-attaching the specific parent spawn_agent call and synthetic output keeps the fork handoff contract intact after trimming
  • the segment-based version still passes the same first-generation and second-generation fork tests locally

The leaked boundary is the inherited live delegation turn, not generic background context.
The strongest current fix shape is: drop the newest surviving user-turn segment from the forked rollout, then re-attach the specific parent spawn_agent call and synthetic output before handing control to the child.

Patch I validated:
https://github.com/SproutSeeds/codex/commit/a26e5df4b4f1198705d5e20eb1ebabd49601837a

Full branch:
https://github.com/SproutSeeds/codex/tree/sprout/issue-14288-fork-turn-segment