diff --git a/codex-rs/app-server/tests/suite/v2/multi_agent_v2_developer_instructions.rs b/codex-rs/app-server/tests/suite/v2/multi_agent_v2_developer_instructions.rs index 6ccea58c01..65e1ce7d79 100644 --- a/codex-rs/app-server/tests/suite/v2/multi_agent_v2_developer_instructions.rs +++ b/codex-rs/app-server/tests/suite/v2/multi_agent_v2_developer_instructions.rs @@ -36,6 +36,7 @@ const ROLE_INSTRUCTIONS: &str = "configured role developer instructions"; #[test_case("blank override"; "blank override")] #[test_case("parent has no instructions"; "parent has no instructions")] #[test_case("explicit configured role"; "explicit configured role")] +#[test_case("full history configured role"; "full history configured role")] #[test_case("implicit configured default"; "implicit configured default")] #[test_case("full fork skips default role"; "full fork skips default role")] #[tokio::test] @@ -44,18 +45,19 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence( ) -> Result<()> { let fork_turns = match case { "bounded history" => Some("1"), - "no history" - | "configured role without instructions" - | "explicit configured role" - | "implicit configured default" => Some("none"), + "no history" | "explicit configured role" | "implicit configured default" => Some("none"), _ => None, }; let agent_type = match case { - "configured role without instructions" | "explicit configured role" => Some("custom"), + "configured role without instructions" + | "explicit configured role" + | "full history configured role" => Some("custom"), _ => None, }; let configured_override = match case { - "unset override" => None, + "unset override" + | "full history configured role" + | "configured role without instructions" => None, "blank override" => Some(" "), "full history" => Some(" child-only developer instructions "), _ => Some(CHILD_INSTRUCTIONS), @@ -69,17 +71,23 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence( case, "configured role without instructions" | "explicit configured role" + | "full history configured role" | "implicit configured default" | "full fork skips default role" ); let role_has_instructions = matches!( case, - "explicit configured role" | "implicit configured default" | "full fork skips default role" + "explicit configured role" + | "full history configured role" + | "implicit configured default" + | "full fork skips default role" ); let expected = match case { - "unset override" => Some(PARENT_INSTRUCTIONS), + "unset override" | "configured role without instructions" => Some(PARENT_INSTRUCTIONS), "blank override" => None, - "explicit configured role" | "implicit configured default" => Some(ROLE_INSTRUCTIONS), + "explicit configured role" + | "full history configured role" + | "implicit configured default" => Some(ROLE_INSTRUCTIONS), _ => Some(CHILD_INSTRUCTIONS), }; const PARENT_PROMPT: &str = "spawn the instruction override worker"; @@ -149,7 +157,12 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence( ); } let codex_home = TempDir::new()?; - let mut config = MockResponsesConfig::new(&server.uri()).with_model("gpt-5.4"); + let configured_model = if case == "full history configured role" { + "gpt-5.5" + } else { + "gpt-5.4" + }; + let mut config = MockResponsesConfig::new(&server.uri()).with_model(configured_model); if role_has_instructions { config = config.with_root_config(&format!("developer_instructions = {ROLE_INSTRUCTIONS:?}")); @@ -206,6 +219,19 @@ async fn spawned_subagents_apply_configured_developer_instruction_precedence( ); } let child_texts = child_request.message_input_texts("developer"); + if case == "full history configured role" { + assert_eq!(child_request.body_json()["model"], json!("gpt-5.5")); + assert!( + child_request.body_contains_text(PARENT_PROMPT), + "the child should inherit the parent's conversation history" + ); + assert!( + child_texts + .iter() + .any(|text| text.contains("")), + "the child should preserve the parent's model context" + ); + } let instruction_texts = child_texts .iter() .map(String::as_str) diff --git a/codex-rs/core/src/agent/control/spawn.rs b/codex-rs/core/src/agent/control/spawn.rs index 8f4f2d62a3..610d26d4fa 100644 --- a/codex-rs/core/src/agent/control/spawn.rs +++ b/codex-rs/core/src/agent/control/spawn.rs @@ -625,7 +625,9 @@ impl AgentControl { .subagent_developer_instructions .as_ref(), ) { - (MultiAgentVersion::V2, Some(_)) => { + (MultiAgentVersion::V2, override_instructions) + if override_instructions.is_some() || session_source.get_agent_role().is_some() => + { let parent_developer_instructions = match parent_thread .session .new_default_turn() @@ -642,7 +644,7 @@ impl AgentControl { ) } (MultiAgentVersion::Disabled | MultiAgentVersion::V1, _) - | (MultiAgentVersion::V2, None) => (None, None), + | (MultiAgentVersion::V2, _) => (None, None), }; let parent_history_mode = parent_thread.config_snapshot().await.history_mode; // `record_conversation_items` only queues persistence writes asynchronously. diff --git a/codex-rs/core/src/tools/handlers/multi_agents_spec.rs b/codex-rs/core/src/tools/handlers/multi_agents_spec.rs index 2eb82e175b..ec3523a870 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_spec.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_spec.rs @@ -640,7 +640,7 @@ fn spawn_agent_common_properties_v2(agent_type_description: &str) -> BTreeMap