diff --git a/codex-rs/core/src/session/review.rs b/codex-rs/core/src/session/review.rs index 62befde3e..4a995d85e 100644 --- a/codex-rs/core/src/session/review.rs +++ b/codex-rs/core/src/session/review.rs @@ -51,6 +51,7 @@ pub(super) async fn spawn_review_thread( .with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled) .with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone()) .with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata) + .with_max_concurrent_threads_per_session(config.agent_max_threads) .with_agent_type_description(crate::agent::role::spawn_tool_spec::build( &config.agent_roles, )); diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index 8c831e6b6..23d6d61fc 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -181,6 +181,7 @@ impl TurnContext { .with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled) .with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone()) .with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata) + .with_max_concurrent_threads_per_session(config.agent_max_threads) .with_agent_type_description(crate::agent::role::spawn_tool_spec::build( &config.agent_roles, )); @@ -442,6 +443,7 @@ impl Session { .with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled) .with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone()) .with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata) + .with_max_concurrent_threads_per_session(per_turn_config.agent_max_threads) .with_agent_type_description(crate::agent::role::spawn_tool_spec::build( &per_turn_config.agent_roles, )); diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index a1e685df7..1c27c3ca0 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -233,6 +233,7 @@ async fn multi_agent_v2_tools_config() -> ToolsConfig { sandbox_policy: &SandboxPolicy::DangerFullAccess, windows_sandbox_level: WindowsSandboxLevel::Disabled, }) + .with_max_concurrent_threads_per_session(Some(4)) } fn multi_agent_v2_spawn_agent_description(tools_config: &ToolsConfig) -> String { @@ -749,6 +750,7 @@ async fn spawn_agent_description_omits_usage_hint_when_disabled() { \s+Spawned\ agents\ inherit\ your\ current\ model\ by\ default\.\ Omit\ `model`\ to\ use\ that\ preferred\ default;\ set\ `model`\ only\ when\ an\ explicit\ override\ is\ needed\. \s+It\ will\ be\ able\ to\ send\ you\ and\ other\ running\ agents\ messages,\ and\ its\ final\ answer\ will\ be\ provided\ to\ you\ when\ it\ finishes\. \s+The\ new\ agent's\ canonical\ task\ name\ will\ be\ provided\ to\ it\ along\ with\ the\ message\. + \s+This\ session\ is\ configured\ with\ `max_concurrent_threads_per_session\ =\ 4`\ for\ concurrently\ open\ agent\ threads\. \s*$ "#, &description, @@ -774,6 +776,7 @@ async fn spawn_agent_description_uses_configured_usage_hint_text() { \s+Spawned\ agents\ inherit\ your\ current\ model\ by\ default\.\ Omit\ `model`\ to\ use\ that\ preferred\ default;\ set\ `model`\ only\ when\ an\ explicit\ override\ is\ needed\. \s+It\ will\ be\ able\ to\ send\ you\ and\ other\ running\ agents\ messages,\ and\ its\ final\ answer\ will\ be\ provided\ to\ you\ when\ it\ finishes\. \s+The\ new\ agent's\ canonical\ task\ name\ will\ be\ provided\ to\ it\ along\ with\ the\ message\. + \s+This\ session\ is\ configured\ with\ `max_concurrent_threads_per_session\ =\ 4`\ for\ concurrently\ open\ agent\ threads\. \s+Custom\ delegation\ guidance\ only\. \s*$ "#, diff --git a/codex-rs/tools/src/agent_tool.rs b/codex-rs/tools/src/agent_tool.rs index 346eaf8ae..42ba9c4d2 100644 --- a/codex-rs/tools/src/agent_tool.rs +++ b/codex-rs/tools/src/agent_tool.rs @@ -16,6 +16,7 @@ pub struct SpawnAgentToolOptions<'a> { pub hide_agent_type_model_reasoning: bool, pub include_usage_hint: bool, pub usage_hint_text: Option, + pub max_concurrent_threads_per_session: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -71,6 +72,7 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions<'_>) -> ToolSpe available_models_description.as_deref(), options.include_usage_hint, options.usage_hint_text, + options.max_concurrent_threads_per_session, ), strict: false, defer_loading: None, @@ -655,8 +657,16 @@ fn spawn_agent_tool_description_v2( available_models_description: Option<&str>, include_usage_hint: bool, usage_hint_text: Option, + max_concurrent_threads_per_session: Option, ) -> String { let agent_role_guidance = available_models_description.unwrap_or_default(); + let concurrency_guidance = max_concurrent_threads_per_session + .map(|limit| { + format!( + "This session is configured with `max_concurrent_threads_per_session = {limit}` for concurrently open agent threads." + ) + }) + .unwrap_or_default(); let tool_description = format!( r#" @@ -666,7 +676,8 @@ You are then able to refer to this agent as `task_3` or `/root/task1/task_3` int The spawned agent will have the same tools as you and the ability to spawn its own subagents. {SPAWN_AGENT_INHERITED_MODEL_GUIDANCE} It will be able to send you and other running agents messages, and its final answer will be provided to you when it finishes. -The new agent's canonical task name will be provided to it along with the message."# +The new agent's canonical task name will be provided to it along with the message. +{concurrency_guidance}"# ); if !include_usage_hint { diff --git a/codex-rs/tools/src/agent_tool_tests.rs b/codex-rs/tools/src/agent_tool_tests.rs index e9fd3546b..eb82636fc 100644 --- a/codex-rs/tools/src/agent_tool_tests.rs +++ b/codex-rs/tools/src/agent_tool_tests.rs @@ -40,6 +40,7 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() { hide_agent_type_model_reasoning: false, include_usage_hint: true, usage_hint_text: None, + max_concurrent_threads_per_session: Some(4), }); let ToolSpec::Function(ResponsesApiTool { @@ -61,6 +62,7 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() { .expect("spawn_agent should use object params"); assert!(description.contains("Spawns an agent to work on the specified task.")); assert!(description.contains("The spawned agent will have the same tools as you")); + assert!(description.contains("`max_concurrent_threads_per_session = 4`")); assert!(description.contains(SPAWN_AGENT_INHERITED_MODEL_GUIDANCE)); assert!( description @@ -101,6 +103,7 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() { hide_agent_type_model_reasoning: false, include_usage_hint: true, usage_hint_text: None, + max_concurrent_threads_per_session: None, }); let ToolSpec::Function(ResponsesApiTool { parameters, .. }) = tool else { diff --git a/codex-rs/tools/src/tool_config.rs b/codex-rs/tools/src/tool_config.rs index 20c812ce9..4c4689132 100644 --- a/codex-rs/tools/src/tool_config.rs +++ b/codex-rs/tools/src/tool_config.rs @@ -107,6 +107,7 @@ pub struct ToolsConfig { pub hide_spawn_agent_metadata: bool, pub spawn_agent_usage_hint: bool, pub spawn_agent_usage_hint_text: Option, + pub max_concurrent_threads_per_session: Option, pub default_mode_request_user_input: bool, pub experimental_supported_tools: Vec, pub agent_jobs_tools: bool, @@ -228,6 +229,7 @@ impl ToolsConfig { hide_spawn_agent_metadata: false, spawn_agent_usage_hint: true, spawn_agent_usage_hint_text: None, + max_concurrent_threads_per_session: None, default_mode_request_user_input: include_default_mode_request_user_input, experimental_supported_tools: model_info.experimental_supported_tools.clone(), agent_jobs_tools: include_agent_jobs, @@ -259,6 +261,14 @@ impl ToolsConfig { self } + pub fn with_max_concurrent_threads_per_session( + mut self, + max_concurrent_threads_per_session: Option, + ) -> Self { + self.max_concurrent_threads_per_session = max_concurrent_threads_per_session; + self + } + pub fn with_allow_login_shell(mut self, allow_login_shell: bool) -> Self { self.allow_login_shell = allow_login_shell; self diff --git a/codex-rs/tools/src/tool_registry_plan.rs b/codex-rs/tools/src/tool_registry_plan.rs index d3b075f5a..8ae620532 100644 --- a/codex-rs/tools/src/tool_registry_plan.rs +++ b/codex-rs/tools/src/tool_registry_plan.rs @@ -400,6 +400,7 @@ pub fn build_tool_registry_plan( hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata, include_usage_hint: config.spawn_agent_usage_hint, usage_hint_text: config.spawn_agent_usage_hint_text.clone(), + max_concurrent_threads_per_session: config.max_concurrent_threads_per_session, }), /*supports_parallel_tool_calls*/ false, config.code_mode_enabled, @@ -445,6 +446,7 @@ pub fn build_tool_registry_plan( hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata, include_usage_hint: config.spawn_agent_usage_hint, usage_hint_text: config.spawn_agent_usage_hint_text.clone(), + max_concurrent_threads_per_session: config.max_concurrent_threads_per_session, }), /*supports_parallel_tool_calls*/ false, config.code_mode_enabled, diff --git a/codex-rs/tools/src/tool_registry_plan_tests.rs b/codex-rs/tools/src/tool_registry_plan_tests.rs index 0b94ef64c..13dffb1c2 100644 --- a/codex-rs/tools/src/tool_registry_plan_tests.rs +++ b/codex-rs/tools/src/tool_registry_plan_tests.rs @@ -2200,6 +2200,7 @@ fn spawn_agent_tool_options(config: &ToolsConfig) -> SpawnAgentToolOptions<'_> { hide_agent_type_model_reasoning: config.hide_spawn_agent_metadata, include_usage_hint: config.spawn_agent_usage_hint, usage_hint_text: config.spawn_agent_usage_hint_text.clone(), + max_concurrent_threads_per_session: config.max_concurrent_threads_per_session, } }