diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 4ce6ac1a8..18c147ba3 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -1761,8 +1761,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index f1fbee5c5..74fb9e739 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2645,8 +2645,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index bf7f0e5b3..ec0321d9b 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -12693,8 +12693,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 7a080b56b..cd0f46bd4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -9097,8 +9097,9 @@ "type": "object" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 11efbb7e8..c19251667 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -663,8 +663,9 @@ ] }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index de242b62b..1a3b22f12 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -663,8 +663,9 @@ ] }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json index 67a0f5c5d..ca2b63634 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadSettingsUpdatedNotification.json @@ -109,8 +109,9 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json index eebb5da96..9932456f1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json @@ -195,8 +195,9 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index 4ad961c22..a754c6bc0 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -663,8 +663,9 @@ ] }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json index 9460f8068..bec8b7d89 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json @@ -142,8 +142,9 @@ "type": "string" }, "MultiAgentMode": { - "description": "Controls whether the model should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help.", + "description": "Controls whether the model receives multi-agent delegation instructions and, when it does, whether it should only spawn sub-agents after an explicit user request or may delegate proactively when doing so would help. `none` leaves the multi-agent tools available without injecting delegation instructions.", "enum": [ + "none", "explicitRequestOnly", "proactive" ], diff --git a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts index 93b790a59..b59be94f8 100644 --- a/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts +++ b/codex-rs/app-server-protocol/schema/typescript/MultiAgentMode.ts @@ -3,7 +3,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. /** - * Controls whether the model should only spawn sub-agents after an explicit - * user request or may delegate proactively when doing so would help. + * Controls whether the model receives multi-agent delegation instructions and, + * when it does, whether it should only spawn sub-agents after an explicit user + * request or may delegate proactively when doing so would help. `none` leaves + * the multi-agent tools available without injecting delegation instructions. */ -export type MultiAgentMode = "explicitRequestOnly" | "proactive"; +export type MultiAgentMode = "none" | "explicitRequestOnly" | "proactive"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index eb3e6c82f..0b1e11133 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1701,6 +1701,7 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::account::AmazonBedrockCredentialSource; use codex_protocol::account::PlanType; + use codex_protocol::config_types::MultiAgentMode; use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY; use codex_protocol::parse_command::ParsedCommand; use codex_protocol::protocol::RealtimeConversationVersion; @@ -2584,7 +2585,7 @@ mod tests { sandbox: v2::SandboxPolicy::DangerFullAccess, active_permission_profile: None, reasoning_effort: None, - multi_agent_mode: None, + multi_agent_mode: MultiAgentMode::ExplicitRequestOnly, }, }; @@ -2633,7 +2634,7 @@ mod tests { }, "activePermissionProfile": null, "reasoningEffort": null, - "multiAgentMode": null + "multiAgentMode": "explicitRequestOnly" } }), serde_json::to_value(&response)?, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 0161cd4ef..74e534be0 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -1,5 +1,6 @@ use super::*; use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest; +use codex_protocol::config_types::MultiAgentMode; use codex_protocol::items::AgentMessageContent; use codex_protocol::items::AgentMessageItem; use codex_protocol::items::FileChangeItem; @@ -3708,7 +3709,11 @@ fn thread_lifecycle_responses_default_missing_optional_fields() { resume.multi_agent_mode, fork.multi_agent_mode, ), - (None, None, None) + ( + MultiAgentMode::ExplicitRequestOnly, + MultiAgentMode::ExplicitRequestOnly, + MultiAgentMode::ExplicitRequestOnly, + ) ); let foreign_source: LegacyAppPathString = diff --git a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs index 3c527d621..6a51f71e5 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/thread.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/thread.rs @@ -94,9 +94,9 @@ pub struct ThreadStartParams { pub developer_instructions: Option, #[ts(optional = nullable)] pub personality: Option, - /// Set the initial multi-agent mode for this thread. - /// Omitted leaves the thread without a selected mode. Eligible multi-agent - /// v2 turns still default to `explicitRequestOnly`. + /// Set the initial multi-agent mode for this thread. `none` leaves the + /// multi-agent tools available without injecting mode instructions. + /// Omitted defaults to `explicitRequestOnly`. #[experimental("thread/start.multiAgentMode")] #[ts(optional = nullable)] pub multi_agent_mode: Option, @@ -186,10 +186,10 @@ pub struct ThreadStartResponse { #[serde(default)] pub active_permission_profile: Option, pub reasoning_effort: Option, - /// Current selected multi-agent mode for this thread, if one was selected. + /// Current multi-agent mode for this thread. #[experimental("thread/start.multiAgentMode")] #[serde(default)] - pub multi_agent_mode: Option, + pub multi_agent_mode: MultiAgentMode, } impl ThreadStartResponse { @@ -279,10 +279,10 @@ pub struct ThreadSettings { pub effort: Option, pub summary: Option, pub collaboration_mode: CollaborationMode, - /// Current selected multi-agent mode for this thread, if one was selected. + /// Current multi-agent mode for this thread. #[experimental("thread/settings.multiAgentMode")] #[serde(default)] - pub multi_agent_mode: Option, + pub multi_agent_mode: MultiAgentMode, pub personality: Option, } @@ -419,10 +419,10 @@ pub struct ThreadResumeResponse { #[serde(default)] pub active_permission_profile: Option, pub reasoning_effort: Option, - /// Current selected multi-agent mode for this thread, if one was selected. + /// Current multi-agent mode for this thread. #[experimental("thread/resume.multiAgentMode")] #[serde(default)] - pub multi_agent_mode: Option, + pub multi_agent_mode: MultiAgentMode, /// `thread/turns/list` page returned when requested by `initialTurnsPage`. #[experimental("thread/resume.initialTurnsPage")] #[serde(default)] @@ -578,10 +578,10 @@ pub struct ThreadForkResponse { #[serde(default)] pub active_permission_profile: Option, pub reasoning_effort: Option, - /// Current selected multi-agent mode for this thread, if one was selected. + /// Current multi-agent mode for this thread. #[experimental("thread/fork.multiAgentMode")] #[serde(default)] - pub multi_agent_mode: Option, + pub multi_agent_mode: MultiAgentMode, } impl ThreadForkResponse { diff --git a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs index 7ecb9f94b..1fc35d151 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/turn.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/turn.rs @@ -151,8 +151,9 @@ pub struct TurnStartParams { #[ts(optional = nullable)] pub collaboration_mode: Option, - /// Controls whether multi-agent v2 delegation requires an explicit user request. - /// Omitted keeps the loaded session's current mode. + /// Controls multi-agent v2 delegation instructions. `none` leaves the + /// multi-agent tools available without injecting mode instructions. Omitted + /// keeps the loaded session's current mode. #[experimental("turn/start.multiAgentMode")] #[ts(optional = nullable)] pub multi_agent_mode: Option, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 8de6e3b8c..e823eb100 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -137,17 +137,17 @@ Example with notification opt-out: ## API Overview -- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. For permissions, prefer experimental `permissions` profile selection by id; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissions`. Experimental `multiAgentMode` selects the initial thread mode; omission leaves the selected mode unset, while eligible multi-agent v2 turns still default to `explicitRequestOnly`. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`. Experimental `selectedCapabilityRoots` selects environment-owned plugin or standalone-skill roots. Skills found below those roots are listed and read through the owning environment. Stdio MCP servers declared by selected plugins are also started in that environment; HTTP MCP declarations remain inactive. +- `thread/start` — create a new thread; emits `thread/started` (including the current `thread.status`) and auto-subscribes you to turn/item events for that thread. When the request includes a `cwd` and the resolved sandbox is `workspace-write` or full access, app-server also marks that project as trusted in the user `config.toml`. Pass `sessionStartSource: "clear"` when starting a replacement thread after clearing the current session so `SessionStart` hooks receive `source: "clear"` instead of the default `"startup"`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. For permissions, prefer experimental `permissions` profile selection by id; the legacy `sandbox` shorthand is still accepted but cannot be combined with `permissions`. Experimental `multiAgentMode` selects the initial thread mode and defaults to `explicitRequestOnly` when omitted; use `none` to keep multi-agent tools available without injecting mode instructions. Experimental `environments` selects the sticky execution environments for turns on the thread; omit it to use the server default, pass `[]` to disable environments, or pass explicit environment ids with per-environment `cwd`. Experimental `selectedCapabilityRoots` selects environment-owned plugin or standalone-skill roots. Skills found below those roots are listed and read through the owning environment. Stdio MCP servers declared by selected plugins are also started in that environment; HTTP MCP declarations remain inactive. - `thread/resume` — reopen an existing thread by id so subsequent `turn/start` calls append to it. Accepts the same permission override rules as `thread/start`. Multi-agent mode restores the last effective mode from rollout history when available; clients can select another mode on the first `turn/start`. - `thread/fork` — fork an existing thread into a new thread id by copying the stored history; if the source thread is currently mid-turn, the fork records the same interruption marker as `turn/interrupt` instead of inheriting an unmarked partial turn suffix. The returned `thread.forkedFromId` points at the source thread when known. Accepts `ephemeral: true` for an in-memory temporary fork, emits `thread/started` (including the current `thread.status`), and auto-subscribes you to turn/item events for the new thread. Experimental clients can pass `excludeTurns: true` when they plan to page fork history via `thread/turns/list` instead of receiving the full turn array immediately. Accepts the same permission override rules as `thread/start`. -- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. `instructionSources` lists loaded instruction files using each source environment's native absolute path syntax, including files loaded from remote environments. Experimental clients can read `runtimeWorkspaceRoots` for the thread-scoped runtime roots and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known. Their experimental `multiAgentMode` field, and the corresponding thread setting, report the thread's current selected mode or `null` when no mode was selected. Turn construction separately determines whether that mode is applicable to the selected model and runtime configuration. +- `thread/start`, `thread/resume`, and `thread/fork` responses include the legacy `sandbox` compatibility projection. `instructionSources` lists loaded instruction files using each source environment's native absolute path syntax, including files loaded from remote environments. Experimental clients can read `runtimeWorkspaceRoots` for the thread-scoped runtime roots and `activePermissionProfile` for the named or implicit built-in profile identity/provenance when known. Their experimental `multiAgentMode` field, and the corresponding thread setting, report the thread's current mode. Turn construction separately determines whether that mode is applicable to the selected model and runtime configuration. - `thread/list` — page through stored threads; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Experimental clients can use `parentThreadId` to filter direct spawned children represented by persisted spawn-edge state. Review and Guardian threads are not included because they do not participate in that spawn-edge lifecycle. Each returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. Subagent threads also include `parentThreadId` when the immediate parent is known. - `thread/loaded/list` — list the thread ids currently loaded in memory. - `thread/read` — read a stored thread by id without resuming it; optionally include turns via `includeTurns`. The returned `thread` includes `status` (`ThreadStatus`), defaulting to `notLoaded` when the thread is not currently loaded. - `thread/turns/list` — experimental; page through a stored thread’s turn history without resuming it; supports cursor-based pagination with `sortDirection`, `itemsView`, `nextCursor`, and `backwardsCursor`. - `thread/turns/items/list` — experimental; reserved for paging full items for one turn. The API shape is present, but app-server currently returns an unsupported-method JSON-RPC error. - `thread/metadata/update` — patch stored thread metadata in sqlite; currently supports updating persisted `gitInfo` fields and returns the refreshed `thread`. -- `thread/settings/update` — experimental; queue a partial update to a loaded thread’s next-turn settings without starting a turn or adding transcript items. Omitted fields leave settings unchanged; `serviceTier: null` clears the tier; `multiAgentMode` selects `explicitRequestOnly` or `proactive` for subsequent turns; `sandboxPolicy` and `permissions` cannot be combined. Returns `{}` when the update is accepted and emits `thread/settings/updated` with the full effective settings only if they actually change. `turn/start` settings overrides emit the same notification when they change the stored settings. +- `thread/settings/update` — experimental; queue a partial update to a loaded thread’s next-turn settings without starting a turn or adding transcript items. Omitted fields leave settings unchanged; `serviceTier: null` clears the tier; `multiAgentMode` selects `none`, `explicitRequestOnly`, or `proactive` for subsequent turns; `sandboxPolicy` and `permissions` cannot be combined. Returns `{}` when the update is accepted and emits `thread/settings/updated` with the full effective settings only if they actually change. `turn/start` settings overrides emit the same notification when they change the stored settings. - `thread/memoryMode/set` — experimental; set a thread’s persisted memory eligibility to `"enabled"` or `"disabled"` for either a loaded thread or a stored rollout; returns `{}` on success. - `memory/reset` — experimental; clear the current `CODEX_HOME/memories` directory and reset persisted memory stage data in sqlite while preserving existing thread memory modes; returns `{}` on success. - `thread/goal/set` — create or update the single persisted goal for a materialized thread; returns the current goal and emits `thread/goal/updated`. @@ -168,7 +168,7 @@ Example with notification opt-out: - `thread/backgroundTerminals/list` — list running background terminals for a loaded thread (experimental; requires `capabilities.experimentalApi`); returns `data` with the running terminal ids. - `thread/backgroundTerminals/terminate` — terminate one running background terminal by app-server `processId` (experimental; requires `capabilities.experimentalApi`); returns whether a process was terminated. - `thread/rollback` — drop the last N turns from the agent’s in-memory context and persist a rollback marker in the rollout so future resumes see the pruned history; returns the updated `thread` (with `turns` populated) on success. -- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. Prefer experimental `permissions` profile selection by id for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". Experimental `multiAgentMode` accepts `explicitRequestOnly` or `proactive`; omission keeps the loaded session's current selected mode, including an unset mode. The requested mode is retained for the loaded session without rejecting unsupported configurations. Eligible multi-agent v2 turns default an unset mode to explicit-request-only, use the selected mode when `features.multi_agent_mode` is enabled, and otherwise use explicit-request-only developer instructions. +- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Experimental `runtimeWorkspaceRoots` replaces the thread-scoped runtime workspace roots used to materialize `:workspace_roots`; paths must be absolute. Prefer experimental `permissions` profile selection by id for permission overrides; the legacy `sandboxPolicy` field is still accepted but cannot be combined with `permissions`. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode". Experimental `multiAgentMode` accepts `none`, `explicitRequestOnly`, or `proactive`; `none` keeps the tools available without injecting mode instructions, and omission keeps the loaded session's current mode. The requested mode is retained for the loaded session without rejecting unsupported configurations, and eligible multi-agent v2 turns use it directly. - `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success. - `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. `clientUserMessageId` is optional; when supplied, the corresponding `userMessage` item echoes it as `clientId`. Review and manual compaction turns reject `turn/steer`. - `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`. diff --git a/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs b/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs index efdf13aae..9ad25ca99 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_settings_update.rs @@ -117,10 +117,7 @@ async fn thread_settings_update_multi_agent_mode_applies_to_future_turns() -> Re write_mock_responses_config_toml( codex_home.path(), &server.uri(), - &BTreeMap::from([ - (Feature::MultiAgentV2, true), - (Feature::MultiAgentMode, true), - ]), + &BTreeMap::from([(Feature::MultiAgentV2, true)]), /*auto_compact_limit*/ 200_000, /*requires_openai_auth*/ None, "mock_provider", @@ -158,7 +155,7 @@ async fn thread_settings_update_multi_agent_mode_applies_to_future_turns() -> Re assert_eq!(updated.thread_id, thread.id); assert_eq!( updated.thread_settings.multi_agent_mode, - Some(MultiAgentMode::Proactive) + MultiAgentMode::Proactive ); start_text_turn(&mut mcp, thread.id).await?; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 10d159996..3edd8da6f 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -1768,10 +1768,7 @@ async fn turn_start_accepts_multi_agent_mode_v2() -> Result<()> { codex_home.path(), &server.uri(), "never", - &BTreeMap::from([ - (Feature::MultiAgentV2, true), - (Feature::MultiAgentMode, true), - ]), + &BTreeMap::from([(Feature::MultiAgentV2, true)]), )?; let mut mcp = TestAppServer::new(codex_home.path()).await?; @@ -1845,10 +1842,7 @@ async fn thread_start_multi_agent_mode_initializes_first_turn() -> Result<()> { codex_home.path(), &server.uri(), "never", - &BTreeMap::from([ - (Feature::MultiAgentV2, true), - (Feature::MultiAgentMode, true), - ]), + &BTreeMap::from([(Feature::MultiAgentV2, true)]), )?; let mut mcp = TestAppServer::new(codex_home.path()).await?; @@ -1871,7 +1865,7 @@ async fn thread_start_multi_agent_mode_initializes_first_turn() -> Result<()> { multi_agent_mode, .. } = to_response::(thread_resp)?; - assert_eq!(multi_agent_mode, Some(MultiAgentMode::Proactive)); + assert_eq!(multi_agent_mode, MultiAgentMode::Proactive); let turn_req = mcp .send_turn_start_request(TurnStartParams { @@ -1911,27 +1905,29 @@ async fn thread_start_multi_agent_mode_initializes_first_turn() -> Result<()> { } #[tokio::test] -async fn thread_start_reports_selected_multi_agent_mode() -> Result<()> { +async fn thread_start_reports_multi_agent_mode() -> Result<()> { skip_if_no_network!(Ok(())); let cases = [ ( BTreeMap::from([(Feature::MultiAgentV2, true)]), Some(MultiAgentMode::Proactive), - Some(MultiAgentMode::Proactive), + MultiAgentMode::Proactive, + ), + ( + BTreeMap::from([(Feature::MultiAgentV2, true)]), + Some(MultiAgentMode::None), + MultiAgentMode::None, ), ( BTreeMap::new(), Some(MultiAgentMode::Proactive), - Some(MultiAgentMode::Proactive), + MultiAgentMode::Proactive, ), ( - BTreeMap::from([ - (Feature::MultiAgentV2, true), - (Feature::MultiAgentMode, true), - ]), - None, + BTreeMap::from([(Feature::MultiAgentV2, true)]), None, + MultiAgentMode::ExplicitRequestOnly, ), ]; diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 003ca6339..b6736c940 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1725,6 +1725,7 @@ "type": "string" }, "usage_hint_enabled": { + "description": "Deprecated compatibility field. Its value is ignored.", "type": "boolean" }, "usage_hint_text": { diff --git a/codex-rs/core/src/agent/control/spawn.rs b/codex-rs/core/src/agent/control/spawn.rs index 3f746ef79..05fac6aaf 100644 --- a/codex-rs/core/src/agent/control/spawn.rs +++ b/codex-rs/core/src/agent/control/spawn.rs @@ -498,7 +498,6 @@ impl AgentControl { } if preserve_reference_context_item && multi_agent_version == MultiAgentVersion::V2 - && config.multi_agent_v2.usage_hint_enabled && let Some(subagent_usage_hint_text) = config.multi_agent_v2.subagent_usage_hint_text.clone() && let Some(subagent_usage_hint_message) = diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index 8dbcab338..7eac69a88 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -850,10 +850,7 @@ async fn spawn_thread_subagent_uses_supplied_initial_multi_agent_mode_without_hi .config_snapshot() .await; - assert_eq!( - child_snapshot.multi_agent_mode, - Some(MultiAgentMode::Proactive) - ); + assert_eq!(child_snapshot.multi_agent_mode, MultiAgentMode::Proactive); } #[tokio::test] @@ -987,7 +984,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { .expect("child thread should be registered"); assert_eq!( child_thread.config_snapshot().await.multi_agent_mode, - Some(MultiAgentMode::Proactive) + MultiAgentMode::Proactive ); assert_ne!(child_thread_id, parent_thread_id); let history = child_thread.codex.session.clone_history().await; @@ -1025,18 +1022,13 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { "full-history forked child should preserve the parent diff baseline" ); - let mut disabled_hint_child_config = harness.config.clone(); - let _ = disabled_hint_child_config - .features - .enable(Feature::MultiAgentV2); - disabled_hint_child_config.multi_agent_v2.usage_hint_enabled = false; - disabled_hint_child_config - .multi_agent_v2 - .subagent_usage_hint_text = Some("Disabled child subagent guidance.".to_string()); - let disabled_hint_child_thread_id = harness + let mut no_hint_child_config = harness.config.clone(); + let _ = no_hint_child_config.features.enable(Feature::MultiAgentV2); + no_hint_child_config.multi_agent_v2.subagent_usage_hint_text = None; + let no_hint_child_thread_id = harness .control .spawn_agent_with_metadata( - disabled_hint_child_config, + no_hint_child_config, text_input("child task without hints"), Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn { parent_thread_id, @@ -1052,24 +1044,17 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { }, ) .await - .expect("forked spawn should honor disabled usage hints") + .expect("forked spawn should honor an empty subagent usage hint") .thread_id; - let disabled_hint_child_thread = harness + let no_hint_child_thread = harness .manager - .get_thread(disabled_hint_child_thread_id) + .get_thread(no_hint_child_thread_id) .await - .expect("disabled-hint child thread should be registered"); - let disabled_hint_history = disabled_hint_child_thread - .codex - .session - .clone_history() - .await; + .expect("no-hint child thread should be registered"); + let no_hint_history = no_hint_child_thread.codex.session.clone_history().await; assert!( - !history_contains_text( - disabled_hint_history.raw_items(), - "Disabled child subagent guidance.", - ), - "full-history forked child should not add subagent guidance when usage hints are disabled" + !history_contains_text(no_hint_history.raw_items(), "Child subagent guidance."), + "full-history forked child should not add empty subagent guidance" ); let expected = ( @@ -1099,9 +1084,9 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { .expect("child shutdown should submit"); let _ = harness .control - .shutdown_live_agent(disabled_hint_child_thread_id) + .shutdown_live_agent(no_hint_child_thread_id) .await - .expect("disabled-hint child shutdown should submit"); + .expect("no-hint child shutdown should submit"); let _ = parent_thread .submit(Op::Shutdown {}) .await @@ -1406,7 +1391,7 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() { .expect("child thread should be registered"); assert_eq!( child_thread.config_snapshot().await.multi_agent_mode, - Some(MultiAgentMode::Proactive) + MultiAgentMode::Proactive ); let history = child_thread.codex.session.clone_history().await; @@ -2422,10 +2407,7 @@ async fn resume_thread_subagent_restores_stored_metadata_and_effective_multi_age assert_eq!(resumed_agent_path, Some(agent_path)); assert_eq!(resumed_nickname, Some(original_nickname)); assert_eq!(resumed_role, Some("explorer".to_string())); - assert_eq!( - resumed_snapshot.multi_agent_mode, - Some(MultiAgentMode::Proactive) - ); + assert_eq!(resumed_snapshot.multi_agent_mode, MultiAgentMode::Proactive); let _ = harness .control diff --git a/codex-rs/core/src/codex_thread.rs b/codex-rs/core/src/codex_thread.rs index 0f63323d4..85ca1e1a6 100644 --- a/codex-rs/core/src/codex_thread.rs +++ b/codex-rs/core/src/codex_thread.rs @@ -72,7 +72,7 @@ pub struct ThreadConfigSnapshot { pub reasoning_summary: Option, pub personality: Option, pub collaboration_mode: CollaborationMode, - pub multi_agent_mode: Option, + pub multi_agent_mode: MultiAgentMode, pub session_source: SessionSource, pub forked_from_thread_id: Option, pub parent_thread_id: Option, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index ce9374016..be332cd60 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -10114,7 +10114,6 @@ max_concurrent_threads_per_session = 5 min_wait_timeout_ms = 2500 max_wait_timeout_ms = 120000 default_wait_timeout_ms = 30000 -usage_hint_enabled = false usage_hint_text = "Custom delegation guidance." root_agent_usage_hint_text = "Root guidance." subagent_usage_hint_text = "Subagent guidance." @@ -10142,7 +10141,6 @@ non_code_mode_only = true ), (None, Some(4)) ); - assert!(!config.multi_agent_v2.usage_hint_enabled); assert_eq!( config.multi_agent_v2.usage_hint_text.as_deref(), Some("Custom delegation guidance.") diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index d5986501a..dba933b11 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -241,8 +241,6 @@ You may also see them addressed as to=/root/..., which indicates your identity i "#; 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. -The goal is to correctly solve the problem in as little time as possible. Therefore, if at any point you can parallelize work by delegating tasks to another agent, you should do so to save time. - All agents share the same directory. In detail: - All agents have access to the same container and filesystem as you. - All agents use the same current working directory. @@ -1128,7 +1126,6 @@ pub struct MultiAgentV2Config { pub min_wait_timeout_ms: i64, pub max_wait_timeout_ms: i64, pub default_wait_timeout_ms: i64, - pub usage_hint_enabled: bool, pub usage_hint_text: Option, pub root_agent_usage_hint_text: Option, pub subagent_usage_hint_text: Option, @@ -1144,7 +1141,6 @@ impl MultiAgentV2Config { min_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_MIN_WAIT_TIMEOUT_MS, max_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_MAX_WAIT_TIMEOUT_MS, default_wait_timeout_ms: DEFAULT_MULTI_AGENT_V2_DEFAULT_WAIT_TIMEOUT_MS, - usage_hint_enabled: true, usage_hint_text: None, root_agent_usage_hint_text: Some(default_multi_agent_v2_usage_hint_text( DEFAULT_MULTI_AGENT_V2_ROOT_AGENT_USAGE_HINT_TEXT, @@ -2492,9 +2488,6 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config let default_wait_timeout_ms = base .and_then(|config| config.default_wait_timeout_ms) .unwrap_or(default.default_wait_timeout_ms); - let usage_hint_enabled = base - .and_then(|config| config.usage_hint_enabled) - .unwrap_or(default.usage_hint_enabled); let usage_hint_text = base .and_then(|config| config.usage_hint_text.as_ref()) .cloned() @@ -2523,7 +2516,6 @@ fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config min_wait_timeout_ms, max_wait_timeout_ms, default_wait_timeout_ms, - usage_hint_enabled, usage_hint_text, root_agent_usage_hint_text, subagent_usage_hint_text, diff --git a/codex-rs/core/src/context/multi_agent_mode_instructions.rs b/codex-rs/core/src/context/multi_agent_mode_instructions.rs index a272e7c2b..fc4debe5a 100644 --- a/codex-rs/core/src/context/multi_agent_mode_instructions.rs +++ b/codex-rs/core/src/context/multi_agent_mode_instructions.rs @@ -4,6 +4,7 @@ use codex_protocol::protocol::MULTI_AGENT_MODE_CLOSE_TAG; use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG; const EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT: &str = "Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegation, or parallel agent work."; +const NO_MULTI_AGENT_MODE_TEXT: &str = "Multi-agent delegation mode instructions are inactive. Any earlier multi-agent mode developer message no longer applies."; const PROACTIVE_MULTI_AGENT_MODE_TEXT: &str = "Proactive multi-agent delegation is active. Any earlier instruction requiring an explicit user request before spawning sub-agents no longer applies. Use sub-agents when parallel work would materially improve speed or quality. This mode remains active until a later multi-agent mode developer message changes it."; #[derive(Debug, Clone, PartialEq, Eq)] @@ -32,6 +33,7 @@ impl ContextualUserFragment for MultiAgentModeInstructions { fn body(&self) -> String { match self.multi_agent_mode { + MultiAgentMode::None => NO_MULTI_AGENT_MODE_TEXT.to_string(), MultiAgentMode::ExplicitRequestOnly => { EXPLICIT_REQUEST_ONLY_MULTI_AGENT_MODE_TEXT.to_string() } diff --git a/codex-rs/core/src/context_manager/updates.rs b/codex-rs/core/src/context_manager/updates.rs index 7cf877a72..22f2dbe6f 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -103,10 +103,8 @@ fn build_multi_agent_mode_update_item( ) -> Option { let effective_multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode( next.multi_agent_version, - &next.config.multi_agent_v2, &next.session_source, next.multi_agent_mode, - next.config.features.enabled(Feature::MultiAgentMode), ); let previous = previous?; if previous.multi_agent_mode == effective_multi_agent_mode { @@ -114,6 +112,9 @@ fn build_multi_agent_mode_update_item( } match effective_multi_agent_mode { + Some(MultiAgentMode::None) => { + Some(MultiAgentModeInstructions::new(MultiAgentMode::None).render()) + } Some(multi_agent_mode) => Some(MultiAgentModeInstructions::new(multi_agent_mode).render()), None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => { Some(MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly).render()) diff --git a/codex-rs/core/src/session/config_lock.rs b/codex-rs/core/src/session/config_lock.rs index 2553f7150..e91c4f489 100644 --- a/codex-rs/core/src/session/config_lock.rs +++ b/codex-rs/core/src/session/config_lock.rs @@ -327,7 +327,6 @@ mod tests { min_wait_timeout_ms: Some(_), max_wait_timeout_ms: Some(_), default_wait_timeout_ms: Some(_), - usage_hint_enabled: Some(_), hide_spawn_agent_metadata: Some(_), .. }) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index c85898555..f3c7a950d 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -586,7 +586,7 @@ impl Codex { .await; let multi_agent_version = resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version); - let multi_agent_mode = initial_multi_agent_mode; + let multi_agent_mode = initial_multi_agent_mode.unwrap_or_default(); config .validate_multi_agent_v2_config() .map_err(|err| CodexErr::InvalidRequest(err.to_string()))?; @@ -3290,19 +3290,20 @@ impl Session { { items.push(usage_hint_message); } - if let Some(multi_agent_mode) = multi_agents::effective_multi_agent_mode( + match multi_agents::effective_multi_agent_mode( turn_context.multi_agent_version, - &turn_context.config.multi_agent_v2, &session_source, turn_context.multi_agent_mode, - turn_context - .config - .features - .enabled(Feature::MultiAgentMode), ) { - items.push(ContextualUserFragment::into( - MultiAgentModeInstructions::new(multi_agent_mode), - )); + Some( + multi_agent_mode + @ (MultiAgentMode::ExplicitRequestOnly | MultiAgentMode::Proactive), + ) => { + items.push(ContextualUserFragment::into( + MultiAgentModeInstructions::new(multi_agent_mode), + )); + } + Some(MultiAgentMode::None) | None => {} } if let Some(contextual_user_message) = crate::context_manager::updates::build_contextual_user_message(contextual_user_sections) diff --git a/codex-rs/core/src/session/multi_agents.rs b/codex-rs/core/src/session/multi_agents.rs index 38651380e..82fcfd128 100644 --- a/codex-rs/core/src/session/multi_agents.rs +++ b/codex-rs/core/src/session/multi_agents.rs @@ -14,10 +14,6 @@ pub(super) fn usage_hint_text<'a>( } let multi_agent_v2 = &turn_context.config.multi_agent_v2; - if !multi_agent_v2.usage_hint_enabled { - return None; - } - configured_usage_hint_text_for_source(multi_agent_v2, session_source) } @@ -39,30 +35,23 @@ fn configured_usage_hint_text_for_source<'a>( } } -fn multi_agent_mode_is_applicable( - multi_agent_version: MultiAgentVersion, - multi_agent_v2: &MultiAgentV2Config, - session_source: &SessionSource, -) -> bool { - multi_agent_version == MultiAgentVersion::V2 - && multi_agent_v2.usage_hint_enabled - && configured_usage_hint_text_for_source(multi_agent_v2, session_source).is_some() -} - pub(crate) fn effective_multi_agent_mode( multi_agent_version: MultiAgentVersion, - multi_agent_v2: &MultiAgentV2Config, session_source: &SessionSource, - requested_multi_agent_mode: Option, - multi_agent_mode_enabled: bool, + multi_agent_mode: MultiAgentMode, ) -> Option { - if !multi_agent_mode_is_applicable(multi_agent_version, multi_agent_v2, session_source) { + if multi_agent_version != MultiAgentVersion::V2 { return None; } - Some(if multi_agent_mode_enabled { - requested_multi_agent_mode.unwrap_or_default() - } else { - MultiAgentMode::ExplicitRequestOnly - }) + match session_source { + SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. }) + | SessionSource::Cli + | SessionSource::VSCode + | SessionSource::Exec + | SessionSource::Mcp + | SessionSource::Custom(_) + | SessionSource::Unknown => Some(multi_agent_mode), + SessionSource::Internal(_) | SessionSource::SubAgent(_) => None, + } } diff --git a/codex-rs/core/src/session/session.rs b/codex-rs/core/src/session/session.rs index 931742834..759d159cc 100644 --- a/codex-rs/core/src/session/session.rs +++ b/codex-rs/core/src/session/session.rs @@ -52,7 +52,7 @@ pub(crate) struct SessionConfiguration { pub(super) provider: ModelProviderInfo, pub(super) collaboration_mode: CollaborationMode, - pub(super) multi_agent_mode: Option, + pub(super) multi_agent_mode: MultiAgentMode, pub(super) model_reasoning_summary: Option, pub(super) service_tier: Option, @@ -231,7 +231,7 @@ impl SessionConfiguration { next_configuration.collaboration_mode = collaboration_mode; } if let Some(multi_agent_mode) = updates.multi_agent_mode { - next_configuration.multi_agent_mode = Some(multi_agent_mode); + next_configuration.multi_agent_mode = multi_agent_mode; } if let Some(summary) = updates.reasoning_summary { next_configuration.model_reasoning_summary = Some(summary); diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index f3f96b74a..edd5ace9e 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -7895,15 +7895,14 @@ async fn build_initial_context_omits_multi_agent_v2_usage_hints_when_feature_dis } #[tokio::test] -async fn build_initial_context_omits_multi_agent_v2_usage_hints_when_hint_disabled() { +async fn build_initial_context_omits_multi_agent_v2_usage_hints_when_hint_is_empty() { let (session, turn_context, _rx_event) = make_session_and_context_with_auth_and_config_and_rx( CodexAuth::from_api_key("Test API Key"), Vec::new(), |config| { let _ = config.features.enable(Feature::MultiAgentV2); - config.multi_agent_v2.usage_hint_enabled = false; - config.multi_agent_v2.root_agent_usage_hint_text = Some("Root guidance.".to_string()); - config.multi_agent_v2.subagent_usage_hint_text = Some("Subagent guidance.".to_string()); + config.multi_agent_v2.root_agent_usage_hint_text = None; + config.multi_agent_v2.subagent_usage_hint_text = None; }, ) .await; diff --git a/codex-rs/core/src/session/turn_context.rs b/codex-rs/core/src/session/turn_context.rs index bb434fa2c..2fb02e7fc 100644 --- a/codex-rs/core/src/session/turn_context.rs +++ b/codex-rs/core/src/session/turn_context.rs @@ -125,7 +125,7 @@ pub struct TurnContext { pub(crate) developer_instructions: Option, pub(crate) user_instructions: Option, pub(crate) collaboration_mode: CollaborationMode, - pub(crate) multi_agent_mode: Option, + pub(crate) multi_agent_mode: MultiAgentMode, pub(crate) multi_agent_version: MultiAgentVersion, pub(crate) personality: Option, pub(crate) approval_policy: Constrained, @@ -386,10 +386,8 @@ impl TurnContext { multi_agent_version: Some(self.multi_agent_version), multi_agent_mode: super::multi_agents::effective_multi_agent_mode( self.multi_agent_version, - &self.config.multi_agent_v2, &self.session_source, self.multi_agent_mode, - self.config.features.enabled(Feature::MultiAgentMode), ), realtime_active: Some(self.realtime_active), effort: self.reasoning_effort.clone(), diff --git a/codex-rs/core/src/thread_manager.rs b/codex-rs/core/src/thread_manager.rs index 3a0e37475..2cce3bb8a 100644 --- a/codex-rs/core/src/thread_manager.rs +++ b/codex-rs/core/src/thread_manager.rs @@ -976,7 +976,7 @@ impl ThreadManager { }; let initial_multi_agent_mode = match source_thread_id { Some(thread_id) => match self.get_thread(thread_id).await { - Ok(thread) => thread.config_snapshot().await.multi_agent_mode, + Ok(thread) => Some(thread.config_snapshot().await.multi_agent_mode), Err(_) => history.get_latest_effective_multi_agent_mode(), }, None => history.get_latest_effective_multi_agent_mode(), diff --git a/codex-rs/core/src/tools/handlers/multi_agents_spec.rs b/codex-rs/core/src/tools/handlers/multi_agents_spec.rs index 70f71f311..08f8ca071 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_spec.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_spec.rs @@ -24,7 +24,6 @@ pub struct SpawnAgentToolOptions { pub available_models: Vec, pub agent_type_description: String, pub hide_agent_type_model_reasoning: bool, - pub include_usage_hint: bool, pub usage_hint_text: Option, } @@ -66,7 +65,6 @@ pub fn create_spawn_agent_tool_v1(options: SpawnAgentToolOptions) -> ToolSpec { available_models_description.as_deref(), inherited_model_guidance, return_value_description, - options.include_usage_hint, options.usage_hint_text, ), strict: false, @@ -99,7 +97,6 @@ pub fn create_spawn_agent_tool_v2(options: SpawnAgentToolOptions) -> ToolSpec { description: spawn_agent_tool_description_v2( available_models_description.as_deref(), inherited_model_guidance, - options.include_usage_hint, options.usage_hint_text, ), strict: false, @@ -648,7 +645,6 @@ fn spawn_agent_tool_description( available_models_description: Option<&str>, inherited_model_guidance: Option<&str>, return_value_description: &str, - include_usage_hint: bool, usage_hint_text: Option, ) -> String { let agent_role_guidance = available_models_description.unwrap_or_default(); @@ -660,9 +656,6 @@ fn spawn_agent_tool_description( Spawn a sub-agent for a well-scoped task. {return_value_description} {inherited_model_guidance}"# ); - if !include_usage_hint { - return tool_description; - } if let Some(usage_hint_text) = usage_hint_text { return format!( r#" @@ -711,7 +704,6 @@ Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegati fn spawn_agent_tool_description_v2( available_models_description: Option<&str>, inherited_model_guidance: Option<&str>, - include_usage_hint: bool, usage_hint_text: Option, ) -> String { let agent_role_guidance = available_models_description.unwrap_or_default(); @@ -731,9 +723,6 @@ The new agent's canonical task name will be provided to it along with the messag Note that passing `fork_turns="none"` will not pass any surrounding context to the spawned subagent, which may cause the agent to lack the context it needs to complete its task, whereas `fork_turns="all"` will provide the subagent with all surrounding context."# ); - if !include_usage_hint { - return tool_description; - } if let Some(usage_hint_text) = usage_hint_text { return format!( r#" diff --git a/codex-rs/core/src/tools/handlers/multi_agents_spec_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_spec_tests.rs index 1ef93b77f..0228f7acb 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_spec_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_spec_tests.rs @@ -45,7 +45,6 @@ fn spawn_agent_tool_v2_requires_task_name_and_lists_visible_models() { ], agent_type_description: "role help".to_string(), hide_agent_type_model_reasoning: false, - include_usage_hint: true, usage_hint_text: None, }); @@ -121,7 +120,6 @@ fn spawn_agent_tool_v1_keeps_legacy_fork_context_field() { available_models: Vec::new(), agent_type_description: "role help".to_string(), hide_agent_type_model_reasoning: false, - include_usage_hint: true, usage_hint_text: None, }); @@ -178,7 +176,6 @@ fn spawn_agent_tool_caps_visible_model_summaries() { ], agent_type_description: "role help".to_string(), hide_agent_type_model_reasoning: false, - include_usage_hint: true, usage_hint_text: None, }); @@ -222,7 +219,6 @@ fn spawn_agent_tool_hides_service_tier_with_spawn_metadata() { available_models: vec![model_preset("visible", /*show_in_picker*/ true)], agent_type_description: "role help".to_string(), hide_agent_type_model_reasoning: true, - include_usage_hint: true, usage_hint_text: None, }); diff --git a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs index eecdfc178..76881f5a0 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_tests.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_tests.rs @@ -1148,11 +1148,7 @@ async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_pat .features .enable(Feature::MultiAgentV2) .expect("test config should allow feature update"); - config - .features - .enable(Feature::MultiAgentMode) - .expect("test config should allow feature update"); - turn.multi_agent_mode = Some(MultiAgentMode::Proactive); + turn.multi_agent_mode = MultiAgentMode::Proactive; set_turn_config(&mut turn, config); let session = Arc::new(session); @@ -1191,10 +1187,7 @@ async fn multi_agent_v2_spawn_returns_path_and_send_message_accepts_relative_pat child_snapshot.session_source.get_agent_path().as_deref(), Some("/root/test_process") ); - assert_eq!( - child_snapshot.multi_agent_mode, - Some(MultiAgentMode::Proactive) - ); + assert_eq!(child_snapshot.multi_agent_mode, MultiAgentMode::Proactive); assert!(manager.captured_ops().iter().any(|(id, op)| { *id == child_thread_id && matches!( diff --git a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs index ed63102d6..c04d46c6e 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs @@ -52,12 +52,8 @@ async fn handle_spawn_agent( let fork_mode = args.fork_mode()?; let multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode( turn.multi_agent_version, - &turn.config.multi_agent_v2, &turn.session_source, turn.multi_agent_mode, - turn.config - .features - .enabled(codex_features::Feature::MultiAgentMode), ); let role_name = args .agent_type diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 3e2268d92..c83422f1a 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -788,7 +788,6 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu .config .multi_agent_v2 .hide_spawn_agent_metadata, - include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled, usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(), }), tool_namespace, @@ -832,7 +831,6 @@ fn add_collaboration_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mu available_models: turn_context.available_models.clone(), agent_type_description, hide_agent_type_model_reasoning: false, - include_usage_hint: turn_context.config.multi_agent_v2.usage_hint_enabled, usage_hint_text: turn_context.config.multi_agent_v2.usage_hint_text.clone(), }), exposure, diff --git a/codex-rs/core/tests/suite/multi_agent_mode.rs b/codex-rs/core/tests/suite/multi_agent_mode.rs index ba91f2fd3..21ef31c49 100644 --- a/codex-rs/core/tests/suite/multi_agent_mode.rs +++ b/codex-rs/core/tests/suite/multi_agent_mode.rs @@ -19,6 +19,7 @@ use pretty_assertions::assert_eq; use serde_json::Value; const NO_SPAWN_TEXT: &str = "Do not spawn sub-agents unless the user explicitly asks for sub-agents, delegation, or parallel agent work."; +const NO_MODE_TEXT: &str = "Multi-agent delegation mode instructions are inactive."; const PROACTIVE_TEXT: &str = "Proactive multi-agent delegation is active."; fn developer_texts(input: &[Value]) -> Vec<&str> { @@ -66,7 +67,7 @@ async fn multi_agent_mode_is_sticky_and_emits_only_on_change() -> Result<()> { let server = start_mock_server().await; let responses = mount_sse_sequence( &server, - (1..=3) + (1..=5) .map(|index| { sse(vec![ ev_response_created(&format!("resp-{index}")), @@ -82,18 +83,24 @@ async fn multi_agent_mode_is_sticky_and_emits_only_on_change() -> Result<()> { .features .enable(Feature::MultiAgentV2) .expect("test config should allow feature update"); - config - .features - .enable(Feature::MultiAgentMode) - .expect("test config should allow feature update"); }) .build(&server) .await?; submit_turn(&test.codex, "turn one", /*mode*/ None).await?; - assert_eq!(test.codex.config_snapshot().await.multi_agent_mode, None); + assert_eq!( + test.codex.config_snapshot().await.multi_agent_mode, + MultiAgentMode::ExplicitRequestOnly + ); submit_turn(&test.codex, "turn two", Some(MultiAgentMode::Proactive)).await?; submit_turn(&test.codex, "turn three", /*mode*/ None).await?; + submit_turn(&test.codex, "turn four", Some(MultiAgentMode::None)).await?; + submit_turn(&test.codex, "turn five", /*mode*/ None).await?; + + assert_eq!( + test.codex.config_snapshot().await.multi_agent_mode, + MultiAgentMode::None + ); let requests = responses.requests(); let inputs = requests @@ -103,6 +110,8 @@ async fn multi_agent_mode_is_sticky_and_emits_only_on_change() -> Result<()> { let first = developer_texts(&inputs[0]); let second = developer_texts(&inputs[1]); let third = developer_texts(&inputs[2]); + let fourth = developer_texts(&inputs[3]); + let fifth = developer_texts(&inputs[4]); assert_eq!( ( @@ -128,12 +137,102 @@ async fn multi_agent_mode_is_sticky_and_emits_only_on_change() -> Result<()> { ), (2, 1, 1) ); + assert_eq!( + ( + count_containing(&fourth, MULTI_AGENT_MODE_OPEN_TAG), + count_containing(&fourth, NO_SPAWN_TEXT), + count_containing(&fourth, PROACTIVE_TEXT), + count_containing(&fourth, NO_MODE_TEXT), + ), + (3, 1, 1, 1) + ); + assert_eq!( + ( + count_containing(&fifth, MULTI_AGENT_MODE_OPEN_TAG), + count_containing(&fifth, NO_SPAWN_TEXT), + count_containing(&fifth, PROACTIVE_TEXT), + count_containing(&fifth, NO_MODE_TEXT), + ), + (3, 1, 1, 1) + ); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn multi_agent_mode_feature_uses_explicit_mode_when_disabled() -> Result<()> { +async fn multi_agent_mode_none_omits_instructions_and_survives_resume() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let responses = mount_sse_sequence( + &server, + (1..=2) + .map(|index| { + sse(vec![ + ev_response_created(&format!("resp-{index}")), + ev_completed(&format!("resp-{index}")), + ]) + }) + .collect(), + ) + .await; + let initial = test_codex() + .with_config(|config| { + config + .features + .enable(Feature::MultiAgentV2) + .expect("test config should allow feature update"); + }) + .build(&server) + .await?; + let home = initial.home.clone(); + let rollout_path = initial + .session_configured + .rollout_path + .clone() + .expect("rollout path"); + + submit_turn(&initial.codex, "before resume", Some(MultiAgentMode::None)).await?; + assert_eq!( + initial.codex.config_snapshot().await.multi_agent_mode, + MultiAgentMode::None + ); + drop(initial); + + let mut resume_builder = test_codex().with_config(|config| { + config + .features + .enable(Feature::MultiAgentV2) + .expect("test config should allow feature update"); + }); + let resumed = resume_builder.resume(&server, home, rollout_path).await?; + submit_turn(&resumed.codex, "after resume", /*mode*/ None).await?; + + assert_eq!( + resumed.codex.config_snapshot().await.multi_agent_mode, + MultiAgentMode::None + ); + let requests = responses.requests(); + assert_eq!(requests.len(), 2); + for request in requests { + let input = request.input(); + let texts = developer_texts(&input); + assert_eq!( + ( + count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG), + count_containing(&texts, NO_SPAWN_TEXT), + count_containing(&texts, PROACTIVE_TEXT), + count_containing(&texts, NO_MODE_TEXT), + ), + (0, 0, 0, 0) + ); + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn multi_agent_mode_applies_without_usage_hint_text() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -148,21 +247,21 @@ async fn multi_agent_mode_feature_uses_explicit_mode_when_disabled() -> Result<( .features .enable(Feature::MultiAgentV2) .expect("test config should allow feature update"); + config.multi_agent_v2.root_agent_usage_hint_text = None; }) .build(&server) .await?; - submit_turn(&test.codex, "hello", /*mode*/ None).await?; + submit_turn(&test.codex, "hello", Some(MultiAgentMode::Proactive)).await?; let input = responses.single_request().input(); let texts = developer_texts(&input); assert_eq!( ( count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG), - count_containing(&texts, NO_SPAWN_TEXT), count_containing(&texts, PROACTIVE_TEXT), ), - (1, 1, 0) + (1, 1) ); Ok(()) @@ -175,7 +274,7 @@ async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result let server = start_mock_server().await; let responses = mount_sse_sequence( &server, - (1..=4) + (1..=2) .map(|index| { sse(vec![ ev_response_created(&format!("resp-{index}")), @@ -214,18 +313,14 @@ async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result .features .enable(Feature::MultiAgentV2) .expect("test config should allow feature update"); - config - .features - .enable(Feature::MultiAgentMode) - .expect("test config should allow feature update"); }); let resumed = resume_builder.resume(&server, home, rollout_path).await?; - submit_turn( - &resumed.codex, - "after resume", - Some(MultiAgentMode::Proactive), - ) - .await?; + submit_turn(&resumed.codex, "after resume", /*mode*/ None).await?; + + assert_eq!( + resumed.codex.config_snapshot().await.multi_agent_mode, + MultiAgentMode::Proactive + ); let requests = responses.requests(); let resumed_input = requests[1].input(); @@ -236,108 +331,14 @@ async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result count_containing(&texts, NO_SPAWN_TEXT), count_containing(&texts, PROACTIVE_TEXT), ), - (2, 1, 1) - ); - - let resumed_rollout_path = resumed - .session_configured - .rollout_path - .clone() - .expect("resumed rollout path"); - let resumed_home = resumed.home.clone(); - drop(resumed); - let mut same_mode_resume_builder = test_codex().with_config(|config| { - config - .features - .enable(Feature::MultiAgentV2) - .expect("test config should allow feature update"); - config - .features - .enable(Feature::MultiAgentMode) - .expect("test config should allow feature update"); - }); - let resumed_same_mode = same_mode_resume_builder - .resume(&server, resumed_home, resumed_rollout_path) - .await?; - submit_turn( - &resumed_same_mode.codex, - "after same-mode resume", - /*mode*/ None, - ) - .await?; - - assert_eq!( - resumed_same_mode - .codex - .config_snapshot() - .await - .multi_agent_mode, - Some(MultiAgentMode::Proactive) - ); - let requests = responses.requests(); - let resumed_same_mode_input = requests[2].input(); - let texts = developer_texts(&resumed_same_mode_input); - assert_eq!( - ( - count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG), - count_containing(&texts, NO_SPAWN_TEXT), - count_containing(&texts, PROACTIVE_TEXT), - ), - (2, 1, 1) - ); - - let resumed_same_mode_rollout_path = resumed_same_mode - .session_configured - .rollout_path - .clone() - .expect("same-mode resumed rollout path"); - let resumed_same_mode_home = resumed_same_mode.home.clone(); - drop(resumed_same_mode); - let mut disabled_mode_resume_builder = test_codex().with_config(|config| { - config - .features - .enable(Feature::MultiAgentV2) - .expect("test config should allow feature update"); - }); - let resumed_disabled_mode = disabled_mode_resume_builder - .resume( - &server, - resumed_same_mode_home, - resumed_same_mode_rollout_path, - ) - .await?; - submit_turn( - &resumed_disabled_mode.codex, - "after disabled-mode resume", - /*mode*/ None, - ) - .await?; - - assert_eq!( - resumed_disabled_mode - .codex - .config_snapshot() - .await - .multi_agent_mode, - Some(MultiAgentMode::Proactive) - ); - let requests = responses.requests(); - let resumed_disabled_mode_input = requests[3].input(); - let texts = developer_texts(&resumed_disabled_mode_input); - assert_eq!( - ( - count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG), - count_containing(&texts, NO_SPAWN_TEXT), - count_containing(&texts, PROACTIVE_TEXT), - ), - (3, 2, 1) + (1, 0, 1) ); Ok(()) } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn explicit_multi_agent_mode_is_retained_without_multi_agent_v2() -> Result<()> { +async fn multi_agent_mode_is_retained_without_multi_agent_v2() -> Result<()> { skip_if_no_network!(Ok(())); let server = start_mock_server().await; @@ -346,21 +347,13 @@ async fn explicit_multi_agent_mode_is_retained_without_multi_agent_v2() -> Resul sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]), ) .await; - let test = test_codex() - .with_config(|config| { - config - .features - .enable(Feature::MultiAgentMode) - .expect("test config should allow feature update"); - }) - .build(&server) - .await?; + let test = test_codex().build(&server).await?; submit_turn(&test.codex, "hello", Some(MultiAgentMode::Proactive)).await?; assert_eq!( test.codex.config_snapshot().await.multi_agent_mode, - Some(MultiAgentMode::Proactive) + MultiAgentMode::Proactive ); let input = responses.single_request().input(); let texts = developer_texts(&input); diff --git a/codex-rs/features/src/feature_configs.rs b/codex-rs/features/src/feature_configs.rs index 15a37fe36..a76aaaa3c 100644 --- a/codex-rs/features/src/feature_configs.rs +++ b/codex-rs/features/src/feature_configs.rs @@ -46,6 +46,7 @@ pub struct MultiAgentV2ConfigToml { #[serde(skip_serializing_if = "Option::is_none")] #[schemars(range(min = 0, max = 3600000))] pub default_wait_timeout_ms: Option, + /// Deprecated compatibility field. Its value is ignored. #[serde(skip_serializing_if = "Option::is_none")] pub usage_hint_enabled: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index 3a04621fe..75808e49d 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -145,7 +145,7 @@ pub enum Feature { Collab, /// Enable task-path-based multi-agent routing. MultiAgentV2, - /// Enable per-turn multi-agent mode selection. + /// Removed compatibility flag retained as a no-op. MultiAgentMode, /// Enable CSV-backed agent job tools. SpawnCsv, @@ -1034,7 +1034,7 @@ pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::MultiAgentMode, key: "multi_agent_mode", - stage: Stage::UnderDevelopment, + stage: Stage::Removed, default_enabled: false, }, FeatureSpec { diff --git a/codex-rs/features/src/tests.rs b/codex-rs/features/src/tests.rs index 7ee46f4ba..476c80aed 100644 --- a/codex-rs/features/src/tests.rs +++ b/codex-rs/features/src/tests.rs @@ -604,45 +604,6 @@ non_code_mode_only = true ); } -#[test] -fn multi_agent_v2_feature_config_usage_hint_enabled_does_not_enable_feature() { - let features_toml: FeaturesToml = toml::from_str( - r#" -[multi_agent_v2] -usage_hint_enabled = false -"#, - ) - .expect("features table should deserialize"); - let features = Features::from_sources( - FeatureConfigSource { - features: Some(&features_toml), - ..Default::default() - }, - FeatureConfigSource::default(), - FeatureOverrides::default(), - ); - - assert_eq!(features.enabled(Feature::MultiAgentV2), false); - assert_eq!(features_toml.entries(), BTreeMap::new()); - assert_eq!( - features_toml.multi_agent_v2, - Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml { - enabled: None, - max_concurrent_threads_per_session: None, - min_wait_timeout_ms: None, - max_wait_timeout_ms: None, - default_wait_timeout_ms: None, - usage_hint_enabled: Some(false), - usage_hint_text: None, - root_agent_usage_hint_text: None, - subagent_usage_hint_text: None, - tool_namespace: None, - hide_spawn_agent_metadata: None, - non_code_mode_only: None, - })) - ); -} - #[test] fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config() { let mut features = Features::with_defaults(); diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index 5c250648a..83bcbdc0a 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -296,8 +296,10 @@ pub enum Personality { Pragmatic, } -/// Controls whether the model should only spawn sub-agents after an explicit -/// user request or may delegate proactively when doing so would help. +/// Controls whether the model receives multi-agent delegation instructions and, +/// when it does, whether it should only spawn sub-agents after an explicit user +/// request or may delegate proactively when doing so would help. `none` leaves +/// the multi-agent tools available without injecting delegation instructions. #[derive( Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default, )] @@ -305,6 +307,7 @@ pub enum Personality { #[ts(rename_all = "camelCase")] #[strum(serialize_all = "camelCase")] pub enum MultiAgentMode { + None, #[default] ExplicitRequestOnly, Proactive, diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 5900ffe3c..ea1df417a 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2005,7 +2005,7 @@ pub struct ThreadSettingsSnapshot { pub personality: Option, pub collaboration_mode: CollaborationMode, #[serde(default)] - pub multi_agent_mode: Option, + pub multi_agent_mode: MultiAgentMode, } #[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, TS)]