Skip to content

Commit 6a0c470

Browse files
authored
feat: spawn v2 make task name as mandatory (openai#15986)
1 parent 2ef91b7 commit 6a0c470

File tree

4 files changed

+50
-12
lines changed

4 files changed

+50
-12
lines changed

codex-rs/core/src/tools/handlers/multi_agents_tests.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,40 @@ async fn spawn_agent_returns_agent_id_without_task_name() {
321321
assert_eq!(success, Some(true));
322322
}
323323

324+
#[tokio::test]
325+
async fn multi_agent_v2_spawn_requires_task_name() {
326+
let (mut session, mut turn) = make_session_and_context().await;
327+
let manager = thread_manager();
328+
let root = manager
329+
.start_thread((*turn.config).clone())
330+
.await
331+
.expect("root thread should start");
332+
session.services.agent_control = manager.agent_control();
333+
session.conversation_id = root.thread_id;
334+
let mut config = (*turn.config).clone();
335+
config
336+
.features
337+
.enable(Feature::MultiAgentV2)
338+
.expect("test config should allow feature update");
339+
turn.config = Arc::new(config);
340+
341+
let invocation = invocation(
342+
Arc::new(session),
343+
Arc::new(turn),
344+
"spawn_agent",
345+
function_payload(json!({
346+
"message": "inspect this repo"
347+
})),
348+
);
349+
let Err(err) = SpawnAgentHandlerV2.handle(invocation).await else {
350+
panic!("missing task_name should be rejected");
351+
};
352+
let FunctionCallError::RespondToModel(message) = err else {
353+
panic!("missing task_name should surface as a model-facing error");
354+
};
355+
assert!(message.contains("missing field `task_name`"));
356+
}
357+
324358
#[tokio::test]
325359
async fn spawn_agent_errors_when_manager_dropped() {
326360
let (session, turn) = make_session_and_context().await;

codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl ToolHandler for Handler {
8383
&turn.session_source,
8484
child_depth,
8585
role_name,
86-
args.task_name.clone(),
86+
Some(args.task_name.clone()),
8787
)?),
8888
SpawnAgentOptions {
8989
fork_parent_spawn_call_id: args.fork_context.then(|| call_id.clone()),
@@ -132,7 +132,6 @@ impl ToolHandler for Handler {
132132
.and_then(|snapshot| snapshot.reasoning_effort)
133133
.unwrap_or(args.reasoning_effort.unwrap_or_default());
134134
let nickname = new_agent_nickname.clone();
135-
let task_name = new_agent_path.clone();
136135
session
137136
.send_event(
138137
&turn,
@@ -150,16 +149,21 @@ impl ToolHandler for Handler {
150149
.into(),
151150
)
152151
.await;
153-
let new_thread_id = result?.thread_id;
152+
let _ = result?;
154153
let role_tag = role_name.unwrap_or(DEFAULT_ROLE_NAME);
155154
turn.session_telemetry.counter(
156155
"codex.multi_agent.spawn",
157156
/*inc*/ 1,
158157
&[("role", role_tag)],
159158
);
159+
let task_name = new_agent_path.ok_or_else(|| {
160+
FunctionCallError::RespondToModel(
161+
"spawned agent is missing a canonical task name".to_string(),
162+
)
163+
})?;
160164

161165
Ok(SpawnAgentResult {
162-
agent_id: task_name.is_none().then(|| new_thread_id.to_string()),
166+
agent_id: None,
163167
task_name,
164168
nickname,
165169
})
@@ -170,7 +174,7 @@ impl ToolHandler for Handler {
170174
struct SpawnAgentArgs {
171175
message: Option<String>,
172176
items: Option<Vec<UserInput>>,
173-
task_name: Option<String>,
177+
task_name: String,
174178
agent_type: Option<String>,
175179
model: Option<String>,
176180
reasoning_effort: Option<ReasoningEffort>,
@@ -181,7 +185,7 @@ struct SpawnAgentArgs {
181185
#[derive(Debug, Serialize)]
182186
pub(crate) struct SpawnAgentResult {
183187
agent_id: Option<String>,
184-
task_name: Option<String>,
188+
task_name: String,
185189
nickname: Option<String>,
186190
}
187191

codex-rs/core/src/tools/spec.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ fn spawn_agent_output_schema_v2() -> JsonValue {
157157
"properties": {
158158
"agent_id": {
159159
"type": ["string", "null"],
160-
"description": "Thread identifier for the spawned agent when no task name was assigned."
160+
"description": "Legacy thread identifier for the spawned agent."
161161
},
162162
"task_name": {
163-
"type": ["string", "null"],
163+
"type": "string",
164164
"description": "Canonical task name for the spawned agent."
165165
},
166166
"nickname": {
@@ -1215,13 +1215,13 @@ fn create_spawn_agent_tool_v1(config: &ToolsConfig) -> ToolSpec {
12151215

12161216
fn create_spawn_agent_tool_v2(config: &ToolsConfig) -> ToolSpec {
12171217
let available_models_description = spawn_agent_models_description(&config.available_models);
1218-
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.";
1218+
let return_value_description = "Returns the canonical task name for the spawned agent, plus the user-facing nickname when available.";
12191219
let mut properties = spawn_agent_common_properties(config);
12201220
properties.insert(
12211221
"task_name".to_string(),
12221222
JsonSchema::String {
12231223
description: Some(
1224-
"Optional task name for the new agent. Use lowercase letters, digits, and underscores."
1224+
"Task name for the new agent. Use lowercase letters, digits, and underscores."
12251225
.to_string(),
12261226
),
12271227
},
@@ -1237,7 +1237,7 @@ fn create_spawn_agent_tool_v2(config: &ToolsConfig) -> ToolSpec {
12371237
defer_loading: None,
12381238
parameters: JsonSchema::Object {
12391239
properties,
1240-
required: None,
1240+
required: Some(vec!["task_name".to_string()]),
12411241
additional_properties: Some(false.into()),
12421242
},
12431243
output_schema: Some(spawn_agent_output_schema_v2()),

codex-rs/core/src/tools/spec_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ fn test_build_specs_multi_agent_v2_uses_task_names_and_hides_resume() {
454454
panic!("spawn_agent should use object params");
455455
};
456456
assert!(properties.contains_key("task_name"));
457-
assert_eq!(required.as_ref(), None);
457+
assert_eq!(required.as_ref(), Some(&vec!["task_name".to_string()]));
458458
let output_schema = output_schema
459459
.as_ref()
460460
.expect("spawn_agent should define output schema");

0 commit comments

Comments
 (0)