Allow disabling the multi-agent wait tool (#34887)

## What changed

- Add a default-on `features.multi_agent_v2.wait_agent_enabled` setting.
- Omit `collaboration.wait_agent` from the tool plan when the setting is disabled, independently of `features.current_time_reminder.sleep_tool`.

## Testing

- Cover configuration parsing and tool exposure with both clock sleep states.

GitOrigin-RevId: c2f34c5edd5bc191d6a5ac41d7bbb80708da1202
This commit is contained in:
rka-oai authored and copyberry committed 2026-07-23 06:28:27 +00:00
1 parent 6e0455fdc4
commit 4462b9deef
8 files changed
+149 -7

No files matched your search

+4
View File
@@ -1824,6 +1824,10 @@
}, },
"usage_hint_text": { "usage_hint_text": {
"type": "string" "type": "string"
},
"wait_agent_enabled": {
"description": "Expose the multi-agent v2 `wait_agent` tool.",
"type": "boolean"
} }
}, },
"type": "object" "type": "object"
+44
View File
@@ -10602,6 +10602,7 @@ multi_agent_mode_hint_text = "Custom mode guidance."
tool_namespace = "agents" tool_namespace = "agents"
hide_spawn_agent_metadata = true hide_spawn_agent_metadata = true
expose_spawn_agent_model_overrides = false expose_spawn_agent_model_overrides = false
wait_agent_enabled = false
non_code_mode_only = true non_code_mode_only = true
[agents] [agents]
@@ -10649,6 +10650,7 @@ max_concurrent_threads_per_session = 9
); );
assert!(config.multi_agent_v2.hide_spawn_agent_metadata); assert!(config.multi_agent_v2.hide_spawn_agent_metadata);
assert!(!config.multi_agent_v2.expose_spawn_agent_model_overrides); assert!(!config.multi_agent_v2.expose_spawn_agent_model_overrides);
assert!(!config.multi_agent_v2.wait_agent_enabled);
assert!(config.multi_agent_v2.non_code_mode_only); assert!(config.multi_agent_v2.non_code_mode_only);
Ok(()) Ok(())
@@ -10752,6 +10754,48 @@ fn multi_agent_v2_exposes_model_overrides_by_default() {
); );
} }
#[tokio::test]
async fn multi_agent_v2_allows_disabled_wait_agent_without_sleep_tool() -> std::io::Result<()> {
for config_toml in [
r#"
[features.multi_agent_v2]
enabled = true
wait_agent_enabled = false
"#,
r#"
[features.multi_agent_v2]
enabled = true
wait_agent_enabled = false
[features.current_time_reminder]
enabled = true
sleep_tool = false
"#,
r#"
[features.multi_agent_v2]
enabled = true
wait_agent_enabled = false
[features.current_time_reminder]
enabled = false
sleep_tool = true
"#,
] {
let codex_home = tempdir()?;
let config_toml = toml::from_str(config_toml).expect("TOML should deserialize");
let config = Config::load_from_base_config_with_overrides(
config_toml,
ConfigOverrides::default(),
codex_home.abs(),
)
.await?;
assert!(!config.multi_agent_v2.wait_agent_enabled);
}
Ok(())
}
#[test] #[test]
fn multi_agent_v2_preserves_empty_mode_hint_override() { fn multi_agent_v2_preserves_empty_mode_hint_override() {
let config_toml = toml::from_str( let config_toml = toml::from_str(
+6
View File
@@ -1177,6 +1177,7 @@ pub struct MultiAgentV2Config {
pub tool_namespace: Option<String>, pub tool_namespace: Option<String>,
pub hide_spawn_agent_metadata: bool, pub hide_spawn_agent_metadata: bool,
pub expose_spawn_agent_model_overrides: bool, pub expose_spawn_agent_model_overrides: bool,
pub wait_agent_enabled: bool,
pub non_code_mode_only: bool, pub non_code_mode_only: bool,
} }
@@ -1200,6 +1201,7 @@ impl MultiAgentV2Config {
tool_namespace: Some(DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE.to_string()), tool_namespace: Some(DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE.to_string()),
hide_spawn_agent_metadata: true, hide_spawn_agent_metadata: true,
expose_spawn_agent_model_overrides: true, expose_spawn_agent_model_overrides: true,
wait_agent_enabled: true,
non_code_mode_only: true, non_code_mode_only: true,
} }
} }
@@ -2568,6 +2570,9 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
let expose_spawn_agent_model_overrides = base let expose_spawn_agent_model_overrides = base
.and_then(|config| config.expose_spawn_agent_model_overrides) .and_then(|config| config.expose_spawn_agent_model_overrides)
.unwrap_or(default.expose_spawn_agent_model_overrides); .unwrap_or(default.expose_spawn_agent_model_overrides);
let wait_agent_enabled = base
.and_then(|config| config.wait_agent_enabled)
.unwrap_or(default.wait_agent_enabled);
let mut default_root_agent_usage_hint_text = default.root_agent_usage_hint_text; 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; let mut default_subagent_usage_hint_text = default.subagent_usage_hint_text;
if expose_spawn_agent_model_overrides { if expose_spawn_agent_model_overrides {
@@ -2612,6 +2617,7 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config
tool_namespace, tool_namespace,
hide_spawn_agent_metadata, hide_spawn_agent_metadata,
expose_spawn_agent_model_overrides, expose_spawn_agent_model_overrides,
wait_agent_enabled,
non_code_mode_only, non_code_mode_only,
} }
} }
+9 -7
View File
@@ -835,13 +835,15 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu
multi_agent_v2_handler(FollowupTaskHandlerV2, tool_namespace), multi_agent_v2_handler(FollowupTaskHandlerV2, tool_namespace),
exposure, exposure,
)); ));
planned_tools.add_arc(override_tool_exposure( if turn_context.config.multi_agent_v2.wait_agent_enabled {
multi_agent_v2_handler( planned_tools.add_arc(override_tool_exposure(
WaitAgentHandlerV2::new(context.wait_agent_timeouts), multi_agent_v2_handler(
tool_namespace, WaitAgentHandlerV2::new(context.wait_agent_timeouts),
), tool_namespace,
exposure, ),
)); exposure,
));
}
planned_tools.add_arc(override_tool_exposure( planned_tools.add_arc(override_tool_exposure(
multi_agent_v2_handler(InterruptAgentHandler, tool_namespace), multi_agent_v2_handler(InterruptAgentHandler, tool_namespace),
exposure, exposure,
@@ -1340,6 +1340,30 @@ async fn multi_agent_v2_message_schemas_are_encrypted() {
} }
} }
#[tokio::test]
async fn multi_agent_v2_can_disable_wait_agent() {
let plan = probe(|turn| {
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ true);
update_config(turn, |config| {
config.multi_agent_v2.wait_agent_enabled = false;
});
})
.await;
assert_eq!(
plan.namespace_function_names(MULTI_AGENT_V2_NAMESPACE),
&[
"followup_task".to_string(),
"interrupt_agent".to_string(),
"list_agents".to_string(),
"send_message".to_string(),
"spawn_agent".to_string(),
]
);
plan.assert_visible_lacks(&["clock"]);
plan.assert_registered_lacks(&["collaboration.wait_agent", "clock.sleep"]);
}
#[tokio::test] #[tokio::test]
async fn tool_mode_selector_overrides_feature_flags() { async fn tool_mode_selector_overrides_feature_flags() {
let direct = probe(|turn| { let direct = probe(|turn| {
@@ -32,6 +32,7 @@ use test_case::test_case;
use tokio::time::sleep; use tokio::time::sleep;
const MULTI_AGENT_V1_NAMESPACE: &str = "multi_agent_v1"; const MULTI_AGENT_V1_NAMESPACE: &str = "multi_agent_v1";
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";
const SPAWN_AGENT_TOOL_NAME: &str = "spawn_agent"; const SPAWN_AGENT_TOOL_NAME: &str = "spawn_agent";
fn spawn_agent_description(body: &Value) -> Option<String> { fn spawn_agent_description(body: &Value) -> Option<String> {
@@ -304,3 +305,59 @@ async fn configured_agent_roles_control_spawn_agent_type(
); );
Ok(()) Ok(())
} }
#[test_case(true, false; "wait agent remains available without clock sleep")]
#[test_case(true, true; "wait agent remains available with clock sleep")]
#[test_case(false, false; "wait agent can be disabled without clock sleep")]
#[test_case(false, true; "wait agent can be disabled with clock sleep")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn multi_agent_v2_wait_agent_tool_follows_configuration(
wait_agent_enabled: bool,
sleep_tool_enabled: bool,
) -> Result<()> {
let current_time_reminder = if sleep_tool_enabled {
r#"
[features.current_time_reminder]
enabled = true
sleep_tool = true
"#
} else {
""
};
let config_toml = format!(
r#"
[features.multi_agent_v2]
enabled = true
wait_agent_enabled = {wait_agent_enabled}
{current_time_reminder}"#
);
let server = start_mock_server().await;
let response = mount_sse_once(
&server,
sse(vec![ev_response_created("resp1"), ev_completed("resp1")]),
)
.await;
let test = test_codex()
.with_pre_build_hook(move |home| {
std::fs::write(home.join("config.toml"), &config_toml)
.expect("write multi-agent configuration");
})
.build_with_auto_env(&server)
.await?;
test.submit_turn("hello").await?;
let request = response.single_request();
let body = request.body_json();
assert!(namespace_child_tool(&body, MULTI_AGENT_V2_NAMESPACE, SPAWN_AGENT_TOOL_NAME).is_some());
assert_eq!(
namespace_child_tool(&body, MULTI_AGENT_V2_NAMESPACE, "wait_agent").is_some(),
wait_agent_enabled
);
assert_eq!(
namespace_child_tool(&body, "clock", "sleep").is_some(),
sleep_tool_enabled
);
Ok(())
}
+3
View File
@@ -66,6 +66,9 @@ pub struct MultiAgentV2ConfigToml {
/// corresponding guidance to root and subagent usage hints. /// corresponding guidance to root and subagent usage hints.
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub expose_spawn_agent_model_overrides: Option<bool>, pub expose_spawn_agent_model_overrides: Option<bool>,
/// Expose the multi-agent v2 `wait_agent` tool.
#[serde(skip_serializing_if = "Option::is_none")]
pub wait_agent_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub non_code_mode_only: Option<bool>, pub non_code_mode_only: Option<bool>,
} }
+2
View File
@@ -412,6 +412,7 @@ multi_agent_mode_hint_text = "Custom mode guidance."
tool_namespace = "agents" tool_namespace = "agents"
hide_spawn_agent_metadata = true hide_spawn_agent_metadata = true
expose_spawn_agent_model_overrides = true expose_spawn_agent_model_overrides = true
wait_agent_enabled = false
non_code_mode_only = true non_code_mode_only = true
"#, "#,
) )
@@ -437,6 +438,7 @@ non_code_mode_only = true
tool_namespace: Some("agents".to_string()), tool_namespace: Some("agents".to_string()),
hide_spawn_agent_metadata: Some(true), hide_spawn_agent_metadata: Some(true),
expose_spawn_agent_model_overrides: Some(true), expose_spawn_agent_model_overrides: Some(true),
wait_agent_enabled: Some(false),
non_code_mode_only: Some(true), non_code_mode_only: Some(true),
})) }))
); );