Expose model overrides for multi-agent v2 spawns (#32749)

## What changed

- Add the `features.multi_agent_v2.expose_spawn_agent_model_overrides` setting, enabled by default, to expose `model` and `reasoning_effort` on the v2 `spawn_agent` tool.
- Keep these controls available when other spawn metadata is hidden, while allowing them to be disabled independently.
- Add root-agent and subagent guidance that overrides require a partial or context-free fork and should only be used when explicitly authorized.

## Testing

- Cover configuration parsing and defaults, usage-hint preservation, and tool-schema behavior with override exposure enabled and disabled.

GitOrigin-RevId: 92370498108c96fbd51f32965624ce531e991d9a
This commit is contained in:
jif authored and copyberry committed 2026-07-13 09:48:33 +00:00
1 parent ea0fd84d94
commit ea15456284
9 files changed
+163 -15

No files matched your search

+51 -3
View File
@@ -10314,6 +10314,7 @@ subagent_usage_hint_text = "Subagent guidance."
multi_agent_mode_hint_text = "Custom mode guidance."
tool_namespace = "agents"
hide_spawn_agent_metadata = true
expose_spawn_agent_model_overrides = false
non_code_mode_only = true
"#,
)?;
@@ -10357,6 +10358,7 @@ non_code_mode_only = true
Some("agents")
);
assert!(config.multi_agent_v2.hide_spawn_agent_metadata);
assert!(!config.multi_agent_v2.expose_spawn_agent_model_overrides);
assert!(config.multi_agent_v2.non_code_mode_only);
Ok(())
@@ -10378,7 +10380,10 @@ enabled = true
.build()
.await?;
assert_eq!(config.multi_agent_v2, MultiAgentV2Config::default());
assert_eq!(
config.multi_agent_v2,
resolve_multi_agent_v2_config(&ConfigToml::default())
);
assert_eq!(
(
config.agent_max_threads,
@@ -10410,7 +10415,50 @@ max_concurrent_threads_per_session = 17
config.subagent_usage_hint_text,
]
.into_iter()
.all(|hint| hint.is_some_and(|hint| hint.ends_with(expected_suffix.as_str())))
.all(|hint| hint.is_some_and(|hint| hint.contains(expected_suffix.as_str())))
);
}
#[test]
fn multi_agent_v2_model_override_exposure_preserves_configured_usage_hints() {
let config_toml = toml::from_str(
r#"[features.multi_agent_v2]
enabled = true
root_agent_usage_hint_text = "Root guidance."
subagent_usage_hint_text = "Subagent guidance."
expose_spawn_agent_model_overrides = true
"#,
)
.expect("multi-agent v2 config should parse");
let config = resolve_multi_agent_v2_config(&config_toml);
assert!(config.expose_spawn_agent_model_overrides);
assert_eq!(
config.root_agent_usage_hint_text.as_deref(),
Some("Root guidance.")
);
assert_eq!(
config.subagent_usage_hint_text.as_deref(),
Some("Subagent guidance.")
);
}
#[test]
fn multi_agent_v2_exposes_model_overrides_by_default() {
let config_toml =
toml::from_str(r#"[features.multi_agent_v2]"#).expect("multi-agent v2 config should parse");
let config = resolve_multi_agent_v2_config(&config_toml);
assert!(config.expose_spawn_agent_model_overrides);
assert!(
[
config.root_agent_usage_hint_text,
config.subagent_usage_hint_text,
]
.into_iter()
.all(|hint| hint.is_some_and(|hint| {
hint.ends_with(DEFAULT_MULTI_AGENT_V2_MODEL_OVERRIDE_USAGE_HINT_TEXT)
}))
);
}
@@ -10425,7 +10473,7 @@ multi_agent_mode_hint_text = ""
let expected = MultiAgentV2Config {
multi_agent_mode_hint_text: Some(String::new()),
..Default::default()
..resolve_multi_agent_v2_config(&ConfigToml::default())
};
assert_eq!(resolve_multi_agent_v2_config(&config_toml), expected);
}
+31 -5
View File
@@ -248,6 +248,7 @@ Payload:
```
You may also see them addressed as to=/root/..., which indicates your identity is /root/...
"#;
const DEFAULT_MULTI_AGENT_V2_MODEL_OVERRIDE_USAGE_HINT_TEXT: &str = "Full-history forks (`fork_turns` omitted or `\"all\"`) inherit the parent model and reasoning effort and do not accept overrides. Only set `model` or `reasoning_effort` when explicitly requested by the user, applicable `AGENTS.md` instructions, or skill instructions; when doing so, set `fork_turns` to `\"none\"` or a positive integer string.";
const DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE: &str = "collaboration";
const DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT: &str = r#"Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `interrupt_agent`, and `list_agents` only as direct tool calls using the recipient shown in their tool definitions, such as `to=functions.collaboration.spawn_agent`, since they are intentionally absent from the `functions.exec` `tools.*` namespace. Available tools in `functions.exec` are explicitly described with a `tools` namespace in the developer message.
@@ -1150,6 +1151,7 @@ pub struct MultiAgentV2Config {
pub multi_agent_mode_hint_text: Option<String>,
pub tool_namespace: Option<String>,
pub hide_spawn_agent_metadata: bool,
pub expose_spawn_agent_model_overrides: bool,
pub non_code_mode_only: bool,
}
@@ -1172,6 +1174,7 @@ impl MultiAgentV2Config {
multi_agent_mode_hint_text: None,
tool_namespace: Some(DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE.to_string()),
hide_spawn_agent_metadata: true,
expose_spawn_agent_model_overrides: true,
non_code_mode_only: true,
}
}
@@ -2516,13 +2519,31 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
.and_then(|config| config.usage_hint_text.as_ref())
.cloned()
.or(default.usage_hint_text);
let hide_spawn_agent_metadata = base
.and_then(|config| config.hide_spawn_agent_metadata)
.unwrap_or(default.hide_spawn_agent_metadata);
let expose_spawn_agent_model_overrides = base
.and_then(|config| config.expose_spawn_agent_model_overrides)
.unwrap_or(default.expose_spawn_agent_model_overrides);
let mut default_root_agent_usage_hint_text = default.root_agent_usage_hint_text;
let mut default_subagent_usage_hint_text = default.subagent_usage_hint_text;
if expose_spawn_agent_model_overrides {
default_root_agent_usage_hint_text = Some(append_usage_hint_text(
default_root_agent_usage_hint_text.as_deref(),
DEFAULT_MULTI_AGENT_V2_MODEL_OVERRIDE_USAGE_HINT_TEXT,
));
default_subagent_usage_hint_text = Some(append_usage_hint_text(
default_subagent_usage_hint_text.as_deref(),
DEFAULT_MULTI_AGENT_V2_MODEL_OVERRIDE_USAGE_HINT_TEXT,
));
}
let root_agent_usage_hint_text = resolve_optional_prompt_text(
base.map(|config| &config.root_agent_usage_hint_text),
default.root_agent_usage_hint_text,
default_root_agent_usage_hint_text,
);
let subagent_usage_hint_text = resolve_optional_prompt_text(
base.map(|config| &config.subagent_usage_hint_text),
default.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())
@@ -2532,9 +2553,6 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
.and_then(|config| config.tool_namespace.as_ref())
.cloned()
.or(default.tool_namespace);
let hide_spawn_agent_metadata = base
.and_then(|config| config.hide_spawn_agent_metadata)
.unwrap_or(default.hide_spawn_agent_metadata);
let non_code_mode_only = base
.and_then(|config| config.non_code_mode_only)
.unwrap_or(default.non_code_mode_only);
@@ -2550,6 +2568,7 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
multi_agent_mode_hint_text,
tool_namespace,
hide_spawn_agent_metadata,
expose_spawn_agent_model_overrides,
non_code_mode_only,
}
}
@@ -2734,6 +2753,13 @@ fn resolve_optional_prompt_text(
}
}
fn append_usage_hint_text(usage_hint_text: Option<&str>, additional_text: &str) -> String {
match usage_hint_text {
Some(usage_hint_text) => format!("{usage_hint_text}\n\n{additional_text}"),
None => additional_text.to_string(),
}
}
fn code_mode_toml_config(features: Option<&FeaturesToml>) -> Option<&CodeModeConfigToml> {
match features?.code_mode.as_ref()? {
FeatureToml::Enabled(_) => None,