Rename multi-agent wait tool to wait_agent (#14631)

- rename the multi-agent tool name the model sees to wait_agent
- update the model-facing prompts and tool descriptions to match

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim 2026-03-13 14:38:05 -07:00 committed by GitHub
parent 6720caf778
commit cfd97b36da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 58 additions and 54 deletions

View file

@ -52,7 +52,7 @@ pub(crate) use close_agent::Handler as CloseAgentHandler;
pub(crate) use resume_agent::Handler as ResumeAgentHandler;
pub(crate) use send_input::Handler as SendInputHandler;
pub(crate) use spawn::Handler as SpawnAgentHandler;
pub(crate) use wait::Handler as WaitHandler;
pub(crate) use wait::Handler as WaitAgentHandler;
/// Minimum wait timeout to prevent tight polling loops from burning CPU.
pub(crate) const MIN_WAIT_TIMEOUT_MS: i64 = 10_000;

View file

@ -14,7 +14,7 @@ pub(crate) struct Handler;
#[async_trait]
impl ToolHandler for Handler {
type Output = WaitResult;
type Output = WaitAgentResult;
fn kind(&self) -> ToolKind {
ToolKind::Function
@ -153,7 +153,7 @@ impl ToolHandler for Handler {
let statuses_map = statuses.clone().into_iter().collect::<HashMap<_, _>>();
let agent_statuses = build_wait_agent_statuses(&statuses_map, &receiver_agents);
let result = WaitResult {
let result = WaitAgentResult {
status: statuses_map.clone(),
timed_out: statuses.is_empty(),
};
@ -182,14 +182,14 @@ struct WaitArgs {
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub(crate) struct WaitResult {
pub(crate) struct WaitAgentResult {
pub(crate) status: HashMap<ThreadId, AgentStatus>,
pub(crate) timed_out: bool,
}
impl ToolOutput for WaitResult {
impl ToolOutput for WaitAgentResult {
fn log_preview(&self) -> String {
tool_output_json_text(self, "wait")
tool_output_json_text(self, "wait_agent")
}
fn success_for_logging(&self) -> bool {
@ -197,11 +197,11 @@ impl ToolOutput for WaitResult {
}
fn to_response_item(&self, call_id: &str, payload: &ToolPayload) -> ResponseInputItem {
tool_output_response_item(call_id, payload, self, None, "wait")
tool_output_response_item(call_id, payload, self, None, "wait_agent")
}
fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue {
tool_output_code_mode_result(self, "wait")
tool_output_code_mode_result(self, "wait_agent")
}
}

View file

@ -737,18 +737,18 @@ async fn resume_agent_rejects_when_depth_limit_exceeded() {
}
#[tokio::test]
async fn wait_rejects_non_positive_timeout() {
async fn wait_agent_rejects_non_positive_timeout() {
let (session, turn) = make_session_and_context().await;
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({
"ids": [ThreadId::new().to_string()],
"timeout_ms": 0
})),
);
let Err(err) = WaitHandler.handle(invocation).await else {
let Err(err) = WaitAgentHandler.handle(invocation).await else {
panic!("non-positive timeout should be rejected");
};
assert_eq!(
@ -758,15 +758,15 @@ async fn wait_rejects_non_positive_timeout() {
}
#[tokio::test]
async fn wait_rejects_invalid_id() {
async fn wait_agent_rejects_invalid_id() {
let (session, turn) = make_session_and_context().await;
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({"ids": ["invalid"]})),
);
let Err(err) = WaitHandler.handle(invocation).await else {
let Err(err) = WaitAgentHandler.handle(invocation).await else {
panic!("invalid id should be rejected");
};
let FunctionCallError::RespondToModel(msg) = err else {
@ -776,15 +776,15 @@ async fn wait_rejects_invalid_id() {
}
#[tokio::test]
async fn wait_rejects_empty_ids() {
async fn wait_agent_rejects_empty_ids() {
let (session, turn) = make_session_and_context().await;
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({"ids": []})),
);
let Err(err) = WaitHandler.handle(invocation).await else {
let Err(err) = WaitAgentHandler.handle(invocation).await else {
panic!("empty ids should be rejected");
};
assert_eq!(
@ -794,7 +794,7 @@ async fn wait_rejects_empty_ids() {
}
#[tokio::test]
async fn wait_returns_not_found_for_missing_agents() {
async fn wait_agent_returns_not_found_for_missing_agents() {
let (mut session, turn) = make_session_and_context().await;
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
@ -803,22 +803,22 @@ async fn wait_returns_not_found_for_missing_agents() {
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({
"ids": [id_a.to_string(), id_b.to_string()],
"timeout_ms": 1000
})),
);
let output = WaitHandler
let output = WaitAgentHandler
.handle(invocation)
.await
.expect("wait should succeed");
.expect("wait_agent should succeed");
let (content, success) = expect_text_output(output);
let result: wait::WaitResult =
serde_json::from_str(&content).expect("wait result should be json");
let result: wait::WaitAgentResult =
serde_json::from_str(&content).expect("wait_agent result should be json");
assert_eq!(
result,
wait::WaitResult {
wait::WaitAgentResult {
status: HashMap::from([(id_a, AgentStatus::NotFound), (id_b, AgentStatus::NotFound),]),
timed_out: false
}
@ -827,7 +827,7 @@ async fn wait_returns_not_found_for_missing_agents() {
}
#[tokio::test]
async fn wait_times_out_when_status_is_not_final() {
async fn wait_agent_times_out_when_status_is_not_final() {
let (mut session, turn) = make_session_and_context().await;
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
@ -837,22 +837,22 @@ async fn wait_times_out_when_status_is_not_final() {
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({
"ids": [agent_id.to_string()],
"timeout_ms": MIN_WAIT_TIMEOUT_MS
})),
);
let output = WaitHandler
let output = WaitAgentHandler
.handle(invocation)
.await
.expect("wait should succeed");
.expect("wait_agent should succeed");
let (content, success) = expect_text_output(output);
let result: wait::WaitResult =
serde_json::from_str(&content).expect("wait result should be json");
let result: wait::WaitAgentResult =
serde_json::from_str(&content).expect("wait_agent result should be json");
assert_eq!(
result,
wait::WaitResult {
wait::WaitAgentResult {
status: HashMap::new(),
timed_out: true
}
@ -867,7 +867,7 @@ async fn wait_times_out_when_status_is_not_final() {
}
#[tokio::test]
async fn wait_clamps_short_timeouts_to_minimum() {
async fn wait_agent_clamps_short_timeouts_to_minimum() {
let (mut session, turn) = make_session_and_context().await;
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
@ -877,17 +877,21 @@ async fn wait_clamps_short_timeouts_to_minimum() {
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({
"ids": [agent_id.to_string()],
"timeout_ms": 10
})),
);
let early = timeout(Duration::from_millis(50), WaitHandler.handle(invocation)).await;
let early = timeout(
Duration::from_millis(50),
WaitAgentHandler.handle(invocation),
)
.await;
assert!(
early.is_err(),
"wait should not return before the minimum timeout clamp"
"wait_agent should not return before the minimum timeout clamp"
);
let _ = thread
@ -898,7 +902,7 @@ async fn wait_clamps_short_timeouts_to_minimum() {
}
#[tokio::test]
async fn wait_returns_final_status_without_timeout() {
async fn wait_agent_returns_final_status_without_timeout() {
let (mut session, turn) = make_session_and_context().await;
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
@ -923,22 +927,22 @@ async fn wait_returns_final_status_without_timeout() {
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"wait",
"wait_agent",
function_payload(json!({
"ids": [agent_id.to_string()],
"timeout_ms": 1000
})),
);
let output = WaitHandler
let output = WaitAgentHandler
.handle(invocation)
.await
.expect("wait should succeed");
.expect("wait_agent should succeed");
let (content, success) = expect_text_output(output);
let result: wait::WaitResult =
serde_json::from_str(&content).expect("wait result should be json");
let result: wait::WaitAgentResult =
serde_json::from_str(&content).expect("wait_agent result should be json");
assert_eq!(
result,
wait::WaitResult {
wait::WaitAgentResult {
status: HashMap::from([(agent_id, AgentStatus::Shutdown)]),
timed_out: false
}

View file

@ -1053,7 +1053,7 @@ fn create_spawn_agent_tool(config: &ToolsConfig) -> ToolSpec {
- For code-edit subtasks, decompose work so each delegated task has a disjoint write set.
### After you delegate
- Call wait very sparingly. Only call wait when you need the result immediately for the next critical-path step and you are blocked until it returns.
- Call wait_agent very sparingly. Only call wait_agent when you need the result immediately for the next critical-path step and you are blocked until it returns.
- Do not redo delegated subagent tasks yourself; focus on integrating results or tackling non-overlapping work.
- While the subagent is running in the background, do meaningful non-overlapping work immediately.
- Do not repeatedly wait by reflex.
@ -1290,7 +1290,7 @@ fn create_resume_agent_tool() -> ToolSpec {
ToolSpec::Function(ResponsesApiTool {
name: "resume_agent".to_string(),
description:
"Resume a previously closed agent by id so it can receive send_input and wait calls."
"Resume a previously closed agent by id so it can receive send_input and wait_agent calls."
.to_string(),
strict: false,
defer_loading: None,
@ -1303,7 +1303,7 @@ fn create_resume_agent_tool() -> ToolSpec {
})
}
fn create_wait_tool() -> ToolSpec {
fn create_wait_agent_tool() -> ToolSpec {
let mut properties = BTreeMap::new();
properties.insert(
"ids".to_string(),
@ -1325,7 +1325,7 @@ fn create_wait_tool() -> ToolSpec {
);
ToolSpec::Function(ResponsesApiTool {
name: "wait".to_string(),
name: "wait_agent".to_string(),
description: "Wait for agents to reach a final status. Completed statuses may include the agent's final message. Returns empty status when timed out. Once the agent reaches a final status, a notification message will be received containing the same completed status."
.to_string(),
strict: false,
@ -2441,7 +2441,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
use crate::tools::handlers::multi_agents::ResumeAgentHandler;
use crate::tools::handlers::multi_agents::SendInputHandler;
use crate::tools::handlers::multi_agents::SpawnAgentHandler;
use crate::tools::handlers::multi_agents::WaitHandler;
use crate::tools::handlers::multi_agents::WaitAgentHandler;
use std::sync::Arc;
let mut builder = ToolRegistryBuilder::new();
@ -2835,7 +2835,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
);
push_tool_spec(
&mut builder,
create_wait_tool(),
create_wait_agent_tool(),
false,
config.code_mode_enabled,
);
@ -2848,7 +2848,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
builder.register_handler("spawn_agent", Arc::new(SpawnAgentHandler));
builder.register_handler("send_input", Arc::new(SendInputHandler));
builder.register_handler("resume_agent", Arc::new(ResumeAgentHandler));
builder.register_handler("wait", Arc::new(WaitHandler));
builder.register_handler("wait_agent", Arc::new(WaitAgentHandler));
builder.register_handler("close_agent", Arc::new(CloseAgentHandler));
}

View file

@ -502,7 +502,7 @@ fn test_build_specs_collab_tools_enabled() {
let (tools, _) = build_specs(&tools_config, None, None, &[]).build();
assert_contains_tool_names(
&tools,
&["spawn_agent", "send_input", "wait", "close_agent"],
&["spawn_agent", "send_input", "wait_agent", "close_agent"],
);
assert_lacks_tool_name(&tools, "spawn_agents_on_csv");
}
@ -530,7 +530,7 @@ fn test_build_specs_enable_fanout_enables_agent_jobs_and_collab_tools() {
&[
"spawn_agent",
"send_input",
"wait",
"wait_agent",
"close_agent",
"spawn_agents_on_csv",
],
@ -651,7 +651,7 @@ fn test_build_specs_agent_job_worker_tools_enabled() {
"spawn_agent",
"send_input",
"resume_agent",
"wait",
"wait_agent",
"close_agent",
"spawn_agents_on_csv",
"report_agent_job_result",

View file

@ -101,6 +101,6 @@ Sub-agents are their to make you go fast and time is a big constraint so leverag
## Flow
1. Understand the task.
2. Spawn the optimal necessary sub-agents.
3. Coordinate them via wait / send_input.
3. Coordinate them via wait_agent / send_input.
4. Iterate on this. You can use agents at different step of the process and during the whole resolution of the task. Never forget to use them.
5. Ask the user before shutting sub-agents down unless you need to because you reached the agent limit.

View file

@ -11,5 +11,5 @@ This feature must be used wisely. For simple or straightforward tasks, you don't
* When spawning multiple agents, you must tell them that they are not alone in the environment so they should not impact/revert the work of others.
* Running tests or some config commands can output a large amount of logs. In order to optimize your own context, you can spawn an agent and ask it to do it for you. In such cases, you must tell this agent that it can't spawn another agent himself (to prevent infinite recursion)
* When you're done with a sub-agent, don't forget to close it using `close_agent`.
* Be careful on the `timeout_ms` parameter you choose for `wait`. It should be wisely scaled.
* Be careful on the `timeout_ms` parameter you choose for `wait_agent`. It should be wisely scaled.
* Sub-agents have access to the same set of tools as you do so you must tell them if they are allowed to spawn sub-agents themselves or not.