From a2f868c9d6a208572992da1ad7a3ea8ed00b84a1 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 23 Apr 2026 18:54:45 +0200 Subject: [PATCH] feat: drop spawned-agent context instructions (#19127) ## Why MultiAgentV2 children should not receive an extra model-visible developer fragment just because they were spawned. The parent/configured developer instructions should carry through normally, but the dedicated `` block is no longer desired. ## What changed - Removed the `SpawnAgentInstructions` context fragment and its `` wrapper. - Stopped appending spawned-agent instructions in `codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs`. - Updated subagent notification coverage to assert inherited parent developer instructions without expecting the spawned-agent wrapper. ## Verification - `cargo test -p codex-core --test all spawned_multi_agent_v2_child_inherits_parent_developer_context -- --nocapture` - `cargo test -p codex-core --test all skills_toggle_skips_instructions_for_parent_and_spawned_child -- --nocapture` - `cargo test -p codex-core --test all subagent_notifications -- --nocapture` --- codex-rs/core/src/context/mod.rs | 2 -- .../src/context/spawn_agent_instructions.rs | 14 --------- .../tools/handlers/multi_agents_v2/spawn.rs | 14 --------- .../tests/suite/subagent_notifications.rs | 31 +++++-------------- 4 files changed, 7 insertions(+), 54 deletions(-) delete mode 100644 codex-rs/core/src/context/spawn_agent_instructions.rs diff --git a/codex-rs/core/src/context/mod.rs b/codex-rs/core/src/context/mod.rs index bbc5bcacf..848a4f7f0 100644 --- a/codex-rs/core/src/context/mod.rs +++ b/codex-rs/core/src/context/mod.rs @@ -20,7 +20,6 @@ mod realtime_end_instructions; mod realtime_start_instructions; mod realtime_start_with_instructions; mod skill_instructions; -mod spawn_agent_instructions; mod subagent_notification; mod turn_aborted; mod user_instructions; @@ -50,7 +49,6 @@ pub(crate) use realtime_end_instructions::RealtimeEndInstructions; pub(crate) use realtime_start_instructions::RealtimeStartInstructions; pub(crate) use realtime_start_with_instructions::RealtimeStartWithInstructions; pub(crate) use skill_instructions::SkillInstructions; -pub(crate) use spawn_agent_instructions::SpawnAgentInstructions; pub(crate) use subagent_notification::SubagentNotification; pub(crate) use turn_aborted::TurnAborted; pub(crate) use user_instructions::UserInstructions; diff --git a/codex-rs/core/src/context/spawn_agent_instructions.rs b/codex-rs/core/src/context/spawn_agent_instructions.rs deleted file mode 100644 index f8a3f4175..000000000 --- a/codex-rs/core/src/context/spawn_agent_instructions.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::ContextualUserFragment; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct SpawnAgentInstructions; - -impl ContextualUserFragment for SpawnAgentInstructions { - const ROLE: &'static str = "developer"; - const START_MARKER: &'static str = ""; - const END_MARKER: &'static str = ""; - - fn body(&self) -> String { - "\nYou are a newly spawned agent in a team of agents collaborating to complete a task. You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. You are responsible for returning the response to your assigned task in the final channel. When you give your response, the contents of your response in the final channel will be immediately delivered back to your parent agent. The prior conversation history was forked from your parent agent. Treat the next user message as your assigned task, and use the forked history only as background context.\n".to_string() - } -} diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index fabb01b7d..00986311a 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -5,8 +5,6 @@ use crate::agent::control::render_input_preview; use crate::agent::next_thread_spawn_depth; use crate::agent::role::DEFAULT_ROLE_NAME; use crate::agent::role::apply_role_to_config; -use crate::context::ContextualUserFragment; -use crate::context::SpawnAgentInstructions; use codex_protocol::AgentPath; use codex_protocol::protocol::InterAgentCommunication; use codex_protocol::protocol::Op; @@ -88,18 +86,6 @@ impl ToolHandler for Handler { } apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?; apply_spawn_agent_overrides(&mut config, child_depth); - let spawn_agent_instructions = SpawnAgentInstructions.render(); - config.developer_instructions = Some( - if let Some(mut existing_instructions) = config.developer_instructions.take() { - if !existing_instructions.ends_with('\n') { - existing_instructions.push('\n'); - } - existing_instructions.push_str(&spawn_agent_instructions); - existing_instructions - } else { - spawn_agent_instructions - }, - ); let spawn_source = thread_spawn_source( session.conversation_id, diff --git a/codex-rs/core/tests/suite/subagent_notifications.rs b/codex-rs/core/tests/suite/subagent_notifications.rs index f12c68427..3f457967c 100644 --- a/codex-rs/core/tests/suite/subagent_notifications.rs +++ b/codex-rs/core/tests/suite/subagent_notifications.rs @@ -37,7 +37,6 @@ const REQUESTED_MODEL: &str = "gpt-5.4"; const REQUESTED_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::Low; const ROLE_MODEL: &str = "gpt-5.4"; const ROLE_REASONING_EFFORT: ReasoningEffort = ReasoningEffort::High; -const SPAWNED_AGENT_DEVELOPER_INSTRUCTIONS: &str = "You are a newly spawned agent in a team of agents collaborating to complete a task. You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. You are responsible for returning the response to your assigned task in the final channel. When you give your response, the contents of your response in the final channel will be immediately delivered back to your parent agent. The prior conversation history was forked from your parent agent. Treat the next user message as your assigned task, and use the forked history only as background context."; fn body_contains(req: &wiremock::Request, text: &str) -> bool { let is_zstd = req @@ -425,7 +424,7 @@ async fn spawn_agent_requested_model_and_reasoning_override_inherited_settings_w } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn spawned_multi_agent_v2_child_receives_xml_tagged_developer_context() -> Result<()> { +async fn spawned_multi_agent_v2_child_inherits_parent_developer_context() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -446,9 +445,7 @@ async fn spawned_multi_agent_v2_child_receives_xml_tagged_developer_context() -> let _child_request_log = mount_sse_once_match( &server, - |req: &wiremock::Request| { - body_contains(req, CHILD_PROMPT) && body_contains(req, "") - }, + |req: &wiremock::Request| body_contains(req, CHILD_PROMPT), sse(vec![ ev_response_created("resp-child-1"), ev_completed("resp-child-1"), @@ -459,9 +456,7 @@ async fn spawned_multi_agent_v2_child_receives_xml_tagged_developer_context() -> let _turn1_followup = mount_sse_once_match( &server, |req: &wiremock::Request| { - body_contains(req, "function_call_output") - && body_contains(req, "/root/worker") - && !body_contains(req, "") + body_contains(req, "function_call_output") && body_contains(req, "/root/worker") }, sse(vec![ ev_response_created("resp-turn1-2"), @@ -494,9 +489,7 @@ async fn spawned_multi_agent_v2_child_receives_xml_tagged_developer_context() -> .unwrap_or_default() .into_iter() .find(|request| { - body_contains(request, CHILD_PROMPT) - && body_contains(request, "") - && body_contains(request, SPAWNED_AGENT_DEVELOPER_INSTRUCTIONS) + body_contains(request, CHILD_PROMPT) && !body_contains(request, SPAWN_CALL_ID) }) { break request; @@ -510,11 +503,6 @@ async fn spawned_multi_agent_v2_child_receives_xml_tagged_developer_context() -> &child_request, "Parent developer instructions." )); - assert!(body_contains(&child_request, "")); - assert!(body_contains( - &child_request, - SPAWNED_AGENT_DEVELOPER_INSTRUCTIONS - )); assert!(body_contains(&child_request, CHILD_PROMPT)); Ok(()) @@ -542,9 +530,7 @@ async fn skills_toggle_skips_instructions_for_parent_and_spawned_child() -> Resu let _child_request_log = mount_sse_once_match( &server, - |req: &wiremock::Request| { - body_contains(req, CHILD_PROMPT) && body_contains(req, "") - }, + |req: &wiremock::Request| body_contains(req, CHILD_PROMPT), sse(vec![ ev_response_created("resp-child-1"), ev_completed("resp-child-1"), @@ -555,9 +541,7 @@ async fn skills_toggle_skips_instructions_for_parent_and_spawned_child() -> Resu let _turn1_followup = mount_sse_once_match( &server, |req: &wiremock::Request| { - body_contains(req, "function_call_output") - && body_contains(req, "/root/worker") - && !body_contains(req, "") + body_contains(req, "function_call_output") && body_contains(req, "/root/worker") }, sse(vec![ ev_response_created("resp-turn1-2"), @@ -599,8 +583,7 @@ async fn skills_toggle_skips_instructions_for_parent_and_spawned_child() -> Resu .unwrap_or_default() .into_iter() .find(|request| { - body_contains(request, CHILD_PROMPT) - && body_contains(request, "") + body_contains(request, CHILD_PROMPT) && !body_contains(request, SPAWN_CALL_ID) }) { break request;