mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Namespace multi-agent v2 tools under collaboration (#29067)
## Summary Multi-agent v2 tools now use the fixed `collaboration` namespace when namespace tools are available. This keeps the model-visible hint and the actual tool surface aligned around `functions.collaboration.*`, without exposing an unshipped namespace knob to users. The PR also removes the old `features.multi_agent_v2.tool_namespace` config/schema surface, updates the MAv2 test fixtures for namespaced calls, and fixes stale `TurnContext.features` references that were breaking `codex-core` builds. ## Changes - Expose MAv2 tools under `collaboration` instead of relying on a configurable namespace. - Remove `tool_namespace` from MAv2 TOML config, resolved config, validation, schema, and tests. - Update tool-planning and integration fixtures to assert or emit namespaced MAv2 tool calls. - Read feature state through `TurnContext.config.features` in the multi-agent mode context paths. ## Testing - `just write-config-schema` - `just test -p codex-features`
This commit is contained in:
@@ -246,7 +246,8 @@ Payload:
|
||||
```
|
||||
You may also see them addressed as to=/root/..., which indicates your identity is /root/...
|
||||
"#;
|
||||
const DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT: &str = r#"Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `interrupt_agent`, and `list_agents` only as direct tool calls using the recipient shown in their tool definitions, such as `to=functions.spawn_agent` without a configured namespace or `to=functions.agents.spawn_agent` with `tool_namespace = "agents"`, since they are intentionally absent from the `functions.exec` `tools.*` namespace. Available tools in `functions.exec` are explicitly described with a `tools` namespace in the developer message.
|
||||
const DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE: &str = "collaboration";
|
||||
const DEFAULT_MULTI_AGENT_V2_SHARED_USAGE_HINT_TEXT: &str = r#"Note that collaboration tools cannot be called from inside `functions.exec`. Call `spawn_agent`, `send_message`, `followup_task`, `wait_agent`, `interrupt_agent`, and `list_agents` only as direct tool calls using the recipient shown in their tool definitions, such as `to=functions.collaboration.spawn_agent`, since they are intentionally absent from the `functions.exec` `tools.*` namespace. Available tools in `functions.exec` are explicitly described with a `tools` namespace in the developer message.
|
||||
|
||||
All agents share the same directory. In detail:
|
||||
- All agents have access to the same container and filesystem as you.
|
||||
@@ -1157,7 +1158,7 @@ impl MultiAgentV2Config {
|
||||
DEFAULT_MULTI_AGENT_V2_SUBAGENT_USAGE_HINT_TEXT,
|
||||
max_concurrent_threads_per_session,
|
||||
)),
|
||||
tool_namespace: None,
|
||||
tool_namespace: Some(DEFAULT_MULTI_AGENT_V2_TOOL_NAMESPACE.to_string()),
|
||||
hide_spawn_agent_metadata: true,
|
||||
non_code_mode_only: true,
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ use crate::tools::router::ToolRouterParams;
|
||||
use crate::tools::router::ToolSuggestCandidates;
|
||||
use crate::tools::router::ToolSuggestPresentation;
|
||||
|
||||
const MULTI_AGENT_V2_NAMESPACE: &str = "collaboration";
|
||||
|
||||
#[derive(Default)]
|
||||
struct ToolPlanInputs {
|
||||
mcp_tools: Option<Vec<ToolInfo>>,
|
||||
@@ -1151,19 +1153,48 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
|
||||
});
|
||||
})
|
||||
.await;
|
||||
v2.assert_visible_contains(&[
|
||||
v2.assert_visible_contains(&[MULTI_AGENT_V2_NAMESPACE]);
|
||||
v2.assert_visible_lacks(&[
|
||||
"spawn_agent",
|
||||
"send_message",
|
||||
"followup_task",
|
||||
"wait_agent",
|
||||
"interrupt_agent",
|
||||
"list_agents",
|
||||
"send_input",
|
||||
"resume_agent",
|
||||
"assign_task",
|
||||
"close_agent",
|
||||
]);
|
||||
v2.assert_visible_lacks(&["send_input", "resume_agent", "assign_task", "close_agent"]);
|
||||
let spawn_agent_description = match v2.visible_spec("spawn_agent") {
|
||||
ToolSpec::Function(tool) => tool.description.as_str(),
|
||||
other => panic!("expected spawn_agent function spec, got {other:?}"),
|
||||
for tool_name in [
|
||||
"spawn_agent",
|
||||
"send_message",
|
||||
"followup_task",
|
||||
"wait_agent",
|
||||
"interrupt_agent",
|
||||
"list_agents",
|
||||
] {
|
||||
assert!(
|
||||
v2.namespace_function_names(MULTI_AGENT_V2_NAMESPACE)
|
||||
.iter()
|
||||
.any(|name| name == tool_name),
|
||||
"expected {tool_name} in {MULTI_AGENT_V2_NAMESPACE} namespace"
|
||||
);
|
||||
}
|
||||
let ToolSpec::Namespace(namespace) = v2.visible_spec(MULTI_AGENT_V2_NAMESPACE) else {
|
||||
panic!("expected {MULTI_AGENT_V2_NAMESPACE} namespace");
|
||||
};
|
||||
let Some(ResponsesApiNamespaceTool::Function(spawn_agent)) =
|
||||
namespace.tools.iter().find(|tool| {
|
||||
matches!(
|
||||
tool,
|
||||
ResponsesApiNamespaceTool::Function(tool) if tool.name == "spawn_agent"
|
||||
)
|
||||
})
|
||||
else {
|
||||
panic!("expected spawn_agent in {MULTI_AGENT_V2_NAMESPACE} namespace");
|
||||
};
|
||||
let spawn_agent_description = spawn_agent.description.as_str();
|
||||
assert!(!spawn_agent_description.contains("max_concurrent_threads_per_session"));
|
||||
assert!(spawn_agent_description.contains(
|
||||
"Note that passing `fork_turns=\"none\"` will not pass any surrounding context to the spawned subagent"
|
||||
@@ -1183,9 +1214,11 @@ async fn multi_agent_feature_selects_one_agent_tool_family() {
|
||||
});
|
||||
})
|
||||
.await;
|
||||
direct_model_only.assert_visible_contains(&["spawn_agent", "send_message", "wait_agent"]);
|
||||
direct_model_only.assert_visible_contains(&[MULTI_AGENT_V2_NAMESPACE]);
|
||||
direct_model_only.assert_visible_lacks(&["spawn_agent", "send_message", "wait_agent"]);
|
||||
assert_eq!(
|
||||
direct_model_only.exposure("spawn_agent"),
|
||||
direct_model_only
|
||||
.exposure(&ToolName::namespaced(MULTI_AGENT_V2_NAMESPACE, "spawn_agent").to_string()),
|
||||
ToolExposure::DirectModelOnly
|
||||
);
|
||||
}
|
||||
@@ -1196,9 +1229,17 @@ async fn multi_agent_v2_message_schemas_are_encrypted() {
|
||||
set_feature(turn, Feature::MultiAgentV2, /*enabled*/ true);
|
||||
})
|
||||
.await;
|
||||
let ToolSpec::Namespace(namespace) = plan.visible_spec(MULTI_AGENT_V2_NAMESPACE) else {
|
||||
panic!("expected {MULTI_AGENT_V2_NAMESPACE} namespace");
|
||||
};
|
||||
for tool_name in ["spawn_agent", "send_message", "followup_task"] {
|
||||
let ToolSpec::Function(tool) = plan.visible_spec(tool_name) else {
|
||||
panic!("expected {tool_name} function spec");
|
||||
let Some(ResponsesApiNamespaceTool::Function(tool)) = namespace.tools.iter().find(|tool| {
|
||||
matches!(
|
||||
tool,
|
||||
ResponsesApiNamespaceTool::Function(tool) if tool.name == tool_name
|
||||
)
|
||||
}) else {
|
||||
panic!("expected {tool_name} in {MULTI_AGENT_V2_NAMESPACE} namespace");
|
||||
};
|
||||
let properties = tool
|
||||
.parameters
|
||||
@@ -1464,12 +1505,7 @@ async fn hosted_tools_follow_provider_auth_model_and_config_gates() {
|
||||
codex_code_mode::WAIT_TOOL_NAME,
|
||||
"request_user_input",
|
||||
// Multi-agent v2 tools.
|
||||
"spawn_agent",
|
||||
"send_message",
|
||||
"followup_task",
|
||||
"wait_agent",
|
||||
"interrupt_agent",
|
||||
"list_agents",
|
||||
MULTI_AGENT_V2_NAMESPACE,
|
||||
// Hosted Responses tools.
|
||||
"web_search",
|
||||
"image_generation",
|
||||
|
||||
Reference in New Issue
Block a user