diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 04a8593f0b..4aba1bab6c 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -2947,6 +2947,10 @@ "description": "Disable the model's direct-message tools; spawning and automatic child results remain available.", "type": "boolean" }, + "disable_native_tools": { + "description": "Hide native multi-agent tools while retaining the V2 runtime for host-provided tools.", + "type": "boolean" + }, "enabled": { "type": "boolean" }, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 6846d4c863..aaf2165a6b 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -12216,6 +12216,7 @@ tool_namespace = "agents" hide_spawn_agent_metadata = true expose_spawn_agent_model_overrides = false wait_agent_enabled = false +disable_native_tools = true disable_direct_message = true message_board_in_memory = true non_code_mode_only = true @@ -12274,6 +12275,7 @@ max_concurrent_threads_per_session = 9 assert!(config.multi_agent_v2.preserve_fork_prefix); assert!(!config.multi_agent_v2.expose_spawn_agent_model_overrides); assert!(!config.multi_agent_v2.wait_agent_enabled); + assert!(config.multi_agent_v2.disable_native_tools); assert!(config.multi_agent_v2.disable_direct_message); assert!(config.multi_agent_v2.message_board_in_memory); assert!(config.multi_agent_v2.non_code_mode_only); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index d55d48bd4a..c5ab36546e 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -1381,6 +1381,7 @@ pub struct MultiAgentV2Config { pub hide_spawn_agent_metadata: bool, pub expose_spawn_agent_model_overrides: bool, pub wait_agent_enabled: bool, + pub disable_native_tools: bool, pub disable_direct_message: bool, pub message_board_in_memory: bool, pub message_board_remote: Option, @@ -1404,6 +1405,7 @@ impl MultiAgentV2Config { hide_spawn_agent_metadata: true, expose_spawn_agent_model_overrides: true, wait_agent_enabled: true, + disable_native_tools: false, disable_direct_message: false, message_board_in_memory: false, message_board_remote: None, @@ -2850,6 +2852,9 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config let wait_agent_enabled = base .and_then(|config| config.wait_agent_enabled) .unwrap_or(default.wait_agent_enabled); + let disable_native_tools = base + .and_then(|config| config.disable_native_tools) + .unwrap_or(default.disable_native_tools); let disable_direct_message = base .and_then(|config| config.disable_direct_message) .unwrap_or(default.disable_direct_message); @@ -2889,6 +2894,7 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config hide_spawn_agent_metadata, expose_spawn_agent_model_overrides, wait_agent_enabled, + disable_native_tools, disable_direct_message, message_board_in_memory, message_board_remote: base.and_then(|config| config.message_board_remote.clone()), diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 5027a22355..fd5b5e14ee 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -733,8 +733,9 @@ fn collab_tools_enabled(turn_context: &TurnContext, model_info: &ModelInfo) -> b turn_context.config.agent_max_depth, ), MultiAgentVersion::V2 => { - turn_context.session_source.get_agent_path().is_none() - || model_info.multi_agent_version == Some(MultiAgentVersion::V2) + !turn_context.config.multi_agent_v2.disable_native_tools + && (turn_context.session_source.get_agent_path().is_none() + || model_info.multi_agent_version == Some(MultiAgentVersion::V2)) } } } diff --git a/codex-rs/core/tests/suite/scenarios.rs b/codex-rs/core/tests/suite/scenarios.rs index 1f37f92792..b2251f83fc 100644 --- a/codex-rs/core/tests/suite/scenarios.rs +++ b/codex-rs/core/tests/suite/scenarios.rs @@ -1097,14 +1097,17 @@ async fn astra_omits_disabled_executor_and_plugin_skills_from_model_context( Ok(()) } +#[test_case::test_case(false; "native_tools")] +#[test_case::test_case(true; "host_tools")] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn multi_agent_catalog_parameters() -> Result<()> { +async fn multi_agent_catalog_parameters(disable_native_tools: bool) -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; let test = test_codex() - .with_config(|config| { + .with_config(move |config| { configure_scenario_catalog(config); + config.multi_agent_v2.disable_native_tools = disable_native_tools; config.workspace_roots = vec![config.cwd.clone()]; }) .with_model_info_override("gpt-6-astra", |model| { @@ -1149,10 +1152,25 @@ async fn multi_agent_catalog_parameters() -> Result<()> { .await; test.submit_turn("Check which agents are working under /root before delegating more work.") .await?; - insta::assert_snapshot!( - "multi_agent_catalog_parameters", - context_snapshot::format_request_history_snapshot( + assert_eq!( + test.codex.multi_agent_version(), + Some(codex_protocol::protocol::MultiAgentVersion::V2) + ); + let (name, description) = if disable_native_tools { + ( + "multi_agent_native_tools_hidden", + "Native tools are unavailable while the V2 runtime and its instructions remain enabled.", + ) + } else { + ( + "multi_agent_catalog_parameters", "Astra calls list_agents using the selected catalog parameter schema.", + ) + }; + insta::assert_snapshot!( + name, + context_snapshot::format_request_history_snapshot( + description, &mock.requests(), &ContextSnapshotOptions::default().include_request_settings(), ) diff --git a/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__multi_agent_native_tools_hidden.snap b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__multi_agent_native_tools_hidden.snap new file mode 100644 index 0000000000..82f9a584f3 --- /dev/null +++ b/codex-rs/core/tests/suite/snapshots/all__suite__scenarios__multi_agent_native_tools_hidden.snap @@ -0,0 +1,99 @@ +--- +source: core/tests/suite/scenarios.rs +assertion_line: 1272 +expression: "context_snapshot::format_request_history_snapshot(description,\n&mock.requests(),\n&ContextSnapshotOptions::default().include_request_settings(),)" +--- +Scenario: Native tools are unavailable while the V2 runtime and its instructions remain enabled. + +## Window 1 +Settings: + include: ["reasoning.encrypted_content"] + model: "gpt-6-astra" + parallel_tool_calls: false + prompt_cache_key: "" + reasoning: {"context":"all_turns","effort":"low"} + store: false + stream: true + text: {"verbosity":"low"} + tool_choice: "auto" +-- request 1 (turn) -- +00:additional_tools/developer (2; hash=d56ea226c3637559): + - namespace/functions; hash=fada4a476a3754dd + - custom/exec + - function/wait + - function/request_user_input + - function/request_user_input_async + - namespace/clock: Tools for reading and waiting on time.; hash=f402e8c5e9b5e317 + - function/sleep +01:message/developer: + You are Codex, an agent based on GPT-6. You and the user share one workspace, and your job is to collabo...them until their intended goal is completely handled. [hash=efa50f074efa5803] + + # When to ask the user for permission + + Use your best judgement given task context for when you really need user permission, like a competent co...ork without ending the turn to clarify with the user. [hash=ca7367e658b51388] + + User authorization and preferences persist across turns. Do not request permission again when the user h... any guidelines provided in skills or external files. [hash=911f91c92c72882c] + + + + - Skill naming: If a plugin contributes skills, those skill entries are prefixed with plugin_name: in the Skills list. + - MCP naming: Plugin-provided MCP tools keep standard MCP identifiers such as mcp__server__tool; use tool provenance to tell which plugin they come from. + - Trigger rules: If the user explicitly names a plugin, prefer capabilities associated with that plugin for that turn. + - Relationship to capabilities: Plugins are not invoked directly. Use their underlying skills, MCP tools, and app tools to help solve the task. + - Relevance: Determine what a plugin can help with from explicit user mention or from the plugin-associa..., MCP tools, and apps exposed elsewhere in this turn. [hash=620459a4e7b590f0] + - Missing/blocked: If the user requests a plugin that does not have relevant callable capabilities for t..., say so briefly and continue with the best fallback. [hash=746b0469a8f6cb3e] + +02:message/developer[2]: + [01] + Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`...ll commands are permitted. Network access is enabled. [hash=c134061cebb94a1b] + Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected. + + [02] # Collaboration Mode: Default + + You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. + + Your active mode changes only when new developer instructions with a different `...<...by themselves. Known mode names are Default and Plan. [hash=e75f6b12fc52d812] + + ## request_user_input availability + + Use the `request_user_input` tool only when it is listed in the available tools for this turn. + + Use the `request_user_input` tool only for optional questions where the answer would materially improve the quality of the work. + + If `request_user_input` returns no answers, continue with best judgment instead of asking again or treating the turn as blocked. + + Never use the `request_user_input` tool for permission requests or permission-related escalations. + +03:message/developer: + You are `/root`, the primary agent in a team of agents collaborating to fulfill the user's goals. + + At the start of your turn, you are the active agent. + You can spawn sub-agents to handle subtasks, and those sub-agents can spawn their own sub-agents. + All agents in the team, including the agents that you can assign tasks to, are equally intelligent and capable, and have access to the same set of tools. + + You can use `spawn_agent` to create a new agent, `followup_task` to give an existing agent a new task an...message to a running agent without triggering a turn. [hash=7d40ff8b58977202] + `send_message` calls may be read by a human, so ensure they are legible. Always put proper spaces between words and/or numbers. + + - All agents use the same current working directory. + - As a result, edits made by one agent are immediately visible to all other agents. + + When calling `wait_agent`, prefer longer waits (minutes) to avoid busy polling. + + There are 4 available concurrency slots, meaning that up to 4 agents can be active at once, including you. + + Full-history forks (`fork_turns` omitted or `"all"`) inherit the parent model and reasoning effort and d... so, set `fork_turns` to `"none"`. [hash=d5928243dc643bcc] +04:message/developer: + Any earlier instruction enabling proactive multi-agent delegation no longer applies. D...elegation, or parallel agent work. [hash=2b6e037067b4390c] +05:message/user: + + + + + + <...e="unrestricted" /> [hash=d40b2f45cb028888] + +06:message/user: + Check which agents are working under /root before delegating more work. +-- request 2 (turn) -- +07:function_call/collaboration.list_agents:{"path_prefix":"/root"} +08:function_call_output:unsupported call: collaborationlist_agents diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index e8b67257db..e3ae224609 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -331,6 +331,9 @@ pub struct MultiAgentV2ConfigToml { /// Expose the multi-agent v2 `wait_agent` tool. #[serde(skip_serializing_if = "Option::is_none")] pub wait_agent_enabled: Option, + /// Hide native multi-agent tools while retaining the V2 runtime for host-provided tools. + #[serde(skip_serializing_if = "Option::is_none")] + pub disable_native_tools: Option, /// Disable the model's direct-message tools; spawning and automatic child results remain available. #[serde(skip_serializing_if = "Option::is_none")] pub disable_direct_message: Option, diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 646ae0fc6d..a8a8fca058 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -761,6 +761,7 @@ tool_namespace = "agents" hide_spawn_agent_metadata = true expose_spawn_agent_model_overrides = true wait_agent_enabled = false +disable_native_tools = true disable_direct_message = true message_board_in_memory = true non_code_mode_only = true @@ -791,6 +792,7 @@ non_code_mode_only = true hide_spawn_agent_metadata: Some(true), expose_spawn_agent_model_overrides: Some(true), wait_agent_enabled: Some(false), + disable_native_tools: Some(true), disable_direct_message: Some(true), message_board_in_memory: Some(true), message_board_remote: None,