feat: surface multi-agent thread limit in spawn description (#19360)

## Summary
- Thread `agent_max_threads` into `ToolsConfig` and
`SpawnAgentToolOptions`.
- Render the configured `max_concurrent_threads_per_session` value in
the MultiAgentV2 `spawn_agent` description.
- Cover the description text in `codex-tools` unit tests and
`codex-core` tool spec tests.

## Validation
- `just fmt`
- `cargo test -p codex-tools`
- `cargo test -p codex-core spawn_agent_description`
- `git diff --check`

## Notes
- `cargo test -p codex-core` was also attempted, but unrelated
environment-sensitive tests failed with the active local environment.
Examples: approvals reviewer defaults observed `AutoReview` instead of
`User`, request-permissions event tests did not emit events, and
proxy-env tests saw `http://127.0.0.1:50604` from the active proxy
environment.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
jif-oai
2026-04-24 15:13:54 +02:00
committed by GitHub
Unverified
parent 9eadff9713
commit deb4509302
8 changed files with 34 additions and 1 deletions
+1
View File
@@ -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,
));
@@ -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,
));
+3
View File
@@ -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*$
"#,
+12 -1
View File
@@ -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<String>,
pub max_concurrent_threads_per_session: Option<usize>,
}
#[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<String>,
max_concurrent_threads_per_session: Option<usize>,
) -> 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 {
+3
View File
@@ -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 {
+10
View File
@@ -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<String>,
pub max_concurrent_threads_per_session: Option<usize>,
pub default_mode_request_user_input: bool,
pub experimental_supported_tools: Vec<String>,
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<usize>,
) -> 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
+2
View File
@@ -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,
@@ -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,
}
}