mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: description multi-agent v2 (#17338)
This commit is contained in:
committed by
GitHub
Unverified
parent
8d58899297
commit
d39a722865
@@ -661,8 +661,11 @@ fn spawn_agent_description_omits_usage_hint_when_disabled() {
|
||||
r#"(?sx)
|
||||
^\s*
|
||||
No\ picker-visible\ models\ are\ currently\ loaded\.
|
||||
\s+Spawn\ a\ sub-agent\ for\ a\ well-scoped\ task\.
|
||||
\s+Returns\ the\ canonical\ task\ name\ for\ the\ spawned\ agent,\ plus\ the\ user-facing\ nickname\ when\ available\.
|
||||
\s+Spawns\ an\ agent\ to\ work\ on\ the\ specified\ task\.\ If\ your\ current\ task\ is\ `/root/task1`\ and\ you\ spawn_agent\ with\ task_name\ "task_3"\ the\ agent\ will\ have\ canonical\ task\ name\ `/root/task1/task_3`\.
|
||||
\s+You\ are\ then\ able\ to\ refer\ to\ this\ agent\ as\ `task_3`\ or\ `/root/task1/task_3`\ interchangeably\.\ However\ an\ agent\ `/root/task2/task_3`\ would\ only\ be\ able\ to\ communicate\ with\ this\ agent\ via\ its\ canonical\ name\ `/root/task1/task_3`\.
|
||||
\s+The\ spawned\ agent\ will\ have\ the\ same\ tools\ as\ you\ and\ the\ ability\ to\ spawn\ its\ own\ subagents\.
|
||||
\s+It\ will\ be\ able\ to\ send\ you\ and\ other\ running\ agents\ messages,\ and\ its\ final\ answer\ will\ be\ provided\ to\ you\ when\ it\ finishes\.
|
||||
\s+The\ new\ agent's\ canonical\ task\ name\ will\ be\ provided\ to\ it\ along\ with\ the\ message\.
|
||||
\s*$
|
||||
"#,
|
||||
&description,
|
||||
@@ -680,8 +683,11 @@ fn spawn_agent_description_uses_configured_usage_hint_text() {
|
||||
r#"(?sx)
|
||||
^\s*
|
||||
No\ picker-visible\ models\ are\ currently\ loaded\.
|
||||
\s+Spawn\ a\ sub-agent\ for\ a\ well-scoped\ task\.
|
||||
\s+Returns\ the\ canonical\ task\ name\ for\ the\ spawned\ agent,\ plus\ the\ user-facing\ nickname\ when\ available\.
|
||||
\s+Spawns\ an\ agent\ to\ work\ on\ the\ specified\ task\.\ If\ your\ current\ task\ is\ `/root/task1`\ and\ you\ spawn_agent\ with\ task_name\ "task_3"\ the\ agent\ will\ have\ canonical\ task\ name\ `/root/task1/task_3`\.
|
||||
\s+You\ are\ then\ able\ to\ refer\ to\ this\ agent\ as\ `task_3`\ or\ `/root/task1/task_3`\ interchangeably\.\ However\ an\ agent\ `/root/task2/task_3`\ would\ only\ be\ able\ to\ communicate\ with\ this\ agent\ via\ its\ canonical\ name\ `/root/task1/task_3`\.
|
||||
\s+The\ spawned\ agent\ will\ have\ the\ same\ tools\ as\ you\ and\ the\ ability\ to\ spawn\ its\ own\ subagents\.
|
||||
\s+It\ will\ be\ able\ to\ send\ you\ and\ other\ running\ agents\ messages,\ and\ its\ final\ answer\ will\ be\ provided\ to\ you\ when\ it\ finishes\.
|
||||
\s+The\ new\ agent's\ canonical\ task\ name\ will\ be\ provided\ to\ it\ along\ with\ the\ message\.
|
||||
\s+Custom\ delegation\ guidance\ only\.
|
||||
\s*$
|
||||
"#,
|
||||
|
||||
@@ -50,11 +50,6 @@ pub fn create_spawn_agent_tool_v1(options: SpawnAgentToolOptions<'_>) -> ToolSpe
|
||||
pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions<'_>) -> ToolSpec {
|
||||
let available_models_description = (!options.hide_agent_type_model_reasoning)
|
||||
.then(|| spawn_agent_models_description(options.available_models));
|
||||
let return_value_description = if options.hide_agent_type_model_reasoning {
|
||||
"Returns the canonical task name for the spawned agent."
|
||||
} else {
|
||||
"Returns the canonical task name for the spawned agent, plus the user-facing nickname when available."
|
||||
};
|
||||
let mut properties = spawn_agent_common_properties_v2(&options.agent_type_description);
|
||||
if options.hide_agent_type_model_reasoning {
|
||||
hide_spawn_agent_metadata_options(&mut properties);
|
||||
@@ -69,9 +64,8 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions<'_>) -> ToolSpe
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "spawn_agent".to_string(),
|
||||
description: spawn_agent_tool_description(
|
||||
description: spawn_agent_tool_description_v2(
|
||||
available_models_description.as_deref(),
|
||||
return_value_description,
|
||||
options.include_usage_hint,
|
||||
options.usage_hint_text,
|
||||
),
|
||||
@@ -127,7 +121,7 @@ pub fn create_send_message_tool() -> ToolSpec {
|
||||
(
|
||||
"target".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Agent id or canonical task name to message (from spawn_agent).".to_string(),
|
||||
"Relative or canonical task name to message (from spawn_agent).".to_string(),
|
||||
)),
|
||||
),
|
||||
(
|
||||
@@ -140,11 +134,15 @@ pub fn create_send_message_tool() -> ToolSpec {
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "send_message".to_string(),
|
||||
description: "Add a message to an existing agent without triggering a new turn. In MultiAgentV2, this tool currently supports text content only."
|
||||
description: "Send a string message to an existing agent without triggering a new turn."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::object(properties, Some(vec!["target".to_string(), "message".to_string()]), Some(false.into())),
|
||||
parameters: JsonSchema::object(
|
||||
properties,
|
||||
Some(vec!["target".to_string(), "message".to_string()]),
|
||||
Some(false.into()),
|
||||
),
|
||||
output_schema: None,
|
||||
})
|
||||
}
|
||||
@@ -166,7 +164,7 @@ pub fn create_followup_task_tool() -> ToolSpec {
|
||||
(
|
||||
"interrupt".to_string(),
|
||||
JsonSchema::boolean(Some(
|
||||
"When true, stop the agent's current task and handle this immediately. When false (default), queue this message."
|
||||
"When true, stop the agent's current task and handle this immediately. When false (default), queue this message; if the target is already running, it starts the target's next turn after the current turn completes."
|
||||
.to_string(),
|
||||
)),
|
||||
),
|
||||
@@ -174,7 +172,7 @@ pub fn create_followup_task_tool() -> ToolSpec {
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "followup_task".to_string(),
|
||||
description: "Add a message to an existing non-root agent and trigger a turn in the target. Use interrupt=true to redirect work immediately. In MultiAgentV2, this tool currently supports text content only."
|
||||
description: "Send a string message to an existing non-root agent and trigger a turn in the target. Use interrupt=true to redirect work immediately. If interrupt=false and the target's turn has not completed, the message is queued and starts the target's next turn after the current turn completes."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
@@ -216,7 +214,7 @@ pub fn create_wait_agent_tool_v1(options: WaitAgentTimeoutOptions) -> ToolSpec {
|
||||
pub fn create_wait_agent_tool_v2(options: WaitAgentTimeoutOptions) -> ToolSpec {
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "wait_agent".to_string(),
|
||||
description: "Wait for a mailbox update from any live agent, including queued messages and final-status notifications. Returns a brief wait summary instead of agent content, or a timeout summary if no mailbox update arrives before the deadline."
|
||||
description: "Wait for a mailbox update from any live agent, including queued messages and final-status notifications. Does not return the content; returns either a summary of which agents have updates (if any), or a timeout summary if no mailbox update arrives before the deadline."
|
||||
.to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
@@ -229,7 +227,7 @@ pub fn create_list_agents_tool() -> ToolSpec {
|
||||
let properties = BTreeMap::from([(
|
||||
"path_prefix".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Optional task-path prefix. Accepts the same relative or absolute task-path syntax as other MultiAgentV2 agent targets."
|
||||
"Optional task-path prefix (not ending with trailing slash). Accepts the same relative or absolute task-path syntax."
|
||||
.to_string(),
|
||||
)),
|
||||
)]);
|
||||
@@ -448,7 +446,7 @@ fn wait_output_schema_v2() -> Value {
|
||||
},
|
||||
"timed_out": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the wait call returned due to timeout before any agent reached a final status."
|
||||
"description": "Whether the wait call returned because no mailbox update arrived before the timeout."
|
||||
}
|
||||
},
|
||||
"required": ["message", "timed_out"],
|
||||
@@ -556,7 +554,7 @@ fn spawn_agent_common_properties_v2(agent_type_description: &str) -> BTreeMap<St
|
||||
(
|
||||
"fork_turns".to_string(),
|
||||
JsonSchema::string(Some(
|
||||
"Optional MultiAgentV2 fork mode. Use `none`, `all`, or a positive integer string such as `3` to fork only the most recent turns."
|
||||
"Optional number of turns to fork. Defaults to `all`. Use `none`, `all`, or a positive integer string such as `3` to fork only the most recent turns."
|
||||
.to_string(),
|
||||
)),
|
||||
),
|
||||
@@ -652,6 +650,36 @@ Requests for depth, thoroughness, research, investigation, or detailed codebase
|
||||
)
|
||||
}
|
||||
|
||||
fn spawn_agent_tool_description_v2(
|
||||
available_models_description: Option<&str>,
|
||||
include_usage_hint: bool,
|
||||
usage_hint_text: Option<String>,
|
||||
) -> String {
|
||||
let agent_role_guidance = available_models_description.unwrap_or_default();
|
||||
|
||||
let tool_description = format!(
|
||||
r#"
|
||||
{agent_role_guidance}
|
||||
Spawns an agent to work on the specified task. If your current task is `/root/task1` and you spawn_agent with task_name "task_3" the agent will have canonical task name `/root/task1/task_3`.
|
||||
You are then able to refer to this agent as `task_3` or `/root/task1/task_3` interchangeably. However an agent `/root/task2/task_3` would only be able to communicate with this agent via its canonical name `/root/task1/task_3`.
|
||||
The spawned agent will have the same tools as you and the ability to spawn its own subagents.
|
||||
It will be able to send you and other running agents messages, and its final answer will be provided to you when it finishes.
|
||||
The new agent's canonical task name will be provided to it along with the message."#
|
||||
);
|
||||
|
||||
if !include_usage_hint {
|
||||
return tool_description;
|
||||
}
|
||||
if let Some(usage_hint_text) = usage_hint_text {
|
||||
return format!(
|
||||
r#"
|
||||
{tool_description}
|
||||
{usage_hint_text}"#
|
||||
);
|
||||
}
|
||||
tool_description
|
||||
}
|
||||
|
||||
fn spawn_agent_models_description(models: &[ModelPreset]) -> String {
|
||||
let visible_models: Vec<&ModelPreset> =
|
||||
models.iter().filter(|model| model.show_in_picker).collect();
|
||||
@@ -713,7 +741,7 @@ fn wait_agent_tool_parameters_v2(options: WaitAgentTimeoutOptions) -> JsonSchema
|
||||
let properties = BTreeMap::from([(
|
||||
"timeout_ms".to_string(),
|
||||
JsonSchema::number(Some(format!(
|
||||
"Optional timeout in milliseconds. Defaults to {}, min {}, max {}. Prefer longer waits (minutes) to avoid busy polling.",
|
||||
"Optional timeout in milliseconds. Defaults to {}, min {}, max {}.",
|
||||
options.default_timeout_ms, options.min_timeout_ms, options.max_timeout_ms,
|
||||
))),
|
||||
)]);
|
||||
|
||||
@@ -59,6 +59,8 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() {
|
||||
.properties
|
||||
.as_ref()
|
||||
.expect("spawn_agent should use object params");
|
||||
assert!(description.contains("Spawns an agent to work on the specified task."));
|
||||
assert!(description.contains("The spawned agent will have the same tools as you"));
|
||||
assert!(description.contains("visible display (`visible-model`)"));
|
||||
assert!(!description.contains("hidden display (`hidden-model`)"));
|
||||
assert!(properties.contains_key("task_name"));
|
||||
@@ -109,6 +111,7 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() {
|
||||
#[test]
|
||||
fn send_message_tool_requires_message_and_has_no_output_schema() {
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description,
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
@@ -128,6 +131,16 @@ fn send_message_tool_requires_message_and_has_no_output_schema() {
|
||||
assert!(properties.contains_key("message"));
|
||||
assert!(!properties.contains_key("interrupt"));
|
||||
assert!(!properties.contains_key("items"));
|
||||
assert_eq!(
|
||||
description,
|
||||
"Send a string message to an existing agent without triggering a new turn."
|
||||
);
|
||||
assert_eq!(
|
||||
properties
|
||||
.get("target")
|
||||
.and_then(|schema| schema.description.as_deref()),
|
||||
Some("Relative or canonical task name to message (from spawn_agent).")
|
||||
);
|
||||
assert_eq!(
|
||||
parameters.required.as_ref(),
|
||||
Some(&vec!["target".to_string(), "message".to_string()])
|
||||
@@ -138,6 +151,7 @@ fn send_message_tool_requires_message_and_has_no_output_schema() {
|
||||
#[test]
|
||||
fn followup_task_tool_requires_message_and_has_no_output_schema() {
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description,
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
@@ -157,6 +171,20 @@ fn followup_task_tool_requires_message_and_has_no_output_schema() {
|
||||
assert!(properties.contains_key("message"));
|
||||
assert!(properties.contains_key("interrupt"));
|
||||
assert!(!properties.contains_key("items"));
|
||||
assert!(description.contains(
|
||||
"Send a string message to an existing non-root agent and trigger a turn in the target."
|
||||
));
|
||||
assert!(description.contains(
|
||||
"If interrupt=false and the target's turn has not completed, the message is queued"
|
||||
));
|
||||
assert_eq!(
|
||||
properties
|
||||
.get("interrupt")
|
||||
.and_then(|schema| schema.description.as_deref()),
|
||||
Some(
|
||||
"When true, stop the agent's current task and handle this immediately. When false (default), queue this message; if the target is already running, it starts the target's next turn after the current turn completes."
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parameters.required.as_ref(),
|
||||
Some(&vec!["target".to_string(), "message".to_string()])
|
||||
@@ -167,6 +195,7 @@ fn followup_task_tool_requires_message_and_has_no_output_schema() {
|
||||
#[test]
|
||||
fn wait_agent_tool_v2_uses_timeout_only_summary_output() {
|
||||
let ToolSpec::Function(ResponsesApiTool {
|
||||
description,
|
||||
parameters,
|
||||
output_schema,
|
||||
..
|
||||
@@ -188,6 +217,15 @@ fn wait_agent_tool_v2_uses_timeout_only_summary_output() {
|
||||
.expect("wait_agent should use object params");
|
||||
assert!(!properties.contains_key("targets"));
|
||||
assert!(properties.contains_key("timeout_ms"));
|
||||
assert!(description.contains(
|
||||
"Does not return the content; returns either a summary of which agents have updates (if any)"
|
||||
));
|
||||
assert_eq!(
|
||||
properties
|
||||
.get("timeout_ms")
|
||||
.and_then(|schema| schema.description.as_deref()),
|
||||
Some("Optional timeout in milliseconds. Defaults to 30000, min 10000, max 3600000.")
|
||||
);
|
||||
assert_eq!(parameters.required.as_ref(), None);
|
||||
assert_eq!(
|
||||
output_schema.expect("wait output schema")["properties"]["message"]["description"],
|
||||
@@ -214,6 +252,14 @@ fn list_agents_tool_includes_path_prefix_and_agent_fields() {
|
||||
.as_ref()
|
||||
.expect("list_agents should use object params");
|
||||
assert!(properties.contains_key("path_prefix"));
|
||||
assert_eq!(
|
||||
properties
|
||||
.get("path_prefix")
|
||||
.and_then(|schema| schema.description.as_deref()),
|
||||
Some(
|
||||
"Optional task-path prefix (not ending with trailing slash). Accepts the same relative or absolute task-path syntax."
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
output_schema.expect("list_agents output schema")["properties"]["agents"]["items"]["required"],
|
||||
json!(["agent_name", "agent_status", "last_task_message"])
|
||||
|
||||
Reference in New Issue
Block a user