From cfd97b36da76a17db407b2d9653ed993636e0a30 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Fri, 13 Mar 2026 14:38:05 -0700 Subject: [PATCH] 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 --- .../core/src/tools/handlers/multi_agents.rs | 2 +- .../src/tools/handlers/multi_agents/wait.rs | 14 ++-- .../src/tools/handlers/multi_agents_tests.rs | 72 ++++++++++--------- codex-rs/core/src/tools/spec.rs | 14 ++-- codex-rs/core/src/tools/spec_tests.rs | 6 +- .../core/templates/agents/orchestrator.md | 2 +- .../templates/collab/experimental_prompt.md | 2 +- 7 files changed, 58 insertions(+), 54 deletions(-) diff --git a/codex-rs/core/src/tools/handlers/multi_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents.rs index 1ccb9fc6c..01537f7f4 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents.rs @@ -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; diff --git a/codex-rs/core/src/tools/handlers/multi_agents/wait.rs b/codex-rs/core/src/tools/handlers/multi_agents/wait.rs index ef33ac932..3bd0922fc 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents/wait.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents/wait.rs @@ -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::>(); 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, 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") } } diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index 548a2bc84..a5921af0c 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -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 } diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 17bab04bd..f06238979 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -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)); } diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index 11be40855..856861f69 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -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", diff --git a/codex-rs/core/templates/agents/orchestrator.md b/codex-rs/core/templates/agents/orchestrator.md index e0976f52e..39d86c2c2 100644 --- a/codex-rs/core/templates/agents/orchestrator.md +++ b/codex-rs/core/templates/agents/orchestrator.md @@ -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. diff --git a/codex-rs/core/templates/collab/experimental_prompt.md b/codex-rs/core/templates/collab/experimental_prompt.md index c6cd6f7ac..1c390adec 100644 --- a/codex-rs/core/templates/collab/experimental_prompt.md +++ b/codex-rs/core/templates/collab/experimental_prompt.md @@ -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.