From 20d36ee92f3e511cc39619e9100f01a095bf709a Mon Sep 17 00:00:00 2001 From: shijie-openai Date: Sun, 28 Jun 2026 17:11:53 -0700 Subject: [PATCH 1/5] Add configurable multi-agent mode hint text --- .../schema/json/ClientRequest.json | 2 +- .../schema/json/ServerNotification.json | 2 +- .../codex_app_server_protocol.schemas.json | 2 +- .../codex_app_server_protocol.v2.schemas.json | 2 +- .../schema/json/v2/ThreadForkResponse.json | 2 +- .../schema/json/v2/ThreadResumeResponse.json | 2 +- .../v2/ThreadSettingsUpdatedNotification.json | 2 +- .../schema/json/v2/ThreadStartParams.json | 2 +- .../schema/json/v2/ThreadStartResponse.json | 2 +- .../schema/json/v2/TurnStartParams.json | 2 +- .../schema/typescript/MultiAgentMode.ts | 6 +- codex-rs/core/config.schema.json | 3 + codex-rs/core/src/config/config_tests.rs | 21 ++++ codex-rs/core/src/config/mod.rs | 7 ++ .../context/multi_agent_mode_instructions.rs | 15 ++- codex-rs/core/src/context_manager/updates.rs | 20 ++-- codex-rs/core/src/session/mod.rs | 22 ++-- codex-rs/core/src/session/multi_agents.rs | 16 ++- codex-rs/core/tests/suite/multi_agent_mode.rs | 104 ++++++++++++++++++ codex-rs/features/src/feature_configs.rs | 2 + codex-rs/features/src/tests.rs | 2 + codex-rs/protocol/src/config_types.rs | 6 +- 22 files changed, 201 insertions(+), 43 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index f3983e31f41c..67edb98993f6 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1786,7 +1786,7 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 23dae42a9dee..84755a804d38 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2713,7 +2713,7 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index b77b512b36e3..c3f99cda0957 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -12860,7 +12860,7 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index bdd61d6b9436..ee677159dc0f 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -9264,7 +9264,7 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 5d20d64e6874..9a88211ae46a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -681,7 +681,7 @@ ] }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index ee1ca83917a3..acb099df6824 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -681,7 +681,7 @@ ] }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json index 8d528246b524..4f3c2e423d21 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json @@ -108,7 +108,7 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index 3a23d13d22b4..ad82813545f8 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -195,7 +195,7 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 9d165986967e..72a39af87f1d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -681,7 +681,7 @@ ] }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 36b4d2a15d82..c76308c8826c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -141,7 +141,7 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", "enum": [ "none", "explicitRequestOnly", diff --git a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts index b59be94f8ecd..03a9e83071be 100644 --- a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts +++ b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts @@ -3,9 +3,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. /** - * Controls whether the model receives multi-agent delegation instructions and, - * when it does, whether it should only spawn sub-agents after an explicit user - * request or may delegate proactively when doing so would help. `none` leaves - * the multi-agent tools available without injecting delegation instructions. + * Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the + * built-in explicit/proactive policy so configured mode instructions can define the policy. */ export type MultiAgentMode = "none" | "explicitRequestOnly" | "proactive"; diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index ab787ba34f2f..876180dd7e88 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1750,6 +1750,9 @@ "minimum": 0.0, "type": "integer" }, + "multi_agent_mode_hint_text": { + "type": "string" + }, "non_code_mode_only": { "type": "boolean" }, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index c34447554196..6156b9b9eae1 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -10307,6 +10307,7 @@ default_wait_timeout_ms = 30000 usage_hint_text = "Custom delegation guidance." root_agent_usage_hint_text = "Root guidance." subagent_usage_hint_text = "Subagent guidance." +multi_agent_mode_hint_text = "Custom mode guidance." tool_namespace = "agents" hide_spawn_agent_metadata = true non_code_mode_only = true @@ -10343,6 +10344,10 @@ non_code_mode_only = true config.multi_agent_v2.subagent_usage_hint_text.as_deref(), Some("Subagent guidance.") ); + assert_eq!( + config.multi_agent_v2.multi_agent_mode_hint_text.as_deref(), + Some("Custom mode guidance.") + ); assert_eq!( config.multi_agent_v2.tool_namespace.as_deref(), Some("agents") @@ -10405,6 +10410,22 @@ max_concurrent_threads_per_session = 17 ); } +#[test] +fn multi_agent_v2_preserves_empty_mode_hint_override() { + let config_toml = toml::from_str( + r#"[features.multi_agent_v2] +multi_agent_mode_hint_text = "" +"#, + ) + .expect("multi-agent v2 config should parse"); + + let expected = MultiAgentV2Config { + multi_agent_mode_hint_text: Some(String::new()), + ..Default::default() + }; + assert_eq!(resolve_multi_agent_v2_config(&config_toml), expected); +} + #[tokio::test] async fn multi_agent_v2_empty_usage_hint_overrides_clear_default_hints() -> 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 d90d010102d5..e29cb3875c9b 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1144,6 +1144,7 @@ pub struct MultiAgentV2Config { pub usage_hint_text: Option, pub root_agent_usage_hint_text: Option, pub subagent_usage_hint_text: Option, + pub multi_agent_mode_hint_text: Option, pub tool_namespace: Option, pub hide_spawn_agent_metadata: bool, pub non_code_mode_only: bool, @@ -1165,6 +1166,7 @@ impl MultiAgentV2Config { DEFAULT_MULTI_AGENT_V2_SUBAGENT_USAGE_HINT_TEXT, max_concurrent_threads_per_session, )), + multi_agent_mode_hint_text: None, tool_namespace: Some(DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE.to_string()), hide_spawn_agent_metadata: true, non_code_mode_only: true, @@ -2501,6 +2503,10 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config base.map(|config| &config.subagent_usage_hint_text), default.subagent_usage_hint_text, ); + let multi_agent_mode_hint_text = base + .and_then(|config| config.multi_agent_mode_hint_text.as_ref()) + .cloned() + .or(default.multi_agent_mode_hint_text); let tool_namespace = base .and_then(|config| config.tool_namespace.as_ref()) .cloned() @@ -2520,6 +2526,7 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config usage_hint_text, root_agent_usage_hint_text, subagent_usage_hint_text, + multi_agent_mode_hint_text, tool_namespace, hide_spawn_agent_metadata, non_code_mode_only, diff --git a/codex-rs/core/src/context/multi_agent_mode_instructions.rs b/codex-rs/core/src/context/multi_agent_mode_instructions.rs index ff471967cacb..4b11563a2714 100644 --- a/codex-rs/core/src/context/multi_agent_mode_instructions.rs +++ b/codex-rs/core/src/context/multi_agent_mode_instructions.rs @@ -4,17 +4,20 @@ use codex_protocol::protocol::MULTI_AGENT_MODE_CLOSE_TAG; use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG; const EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT: &str = "Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work."; -const NO_MULTI_AGENT_MODE_TEXT: &str = "Multi-agent delegation mode instructions are inactive. Any earlier multi-agent mode developer message no longer applies."; const PROACTIVE_MULTI_AGENT_MODE_TEXT: &str = "Proactive multi-agent delegation is active. Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies. Use sub-agents when parallel work would materially improve speed or quality. This mode remains active until a later multi-agent mode developer message changes it."; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct MultiAgentModeInstructions { multi_agent_mode: MultiAgentMode, + hint_text: Option, } impl MultiAgentModeInstructions { - pub(crate) fn new(multi_agent_mode: MultiAgentMode) -> Self { - Self { multi_agent_mode } + pub(crate) fn new(multi_agent_mode: MultiAgentMode, hint_text: Option) -> Self { + Self { + multi_agent_mode, + hint_text, + } } } @@ -32,8 +35,12 @@ impl ContextualUserFragment for MultiAgentModeInstructions { } fn body(&self) -> String { + if let Some(hint_text) = &self.hint_text { + return hint_text.clone(); + } + match self.multi_agent_mode { - MultiAgentMode::None => NO_MULTI_AGENT_MODE_TEXT.to_string(), + MultiAgentMode::None => String::new(), MultiAgentMode::ExplicitRequestOnly => { EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT.to_string() } diff --git a/codex-rs/core/src/context_manager/updates.rs b/codex-rs/core/src/context_manager/updates.rs index 5e81bc2a7d11..cfc9e7b5b9d7 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -85,13 +85,19 @@ fn build_multi_agent_mode_update_item( } match effective_multi_agent_mode { - Some(MultiAgentMode::None) => { - Some(MultiAgentModeInstructions::new(MultiAgentMode::None).render()) - } - Some(multi_agent_mode) => Some(MultiAgentModeInstructions::new(multi_agent_mode).render()), - None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => { - Some(MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly).render()) - } + Some(multi_agent_mode) => Some( + MultiAgentModeInstructions::new( + multi_agent_mode, + next.config + .multi_agent_v2 + .multi_agent_mode_hint_text + .clone(), + ) + .render(), + ), + None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => Some( + MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly, None).render(), + ), None => None, } } diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 809fa92015bf..90c2177c8e5d 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -94,7 +94,6 @@ use codex_protocol::approvals::NetworkPolicyRuleAction; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::config_types::AutoCompactTokenLimitScope; use codex_protocol::config_types::ModeKind; -use codex_protocol::config_types::MultiAgentMode; use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE; use codex_protocol::config_types::Settings; use codex_protocol::config_types::WebSearchMode; @@ -3430,16 +3429,17 @@ impl Session { { items.push(usage_hint_message); } - match multi_agents::effective_multi_agent_mode(turn_context) { - Some( - multi_agent_mode - @ (MultiAgentMode::ExplicitRequestOnly | MultiAgentMode::Proactive), - ) => { - items.push(ContextualUserFragment::into( - MultiAgentModeInstructions::new(multi_agent_mode), - )); - } - Some(MultiAgentMode::None) | None => {} + if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode(turn_context) { + items.push(ContextualUserFragment::into( + MultiAgentModeInstructions::new( + multi_agent_mode, + turn_context + .config + .multi_agent_v2 + .multi_agent_mode_hint_text + .clone(), + ), + )); } if let Some(contextual_user_message) = crate::context_manager::updates::build_contextual_user_message(contextual_user_sections) diff --git a/codex-rs/core/src/session/multi_agents.rs b/codex-rs/core/src/session/multi_agents.rs index 385f2281ba97..6b3c5910dbd0 100644 --- a/codex-rs/core/src/session/multi_agents.rs +++ b/codex-rs/core/src/session/multi_agents.rs @@ -41,9 +41,19 @@ pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option MultiAgentMode::Proactive, - _ => MultiAgentMode::ExplicitRequestOnly, + // A configured hint fully defines the mode instructions, so select the `None` variant to + // bypass effort-derived explicit/proactive policy. `Some("")` intentionally suppresses both + // built-in instructions. + let multi_agent_mode = match &turn_context + .config + .multi_agent_v2 + .multi_agent_mode_hint_text + { + Some(_) => MultiAgentMode::None, + None => match turn_context.effective_reasoning_effort() { + Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive, + _ => MultiAgentMode::ExplicitRequestOnly, + }, }; match &turn_context.session_source { diff --git a/codex-rs/core/tests/suite/multi_agent_mode.rs b/codex-rs/core/tests/suite/multi_agent_mode.rs index fd177e7ed9ef..06d66556d3cf 100644 --- a/codex-rs/core/tests/suite/multi_agent_mode.rs +++ b/codex-rs/core/tests/suite/multi_agent_mode.rs @@ -23,6 +23,7 @@ use serde_json::Value; const NO_SPAWN_TEXT: &str = "Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work."; const PROACTIVE_TEXT: &str = "Proactive multi-agent delegation is active."; +const CUSTOM_MODE_HINT_TEXT: &str = "Use the configured delegation policy."; fn add_ultra_reasoning(model_info: &mut ModelInfo) { model_info.supports_reasoning_summaries = true; @@ -41,6 +42,11 @@ fn configure_multi_agent_v2(config: &mut Config) { .expect("test config should allow feature update"); } +fn configure_custom_mode_hint(config: &mut Config) { + configure_multi_agent_v2(config); + config.multi_agent_v2.multi_agent_mode_hint_text = Some(CUSTOM_MODE_HINT_TEXT.to_string()); +} + fn configure_ultra(config: &mut Config) { configure_multi_agent_v2(config); config.model_reasoning_effort = Some(ReasoningEffort::Ultra); @@ -120,6 +126,104 @@ async fn ultra_reasoning_uses_max_and_proactive_mode() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn configured_mode_hint_uses_none_mode_across_reasoning_efforts() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let responses = mount_sse_sequence( + &server, + (1..=2) + .map(|index| { + sse(vec![ + ev_response_created(&format!("resp-{index}")), + ev_completed(&format!("resp-{index}")), + ]) + }) + .collect(), + ) + .await; + let test = test_codex() + .with_model_info_override("gpt-5.4", add_ultra_reasoning) + .with_config(configure_custom_mode_hint) + .build(&server) + .await?; + let rollout_path = test + .session_configured + .rollout_path + .clone() + .expect("rollout path"); + + submit_turn(&test.codex, "explicit", Some(ReasoningEffort::High)).await?; + submit_turn(&test.codex, "proactive", Some(ReasoningEffort::Ultra)).await?; + + let requests = responses.requests(); + let first_input = requests[0].input(); + let first_texts = developer_texts(&first_input); + let second_input = requests[1].input(); + let second_texts = developer_texts(&second_input); + let instruction_counts = |texts: &[&str]| { + ( + count_containing(texts, CUSTOM_MODE_HINT_TEXT), + count_containing(texts, NO_SPAWN_TEXT), + count_containing(texts, PROACTIVE_TEXT), + ) + }; + assert_eq!(instruction_counts(&first_texts), (1, 0, 0)); + assert_eq!(instruction_counts(&second_texts), (1, 0, 0)); + let rollout_values = std::fs::read_to_string(rollout_path)? + .lines() + .map(serde_json::from_str::) + .collect::>>()?; + let recorded_modes = rollout_values + .iter() + .filter(|value| value.get("type").and_then(Value::as_str) == Some("turn_context")) + .filter_map(|value| { + value + .pointer("/payload/multi_agent_mode") + .and_then(Value::as_str) + .map(str::to_string) + }) + .collect::>(); + assert_eq!(recorded_modes, ["none", "none"]); + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn empty_configured_mode_hint_suppresses_builtin_text() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let response = mount_sse_once( + &server, + sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), + ) + .await; + let test = test_codex() + .with_config(|config| { + configure_multi_agent_v2(config); + config.multi_agent_v2.multi_agent_mode_hint_text = Some(String::new()); + }) + .build(&server) + .await?; + + submit_turn(&test.codex, "hello", Some(ReasoningEffort::High)).await?; + + let input = response.single_request().input(); + let texts = developer_texts(&input); + assert_eq!( + ( + count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG), + count_containing(&texts, NO_SPAWN_TEXT), + count_containing(&texts, PROACTIVE_TEXT), + ), + (1, 0, 0) + ); + + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn leaving_ultra_after_cold_resume_emits_explicit_mode() -> Result<()> { skip_if_no_network!(Ok(())); diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 9fc5df2d590a..0100dc1d8387 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -56,6 +56,8 @@ pub struct MultiAgentV2ConfigToml { #[serde(skip_serializing_if = "Option::is_none")] pub subagent_usage_hint_text: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub multi_agent_mode_hint_text: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[schemars(length(min = 1, max = 64), regex(pattern = r"^[a-zA-Z0-9_-]+$"))] pub tool_namespace: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 69fd59e13a9e..13abd9ecd270 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -610,6 +610,7 @@ usage_hint_enabled = false usage_hint_text = "Custom delegation guidance." root_agent_usage_hint_text = "Root guidance." subagent_usage_hint_text = "Subagent guidance." +multi_agent_mode_hint_text = "Custom mode guidance." tool_namespace = "agents" hide_spawn_agent_metadata = true non_code_mode_only = true @@ -633,6 +634,7 @@ non_code_mode_only = true usage_hint_text: Some("Custom delegation guidance.".to_string()), root_agent_usage_hint_text: Some("Root guidance.".to_string()), subagent_usage_hint_text: Some("Subagent guidance.".to_string()), + multi_agent_mode_hint_text: Some("Custom mode guidance.".to_string()), tool_namespace: Some("agents".to_string()), hide_spawn_agent_metadata: Some(true), non_code_mode_only: Some(true), diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 83bcbdc0ae25..8ba6f8a9c328 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -296,10 +296,8 @@ pub enum Personality { Pragmatic, } -/// Controls whether the model receives multi-agent delegation instructions and, -/// when it does, whether it should only spawn sub-agents after an explicit user -/// request or may delegate proactively when doing so would help. `none` leaves -/// the multi-agent tools available without injecting delegation instructions. +/// Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the +/// built-in explicit/proactive policy so configured mode instructions can define the policy. #[derive( Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default, )] From 248c46cb7d4e88eeecc0dfc2bf92a3cec2e36cc9 Mon Sep 17 00:00:00 2001 From: shijie-openai Date: Sun, 28 Jun 2026 22:50:42 -0700 Subject: [PATCH 2/5] codex: fix CI failure on PR #30493 --- codex-rs/core/src/context_manager/updates.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/context_manager/updates.rs b/codex-rs/core/src/context_manager/updates.rs index cfc9e7b5b9d7..811b37624159 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -96,7 +96,11 @@ fn build_multi_agent_mode_update_item( .render(), ), None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => Some( - MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly, None).render(), + MultiAgentModeInstructions::new( + MultiAgentMode::ExplicitRequestOnly, + /*hint_text*/ None, + ) + .render(), ), None => None, } From a3c3ed365f8c6f16ea31da201a40a31377b1e9a1 Mon Sep 17 00:00:00 2001 From: shijie-openai Date: Thu, 2 Jul 2026 15:13:54 -0700 Subject: [PATCH 3/5] codex: distinguish custom multi-agent mode --- .../app-server-protocol/schema/json/ClientRequest.json | 4 ++-- .../schema/json/ServerNotification.json | 4 ++-- .../schema/json/codex_app_server_protocol.schemas.json | 4 ++-- .../schema/json/codex_app_server_protocol.v2.schemas.json | 4 ++-- .../schema/json/v2/ThreadForkResponse.json | 4 ++-- .../schema/json/v2/ThreadResumeResponse.json | 4 ++-- .../schema/json/v2/ThreadSettingsUpdatedNotification.json | 4 ++-- .../schema/json/v2/ThreadStartParams.json | 4 ++-- .../schema/json/v2/ThreadStartResponse.json | 4 ++-- .../schema/json/v2/TurnStartParams.json | 4 ++-- .../schema/typescript/MultiAgentMode.ts | 6 +++--- codex-rs/core/src/context/multi_agent_mode_instructions.rs | 2 +- codex-rs/core/src/session/multi_agents.rs | 7 +++---- codex-rs/core/tests/suite/multi_agent_mode.rs | 4 ++-- codex-rs/protocol/src/config_types.rs | 7 ++++--- 15 files changed, 33 insertions(+), 33 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 67edb98993f6..9c10e329eb9a 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1786,9 +1786,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 84755a804d38..2e8c5b69aadd 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2713,9 +2713,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index c3f99cda0957..14f2ec536863 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -12860,9 +12860,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index ee677159dc0f..6f86b94ed7b4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -9264,9 +9264,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 9a88211ae46a..4d365b2111ea 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -681,9 +681,9 @@ ] }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index acb099df6824..7b524d6653aa 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -681,9 +681,9 @@ ] }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json index 4f3c2e423d21..b46eb935ce5d 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json @@ -108,9 +108,9 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index ad82813545f8..c5032bc1f85f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -195,9 +195,9 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 72a39af87f1d..be01bf6a7d37 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -681,9 +681,9 @@ ] }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index c76308c8826c..3feb041bef54 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -141,9 +141,9 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the built-in explicit/proactive policy so configured mode instructions can define the policy.", + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", "enum": [ - "none", + "custom", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts index 03a9e83071be..89bfb3022897 100644 --- a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts +++ b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts @@ -3,7 +3,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. /** - * Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the - * built-in explicit/proactive policy so configured mode instructions can define the policy. + * Controls the effective multi-agent delegation instructions for a turn. `custom` means the + * configured mode hint defines the policy instead of a built-in policy. */ -export type MultiAgentMode = "none" | "explicitRequestOnly" | "proactive"; +export type MultiAgentMode = "custom" | "explicitRequestOnly" | "proactive"; diff --git a/codex-rs/core/src/context/multi_agent_mode_instructions.rs b/codex-rs/core/src/context/multi_agent_mode_instructions.rs index 4b11563a2714..166886e58d58 100644 --- a/codex-rs/core/src/context/multi_agent_mode_instructions.rs +++ b/codex-rs/core/src/context/multi_agent_mode_instructions.rs @@ -40,7 +40,7 @@ impl ContextualUserFragment for MultiAgentModeInstructions { } match self.multi_agent_mode { - MultiAgentMode::None => String::new(), + MultiAgentMode::Custom => String::new(), MultiAgentMode::ExplicitRequestOnly => { EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT.to_string() } diff --git a/codex-rs/core/src/session/multi_agents.rs b/codex-rs/core/src/session/multi_agents.rs index 6b3c5910dbd0..4f8db169fc44 100644 --- a/codex-rs/core/src/session/multi_agents.rs +++ b/codex-rs/core/src/session/multi_agents.rs @@ -41,15 +41,14 @@ pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option MultiAgentMode::None, + Some(_) => MultiAgentMode::Custom, None => match turn_context.effective_reasoning_effort() { Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive, _ => MultiAgentMode::ExplicitRequestOnly, diff --git a/codex-rs/core/tests/suite/multi_agent_mode.rs b/codex-rs/core/tests/suite/multi_agent_mode.rs index 06d66556d3cf..a732e5dc90b2 100644 --- a/codex-rs/core/tests/suite/multi_agent_mode.rs +++ b/codex-rs/core/tests/suite/multi_agent_mode.rs @@ -127,7 +127,7 @@ async fn ultra_reasoning_uses_max_and_proactive_mode() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn configured_mode_hint_uses_none_mode_across_reasoning_efforts() -> Result<()> { +async fn configured_mode_hint_uses_custom_mode_across_reasoning_efforts() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -185,7 +185,7 @@ async fn configured_mode_hint_uses_none_mode_across_reasoning_efforts() -> Resul .map(str::to_string) }) .collect::>(); - assert_eq!(recorded_modes, ["none", "none"]); + assert_eq!(recorded_modes, ["custom", "custom"]); Ok(()) } diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 8ba6f8a9c328..dba7a4b8c8d1 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -296,8 +296,8 @@ pub enum Personality { Pragmatic, } -/// Controls the effective multi-agent delegation instructions for a turn. `none` bypasses the -/// built-in explicit/proactive policy so configured mode instructions can define the policy. +/// Controls the effective multi-agent delegation instructions for a turn. `custom` means the +/// configured mode hint defines the policy instead of a built-in policy. #[derive( Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default, )] @@ -305,7 +305,8 @@ pub enum Personality { #[ts(rename_all = "camelCase")] #[strum(serialize_all = "camelCase")] pub enum MultiAgentMode { - None, + #[serde(alias = "none")] + Custom, #[default] ExplicitRequestOnly, Proactive, From 329c5f148af4990c83e782804eac969c89476428 Mon Sep 17 00:00:00 2001 From: shijie-openai Date: Thu, 2 Jul 2026 18:07:17 -0700 Subject: [PATCH 4/5] codex: persist custom hint in multi-agent mode --- .../schema/json/ClientRequest.json | 28 ++++++++++++++---- .../schema/json/ServerNotification.json | 28 ++++++++++++++---- .../codex_app_server_protocol.schemas.json | 28 ++++++++++++++---- .../codex_app_server_protocol.v2.schemas.json | 28 ++++++++++++++---- .../schema/json/v2/ThreadForkResponse.json | 28 ++++++++++++++---- .../schema/json/v2/ThreadResumeResponse.json | 28 ++++++++++++++---- .../v2/ThreadSettingsUpdatedNotification.json | 28 ++++++++++++++---- .../schema/json/v2/ThreadStartParams.json | 28 ++++++++++++++---- .../schema/json/v2/ThreadStartResponse.json | 28 ++++++++++++++---- .../schema/json/v2/TurnStartParams.json | 28 ++++++++++++++---- .../schema/typescript/MultiAgentMode.ts | 2 +- .../context/multi_agent_mode_instructions.rs | 16 +++------- codex-rs/core/src/context_manager/updates.rs | 21 +++----------- codex-rs/core/src/session/mod.rs | 9 +----- codex-rs/core/src/session/multi_agents.rs | 2 +- codex-rs/core/tests/suite/multi_agent_mode.rs | 16 +++++----- codex-rs/protocol/src/config_types.rs | 29 +++++++++++++++---- codex-rs/protocol/src/protocol.rs | 21 +++++++++++++- 18 files changed, 283 insertions(+), 113 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 9c10e329eb9a..7f7ead662b3b 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1787,12 +1787,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 2e8c5b69aadd..e7a533d76bc0 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2714,12 +2714,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 14f2ec536863..a717ec342852 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -12861,12 +12861,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 6f86b94ed7b4..6155844a8b8a 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -9265,12 +9265,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 4d365b2111ea..59a14cc070b1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -682,12 +682,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index 7b524d6653aa..80fbb183f8a3 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -682,12 +682,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json index b46eb935ce5d..f3296fa376ac 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json @@ -109,12 +109,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index c5032bc1f85f..3819f1f7611e 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -196,12 +196,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "Personality": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index be01bf6a7d37..3ef1577038df 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -682,12 +682,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 3feb041bef54..cc4869e1d18c 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -142,12 +142,28 @@ }, "MultiAgentMode": { "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", - "enum": [ - "custom", - "explicitRequestOnly", - "proactive" - ], - "type": "string" + "oneOf": [ + { + "enum": [ + "explicitRequestOnly", + "proactive" + ], + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "custom": { + "type": "string" + } + }, + "required": [ + "custom" + ], + "title": "CustomMultiAgentMode", + "type": "object" + } + ] }, "NetworkAccess": { "enum": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts index 89bfb3022897..7784a6f5ca17 100644 --- a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts +++ b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts @@ -6,4 +6,4 @@ * Controls the effective multi-agent delegation instructions for a turn. `custom` means the * configured mode hint defines the policy instead of a built-in policy. */ -export type MultiAgentMode = "custom" | "explicitRequestOnly" | "proactive"; +export type MultiAgentMode = { "custom": string } | "explicitRequestOnly" | "proactive"; diff --git a/codex-rs/core/src/context/multi_agent_mode_instructions.rs b/codex-rs/core/src/context/multi_agent_mode_instructions.rs index 166886e58d58..9c8f5ad5ed6d 100644 --- a/codex-rs/core/src/context/multi_agent_mode_instructions.rs +++ b/codex-rs/core/src/context/multi_agent_mode_instructions.rs @@ -9,15 +9,11 @@ const PROACTIVE_MULTI_AGENT_MODE_TEXT: &str = "Proactive multi-agent delegation #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct MultiAgentModeInstructions { multi_agent_mode: MultiAgentMode, - hint_text: Option, } impl MultiAgentModeInstructions { - pub(crate) fn new(multi_agent_mode: MultiAgentMode, hint_text: Option) -> Self { - Self { - multi_agent_mode, - hint_text, - } + pub(crate) fn new(multi_agent_mode: MultiAgentMode) -> Self { + Self { multi_agent_mode } } } @@ -35,12 +31,8 @@ impl ContextualUserFragment for MultiAgentModeInstructions { } fn body(&self) -> String { - if let Some(hint_text) = &self.hint_text { - return hint_text.clone(); - } - - match self.multi_agent_mode { - MultiAgentMode::Custom => String::new(), + match &self.multi_agent_mode { + MultiAgentMode::Custom(hint_text) => hint_text.clone(), MultiAgentMode::ExplicitRequestOnly => { EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT.to_string() } diff --git a/codex-rs/core/src/context_manager/updates.rs b/codex-rs/core/src/context_manager/updates.rs index 811b37624159..a520e6f595b7 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -85,23 +85,10 @@ fn build_multi_agent_mode_update_item( } match effective_multi_agent_mode { - Some(multi_agent_mode) => Some( - MultiAgentModeInstructions::new( - multi_agent_mode, - next.config - .multi_agent_v2 - .multi_agent_mode_hint_text - .clone(), - ) - .render(), - ), - None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => Some( - MultiAgentModeInstructions::new( - MultiAgentMode::ExplicitRequestOnly, - /*hint_text*/ None, - ) - .render(), - ), + Some(multi_agent_mode) => Some(MultiAgentModeInstructions::new(multi_agent_mode).render()), + None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => { + Some(MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly).render()) + } None => None, } } diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index 90c2177c8e5d..978768ba008a 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -3431,14 +3431,7 @@ impl Session { } if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode(turn_context) { items.push(ContextualUserFragment::into( - MultiAgentModeInstructions::new( - multi_agent_mode, - turn_context - .config - .multi_agent_v2 - .multi_agent_mode_hint_text - .clone(), - ), + MultiAgentModeInstructions::new(multi_agent_mode), )); } if let Some(contextual_user_message) = diff --git a/codex-rs/core/src/session/multi_agents.rs b/codex-rs/core/src/session/multi_agents.rs index 4f8db169fc44..c2f139e68b5c 100644 --- a/codex-rs/core/src/session/multi_agents.rs +++ b/codex-rs/core/src/session/multi_agents.rs @@ -48,7 +48,7 @@ pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option MultiAgentMode::Custom, + Some(hint_text) => MultiAgentMode::Custom(hint_text.clone()), None => match turn_context.effective_reasoning_effort() { Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive, _ => MultiAgentMode::ExplicitRequestOnly, diff --git a/codex-rs/core/tests/suite/multi_agent_mode.rs b/codex-rs/core/tests/suite/multi_agent_mode.rs index a732e5dc90b2..fb26afdeeb00 100644 --- a/codex-rs/core/tests/suite/multi_agent_mode.rs +++ b/codex-rs/core/tests/suite/multi_agent_mode.rs @@ -20,6 +20,7 @@ use core_test_support::test_codex::test_codex; use core_test_support::wait_for_event; use pretty_assertions::assert_eq; use serde_json::Value; +use serde_json::json; const NO_SPAWN_TEXT: &str = "Do not spawn sub-agents unless the user or applicable AGENTS.md/skill instructions explicitly ask for sub-agents, delegation, or parallel agent work."; const PROACTIVE_TEXT: &str = "Proactive multi-agent delegation is active."; @@ -178,14 +179,15 @@ async fn configured_mode_hint_uses_custom_mode_across_reasoning_efforts() -> Res let recorded_modes = rollout_values .iter() .filter(|value| value.get("type").and_then(Value::as_str) == Some("turn_context")) - .filter_map(|value| { - value - .pointer("/payload/multi_agent_mode") - .and_then(Value::as_str) - .map(str::to_string) - }) + .filter_map(|value| value.pointer("/payload/multi_agent_mode").cloned()) .collect::>(); - assert_eq!(recorded_modes, ["custom", "custom"]); + assert_eq!( + recorded_modes, + [ + json!({"custom": CUSTOM_MODE_HINT_TEXT}), + json!({"custom": CUSTOM_MODE_HINT_TEXT}), + ] + ); Ok(()) } diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index dba7a4b8c8d1..7732efbbe5a2 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -298,20 +298,37 @@ pub enum Personality { /// Controls the effective multi-agent delegation instructions for a turn. `custom` means the /// configured mode hint defines the policy instead of a built-in policy. -#[derive( - Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default, -)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Display, JsonSchema, TS, Default)] +#[serde(rename_all = "camelCase", from = "MultiAgentModeWire")] #[ts(rename_all = "camelCase")] #[strum(serialize_all = "camelCase")] pub enum MultiAgentMode { - #[serde(alias = "none")] - Custom, + Custom(String), #[default] ExplicitRequestOnly, Proactive, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +enum MultiAgentModeWire { + None, + Custom(String), + ExplicitRequestOnly, + Proactive, +} + +impl From for MultiAgentMode { + fn from(value: MultiAgentModeWire) -> Self { + match value { + MultiAgentModeWire::None => Self::Custom(String::new()), + MultiAgentModeWire::Custom(hint_text) => Self::Custom(hint_text), + MultiAgentModeWire::ExplicitRequestOnly => Self::ExplicitRequestOnly, + MultiAgentModeWire::Proactive => Self::Proactive, + } + } +} + #[derive( Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default, )] diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 96a793bcd38f..0096915efb43 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2668,7 +2668,7 @@ impl InitialHistory { | RolloutItem::WorldState(_) | RolloutItem::EventMsg(_) => None, }) - .and_then(|turn_context| turn_context.multi_agent_mode) + .and_then(|turn_context| turn_context.multi_agent_mode.clone()) } pub fn get_resumed_session_sources(&self) -> Option<(SessionSource, Option)> { @@ -5741,6 +5741,25 @@ mod tests { Ok(()) } + #[test] + fn latest_effective_multi_agent_mode_maps_legacy_none_to_empty_custom() -> Result<()> { + let value = json!({ + "cwd": test_path_buf("/tmp"), + "approval_policy": "never", + "sandbox_policy": { "type": "danger-full-access" }, + "model": "gpt-5", + "multi_agent_mode": "none", + "summary": "auto", + }); + let item = RolloutItem::TurnContext(serde_json::from_value(value)?); + + assert_eq!( + InitialHistory::Forked(vec![item]).get_latest_effective_multi_agent_mode(), + Some(MultiAgentMode::Custom(String::new())) + ); + Ok(()) + } + #[test] fn turn_context_item_serializes_network_when_present() -> Result<()> { let item = TurnContextItem { From e2a559dc68a65c810c922d1b012653b17bd8f159 Mon Sep 17 00:00:00 2001 From: shijie-openai Date: Thu, 2 Jul 2026 18:28:50 -0700 Subject: [PATCH 5/5] codex: document multi-agent test setup --- codex-rs/core/tests/suite/multi_agent_mode.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/codex-rs/core/tests/suite/multi_agent_mode.rs b/codex-rs/core/tests/suite/multi_agent_mode.rs index fb26afdeeb00..283ed6c5ccda 100644 --- a/codex-rs/core/tests/suite/multi_agent_mode.rs +++ b/codex-rs/core/tests/suite/multi_agent_mode.rs @@ -43,6 +43,7 @@ fn configure_multi_agent_v2(config: &mut Config) { .expect("test config should allow feature update"); } +// Configuring a custom mode hint also enables multi-agent V2 for the test. fn configure_custom_mode_hint(config: &mut Config) { configure_multi_agent_v2(config); config.multi_agent_v2.multi_agent_mode_hint_text = Some(CUSTOM_MODE_HINT_TEXT.to_string());