Rename multi-agent v2 assignment tool (#25267)

## Summary
- rename the multi-agent v2 follow-up task tool surface to assign_task
- update core tests and spec-plan expectations
- keep rollout-trace classification backward-compatible with legacy
followup_task

## Tests
- just fmt
- just test -p codex-core
multi_agents_spec::tests::assign_task_tool_requires_message_and_has_no_output_schema
- just test -p codex-rollout-trace
- just fix -p codex-core
- just fix -p codex-rollout-trace

Note: a broad just test -p codex-core run was attempted locally, but
this sandbox produced unrelated environment failures around
sandbox-exec, missing test_stdio_server, and realtime timeouts.
This commit is contained in:
jif-oai
2026-05-30 14:13:05 +02:00
committed by GitHub
Unverified
parent 3e7baa00e4
commit 8acaec73b6
10 changed files with 39 additions and 37 deletions
@@ -179,7 +179,7 @@ pub fn create_send_message_tool() -> ToolSpec {
})
}
pub fn create_followup_task_tool() -> ToolSpec {
pub fn create_assign_task_tool() -> ToolSpec {
let properties = BTreeMap::from([
(
"target".to_string(),
@@ -196,7 +196,7 @@ pub fn create_followup_task_tool() -> ToolSpec {
]);
ToolSpec::Function(ResponsesApiTool {
name: "followup_task".to_string(),
name: "assign_task".to_string(),
description: "Send a message to an existing non-root target agent and trigger a turn in that target. If the target is currently mid-turn, the message is queued and will be used to start the target's next turn, after the current turn completes."
.to_string(),
strict: false,
@@ -247,15 +247,17 @@ fn send_message_tool_requires_message_and_has_no_output_schema() {
}
#[test]
fn followup_task_tool_requires_message_and_has_no_output_schema() {
fn assign_task_tool_requires_message_and_has_no_output_schema() {
let ToolSpec::Function(ResponsesApiTool {
name,
parameters,
output_schema,
..
}) = create_followup_task_tool()
}) = create_assign_task_tool()
else {
panic!("followup_task should be a function tool");
panic!("assign_task should be a function tool");
};
assert_eq!(name, "assign_task");
assert_eq!(
parameters.schema_type,
Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object))
@@ -263,7 +265,7 @@ fn followup_task_tool_requires_message_and_has_no_output_schema() {
let properties = parameters
.properties
.as_ref()
.expect("followup_task should use object params");
.expect("assign_task should use object params");
assert!(properties.contains_key("target"));
assert!(properties.contains_key("message"));
assert!(!properties.contains_key("items"));
@@ -8,8 +8,8 @@ use crate::session::tests::make_session_and_context;
use crate::session_prefix::format_subagent_notification_message;
use crate::thread_manager::thread_store_from_config;
use crate::tools::context::ToolOutput;
use crate::tools::handlers::multi_agents_v2::AssignTaskHandler as AssignTaskHandlerV2;
use crate::tools::handlers::multi_agents_v2::CloseAgentHandler as CloseAgentHandlerV2;
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHandlerV2;
use crate::tools::handlers::multi_agents_v2::SendMessageHandler as SendMessageHandlerV2;
use crate::tools::handlers::multi_agents_v2::SpawnAgentHandler as SpawnAgentHandlerV2;
@@ -1413,7 +1413,7 @@ async fn multi_agent_v2_send_message_accepts_root_target_from_child() {
}
#[tokio::test]
async fn multi_agent_v2_followup_task_rejects_root_target_from_child() {
async fn multi_agent_v2_assign_task_rejects_root_target_from_child() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
@@ -1461,11 +1461,11 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() {
agent_role: None,
});
let Err(err) = FollowupTaskHandlerV2
let Err(err) = AssignTaskHandlerV2
.handle(invocation(
Arc::new(session),
Arc::new(turn),
"followup_task",
"assign_task",
function_payload(json!({
"target": "/root",
"message": "run this",
@@ -1473,7 +1473,7 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() {
))
.await
else {
panic!("followup_task should reject the root target");
panic!("assign_task should reject the root target");
};
assert_eq!(
@@ -1868,7 +1868,7 @@ async fn multi_agent_v2_send_message_rejects_interrupt_parameter() {
}
#[tokio::test]
async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn() {
async fn multi_agent_v2_assign_task_completion_notifies_parent_on_every_turn() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
@@ -1923,18 +1923,18 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
)
.await;
FollowupTaskHandlerV2
AssignTaskHandlerV2
.handle(invocation(
session,
turn,
"followup_task",
"assign_task",
function_payload(json!({
"target": agent_id.to_string(),
"message": "continue",
})),
))
.await
.expect("followup_task should succeed");
.expect("assign_task should succeed");
let second_turn = thread.codex.session.new_default_turn().await;
thread
@@ -2003,7 +2003,7 @@ async fn multi_agent_v2_followup_task_completion_notifies_parent_on_every_turn()
}
#[tokio::test]
async fn multi_agent_v2_followup_task_rejects_legacy_items_field() {
async fn multi_agent_v2_assign_task_rejects_legacy_items_field() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
@@ -2039,14 +2039,14 @@ async fn multi_agent_v2_followup_task_rejects_legacy_items_field() {
let invocation = invocation(
session,
turn,
"followup_task",
"assign_task",
function_payload(json!({
"target": agent_id.to_string(),
"items": [{"type": "text", "text": "continue"}],
})),
);
let Err(err) = FollowupTaskHandlerV2.handle(invocation).await else {
let Err(err) = AssignTaskHandlerV2.handle(invocation).await else {
panic!("legacy items field should be rejected in v2");
};
let FunctionCallError::RespondToModel(message) = err else {
@@ -28,15 +28,15 @@ use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
pub(crate) use assign_task::Handler as AssignTaskHandler;
pub(crate) use close_agent::Handler as CloseAgentHandler;
pub(crate) use followup_task::Handler as FollowupTaskHandler;
pub(crate) use list_agents::Handler as ListAgentsHandler;
pub(crate) use send_message::Handler as SendMessageHandler;
pub(crate) use spawn::Handler as SpawnAgentHandler;
pub(crate) use wait::Handler as WaitAgentHandler;
mod assign_task;
mod close_agent;
mod followup_task;
mod list_agents;
mod message_tool;
mod send_message;
@@ -1,8 +1,8 @@
use super::message_tool::FollowupTaskArgs;
use super::message_tool::AssignTaskArgs;
use super::message_tool::MessageDeliveryMode;
use super::message_tool::handle_message_string_tool;
use super::*;
use crate::tools::handlers::multi_agents_spec::create_followup_task_tool;
use crate::tools::handlers::multi_agents_spec::create_assign_task_tool;
use codex_tools::ToolSpec;
pub(crate) struct Handler;
@@ -10,11 +10,11 @@ pub(crate) struct Handler;
#[async_trait::async_trait]
impl ToolExecutor<ToolInvocation> for Handler {
fn tool_name(&self) -> ToolName {
ToolName::plain("followup_task")
ToolName::plain("assign_task")
}
fn spec(&self) -> ToolSpec {
create_followup_task_tool()
create_assign_task_tool()
}
async fn handle(
@@ -22,7 +22,7 @@ impl ToolExecutor<ToolInvocation> for Handler {
invocation: ToolInvocation,
) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {
let arguments = function_arguments(invocation.payload.clone())?;
let args: FollowupTaskArgs = parse_arguments(&arguments)?;
let args: AssignTaskArgs = parse_arguments(&arguments)?;
handle_message_string_tool(
invocation,
MessageDeliveryMode::TriggerTurn,
@@ -1,6 +1,6 @@
//! Shared argument parsing and dispatch for the v2 text-only agent messaging tools.
//!
//! `send_message` and `followup_task` share the same submission path and differ only in whether the
//! `send_message` and `assign_task` share the same submission path and differ only in whether the
//! resulting `InterAgentCommunication` should wake the target immediately.
use super::*;
@@ -40,8 +40,8 @@ pub(crate) struct SendMessageArgs {
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
/// Input for the MultiAgentV2 `followup_task` tool.
pub(crate) struct FollowupTaskArgs {
/// Input for the MultiAgentV2 `assign_task` tool.
pub(crate) struct AssignTaskArgs {
pub(crate) target: String,
pub(crate) message: String,
}
@@ -55,7 +55,7 @@ fn message_content(message: String) -> Result<String, FunctionCallError> {
Ok(message)
}
/// Handles the shared MultiAgentV2 plain-text message flow for both `send_message` and `followup_task`.
/// Handles the shared MultiAgentV2 plain-text message flow for both `send_message` and `assign_task`.
pub(crate) async fn handle_message_string_tool(
invocation: ToolInvocation,
mode: MessageDeliveryMode,
+2 -2
View File
@@ -38,8 +38,8 @@ use crate::tools::handlers::multi_agents_common::MAX_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_common::MIN_WAIT_TIMEOUT_MS;
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
use crate::tools::handlers::multi_agents_spec::WaitAgentTimeoutOptions;
use crate::tools::handlers::multi_agents_v2::AssignTaskHandler as AssignTaskHandlerV2;
use crate::tools::handlers::multi_agents_v2::CloseAgentHandler as CloseAgentHandlerV2;
use crate::tools::handlers::multi_agents_v2::FollowupTaskHandler as FollowupTaskHandlerV2;
use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHandlerV2;
use crate::tools::handlers::multi_agents_v2::SendMessageHandler as SendMessageHandlerV2;
use crate::tools::handlers::multi_agents_v2::SpawnAgentHandler as SpawnAgentHandlerV2;
@@ -686,7 +686,7 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu
exposure,
));
planned_tools.add_arc(override_tool_exposure(
multi_agent_v2_handler(FollowupTaskHandlerV2, tool_namespace),
multi_agent_v2_handler(AssignTaskHandlerV2, tool_namespace),
exposure,
));
planned_tools.add_arc(override_tool_exposure(
+4 -4
View File
@@ -766,7 +766,7 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
"wait_agent",
"close_agent",
"send_message",
"followup_task",
"assign_task",
"list_agents",
]);
assert_eq!(
@@ -790,7 +790,7 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
v2.assert_visible_contains(&[
"spawn_agent",
"send_message",
"followup_task",
"assign_task",
"wait_agent",
"close_agent",
"list_agents",
@@ -895,7 +895,7 @@ async fn multi_agent_v2_can_use_configured_tool_namespace() {
for tool_name in [
"spawn_agent",
"send_message",
"followup_task",
"assign_task",
"wait_agent",
"close_agent",
"list_agents",
@@ -969,7 +969,7 @@ async fn code_mode_only_can_expose_namespaced_multi_agent_v2_as_normal_tools() {
for tool_name in [
"spawn_agent",
"send_message",
"followup_task",
"assign_task",
"wait_agent",
"close_agent",
"list_agents",
+1 -1
View File
@@ -177,7 +177,7 @@ the edges between them.
```mermaid
flowchart LR
RootTool["root ToolCall\nspawn_agent / followup_task / send_message"]
RootTool["root ToolCall\nspawn_agent / assign_task / send_message"]
ChildInput["child ConversationItem\ninjected task/message"]
ChildThread["child AgentThread"]
ChildResult["child assistant ConversationItem\nresult message"]
+1 -1
View File
@@ -267,7 +267,7 @@ fn dispatched_tool_kind(tool_name: &str, _payload: &ToolDispatchPayload) -> Tool
"image_generation" | "image_query" => ToolCallKind::ImageGeneration,
"spawn_agent" => ToolCallKind::SpawnAgent,
"send_message" => ToolCallKind::SendMessage,
"followup_task" => ToolCallKind::AssignAgentTask,
"assign_task" | "followup_task" => ToolCallKind::AssignAgentTask,
"wait_agent" => ToolCallKind::WaitAgent,
"close_agent" => ToolCallKind::CloseAgent,
other => ToolCallKind::Other {