resume_agent restores subagent history but not original model/reasoning settings

Open 💬 3 comments Opened Jun 6, 2026 by umiskky

What version of Codex CLI is running?

Observed while investigating codex-cli 0.137.0; I also checked current main and did not find resume-time restoration of the child thread model/reasoning settings from the rollout TurnContextItem.

What subscription do you have?

N/A - source-level bug report.

Which model were you using?

N/A. The bug is about resuming a previously spawned subagent whose stored turn context used a different model/reasoning effort than the parent/current session.

What platform is your computer?

Linux x86_64.

What terminal emulator and version are you using, if applicable?

N/A.

Codex doctor report

Not available for this source-level report.

What issue are you seeing?

resume_agent can restore a subagent thread history without restoring the subagent's original runtime model/reasoning settings.

For a spawned custom subagent, the rollout contains TurnContextItem entries recording the child turn context, including model, explicit effort, and/or collaboration_mode. However, the resume path constructs the resumed session from the caller-provided/current Config and the rollout history. It does not appear to apply the latest persisted TurnContextItem back onto the Config before starting the resumed session.

The result is that a resumed subagent can silently run under the current parent/default model and reasoning effort rather than the child thread's original custom-agent settings. This is especially visible for project/custom agents that pin a cheaper or narrower model, e.g. model = "gpt-5.4-mini" and model_reasoning_effort = "medium", while the parent uses a larger/high-effort model.

What steps can reproduce the bug?

One source-level reproduction approach:

  1. Spawn a custom subagent whose role config sets a different model/reasoning effort than the parent session, for example:
name = "researcher"
model = "gpt-5.4-mini"
model_reasoning_effort = "medium"
developer_instructions = "Act as a read-only researcher."
  1. Let the child run at least one turn so the rollout persists a TurnContextItem for the child with that model/reasoning configuration.
  1. Resume the child thread via resume_agent from a parent/current config using a different model/reasoning effort.
  1. Inspect the resumed session config or emitted turn context. The resumed session can use the current/default config instead of the latest persisted child TurnContextItem.

A focused unit test also demonstrates the issue: build a stored rollout with a latest RolloutItem::TurnContext whose model and effort differ from the supplied resume config, invoke the resume path, and assert the resumed config uses the rollout values. That assertion fails without applying the persisted turn context before session creation.

What is the expected behavior?

When resuming a subagent thread, Codex should preserve the subagent's original runtime settings from the latest persisted child TurnContextItem, at least for:

  • model
  • explicit effort, or the effort implied by collaboration_mode when effort is absent

This keeps resumed custom agents consistent with the model/reasoning configuration they were spawned with.

Additional information

A minimal patch outline that worked locally:

  • in the resume-from-rollout path, after loading rollout history and before constructing the resumed session, find the latest RolloutItem::TurnContext;
  • set config.model = Some(turn_context.model.clone());
  • set config.model_reasoning_effort = turn_context.effort.or_else(|| turn_context.collaboration_mode.as_ref()?.reasoning_effort());
  • add regression coverage that resumes from a rollout whose latest TurnContextItem uses a different model/effort than the supplied config.

This is related to custom subagent configuration issues such as #26363/#26408, but it is a separate resume-time consistency problem: even if spawn applies the custom role correctly, resume can lose the child's original runtime settings.

View original on GitHub ↗

3 Comments

umiskky · 2 months ago

Possible implementation direction:

The resume path can restore the child runtime settings from the latest persisted RolloutItem::TurnContext before constructing the resumed session.

Roughly:

fn apply_resume_config_from_rollout_history(
    config: &mut Config,
    history: &[RolloutItem],
) {
    let Some(turn_context) = history.iter().rev().find_map(|item| match item {
        RolloutItem::TurnContext(turn_context) => Some(turn_context),
        _ => None,
    }) else {
        return;
    };

    config.model = Some(turn_context.model.clone());
    config.model_reasoning_effort = turn_context
        .effort
        .or_else(|| turn_context.collaboration_mode.as_ref()?.reasoning_effort());
}

Then call this after loading rollout history and before creating/starting the resumed session.

A focused regression test would build a rollout history whose latest TurnContextItem has a different model and effort than the supplied resume config, then assert the resumed session config uses the rollout values.

Necmttn · 2 months ago

resume_agent should restore the child runtime profile as part of the child thread, not inherit it from the current parent.

Persisted turn context can be treated as the source of truth for child model, reasoning effort, sandbox/approval, tool profile, and provider. On resume, emit a small drift warning if the resumed child differs from the stored profile.

That makes cost and behavior changes auditable.

---

_Generated with ax._

ferueda · 1 month ago

<!-- codex-26718:auto-reload-diagnosis -->

While investigating a separate cross-thread settings issue in #26930, I traced this report against current main at 393f64565ab46f09d99ca4d9bd973537e72a114b. The configuration-ownership problem is still present, and there is an additional automatic trigger beyond explicit resume_agent: delivering a message to an unloaded/evicted V2 agent.

Confirmed automatic-reload path

  1. V2 send_message / followup_task calls build_agent_resume_config(turn) using the sender's turn before ensuring that the recipient is loaded:

https://github.com/openai/codex/blob/393f64565ab46f09d99ca4d9bd973537e72a114b/codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs#L92-L97

  1. That helper copies the sender's model, provider, and reasoning effort into the resume config:

https://github.com/openai/codex/blob/393f64565ab46f09d99ca4d9bd973537e72a114b/codex-rs/core/src/tools/handlers/multi_agents_common.rs#L181-L199

  1. If the target is absent from ThreadManager, ensure_v2_agent_loaded resumes the target using that supplied config:

https://github.com/openai/codex/blob/393f64565ab46f09d99ca4d9bd973537e72a114b/codex-rs/core/src/agent/control/spawn.rs#L221-L283

Loaded recipients are unaffected because ensure_v2_agent_loaded returns before consuming the sender-derived config. The defect appears only when the logical recipient is still registered but its runtime session has been unloaded.

Merged PR #26623 introduced reload-on-delivery. Its regression uses the same configuration for sender and recipient, so it proves history/message recovery but cannot detect profile inheritance:
https://github.com/openai/codex/blob/393f64565ab46f09d99ca4d9bd973537e72a114b/codex-rs/core/src/agent/control_tests.rs#L592-L685

Focused regression

A test that should fail on current main:

  1. Spawn a target with model/effort A.
  2. Let it persist a turn, then unload it while retaining its V2 identity.
  3. From a sender using model/effort B, call send_message.
  4. Assert the message is delivered and the reloaded target's ThreadConfigSnapshot still reports A.
  5. Assert the sender remains B.

The same shared restoration behavior should cover explicit resume_agent; either one shared control-level regression plus a delivery integration test, or focused coverage for both entry points, would make the ownership boundary clear.

Smallest fix boundary

Keep the change in subagent resume/reload rather than generic thread-start override semantics. Before constructing the resumed subagent session, treat the stored target thread's latest model and reasoning effort as target-owned state and overlay them onto the caller-derived fallback config. Model and effort already exist in stored thread metadata and are synchronized from target turn/settings events.

Provider restoration is adjacent, but model plus effort is the smallest issue-backed correction. I would avoid bundling sandbox/tool-profile restoration until their resume ownership contract is explicit.

Codex's contribution guide says external PRs are invitation-only, so I have not opened one. Would maintainers like an external implementation of this scoped fix and regression? I can prepare it if invited.