From bd07d748728e22524b468539aa097adec55b42d2 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 29 Apr 2026 09:58:22 +0100 Subject: [PATCH] feat: multi-agents v2 ignore max depth --- codex-rs/core/src/agent/control.rs | 1 + codex-rs/core/src/session/mod.rs | 1 + .../src/tools/handlers/multi_agents_tests.rs | 54 +++++++++++++++++++ .../src/tools/handlers/multi_agents_v2.rs | 1 - .../tools/handlers/multi_agents_v2/spawn.rs | 6 --- 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 19ef2a54fe13..339b99787a1f 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -521,6 +521,7 @@ impl AgentControl { ) -> CodexResult { if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = &session_source && *depth >= config.agent_max_depth + && !config.features.enabled(Feature::MultiAgentV2) { let _ = config.features.disable(Feature::SpawnCsv); let _ = config.features.disable(Feature::Collab); diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 415b89c0deba..0c5af3fc5eed 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -489,6 +489,7 @@ impl Codex { if let SessionSource::SubAgent(SubAgentSource::ThreadSpawn { depth, .. }) = session_source && depth >= config.agent_max_depth + && !config.features.enabled(Feature::MultiAgentV2) { let _ = config.features.disable(Feature::SpawnCsv); let _ = config.features.disable(Feature::Collab); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 0b53fb973517..8804e6d258df 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -1870,6 +1870,60 @@ async fn spawn_agent_allows_depth_up_to_configured_max_depth() { assert_eq!(success, Some(true)); } +#[tokio::test] +async fn multi_agent_v2_spawn_agent_ignores_configured_max_depth() { + #[derive(Debug, Deserialize)] + struct SpawnAgentResult { + task_name: String, + nickname: Option, + } + + let (mut session, mut turn) = make_session_and_context().await; + let manager = thread_manager(); + let mut config = (*turn.config).clone(); + config.agent_max_depth = 1; + config + .features + .enable(Feature::MultiAgentV2) + .expect("test config should allow feature update"); + let root = manager + .start_thread(config.clone()) + .await + .expect("root thread should start"); + session.services.agent_control = manager.agent_control(); + session.conversation_id = root.thread_id; + turn.config = Arc::new(config); + let parent_path = AgentPath::try_from("/root/parent").expect("agent path"); + turn.session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn { + parent_thread_id: root.thread_id, + depth: 1, + agent_path: Some(parent_path), + agent_nickname: None, + agent_role: None, + }); + + let invocation = invocation( + Arc::new(session), + Arc::new(turn), + "spawn_agent", + function_payload(json!({ + "message": "hello", + "task_name": "child", + "fork_turns": "none" + })), + ); + let output = SpawnAgentHandlerV2 + .handle(invocation) + .await + .expect("multi-agent v2 spawn should ignore max depth"); + let (content, success) = expect_text_output(output); + let result: SpawnAgentResult = + serde_json::from_str(&content).expect("spawn_agent result should be json"); + assert_eq!(result.task_name, "/root/parent/child"); + assert!(result.nickname.is_some()); + assert_eq!(success, Some(true)); +} + #[tokio::test] async fn send_input_rejects_empty_message() { let (session, turn) = make_session_and_context().await; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2.rs index 51b80e4a7bf7..b561c5acb43f 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2.rs @@ -2,7 +2,6 @@ use crate::agent::AgentStatus; use crate::agent::agent_resolver::resolve_agent_target; -use crate::agent::exceeds_thread_spawn_depth_limit; use crate::function_tool::FunctionCallError; use crate::tools::context::ToolInvocation; use crate::tools::context::ToolOutput; 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 21b4638c016d..26b6750c46f5 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 @@ -45,12 +45,6 @@ impl ToolHandler for Handler { let session_source = turn.session_source.clone(); let child_depth = next_thread_spawn_depth(&session_source); - let max_depth = turn.config.agent_max_depth; - if exceeds_thread_spawn_depth_limit(child_depth, max_depth) { - return Err(FunctionCallError::RespondToModel( - "Agent depth limit reached. Solve the task yourself.".to_string(), - )); - } session .send_event( &turn,