[codex] Add configurable multi-agent mode hint text (#30493)

## Why

Multi-agent V2 normally derives its mode instructions from reasoning
effort: Ultra enables proactive delegation, while other efforts require
an explicit request. Some deployments need to provide one configured
delegation policy that replaces those built-ins and remains stable when
reasoning effort changes.

## What changed

- Add `features.multi_agent_v2.multi_agent_mode_hint_text` alongside the
existing root and subagent hint settings.
- Treat any configured value, including an empty string, as
`MultiAgentMode::Custom(hint_text)`, so the configured text replaces the
built-in explicit-only and proactive policies.
- Persist the full custom variant and hint text in the turn-context
snapshot, so the durable comparison baseline detects both
reasoning-effort changes and configured policy-text changes.
- Preserve the existing explicit-only/proactive behavior when the
setting is absent.
- Replace the ambiguous `MultiAgentMode::None` variant with
`MultiAgentMode::Custom(String)` in new rollouts and API schemas. A
compatibility wire type maps legacy serialized `none` values to
`Custom("")` when resuming existing rollouts.
- Regenerate the config and app-server schemas.

## Configuration examples

The distinction is whether `multi_agent_mode_hint_text` is present. An
empty string is still a configured value and intentionally suppresses
the built-in mode instructions.

### Unset: preserve existing effort-derived behavior

```toml
[features.multi_agent_v2]
enabled = true
# multi_agent_mode_hint_text is omitted
```

- Ultra reasoning uses the built-in proactive delegation instructions.
- Other reasoning efforts use the built-in explicit-request-only
instructions.

### Empty: suppress all mode hint text

```toml
[features.multi_agent_v2]
enabled = true
multi_agent_mode_hint_text = ""
```

This selects effective mode `custom` at every reasoning effort and
injects an empty mode body, suppressing both built-in policies.

### Set: always use the configured text

```toml
[features.multi_agent_v2]
enabled = true
multi_agent_mode_hint_text = "Delegate to subagents when it will materially improve the result."
```

This selects effective mode `custom` at every reasoning effort and
injects the configured text verbatim instead of either built-in policy.

## Verification

- `just test -p codex-core multi_agent_mode`
- Covers a configured hint across High and Ultra reasoning efforts and
verifies the full custom hint is recorded for both turns.
- Covers an empty-string override suppressing both built-in instruction
bodies.
- `just test -p codex-protocol -p codex-app-server-protocol`
- Covers legacy `none` turn-context deserialization as `Custom("")` and
verifies the regenerated schemas.
This commit is contained in:
Shijie Rao
2026-07-02 18:44:34 -07:00
committed by GitHub
parent beca198b8a
commit da4c8ca57d
23 changed files with 438 additions and 105 deletions
+23 -7
View File
@@ -1786,13 +1786,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -2713,13 +2713,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -12860,13 +12860,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -9264,13 +9264,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -681,13 +681,29 @@
]
},
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -681,13 +681,29 @@
]
},
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -108,13 +108,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -195,13 +195,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"Personality": {
"enum": [
@@ -681,13 +681,29 @@
]
},
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -141,13 +141,29 @@
"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.",
"enum": [
"none",
"explicitRequestOnly",
"proactive"
],
"type": "string"
"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.",
"oneOf": [
{
"enum": [
"explicitRequestOnly",
"proactive"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"custom": {
"type": "string"
}
},
"required": [
"custom"
],
"title": "CustomMultiAgentMode",
"type": "object"
}
]
},
"NetworkAccess": {
"enum": [
@@ -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. `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": string } | "explicitRequestOnly" | "proactive";
+3
View File
@@ -1750,6 +1750,9 @@
"minimum": 0.0,
"type": "integer"
},
"multi_agent_mode_hint_text": {
"type": "string"
},
"non_code_mode_only": {
"type": "boolean"
},
+21
View File
@@ -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()?;
+7
View File
@@ -1144,6 +1144,7 @@ pub struct MultiAgentV2Config {
pub usage_hint_text: Option<String>,
pub root_agent_usage_hint_text: Option<String>,
pub subagent_usage_hint_text: Option<String>,
pub multi_agent_mode_hint_text: Option<String>,
pub tool_namespace: Option<String>,
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,
@@ -4,7 +4,6 @@ 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)]
@@ -32,8 +31,8 @@ impl ContextualUserFragment for MultiAgentModeInstructions {
}
fn body(&self) -> String {
match self.multi_agent_mode {
MultiAgentMode::None => NO_MULTI_AGENT_MODE_TEXT.to_string(),
match &self.multi_agent_mode {
MultiAgentMode::Custom(hint_text) => hint_text.clone(),
MultiAgentMode::ExplicitRequestOnly => {
EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT.to_string()
}
@@ -85,9 +85,6 @@ 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())
+4 -11
View File
@@ -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,10 @@ 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),
));
}
if let Some(contextual_user_message) =
crate::context_manager::updates::build_contextual_user_message(contextual_user_sections)
+12 -3
View File
@@ -41,9 +41,18 @@ pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option<M
return None;
}
let multi_agent_mode = match turn_context.effective_reasoning_effort() {
Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive,
_ => MultiAgentMode::ExplicitRequestOnly,
// A configured hint, including an empty string, defines a custom policy instead of an
// effort-derived built-in policy.
let multi_agent_mode = match &turn_context
.config
.multi_agent_v2
.multi_agent_mode_hint_text
{
Some(hint_text) => MultiAgentMode::Custom(hint_text.clone()),
None => match turn_context.effective_reasoning_effort() {
Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive,
_ => MultiAgentMode::ExplicitRequestOnly,
},
};
match &turn_context.session_source {
@@ -20,9 +20,11 @@ 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.";
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 +43,12 @@ 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());
}
fn configure_ultra(config: &mut Config) {
configure_multi_agent_v2(config);
config.model_reasoning_effort = Some(ReasoningEffort::Ultra);
@@ -120,6 +128,105 @@ 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_custom_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::<Value>)
.collect::<serde_json::Result<Vec<_>>>()?;
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").cloned())
.collect::<Vec<_>>();
assert_eq!(
recorded_modes,
[
json!({"custom": CUSTOM_MODE_HINT_TEXT}),
json!({"custom": CUSTOM_MODE_HINT_TEXT}),
]
);
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(()));
+2
View File
@@ -56,6 +56,8 @@ pub struct MultiAgentV2ConfigToml {
#[serde(skip_serializing_if = "Option::is_none")]
pub subagent_usage_hint_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multi_agent_mode_hint_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(length(min = 1, max = 64), regex(pattern = r"^[a-zA-Z0-9_-]+$"))]
pub tool_namespace: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
+2
View File
@@ -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),
+25 -9
View File
@@ -296,23 +296,39 @@ 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.
#[derive(
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
)]
#[serde(rename_all = "camelCase")]
/// 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, PartialEq, Eq, Display, JsonSchema, TS, Default)]
#[serde(rename_all = "camelCase", from = "MultiAgentModeWire")]
#[ts(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum MultiAgentMode {
None,
Custom(String),
#[default]
ExplicitRequestOnly,
Proactive,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
enum MultiAgentModeWire {
None,
Custom(String),
ExplicitRequestOnly,
Proactive,
}
impl From<MultiAgentModeWire> 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,
)]
+20 -1
View File
@@ -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<ThreadSource>)> {
@@ -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 {