From c677bf1f9c6b263c185fd65a4fe8da2a1eb8fed1 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 3 Jun 2026 22:35:52 +0100 Subject: [PATCH 1/2] feat: catalog multi-agent v2 config --- codex-rs/core/src/agent/control.rs | 8 ++--- codex-rs/core/src/config/config_tests.rs | 16 +++++---- codex-rs/core/src/config/mod.rs | 36 +++++++++++-------- codex-rs/core/src/session/mod.rs | 13 ++++--- .../core/src/tools/handlers/agent_jobs.rs | 5 +-- .../tests/suite/model_runtime_selectors.rs | 1 + 6 files changed, 43 insertions(+), 36 deletions(-) diff --git a/codex-rs/core/src/agent/control.rs b/codex-rs/core/src/agent/control.rs index 821611e60ef1..549852e3602a 100644 --- a/codex-rs/core/src/agent/control.rs +++ b/codex-rs/core/src/agent/control.rs @@ -231,9 +231,7 @@ impl AgentControl { &config, ) .await; - let agent_max_threads = config - .effective_agent_max_threads(multi_agent_version) - .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; + let agent_max_threads = config.effective_agent_max_threads(multi_agent_version); let mut reservation = self.state.reserve_spawn_slot(agent_max_threads)?; let inheritance = SpawnAgentThreadInheritance { shell_snapshot: self @@ -632,9 +630,7 @@ impl AgentControl { &config, ) .await; - let agent_max_threads = config - .effective_agent_max_threads(multi_agent_version) - .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; + let agent_max_threads = config.effective_agent_max_threads(multi_agent_version); let mut reservation = self.state.reserve_spawn_slot(agent_max_threads)?; let (session_source, agent_metadata) = match session_source { SessionSource::SubAgent(SubAgentSource::ThreadSpawn { diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 87f00fed86cd..12186c227b2a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -9836,7 +9836,7 @@ non_code_mode_only = true assert_eq!( ( config.agent_max_threads, - config.effective_agent_max_threads(MultiAgentVersion::V2)? + config.effective_agent_max_threads(MultiAgentVersion::V2) ), (None, Some(4)) ); @@ -9886,7 +9886,7 @@ enabled = true assert_eq!( ( config.agent_max_threads, - config.effective_agent_max_threads(MultiAgentVersion::V2)? + config.effective_agent_max_threads(MultiAgentVersion::V2) ), (None, Some(3)) ); @@ -9945,7 +9945,7 @@ subagent_usage_hint_text = "" } #[tokio::test] -async fn multi_agent_v2_rejects_agents_max_threads() -> std::io::Result<()> { +async fn multi_agent_v2_config_selection_rejects_agents_max_threads() -> std::io::Result<()> { let codex_home = TempDir::new()?; std::fs::write( codex_home.path().join(CONFIG_TOML_FILE), @@ -9963,13 +9963,17 @@ max_threads = 3 .build() .await?; let err = config - .effective_agent_max_threads(MultiAgentVersion::V2) + .validate_config_selected_multi_agent_version(MultiAgentVersion::V2) .expect_err("agents.max_threads should conflict with multi_agent_v2"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert_eq!( err.to_string(), - "agents.max_threads cannot be set when the multi-agent runtime is v2" + "agents.max_threads cannot be set when features.multi_agent_v2 selects the multi-agent runtime" + ); + assert_eq!( + config.effective_agent_max_threads(MultiAgentVersion::V2), + Some(3) ); Ok(()) @@ -10231,7 +10235,7 @@ max_concurrent_threads_per_session = 1 assert_eq!( ( config.agent_max_threads, - config.effective_agent_max_threads(MultiAgentVersion::V2)? + config.effective_agent_max_threads(MultiAgentVersion::V2) ), (None, Some(0)) ); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 43b2a2ed291b..a449731e6b14 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1292,26 +1292,32 @@ impl Config { } } + pub(crate) fn validate_config_selected_multi_agent_version( + &self, + multi_agent_version: MultiAgentVersion, + ) -> std::io::Result<()> { + if multi_agent_version == MultiAgentVersion::V2 && self.agent_max_threads.is_some() { + Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "agents.max_threads cannot be set when features.multi_agent_v2 selects the multi-agent runtime", + )) + } else { + Ok(()) + } + } + pub(crate) fn effective_agent_max_threads( &self, multi_agent_version: MultiAgentVersion, - ) -> std::io::Result> { + ) -> Option { match multi_agent_version { - MultiAgentVersion::V2 => { - if self.agent_max_threads.is_some() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "agents.max_threads cannot be set when the multi-agent runtime is v2", - )); - } - Ok(Some( - self.multi_agent_v2 - .max_concurrent_threads_per_session - .saturating_sub(1), - )) - } + MultiAgentVersion::V2 => Some( + self.multi_agent_v2 + .max_concurrent_threads_per_session + .saturating_sub(1), + ), MultiAgentVersion::Disabled | MultiAgentVersion::V1 => { - Ok(self.agent_max_threads.or(DEFAULT_AGENT_MAX_THREADS)) + self.agent_max_threads.or(DEFAULT_AGENT_MAX_THREADS) } } } diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 8ce16de7409b..7fd08fc50b2b 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -557,12 +557,15 @@ impl Codex { .await; let multi_agent_version = resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version); - let startup_multi_agent_version = multi_agent_version + if multi_agent_version .or(model_info.multi_agent_version) - .unwrap_or_else(|| config.multi_agent_version_from_features()); - let _ = config - .effective_agent_max_threads(startup_multi_agent_version) - .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; + .is_none() + { + let multi_agent_version = config.multi_agent_version_from_features(); + config + .validate_config_selected_multi_agent_version(multi_agent_version) + .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; + } let base_instructions = config .base_instructions .clone() diff --git a/codex-rs/core/src/tools/handlers/agent_jobs.rs b/codex-rs/core/src/tools/handlers/agent_jobs.rs index 25c8f9c4ca9c..360744973c56 100644 --- a/codex-rs/core/src/tools/handlers/agent_jobs.rs +++ b/codex-rs/core/src/tools/handlers/agent_jobs.rs @@ -116,10 +116,7 @@ async fn build_runner_options( "multi-agent runtime is disabled; this session cannot spawn workers".to_string(), )); } - let agent_max_threads = turn - .config - .effective_agent_max_threads(multi_agent_version) - .map_err(|err| FunctionCallError::Fatal(err.to_string()))?; + let agent_max_threads = turn.config.effective_agent_max_threads(multi_agent_version); if agent_max_threads == Some(0) { return Err(FunctionCallError::RespondToModel( "agent thread limit reached; this session cannot spawn more subagents".to_string(), diff --git a/codex-rs/core/tests/suite/model_runtime_selectors.rs b/codex-rs/core/tests/suite/model_runtime_selectors.rs index c17121b52105..15ed519af7bf 100644 --- a/codex-rs/core/tests/suite/model_runtime_selectors.rs +++ b/codex-rs/core/tests/suite/model_runtime_selectors.rs @@ -190,6 +190,7 @@ async fn remote_multi_agent_selector_overrides_feature_flags() -> Result<()> { let mut v2_model = remote_model("test-multi-agent-v2"); v2_model.multi_agent_version = Some(MultiAgentVersion::V2); let v2_body = response_body_for_remote_model(v2_model, |config| { + config.agent_max_threads = Some(3); config .features .enable(Feature::Collab) From a44923c056afdde77ad82f620825d95df392e0f1 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Wed, 3 Jun 2026 22:56:25 +0100 Subject: [PATCH 2/2] cleaning --- codex-rs/core/src/config/config_tests.rs | 34 +++++++++++++++++++++--- codex-rs/core/src/config/mod.rs | 9 +++---- codex-rs/core/src/session/mod.rs | 12 +++------ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 12186c227b2a..dac8286749fa 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -9945,7 +9945,7 @@ subagent_usage_hint_text = "" } #[tokio::test] -async fn multi_agent_v2_config_selection_rejects_agents_max_threads() -> std::io::Result<()> { +async fn multi_agent_v2_feature_rejects_agents_max_threads() -> std::io::Result<()> { let codex_home = TempDir::new()?; std::fs::write( codex_home.path().join(CONFIG_TOML_FILE), @@ -9963,13 +9963,13 @@ max_threads = 3 .build() .await?; let err = config - .validate_config_selected_multi_agent_version(MultiAgentVersion::V2) + .validate_multi_agent_v2_config() .expect_err("agents.max_threads should conflict with multi_agent_v2"); assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput); assert_eq!( err.to_string(), - "agents.max_threads cannot be set when features.multi_agent_v2 selects the multi-agent runtime" + "agents.max_threads cannot be set when features.multi_agent_v2 is enabled" ); assert_eq!( config.effective_agent_max_threads(MultiAgentVersion::V2), @@ -9979,6 +9979,34 @@ max_threads = 3 Ok(()) } +#[tokio::test] +async fn catalog_v2_allows_agents_max_threads_when_feature_disabled() -> std::io::Result<()> { + let codex_home = TempDir::new()?; + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#"[features.multi_agent_v2] +enabled = false + +[agents] +max_threads = 3 +"#, + )?; + + let config = ConfigBuilder::without_managed_config_for_tests() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .build() + .await?; + + config.validate_multi_agent_v2_config()?; + assert_eq!( + config.effective_agent_max_threads(MultiAgentVersion::V2), + Some(3) + ); + + Ok(()) +} + #[tokio::test] async fn multi_agent_v2_rejects_invalid_wait_timeouts() -> std::io::Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index a449731e6b14..9d0b13dae50f 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1292,14 +1292,11 @@ impl Config { } } - pub(crate) fn validate_config_selected_multi_agent_version( - &self, - multi_agent_version: MultiAgentVersion, - ) -> std::io::Result<()> { - if multi_agent_version == MultiAgentVersion::V2 && self.agent_max_threads.is_some() { + pub(crate) fn validate_multi_agent_v2_config(&self) -> std::io::Result<()> { + if self.features.enabled(Feature::MultiAgentV2) && self.agent_max_threads.is_some() { Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, - "agents.max_threads cannot be set when features.multi_agent_v2 selects the multi-agent runtime", + "agents.max_threads cannot be set when features.multi_agent_v2 is enabled", )) } else { Ok(()) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 7fd08fc50b2b..3ea33a6bde3b 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -557,15 +557,9 @@ impl Codex { .await; let multi_agent_version = resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version); - if multi_agent_version - .or(model_info.multi_agent_version) - .is_none() - { - let multi_agent_version = config.multi_agent_version_from_features(); - config - .validate_config_selected_multi_agent_version(multi_agent_version) - .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; - } + config + .validate_multi_agent_v2_config() + .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; let base_instructions = config .base_instructions .clone()