diff --git a/codex-rs/config/src/config_toml.rs b/codex-rs/config/src/config_toml.rs index 1831316c02..0709a267da 100644 --- a/codex-rs/config/src/config_toml.rs +++ b/codex-rs/config/src/config_toml.rs @@ -688,9 +688,6 @@ pub struct AgentsToml { pub max_concurrent_threads_per_session: Option, /// Maximum nesting depth for V1 agent threads. Ignored by V2. pub max_depth: Option, - /// Reserved for controlling whether the spawn tool supports agent types. - /// Currently ignored. - pub support_agent_type: Option, /// Reserved default model for spawned subagents. Currently ignored. pub default_subagent_model: Option, /// Reserved default reasoning effort for spawned subagents. Currently ignored. diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 6d6e1e7bbb..7b09c84e78 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -72,10 +72,6 @@ "description": "Maximum nesting depth for V1 agent threads. Ignored by V2.", "format": "int32", "type": "integer" - }, - "support_agent_type": { - "description": "Reserved for controlling whether the spawn tool supports agent types. Currently ignored.", - "type": "boolean" } }, "type": "object" diff --git a/codex-rs/core/src/agent/role.rs b/codex-rs/core/src/agent/role.rs index b34aa8aa21..156ace1f39 100644 --- a/codex-rs/core/src/agent/role.rs +++ b/codex-rs/core/src/agent/role.rs @@ -241,10 +241,7 @@ pub(crate) mod spawn_tool_spec { } } - format!( - "Optional type name for the new agent. If omitted, `{DEFAULT_ROLE_NAME}` is used.\nAvailable roles:\n{}", - formatted_roles.join("\n"), - ) + format!("Available roles:\n{}", formatted_roles.join("\n")) } fn format_role(name: &str, declaration: &AgentRoleConfig) -> String { diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 370f1508ac..739cc2ad49 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -7351,7 +7351,6 @@ async fn load_config_rejects_missing_agent_role_config_file() -> std::io::Result enabled: None, max_concurrent_threads_per_session: None, max_depth: None, - support_agent_type: None, default_subagent_model: None, default_subagent_reasoning_effort: None, job_max_runtime_seconds: None, @@ -8279,7 +8278,6 @@ async fn load_config_resolves_agent_controls() -> std::io::Result<()> { agents: Some(AgentsToml { enabled: Some(false), max_depth: Some(2), - support_agent_type: Some(true), default_subagent_model: Some("gpt-5.6-terra".to_string()), default_subagent_reasoning_effort: Some(ReasoningEffort::High), interrupt_message: Some(false), @@ -8299,7 +8297,6 @@ async fn load_config_resolves_agent_controls() -> std::io::Result<()> { ( config.agents_enabled, config.agent_max_depth, - config.agent_support_agent_type, config.agent_default_subagent_model.as_deref(), config.agent_default_subagent_reasoning_effort, config.agent_interrupt_message_enabled, @@ -8307,7 +8304,6 @@ async fn load_config_resolves_agent_controls() -> std::io::Result<()> { ( false, 2, - Some(true), Some("gpt-5.6-terra"), Some(ReasoningEffort::High), false, @@ -8346,7 +8342,6 @@ async fn load_config_normalizes_agent_role_nickname_candidates() -> std::io::Res enabled: None, max_concurrent_threads_per_session: None, max_depth: None, - support_agent_type: None, default_subagent_model: None, default_subagent_reasoning_effort: None, job_max_runtime_seconds: None, @@ -8393,7 +8388,6 @@ async fn load_config_rejects_empty_agent_role_nickname_candidates() -> std::io:: enabled: None, max_concurrent_threads_per_session: None, max_depth: None, - support_agent_type: None, default_subagent_model: None, default_subagent_reasoning_effort: None, job_max_runtime_seconds: None, @@ -8434,7 +8428,6 @@ async fn load_config_rejects_duplicate_agent_role_nickname_candidates() -> std:: enabled: None, max_concurrent_threads_per_session: None, max_depth: None, - support_agent_type: None, default_subagent_model: None, default_subagent_reasoning_effort: None, job_max_runtime_seconds: None, @@ -8475,7 +8468,6 @@ async fn load_config_rejects_unsafe_agent_role_nickname_candidates() -> std::io: enabled: None, max_concurrent_threads_per_session: None, max_depth: None, - support_agent_type: None, default_subagent_model: None, default_subagent_reasoning_effort: None, job_max_runtime_seconds: None, diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index b93b4298c9..5a640aef13 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -863,9 +863,6 @@ pub struct Config { /// User-configured maximum number of spawned agent threads per session. pub agent_max_threads: Option, - /// Reserved setting for whether the spawn tool supports agent types. - pub agent_support_agent_type: Option, - /// Reserved default model for spawned subagents. pub agent_default_subagent_model: Option, @@ -3606,10 +3603,6 @@ impl Config { .as_ref() .and_then(|agents| agents.max_depth) .unwrap_or(DEFAULT_AGENT_MAX_DEPTH); - let agent_support_agent_type = cfg - .agents - .as_ref() - .and_then(|agents| agents.support_agent_type); let agent_default_subagent_model = cfg .agents .as_ref() @@ -3970,7 +3963,6 @@ impl Config { tool_output_token_limit: cfg.tool_output_token_limit, agents_enabled, agent_max_threads, - agent_support_agent_type, agent_default_subagent_model, agent_default_subagent_reasoning_effort, agent_max_depth, diff --git a/codex-rs/core/src/session/config_lock.rs b/codex-rs/core/src/session/config_lock.rs index 3fca512d87..da0c7a43f6 100644 --- a/codex-rs/core/src/session/config_lock.rs +++ b/codex-rs/core/src/session/config_lock.rs @@ -180,7 +180,6 @@ fn save_config_resolved_fields( agents.enabled = Some(config.agents_enabled); agents.max_concurrent_threads_per_session = config.agent_max_threads; agents.max_depth = Some(config.agent_max_depth); - agents.support_agent_type = config.agent_support_agent_type; agents.default_subagent_model = config.agent_default_subagent_model.clone(); agents.default_subagent_reasoning_effort = config.agent_default_subagent_reasoning_effort.clone(); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_spec.rs b/codex-rs/core/src/tools/handlers/multi_agents_spec.rs index d425c8796f..f888811052 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_spec.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_spec.rs @@ -15,6 +15,7 @@ pub const MULTI_AGENT_V1_NAMESPACE: &str = "multi_agent_v1"; const MULTI_AGENT_V1_NAMESPACE_DESCRIPTION: &str = "Tools for spawning and managing sub-agents."; const SPAWN_AGENT_INHERITED_MODEL_GUIDANCE: &str = "Spawned agents inherit your current model by default. Omit `model` to use that preferred default; set `model` only when an explicit override is needed."; +const SPAWN_AGENT_TYPE_OVERRIDE_DESCRIPTION_V1: &str = "Agent type override for the new agent. Omit to inherit the parent agent type with a full-history fork; otherwise, `default` is used."; const SPAWN_AGENT_MODEL_OVERRIDE_DESCRIPTION: &str = "Model override for the new agent. Omit unless an explicit override is needed."; const SPAWN_AGENT_SERVICE_TIER_OVERRIDE_DESCRIPTION: &str = @@ -25,6 +26,7 @@ const MAX_REASONING_EFFORT_CHARS_IN_SPAWN_AGENT_DESCRIPTION: usize = 64; pub struct SpawnAgentToolOptions { pub available_models: Vec, pub agent_type_description: String, + pub expose_agent_type: bool, pub hide_agent_type_model_reasoning: bool, pub expose_spawn_agent_model_overrides: bool, pub multi_agent_version: MultiAgentVersion, @@ -36,6 +38,7 @@ impl Default for SpawnAgentToolOptions { Self { available_models: Vec::new(), agent_type_description: String::new(), + expose_agent_type: true, hide_agent_type_model_reasoning: false, expose_spawn_agent_model_overrides: false, multi_agent_version: MultiAgentVersion::Disabled, @@ -70,6 +73,9 @@ pub fn create_spawn_agent_tool_v1(options: SpawnAgentToolOptions) -> ToolSpec { let return_value_description = "Returns the spawned agent id plus the user-facing nickname when available."; let mut properties = spawn_agent_common_properties_v1(&options.agent_type_description); + if !options.expose_agent_type { + properties.remove("agent_type"); + } if options.hide_agent_type_model_reasoning { hide_spawn_agent_metadata_options(&mut properties); } @@ -101,8 +107,10 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions) -> ToolSpec { && !options.hide_agent_type_model_reasoning) .then_some(SPAWN_AGENT_INHERITED_MODEL_GUIDANCE); let mut properties = spawn_agent_common_properties_v2(&options.agent_type_description); - if options.hide_agent_type_model_reasoning { + if !options.expose_agent_type { properties.remove("agent_type"); + } + if options.hide_agent_type_model_reasoning { properties.remove("service_tier"); } if !options.expose_spawn_agent_model_overrides { @@ -582,7 +590,9 @@ fn spawn_agent_common_properties_v1(agent_type_description: &str) -> BTreeMap BTreeMap, planned_tools: &mu .flatten(); let agent_type_description = agent_type_description(turn_context, context.default_agent_type_description); - // TODO(jif): Wire `agents.support_agent_type` into the V2 spawn tool surface. + let hide_spawn_agent_metadata = + turn_context.config.multi_agent_v2.hide_spawn_agent_metadata; planned_tools.add_arc(override_tool_exposure( multi_agent_v2_handler( SpawnAgentHandlerV2::new(SpawnAgentToolOptions { available_models: turn_context.available_models.clone(), agent_type_description, - hide_agent_type_model_reasoning: turn_context - .config - .multi_agent_v2 - .hide_spawn_agent_metadata, + expose_agent_type: !turn_context.config.agent_roles.is_empty(), + hide_agent_type_model_reasoning: hide_spawn_agent_metadata, expose_spawn_agent_model_overrides: turn_context .config .multi_agent_v2 @@ -854,6 +853,7 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu SpawnAgentHandler::new(SpawnAgentToolOptions { available_models: turn_context.available_models.clone(), agent_type_description, + expose_agent_type: !turn_context.config.agent_roles.is_empty(), hide_agent_type_model_reasoning: false, expose_spawn_agent_model_overrides: true, multi_agent_version: turn_context.multi_agent_version, diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index c084e30ac1..3f9fa3bc1b 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -1207,12 +1207,13 @@ async fn multi_agent_feature_selects_one_agent_tool_family() { .properties .as_ref() .expect("spawn_agent should use object params"); - for property in ["agent_type", "model", "reasoning_effort", "service_tier"] { + for property in ["model", "reasoning_effort", "service_tier"] { assert!( properties.contains_key(property), "expected v1 spawn_agent to expose `{property}`" ); } + assert!(!properties.contains_key("agent_type")); let v2 = probe(|turn| { set_feature(turn, Feature::MultiAgentV2, /*enabled*/ true); diff --git a/codex-rs/core/tests/suite/spawn_agent_description.rs b/codex-rs/core/tests/suite/spawn_agent_description.rs index 87eb48abb4..dcdeac9106 100644 --- a/codex-rs/core/tests/suite/spawn_agent_description.rs +++ b/codex-rs/core/tests/suite/spawn_agent_description.rs @@ -2,6 +2,7 @@ #![allow(clippy::unwrap_used)] use anyhow::Result; +use codex_core::config::AgentRoleConfig; use codex_features::Feature; use codex_login::CodexAuth; use codex_models_manager::manager::RefreshStrategy; @@ -27,6 +28,7 @@ use core_test_support::test_codex::test_codex; use serde_json::Value; use std::time::Duration; use std::time::Instant; +use test_case::test_case; use tokio::time::sleep; const MULTI_AGENT_V1_NAMESPACE: &str = "multi_agent_v1"; @@ -39,6 +41,12 @@ fn spawn_agent_description(body: &Value) -> Option { .map(str::to_string) } +fn spawn_agent_exposes_agent_type(body: &Value, namespace: &str) -> bool { + namespace_child_tool(body, namespace, SPAWN_AGENT_TOOL_NAME) + .and_then(|tool| tool.pointer("/parameters/properties/agent_type")) + .is_some() +} + fn test_model_info( slug: &str, display_name: &str, @@ -242,3 +250,57 @@ async fn spawn_agent_description_lists_visible_models_and_reasoning_efforts() -> Ok(()) } + +#[test_case(false, false, MULTI_AGENT_V1_NAMESPACE; "v1 hides agent type without roles")] +#[test_case(true, true, "collaboration"; "v2 exposes agent type with a role")] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn configured_agent_roles_control_spawn_agent_type( + multi_agent_v2: bool, + has_agent_role: bool, + namespace: &str, +) -> Result<()> { + let server = start_mock_server().await; + let response = mount_sse_once( + &server, + sse(vec![ev_response_created("resp1"), ev_completed("resp1")]), + ) + .await; + let test = test_codex() + .with_config(move |config| { + config + .features + .enable(Feature::Collab) + .expect("test config should allow feature update"); + if multi_agent_v2 { + config + .features + .enable(Feature::MultiAgentV2) + .expect("test config should allow feature update"); + } else { + config + .features + .disable(Feature::MultiAgentV2) + .expect("test config should allow feature update"); + } + if has_agent_role { + config.agent_roles.insert( + "researcher".to_string(), + AgentRoleConfig { + description: Some("Research role".to_string()), + config_file: None, + nickname_candidates: None, + }, + ); + } + }) + .build_with_auto_env(&server) + .await?; + + test.submit_turn("hello").await?; + + assert_eq!( + spawn_agent_exposes_agent_type(&response.single_request().body_json(), namespace), + has_agent_role + ); + Ok(()) +} diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index 8f7b83d22d..c42f29172f 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -242,7 +242,6 @@ fn new_config(model: Option, arg0_paths: Arg0DispatchPaths) -> anyhow::R tool_output_token_limit: None, agents_enabled: true, agent_max_threads: Some(6), - agent_support_agent_type: None, agent_default_subagent_model: None, agent_default_subagent_reasoning_effort: None, agent_job_max_runtime_seconds: None, diff --git a/codex-rs/tui/src/debug_config.rs b/codex-rs/tui/src/debug_config.rs index 19abc3357e..a74fba53b5 100644 --- a/codex-rs/tui/src/debug_config.rs +++ b/codex-rs/tui/src/debug_config.rs @@ -70,11 +70,6 @@ fn render_agents_config_lines(config: &Config) -> Vec> { config.agent_max_depth ) .into(), - format!( - " - support_agent_type = {} (not yet wired)", - format_optional(config.agent_support_agent_type) - ) - .into(), format!( " - default_subagent_model = {} (not yet wired)", format_optional(config.agent_default_subagent_model.as_deref()) @@ -654,7 +649,6 @@ mod tests { enabled = false max_concurrent_threads_per_session = 7 max_depth = -2 -support_agent_type = true default_subagent_model = "gpt-5.6-terra" default_subagent_reasoning_effort = "high" job_max_runtime_seconds = 900 diff --git a/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_output_lists_agents_fields.snap b/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_output_lists_agents_fields.snap index aec47700d2..5aaff27d21 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_output_lists_agents_fields.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__debug_config__tests__debug_config_output_lists_agents_fields.snap @@ -8,7 +8,6 @@ expression: render_to_text(&render_agents_config_lines(&config)) - enabled = false - max_concurrent_threads_per_session = 7 - max_depth = -2 (V1 only; ignored by V2) - - support_agent_type = true (not yet wired) - default_subagent_model = gpt-5.6-terra (not yet wired) - default_subagent_reasoning_effort = high (not yet wired) - job_max_runtime_seconds = 900