diff --git a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs index 67f109b738..5d4d1d6ac1 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs @@ -82,6 +82,9 @@ impl ToolHandler for Handler { .await; let mut config = build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?; + if let Some(service_tier) = args.service_tier.as_ref() { + config.service_tier = Some(service_tier.clone()); + } if args.fork_context { reject_full_fork_spawn_overrides( role_name, diff --git a/codex-rs/core/src/tools/handlers/multi_agents_common.rs b/codex-rs/core/src/tools/handlers/multi_agents_common.rs index 9c28b5426a..7058024acd 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_common.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_common.rs @@ -342,9 +342,13 @@ pub(crate) async fn apply_spawn_agent_service_tier( parent_service_tier: Option<&str>, requested_service_tier: Option<&str>, ) -> Result<(), FunctionCallError> { - let Some(candidate_service_tier) = requested_service_tier - .map(str::to_string) - .or_else(|| config.service_tier.clone()) + let requested_service_tier_is_effective = + requested_service_tier.is_some_and(|requested_service_tier| { + config.service_tier.as_deref() == Some(requested_service_tier) + }); + let Some(candidate_service_tier) = config + .service_tier + .clone() .or_else(|| parent_service_tier.map(str::to_string)) else { config.service_tier = None; @@ -366,7 +370,7 @@ pub(crate) async fn apply_spawn_agent_service_tier( return Ok(()); } - if requested_service_tier.is_none() { + if !requested_service_tier_is_effective { config.service_tier = None; return Ok(()); } diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 94f8ed3d87..fa5dd442df 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -567,6 +567,52 @@ async fn spawn_agent_role_service_tier_persists_in_child_config() { ); } +#[tokio::test] +async fn spawn_agent_role_service_tier_overrides_spawn_argument() { + #[derive(Debug, Deserialize)] + struct SpawnAgentResult { + agent_id: String, + } + + let (mut session, mut turn) = make_session_and_context().await; + let role_name = install_role_with_service_tier(&mut turn, "gpt-5.4").await; + let manager = thread_manager(); + let root = manager + .start_thread((*turn.config).clone()) + .await + .expect("root thread should start"); + session.services.agent_control = manager.agent_control(); + session.conversation_id = root.thread_id; + + let output = SpawnAgentHandler::default() + .handle(invocation( + Arc::new(session), + Arc::new(turn), + "spawn_agent", + function_payload(json!({ + "message": "inspect this repo", + "agent_type": role_name, + "service_tier": "turbo" + })), + )) + .await + .expect("role-configured service tier should win over the spawn argument"); + let (content, _) = expect_text_output(output); + let result: SpawnAgentResult = + serde_json::from_str(&content).expect("spawn_agent result should be json"); + let snapshot = manager + .get_thread(parse_agent_id(&result.agent_id)) + .await + .expect("spawned agent thread should exist") + .config_snapshot() + .await; + + assert_eq!( + snapshot.service_tier, + Some(ServiceTier::Fast.request_value().to_string()) + ); +} + #[tokio::test] async fn spawn_agent_service_tier_override_rejects_unknown_tier() { let (session, turn) = make_session_and_context().await; diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index 79772be91f..5e4b7863a2 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -81,6 +81,9 @@ impl ToolHandler for Handler { .await; let mut config = build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?; + if let Some(service_tier) = args.service_tier.as_ref() { + config.service_tier = Some(service_tier.clone()); + } if matches!(fork_mode, Some(SpawnAgentForkMode::FullHistory)) { reject_full_fork_spawn_overrides( role_name,