feat: spawn v2 make task name as mandatory (#15986)

This commit is contained in:
jif-oai
2026-03-27 11:30:22 +01:00
committed by GitHub
parent 2ef91b7140
commit 6a0c4709ca
4 changed files with 50 additions and 12 deletions

View File

@@ -321,6 +321,40 @@ async fn spawn_agent_returns_agent_id_without_task_name() {
assert_eq!(success, Some(true));
}
#[tokio::test]
async fn multi_agent_v2_spawn_requires_task_name() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
let root = manager
.start_thread((*turn.config).clone())
.await
.expect("root thread should start");
session.services.agent_control = manager.agent_control();
session.conversation_id = root.thread_id;
let mut config = (*turn.config).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
turn.config = Arc::new(config);
let invocation = invocation(
Arc::new(session),
Arc::new(turn),
"spawn_agent",
function_payload(json!({
"message": "inspect this repo"
})),
);
let Err(err) = SpawnAgentHandlerV2.handle(invocation).await else {
panic!("missing task_name should be rejected");
};
let FunctionCallError::RespondToModel(message) = err else {
panic!("missing task_name should surface as a model-facing error");
};
assert!(message.contains("missing field `task_name`"));
}
#[tokio::test]
async fn spawn_agent_errors_when_manager_dropped() {
let (session, turn) = make_session_and_context().await;

View File

@@ -83,7 +83,7 @@ impl ToolHandler for Handler {
&turn.session_source,
child_depth,
role_name,
args.task_name.clone(),
Some(args.task_name.clone()),
)?),
SpawnAgentOptions {
fork_parent_spawn_call_id: args.fork_context.then(|| call_id.clone()),
@@ -132,7 +132,6 @@ impl ToolHandler for Handler {
.and_then(|snapshot| snapshot.reasoning_effort)
.unwrap_or(args.reasoning_effort.unwrap_or_default());
let nickname = new_agent_nickname.clone();
let task_name = new_agent_path.clone();
session
.send_event(
&turn,
@@ -150,16 +149,21 @@ impl ToolHandler for Handler {
.into(),
)
.await;
let new_thread_id = result?.thread_id;
let _ = result?;
let role_tag = role_name.unwrap_or(DEFAULT_ROLE_NAME);
turn.session_telemetry.counter(
"codex.multi_agent.spawn",
/*inc*/ 1,
&[("role", role_tag)],
);
let task_name = new_agent_path.ok_or_else(|| {
FunctionCallError::RespondToModel(
"spawned agent is missing a canonical task name".to_string(),
)
})?;
Ok(SpawnAgentResult {
agent_id: task_name.is_none().then(|| new_thread_id.to_string()),
agent_id: None,
task_name,
nickname,
})
@@ -170,7 +174,7 @@ impl ToolHandler for Handler {
struct SpawnAgentArgs {
message: Option<String>,
items: Option<Vec<UserInput>>,
task_name: Option<String>,
task_name: String,
agent_type: Option<String>,
model: Option<String>,
reasoning_effort: Option<ReasoningEffort>,
@@ -181,7 +185,7 @@ struct SpawnAgentArgs {
#[derive(Debug, Serialize)]
pub(crate) struct SpawnAgentResult {
agent_id: Option<String>,
task_name: Option<String>,
task_name: String,
nickname: Option<String>,
}

View File

@@ -157,10 +157,10 @@ fn spawn_agent_output_schema_v2() -> JsonValue {
"properties": {
"agent_id": {
"type": ["string", "null"],
"description": "Thread identifier for the spawned agent when no task name was assigned."
"description": "Legacy thread identifier for the spawned agent."
},
"task_name": {
"type": ["string", "null"],
"type": "string",
"description": "Canonical task name for the spawned agent."
},
"nickname": {
@@ -1215,13 +1215,13 @@ fn create_spawn_agent_tool_v1(config: &ToolsConfig) -> ToolSpec {
fn create_spawn_agent_tool_v2(config: &ToolsConfig) -> ToolSpec {
let available_models_description = spawn_agent_models_description(&config.available_models);
let return_value_description = "Returns the canonical task name when the spawned agent was named, otherwise the agent id, plus the user-facing nickname when available.";
let return_value_description = "Returns the canonical task name for the spawned agent, plus the user-facing nickname when available.";
let mut properties = spawn_agent_common_properties(config);
properties.insert(
"task_name".to_string(),
JsonSchema::String {
description: Some(
"Optional task name for the new agent. Use lowercase letters, digits, and underscores."
"Task name for the new agent. Use lowercase letters, digits, and underscores."
.to_string(),
),
},
@@ -1237,7 +1237,7 @@ fn create_spawn_agent_tool_v2(config: &ToolsConfig) -> ToolSpec {
defer_loading: None,
parameters: JsonSchema::Object {
properties,
required: None,
required: Some(vec!["task_name".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: Some(spawn_agent_output_schema_v2()),

View File

@@ -454,7 +454,7 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
panic!("spawn_agent should use object params");
};
assert!(properties.contains_key("task_name"));
assert_eq!(required.as_ref(), None);
assert_eq!(required.as_ref(), Some(&vec!["task_name".to_string()]));
let output_schema = output_schema
.as_ref()
.expect("spawn_agent should define output schema");