Regression: MultiAgentV2 child threads are read-only, preventing direct user correction and steering

Open 💬 1 comment Opened Jul 17, 2026 by ignatremizov

What version of Codex CLI is running?

Upstream main at 1fc15cc9a98e065eb02b601342243e9af73d315b, after #33841 (Make parent-owned sub-agent threads read-only in the TUI) (aka post-0.145.0). The underlying app-server rejection was introduced earlier by #27173.

What subscription do you have?

Pro 20x. Not subscription-specific.

Which model were you using?

Not model-specific. This affects any parent using MultiAgentV2 thread-spawn subagents (i.e., GPT-5.6-Sol and GPT 5.6-Terra).

What platform is your computer?

Not platform-specific.

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

Not terminal-specific.

Codex doctor report

Not applicable. This is an explicit app-server and TUI policy in current upstream source rather than an environment-dependent failure.

What issue are you seeing?

MultiAgentV2 child threads can be opened and inspected in the TUI, but they are now deliberately read-only. After explicitly switching to a child thread, the composer says Viewing sub-agent — direct input is disabled; submitting a correction is blocked locally. App-server clients cannot bypass the TUI restriction because both turn/start and turn/steer reject the same child with:

direct app-server input is not allowed for multi-agent v2 sub-agents

This prevents a user from directly correcting a child that misunderstood its task, adding a missing constraint, steering an active child away from a bad approach, or continuing a completed child for a closely related follow-up.

The only supported route is to return to the parent, spend another parent-model turn asking it to relay the correction, and wait for the parent to call the inter-agent messaging tool. That costs tokens and time before the child receives the instruction. It also compounds #28058: the relay is encrypted and the readable instruction is not retained in the local audit trail, so the user cannot verify exactly what correction the parent sent.

This is not an inherent inability of the child thread to accept input. Core AgentControl::send_input can still submit rich user input directly to an existing child. The rejection is based on whether the caller is the parent agent or an app-server user/client.

The read-only policy also goes beyond preventing accidental thread targeting. It blocks intentional input after the user has explicitly selected the child, queued follow-ups, review/compact operations, agent-directed slash commands, and settings shortcuts.

What steps can reproduce the bug?

  1. Start a session with MultiAgentV2 enabled.
  2. Have the parent spawn a child agent.
  3. With the composer empty, press Alt+Right to switch from the parent to the child thread. Alternatively, use /agent to select the child explicitly.
  4. Enter a correction or follow-up and press Enter.
  5. Observe that the TUI preserves the draft and reports This sub-agent is controlled by its parent. Direct input is disabled.
  6. As an app-server control, send either turn/start or turn/steer for the child thread ID.
  7. Observe that both requests fail with direct app-server input is not allowed for multi-agent v2 sub-agents.

What is the expected behavior?

An explicitly selected MultiAgentV2 child should accept direct user input.

  • If the child is idle or completed, the user should be able to start a follow-up turn in that child.
  • If the child has an active turn, the user should be able to steer it using the normal turn/steer lifecycle and turn-ID validation.
  • The TUI should clearly show that the active thread is a child and identify its parent, but it should not make the child read-only.
  • Normal user turns should still default to the parent unless the user explicitly switches or resumes the child. This preserves the safety property requested in #18684 without removing intentional child interaction.
  • Direct user corrections should be persisted as ordinary readable user input in the child rollout rather than requiring an encrypted parent relay.

Additional information

Related PRs/issues:

  • #27173 introduced the app-server rejection for direct turn/start and turn/steer.
  • #33841 propagated that policy into the TUI and made selected child threads read-only.
  • #18684 reported accidental continuation in a completed child. Its expected behavior was to keep ordinary turns on the parent unless the user explicitly switches/resumes the child, not to prohibit intentional child input.
  • #28058 explains why routing every correction through encrypted inter-agent communication also removes auditability.
  • #32782 and #31814 cover related MultiAgentV2 restrictions on selecting the child role/model. A user who cannot select the intended child configuration also cannot directly correct the resulting child.
  • #32587 already contains a real-world instance of the exact app-server error while attempting to correct a wrongly configured child, but that issue is primarily about model-routing fallback.

A minimal fork fix currently reverts both policy layers:

Source analysis

The app-server policy is explicit and applies only when the resolved version is V2 and the session source is a thread-spawn subagent:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/app-server/src/request_processors/turn_processor.rs#L18-L31

The shared validator returns an invalid-request error before applying the input:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/app-server/src/request_processors/turn_processor.rs#L336-L351

Both new turns and active-turn steering invoke that validator:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/app-server/src/request_processors/turn_processor.rs#L473-L488

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/app-server/src/request_processors/turn_processor.rs#L919-L931

The app-server integration test intentionally spawns a real V2 child and asserts that both turn/start and turn/steer fail with the same error:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/app-server/tests/suite/v2/turn_start.rs#L3737-L3870

The server exposes the policy as an experimental canAcceptDirectInput thread capability, and the TUI converts it into blocks_direct_input:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L211-L214

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/app_server_session.rs#L232-L238

Once set, the TUI changes the composer placeholder and blocks direct submission:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/bottom_pane/chat_composer.rs#L1430-L1432

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/bottom_pane/chat_composer.rs#L2979-L2997

The higher-level operation path separately rejects user turns, reviews, and compaction:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/chatwidget.rs#L1734-L1747

Queued input is prevented from draining, and programmatic mode-aware submission is also rejected:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/chatwidget/input_flow.rs#L130-L137

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/chatwidget/input_flow.rs#L198-L205

The TUI tests explicitly cover blocking composer input, slash commands, review/compact operations, settings shortcuts, the initial prompt, and queued follow-ups:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/tui/src/chatwidget/tests/composer_submission.rs#L12-L153

By contrast, parent-side AgentControl::send_input remains capable of sending ordinary rich user input to an existing agent thread. It does not apply the app-server source-based rejection:

https://github.com/openai/codex/blob/1fc15cc9a98e065eb02b601342243e9af73d315b/codex-rs/core/src/agent/control.rs#L140-L165

That difference shows that the child remains writable at runtime; only direct user/app-server access is prohibited.

Implementation / fix spec

  1. Remove the unconditional MultiAgentVersion::V2 + SubAgentSource::ThreadSpawn rejection from app-server turn/start and turn/steer.
  2. Apply the existing thread lifecycle rules instead:
  • allow turn/start for an idle/completed child;
  • allow turn/steer for an active child when expectedTurnId is valid;
  • preserve the normal errors for stale turn IDs or incompatible concurrent operations.
  1. Keep child ownership and parent identity visible in the TUI, but enable the composer after a user explicitly selects or resumes the child.
  2. Keep the parent as the default target after delegation completes. Do not silently switch ordinary parent conversation input into a child, addressing #18684 at the navigation/active-thread layer.
  3. Persist direct corrections as normal child user turns so they remain readable and auditable without an inter-agent relay.
  4. Preserve current V1, root-thread, review-thread, compact-thread, and non-thread-spawn behavior.
  5. If maintainers still want a parent-only mode, make it an explicit opt-in policy/capability rather than an unconditional property of every MultiAgentV2 child.

Acceptance criteria

  • After explicitly selecting a MultiAgentV2 child in the TUI, the composer accepts and submits a user correction.
  • App-server turn/start succeeds for an idle/completed V2 child under the normal thread lifecycle.
  • App-server turn/steer succeeds for an active V2 child with the correct expectedTurnId.
  • Invalid or stale turn state still returns the normal lifecycle error instead of a blanket source-based rejection.
  • The TUI visibly identifies the child and its parent while keeping the child writable.
  • Completing a child does not silently make it the target of later parent conversation turns; intentional selection/resume remains required.
  • The correction is recorded as readable user input in the child rollout.
  • The workflow does not require an additional parent inference or encrypted inter-agent relay.
  • Regression coverage includes explicit TUI child selection, app-server start/steer, queued follow-up handling, resume, and parent-default navigation.
  • Existing V1 and non-thread-spawn behavior remains unchanged.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗