Expose spawn agent types only when roles are configured (#33572)

## Why

The `agent_type` argument is only useful when agent roles are configured. Avoid
advertising an inapplicable override in the model-visible `spawn_agent` schema.

## What changed

- Include `agent_type` in the V1 and V2 spawn tools only when configured roles
  are available.
- Clarify that `agent_type` is an explicit override and document its interaction
  with context forking.
- Remove the unused `agents.support_agent_type` configuration field.

## Testing

- Add coverage for hiding `agent_type` without roles and exposing it when a role
  is configured.

GitOrigin-RevId: 32e0f123b15f317e9cdb71774029375bc073d9ad
This commit is contained in:
jif
2026-07-16 10:28:36 +00:00
committed by copyberry
parent 03bb3b1236
commit 9ff47868eb
14 changed files with 96 additions and 49 deletions
@@ -15,6 +15,7 @@ pub const MULTI_AGENT_V1_NAMESPACE: &str = "multi_agent_v1";
const MULTI_AGENT_V1_NAMESPACE_DESCRIPTION: &str = "Tools for spawning and managing sub-agents.";
const SPAWN_AGENT_INHERITED_MODEL_GUIDANCE: &str = "Spawned agents inherit your current model by default. Omit `model` to use that preferred default; set `model` only when an explicit override is needed.";
const SPAWN_AGENT_TYPE_OVERRIDE_DESCRIPTION_V1: &str = "Agent type override for the new agent. Omit to inherit the parent agent type with a full-history fork; otherwise, `default` is used.";
const SPAWN_AGENT_MODEL_OVERRIDE_DESCRIPTION: &str =
"Model override for the new agent. Omit unless an explicit override is needed.";
const SPAWN_AGENT_SERVICE_TIER_OVERRIDE_DESCRIPTION: &str =
@@ -25,6 +26,7 @@ const MAX_REASONING_EFFORT_CHARS_IN_SPAWN_AGENT_DESCRIPTION: usize = 64;
pub struct SpawnAgentToolOptions {
pub available_models: Vec<ModelPreset>,
pub agent_type_description: String,
pub expose_agent_type: bool,
pub hide_agent_type_model_reasoning: bool,
pub expose_spawn_agent_model_overrides: bool,
pub multi_agent_version: MultiAgentVersion,
@@ -36,6 +38,7 @@ impl Default for SpawnAgentToolOptions {
Self {
available_models: Vec::new(),
agent_type_description: String::new(),
expose_agent_type: true,
hide_agent_type_model_reasoning: false,
expose_spawn_agent_model_overrides: false,
multi_agent_version: MultiAgentVersion::Disabled,
@@ -70,6 +73,9 @@ pub fn create_spawn_agent_tool_v1(options: SpawnAgentToolOptions) -> ToolSpec {
let return_value_description =
"Returns the spawned agent id plus the user-facing nickname when available.";
let mut properties = spawn_agent_common_properties_v1(&options.agent_type_description);
if !options.expose_agent_type {
properties.remove("agent_type");
}
if options.hide_agent_type_model_reasoning {
hide_spawn_agent_metadata_options(&mut properties);
}
@@ -101,8 +107,10 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions) -> ToolSpec {
&& !options.hide_agent_type_model_reasoning)
.then_some(SPAWN_AGENT_INHERITED_MODEL_GUIDANCE);
let mut properties = spawn_agent_common_properties_v2(&options.agent_type_description);
if options.hide_agent_type_model_reasoning {
if !options.expose_agent_type {
properties.remove("agent_type");
}
if options.hide_agent_type_model_reasoning {
properties.remove("service_tier");
}
if !options.expose_spawn_agent_model_overrides {
@@ -582,7 +590,9 @@ fn spawn_agent_common_properties_v1(agent_type_description: &str) -> BTreeMap<St
("items".to_string(), create_collab_input_items_schema()),
(
"agent_type".to_string(),
JsonSchema::string(Some(agent_type_description.to_string())),
JsonSchema::string(Some(format!(
"{SPAWN_AGENT_TYPE_OVERRIDE_DESCRIPTION_V1}\n{agent_type_description}"
))),
),
(
"fork_context".to_string(),
@@ -624,7 +634,9 @@ fn spawn_agent_common_properties_v2(agent_type_description: &str) -> BTreeMap<St
),
(
"agent_type".to_string(),
JsonSchema::string(Some(agent_type_description.to_string())),
JsonSchema::string(Some(format!(
"Agent type override for the new agent. Omit unless explicitly asked. Set `fork_turns` to `none` or a positive integer when an explicit override is needed.\n{agent_type_description}"
))),
),
(
"fork_turns".to_string(),
@@ -48,6 +48,7 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
incompatible,
],
agent_type_description: "role help".to_string(),
expose_agent_type: true,
hide_agent_type_model_reasoning: false,
expose_spawn_agent_model_overrides: true,
multi_agent_version: MultiAgentVersion::V2,
@@ -95,10 +96,6 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
assert!(properties.contains_key("fork_turns"));
assert!(!properties.contains_key("items"));
assert!(!properties.contains_key("fork_context"));
assert_eq!(
properties.get("agent_type"),
Some(&JsonSchema::string(Some("role help".to_string())))
);
assert_eq!(
properties
.get("model")
@@ -132,6 +129,7 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() {
let tool = create_spawn_agent_tool_v1(SpawnAgentToolOptions {
available_models: Vec::new(),
agent_type_description: "role help".to_string(),
expose_agent_type: true,
hide_agent_type_model_reasoning: false,
expose_spawn_agent_model_overrides: true,
multi_agent_version: MultiAgentVersion::V1,
@@ -158,6 +156,12 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() {
assert!(properties.contains_key("fork_context"));
assert!(!properties.contains_key("fork_turns"));
assert_eq!(
properties.get("agent_type"),
Some(&JsonSchema::string(Some(format!(
"{SPAWN_AGENT_TYPE_OVERRIDE_DESCRIPTION_V1}\nrole help"
))))
);
assert_eq!(
properties
.get("message")
@@ -190,6 +194,7 @@ fn spawn_agent_tool_caps_visible_model_summaries() {
model_preset("sixth", /*show_in_picker*/ true),
],
agent_type_description: "role help".to_string(),
expose_agent_type: true,
hide_agent_type_model_reasoning: false,
expose_spawn_agent_model_overrides: true,
multi_agent_version: MultiAgentVersion::V2,
@@ -235,6 +240,7 @@ fn spawn_agent_tool_keeps_model_controls_when_spawn_metadata_is_hidden() {
let tool = create_spawn_agent_tool_v2(SpawnAgentToolOptions {
available_models: vec![model_preset("visible", /*show_in_picker*/ true)],
agent_type_description: "role help".to_string(),
expose_agent_type: false,
hide_agent_type_model_reasoning: true,
expose_spawn_agent_model_overrides: true,
multi_agent_version: MultiAgentVersion::V2,
@@ -267,6 +273,7 @@ fn spawn_agent_tool_hides_model_controls_without_override_exposure() {
let tool = create_spawn_agent_tool_v2(SpawnAgentToolOptions {
available_models: vec![model_preset("visible", /*show_in_picker*/ true)],
agent_type_description: "role help".to_string(),
expose_agent_type: false,
hide_agent_type_model_reasoning: true,
expose_spawn_agent_model_overrides: false,
multi_agent_version: MultiAgentVersion::V2,
+5 -5
View File
@@ -798,16 +798,15 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu
.flatten();
let agent_type_description =
agent_type_description(turn_context, context.default_agent_type_description);
// TODO(jif): Wire `agents.support_agent_type` into the V2 spawn tool surface.
let hide_spawn_agent_metadata =
turn_context.config.multi_agent_v2.hide_spawn_agent_metadata;
planned_tools.add_arc(override_tool_exposure(
multi_agent_v2_handler(
SpawnAgentHandlerV2::new(SpawnAgentToolOptions {
available_models: turn_context.available_models.clone(),
agent_type_description,
hide_agent_type_model_reasoning: turn_context
.config
.multi_agent_v2
.hide_spawn_agent_metadata,
expose_agent_type: !turn_context.config.agent_roles.is_empty(),
hide_agent_type_model_reasoning: hide_spawn_agent_metadata,
expose_spawn_agent_model_overrides: turn_context
.config
.multi_agent_v2
@@ -854,6 +853,7 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu
SpawnAgentHandler::new(SpawnAgentToolOptions {
available_models: turn_context.available_models.clone(),
agent_type_description,
expose_agent_type: !turn_context.config.agent_roles.is_empty(),
hide_agent_type_model_reasoning: false,
expose_spawn_agent_model_overrides: true,
multi_agent_version: turn_context.multi_agent_version,
+2 -1
View File
@@ -1207,12 +1207,13 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
.properties
.as_ref()
.expect("spawn_agent should use object params");
for property in ["agent_type", "model", "reasoning_effort", "service_tier"] {
for property in ["model", "reasoning_effort", "service_tier"] {
assert!(
properties.contains_key(property),
"expected v1 spawn_agent to expose `{property}`"
);
}
assert!(!properties.contains_key("agent_type"));
let v2 = probe(|turn| {
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ true);