diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index ca7fb5157..c81c4e174 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -41,12 +41,6 @@ "minimum": 1.0, "type": "integer" }, - "max_spawn_depth": { - "description": "Maximum depth for thread-spawned subagents.", - "format": "uint", - "minimum": 1.0, - "type": "integer" - }, "max_threads": { "description": "Maximum number of agent threads that can be open concurrently. When unset, no limit is enforced.", "format": "uint", diff --git a/codex-rs/core/src/agent/guards.rs b/codex-rs/core/src/agent/guards.rs index b8db6e397..4213cd0ed 100644 --- a/codex-rs/core/src/agent/guards.rs +++ b/codex-rs/core/src/agent/guards.rs @@ -1,4 +1,3 @@ -use crate::config::DEFAULT_AGENT_MAX_SPAWN_DEPTH; use crate::error::CodexErr; use crate::error::Result; use codex_protocol::ThreadId; @@ -43,10 +42,6 @@ pub(crate) fn next_thread_spawn_depth(session_source: &SessionSource) -> i32 { session_depth(session_source).saturating_add(1) } -pub(crate) fn max_thread_spawn_depth(max_depth: Option) -> i32 { - let max_depth = max_depth.or(DEFAULT_AGENT_MAX_SPAWN_DEPTH).unwrap_or(1); - i32::try_from(max_depth).unwrap_or(i32::MAX) -} pub(crate) fn exceeds_thread_spawn_depth_limit(depth: i32, max_depth: i32) -> bool { depth > max_depth } diff --git a/codex-rs/core/src/agent/mod.rs b/codex-rs/core/src/agent/mod.rs index 6ae7d9615..15be909c3 100644 --- a/codex-rs/core/src/agent/mod.rs +++ b/codex-rs/core/src/agent/mod.rs @@ -6,6 +6,5 @@ pub(crate) mod status; pub(crate) use codex_protocol::protocol::AgentStatus; pub(crate) use control::AgentControl; pub(crate) use guards::exceeds_thread_spawn_depth_limit; -pub(crate) use guards::max_thread_spawn_depth; pub(crate) use guards::next_thread_spawn_depth; pub(crate) use status::agent_status_from_event; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index caa6a523f..6b0c05fef 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -115,7 +115,6 @@ pub use codex_git::GhostSnapshotConfig; /// the context window. pub(crate) const PROJECT_DOC_MAX_BYTES: usize = 32 * 1024; // 32 KiB pub(crate) const DEFAULT_AGENT_MAX_THREADS: Option = Some(6); -pub(crate) const DEFAULT_AGENT_MAX_SPAWN_DEPTH: Option = Some(2); pub(crate) const DEFAULT_AGENT_MAX_DEPTH: i32 = 1; pub(crate) const DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS: Option = None; @@ -357,8 +356,6 @@ pub struct Config { /// Maximum number of agent threads that can be open concurrently. pub agent_max_threads: Option, - /// Maximum depth for thread-spawned subagents. - pub agent_max_spawn_depth: Option, /// Maximum runtime in seconds for agent job workers before they are failed. pub agent_job_max_runtime_seconds: Option, @@ -1340,9 +1337,6 @@ pub struct AgentsToml { /// When unset, no limit is enforced. #[schemars(range(min = 1))] pub max_threads: Option, - /// Maximum depth for thread-spawned subagents. - #[schemars(range(min = 1))] - pub max_spawn_depth: Option, /// Maximum nesting depth allowed for spawned agent threads. /// Root sessions start at depth 0. #[schemars(range(min = 1))] @@ -1865,25 +1859,6 @@ impl Config { }) .transpose()? .unwrap_or_default(); - let agent_max_spawn_depth = cfg - .agents - .as_ref() - .and_then(|agents| agents.max_spawn_depth) - .or(DEFAULT_AGENT_MAX_SPAWN_DEPTH); - if agent_max_spawn_depth == Some(0) { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "agents.max_spawn_depth must be at least 1", - )); - } - if let Some(max_spawn_depth) = agent_max_spawn_depth - && max_spawn_depth > i32::MAX as usize - { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "agents.max_spawn_depth must fit within a 32-bit signed integer", - )); - } let agent_job_max_runtime_seconds = cfg .agents .as_ref() @@ -2149,7 +2124,6 @@ impl Config { agent_max_depth, agent_roles, memories: cfg.memories.unwrap_or_default().into(), - agent_max_spawn_depth, agent_job_max_runtime_seconds, codex_home, sqlite_home, @@ -4487,7 +4461,6 @@ model = "gpt-5.1-codex" let cfg = ConfigToml { agents: Some(AgentsToml { max_threads: None, - max_spawn_depth: None, max_depth: None, job_max_runtime_seconds: None, roles: BTreeMap::from([( @@ -4763,7 +4736,6 @@ model_verbosity = "high" agent_max_depth: DEFAULT_AGENT_MAX_DEPTH, agent_roles: BTreeMap::new(), memories: MemoriesConfig::default(), - agent_max_spawn_depth: DEFAULT_AGENT_MAX_SPAWN_DEPTH, agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS, codex_home: fixture.codex_home(), sqlite_home: fixture.codex_home(), @@ -4890,7 +4862,6 @@ model_verbosity = "high" agent_max_depth: DEFAULT_AGENT_MAX_DEPTH, agent_roles: BTreeMap::new(), memories: MemoriesConfig::default(), - agent_max_spawn_depth: DEFAULT_AGENT_MAX_SPAWN_DEPTH, agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS, codex_home: fixture.codex_home(), sqlite_home: fixture.codex_home(), @@ -5015,7 +4986,6 @@ model_verbosity = "high" agent_max_depth: DEFAULT_AGENT_MAX_DEPTH, agent_roles: BTreeMap::new(), memories: MemoriesConfig::default(), - agent_max_spawn_depth: DEFAULT_AGENT_MAX_SPAWN_DEPTH, agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS, codex_home: fixture.codex_home(), sqlite_home: fixture.codex_home(), @@ -5126,7 +5096,6 @@ model_verbosity = "high" agent_max_depth: DEFAULT_AGENT_MAX_DEPTH, agent_roles: BTreeMap::new(), memories: MemoriesConfig::default(), - agent_max_spawn_depth: DEFAULT_AGENT_MAX_SPAWN_DEPTH, agent_job_max_runtime_seconds: DEFAULT_AGENT_JOB_MAX_RUNTIME_SECONDS, codex_home: fixture.codex_home(), sqlite_home: fixture.codex_home(), diff --git a/codex-rs/core/src/tools/handlers/agent_jobs.rs b/codex-rs/core/src/tools/handlers/agent_jobs.rs index 6b36be05d..c4891a6b3 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs.rs @@ -1,5 +1,4 @@ use crate::agent::exceeds_thread_spawn_depth_limit; -use crate::agent::max_thread_spawn_depth; use crate::agent::next_thread_spawn_depth; use crate::agent::status::is_final; use crate::codex::Session; @@ -531,7 +530,7 @@ async fn build_runner_options( ) -> Result { let session_source = turn.session_source.clone(); let child_depth = next_thread_spawn_depth(&session_source); - let max_depth = max_thread_spawn_depth(turn.config.agent_max_spawn_depth); + 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; this session cannot spawn more subagents".to_string(), @@ -540,7 +539,7 @@ async fn build_runner_options( let max_concurrency = normalize_concurrency(requested_concurrency, turn.config.agent_max_threads); let base_instructions = session.get_base_instructions().await; - let spawn_config = build_agent_spawn_config(&base_instructions, turn.as_ref(), child_depth)?; + let spawn_config = build_agent_spawn_config(&base_instructions, turn.as_ref())?; Ok(JobRunnerOptions { max_concurrency, spawn_config, diff --git a/codex-rs/core/src/tools/handlers/multi_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents.rs index 9000e6d23..6e62fd04f 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents.rs @@ -1,6 +1,5 @@ use crate::agent::AgentStatus; use crate::agent::exceeds_thread_spawn_depth_limit; -use crate::agent::max_thread_spawn_depth; use crate::codex::Session; use crate::codex::TurnContext; use crate::config::Config; @@ -96,7 +95,6 @@ mod spawn { use crate::agent::role::apply_role_to_config; use crate::agent::exceeds_thread_spawn_depth_limit; - use crate::agent::max_thread_spawn_depth; use crate::agent::next_thread_spawn_depth; use std::sync::Arc; @@ -129,7 +127,7 @@ mod spawn { let prompt = input_preview(&input_items); let session_source = turn.session_source.clone(); let child_depth = next_thread_spawn_depth(&session_source); - let max_depth = max_thread_spawn_depth(turn.config.agent_max_spawn_depth); + 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(), @@ -146,11 +144,8 @@ mod spawn { .into(), ) .await; - let mut config = build_agent_spawn_config( - &session.get_base_instructions().await, - turn.as_ref(), - child_depth, - )?; + let mut config = + build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?; apply_role_to_config(&mut config, role_name) .await .map_err(FunctionCallError::RespondToModel)?; @@ -346,7 +341,7 @@ mod resume_agent { .await .unwrap_or((None, None)); let child_depth = next_thread_spawn_depth(&turn.session_source); - let max_depth = max_thread_spawn_depth(turn.config.agent_max_spawn_depth); + 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(), @@ -896,9 +891,8 @@ fn input_preview(items: &[UserInput]) -> String { pub(crate) fn build_agent_spawn_config( base_instructions: &BaseInstructions, turn: &TurnContext, - child_depth: i32, ) -> Result { - let mut config = build_agent_shared_config(turn, child_depth)?; + let mut config = build_agent_shared_config(turn)?; config.base_instructions = Some(base_instructions.text.clone()); Ok(config) } @@ -907,16 +901,14 @@ fn build_agent_resume_config( turn: &TurnContext, child_depth: i32, ) -> Result { - let mut config = build_agent_shared_config(turn, child_depth)?; + let mut config = build_agent_shared_config(turn)?; + apply_spawn_agent_overrides(&mut config, child_depth); // For resume, keep base instructions sourced from rollout/session metadata. config.base_instructions = None; Ok(config) } -fn build_agent_shared_config( - turn: &TurnContext, - child_depth: i32, -) -> Result { +fn build_agent_shared_config(turn: &TurnContext) -> Result { let base_config = turn.config.clone(); let mut config = (*base_config).clone(); config.model = Some(turn.model_info.slug.clone()); @@ -926,7 +918,6 @@ fn build_agent_shared_config( config.developer_instructions = turn.developer_instructions.clone(); config.compact_prompt = turn.compact_prompt.clone(); apply_spawn_agent_runtime_overrides(&mut config, turn)?; - apply_spawn_agent_overrides(&mut config, child_depth); Ok(config) } @@ -956,8 +947,7 @@ fn apply_spawn_agent_runtime_overrides( } fn apply_spawn_agent_overrides(config: &mut Config, child_depth: i32) { - let max_depth = max_thread_spawn_depth(config.agent_max_spawn_depth); - if exceeds_thread_spawn_depth_limit(child_depth + 1, max_depth) { + if child_depth >= config.agent_max_depth { config.features.disable(Feature::Collab); } } @@ -968,7 +958,6 @@ mod tests { use crate::AuthManager; use crate::CodexAuth; use crate::ThreadManager; - use crate::agent::max_thread_spawn_depth; use crate::built_in_model_providers; use crate::codex::make_session_and_context; use crate::config::DEFAULT_AGENT_MAX_DEPTH; @@ -1273,7 +1262,7 @@ mod tests { let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let max_depth = max_thread_spawn_depth(turn.config.agent_max_spawn_depth); + let max_depth = turn.config.agent_max_depth; turn.session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id: session.conversation_id, depth: max_depth, @@ -1704,7 +1693,7 @@ mod tests { let manager = thread_manager(); session.services.agent_control = manager.agent_control(); - let max_depth = max_thread_spawn_depth(turn.config.agent_max_spawn_depth); + let max_depth = turn.config.agent_max_depth; turn.session_source = SessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id: session.conversation_id, depth: max_depth, @@ -2051,7 +2040,7 @@ mod tests { .set(AskForApproval::OnRequest) .expect("approval policy set"); - let config = build_agent_spawn_config(&base_instructions, &turn, 0).expect("spawn config"); + let config = build_agent_spawn_config(&base_instructions, &turn).expect("spawn config"); let mut expected = (*turn.config).clone(); expected.base_instructions = Some(base_instructions.text); expected.model = Some(turn.model_info.slug.clone()); @@ -2087,7 +2076,7 @@ mod tests { text: "base".to_string(), }; - let config = build_agent_spawn_config(&base_instructions, &turn, 0).expect("spawn config"); + let config = build_agent_spawn_config(&base_instructions, &turn).expect("spawn config"); assert_eq!(config.user_instructions, base_config.user_instructions); }