mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Add Ultra reasoning effort (#29899)
## Why Ultra should be one user-facing reasoning selection for work that benefits from both maximum reasoning and proactive multi-agent delegation. Without it, clients must coordinate maximum reasoning with the experimental `multiAgentMode` setting, even though the inference backend still expects its existing `max` effort value. This change makes reasoning effort the source of truth: clients select `ultra`, core derives proactive multi-agent behavior when the turn is eligible for multi-agent V2, and inference requests continue to use the backend-compatible `max` value. ## What changed - Add `ultra` as a first-class reasoning effort and preserve model-catalog ordering when exposing it to clients. - Convert `ultra` to `max` at the inference request boundary, including Responses HTTP/WebSocket requests, startup prewarm, compaction, and memory summarization. - Derive effective multi-agent mode per turn from effective reasoning effort: - eligible multi-agent V2 + `ultra` → `proactive` - eligible multi-agent V2 + any other effort → `explicitRequestOnly` - V1 or otherwise ineligible sessions → no multi-agent mode instruction - Keep the derived effective mode in turn context history so successive turns can emit a developer-message update only when the effective mode changes. - Remove selected multi-agent mode from core session configuration, turn construction, thread settings, resume/fork restoration, and subagent spawn plumbing. Subagents inherit reasoning effort and derive their own effective mode. - Retain the experimental app-server `multiAgentMode` fields for wire compatibility while marking them deprecated. Request values are accepted but ignored; compatibility response fields report `explicitRequestOnly`. - Display Ultra in the TUI using the order supplied by `model/list`. ## Validation - `just test -p codex-core ultra_reasoning_uses_max_for_requests` - `just test -p codex-tui model_reasoning_selection_popup`
This commit is contained in:
committed by
GitHub
Unverified
parent
fa036d39aa
commit
df1199fddb
@@ -94,9 +94,7 @@ pub struct ThreadStartParams {
|
||||
pub developer_instructions: Option<String>,
|
||||
#[ts(optional = nullable)]
|
||||
pub personality: Option<Personality>,
|
||||
/// Set the initial multi-agent mode for this thread. `none` leaves the
|
||||
/// multi-agent tools available without injecting mode instructions.
|
||||
/// Omitted defaults to `explicitRequestOnly`.
|
||||
/// @deprecated Ignored. Use Ultra reasoning effort for proactive multi-agent behavior.
|
||||
#[experimental("thread/start.multiAgentMode")]
|
||||
#[ts(optional = nullable)]
|
||||
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||
@@ -186,7 +184,7 @@ pub struct ThreadStartResponse {
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
/// Current multi-agent mode for this thread.
|
||||
/// @deprecated Always `explicitRequestOnly`. Use `reasoningEffort` for Ultra behavior.
|
||||
#[experimental("thread/start.multiAgentMode")]
|
||||
#[serde(default)]
|
||||
pub multi_agent_mode: MultiAgentMode,
|
||||
@@ -250,7 +248,7 @@ pub struct ThreadSettingsUpdateParams {
|
||||
#[experimental("thread/settings/update.collaborationMode")]
|
||||
#[ts(optional = nullable)]
|
||||
pub collaboration_mode: Option<CollaborationMode>,
|
||||
/// Select the multi-agent mode for subsequent turns.
|
||||
/// @deprecated Ignored. Use `effort: "ultra"` for proactive multi-agent behavior.
|
||||
#[experimental("thread/settings/update.multiAgentMode")]
|
||||
#[ts(optional = nullable)]
|
||||
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||
@@ -279,7 +277,7 @@ pub struct ThreadSettings {
|
||||
pub effort: Option<ReasoningEffort>,
|
||||
pub summary: Option<ReasoningSummary>,
|
||||
pub collaboration_mode: CollaborationMode,
|
||||
/// Current multi-agent mode for this thread.
|
||||
/// @deprecated Always `explicitRequestOnly`. Use `effort` for Ultra behavior.
|
||||
#[experimental("thread/settings.multiAgentMode")]
|
||||
#[serde(default)]
|
||||
pub multi_agent_mode: MultiAgentMode,
|
||||
@@ -419,7 +417,7 @@ pub struct ThreadResumeResponse {
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
/// Current multi-agent mode for this thread.
|
||||
/// @deprecated Always `explicitRequestOnly`. Use `reasoningEffort` for Ultra behavior.
|
||||
#[experimental("thread/resume.multiAgentMode")]
|
||||
#[serde(default)]
|
||||
pub multi_agent_mode: MultiAgentMode,
|
||||
@@ -578,7 +576,7 @@ pub struct ThreadForkResponse {
|
||||
#[serde(default)]
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
/// Current multi-agent mode for this thread.
|
||||
/// @deprecated Always `explicitRequestOnly`. Use `reasoningEffort` for Ultra behavior.
|
||||
#[experimental("thread/fork.multiAgentMode")]
|
||||
#[serde(default)]
|
||||
pub multi_agent_mode: MultiAgentMode,
|
||||
|
||||
@@ -151,9 +151,7 @@ pub struct TurnStartParams {
|
||||
#[ts(optional = nullable)]
|
||||
pub collaboration_mode: Option<CollaborationMode>,
|
||||
|
||||
/// 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.
|
||||
/// @deprecated Ignored. Use `effort: "ultra"` for proactive multi-agent behavior.
|
||||
#[experimental("turn/start.multiAgentMode")]
|
||||
#[ts(optional = nullable)]
|
||||
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||
|
||||
@@ -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 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 using environment-native absolute paths. 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/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`. Deprecated experimental `multiAgentMode` is ignored; use Ultra reasoning effort for proactive multi-agent behavior. 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 using environment-native absolute paths. 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`.
|
||||
- `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 mode. 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 deprecated experimental `multiAgentMode` field, and the corresponding thread setting, always report `explicitRequestOnly`; Ultra reasoning effort is the source of proactive multi-agent behavior.
|
||||
- `thread/list` — page through stored threads; supports cursor-based pagination and optional `modelProviders`, `sourceKinds`, `archived`, `cwd`, and `searchTerm` filters. Experimental clients can use `parentThreadId` for direct spawned children or `ancestorThreadId` for spawned descendants at any depth; the two filters are mutually exclusive. 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/items/list` — experimental; page through persisted thread items without resuming the thread. Pass `turnId` to restrict results to one turn, or omit it to page items across the thread. The active thread store must support item pagination.
|
||||
- `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 `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/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; deprecated `multiAgentMode` is ignored, while Ultra reasoning effort enables proactive multi-agent behavior; `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 `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.
|
||||
- `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". Deprecated experimental `multiAgentMode` is ignored; Ultra reasoning effort selects proactive behavior.
|
||||
- `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"`.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
|
||||
pub(super) const THREAD_UNLOADING_DELAY: Duration = Duration::from_secs(30 * 60);
|
||||
|
||||
@@ -639,7 +640,6 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
active_permission_profile,
|
||||
workspace_roots,
|
||||
reasoning_effort,
|
||||
multi_agent_mode,
|
||||
..
|
||||
} = config_snapshot;
|
||||
let instruction_sources = pending.instruction_sources;
|
||||
@@ -662,7 +662,7 @@ pub(super) async fn handle_pending_thread_resume_request(
|
||||
sandbox,
|
||||
active_permission_profile,
|
||||
reasoning_effort,
|
||||
multi_agent_mode,
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
initial_turns_page,
|
||||
};
|
||||
outgoing.send_response(request_id, response).await;
|
||||
|
||||
@@ -903,7 +903,7 @@ impl ThreadRequestProcessor {
|
||||
mock_experimental_field: _mock_experimental_field,
|
||||
experimental_raw_events,
|
||||
personality,
|
||||
multi_agent_mode,
|
||||
multi_agent_mode: _multi_agent_mode,
|
||||
ephemeral,
|
||||
session_start_source,
|
||||
thread_source,
|
||||
@@ -957,7 +957,6 @@ impl ThreadRequestProcessor {
|
||||
supports_openai_form_elicitation,
|
||||
config,
|
||||
typesafe_overrides,
|
||||
multi_agent_mode,
|
||||
dynamic_tools,
|
||||
selected_capability_roots.unwrap_or_default(),
|
||||
session_start_source,
|
||||
@@ -1032,7 +1031,6 @@ impl ThreadRequestProcessor {
|
||||
supports_openai_form_elicitation: bool,
|
||||
config_overrides: Option<HashMap<String, serde_json::Value>>,
|
||||
typesafe_overrides: ConfigOverrides,
|
||||
multi_agent_mode: Option<MultiAgentMode>,
|
||||
dynamic_tools: Option<Vec<DynamicToolSpec>>,
|
||||
selected_capability_roots: Vec<SelectedCapabilityRoot>,
|
||||
session_start_source: Option<codex_app_server_protocol::ThreadStartSource>,
|
||||
@@ -1156,7 +1154,6 @@ impl ThreadRequestProcessor {
|
||||
thread_source,
|
||||
dynamic_tools,
|
||||
metrics_service_name: service_name,
|
||||
multi_agent_mode,
|
||||
parent_trace: request_trace,
|
||||
environments,
|
||||
thread_extension_init,
|
||||
@@ -1262,7 +1259,7 @@ impl ThreadRequestProcessor {
|
||||
sandbox,
|
||||
active_permission_profile,
|
||||
reasoning_effort: config_snapshot.reasoning_effort,
|
||||
multi_agent_mode: config_snapshot.multi_agent_mode,
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
};
|
||||
let notif = thread_started_notification(thread);
|
||||
listener_task_context
|
||||
@@ -2856,7 +2853,7 @@ impl ThreadRequestProcessor {
|
||||
sandbox,
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
multi_agent_mode: config_snapshot.multi_agent_mode,
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
initial_turns_page,
|
||||
};
|
||||
|
||||
@@ -3576,7 +3573,7 @@ impl ThreadRequestProcessor {
|
||||
sandbox,
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
multi_agent_mode: config_snapshot.multi_agent_mode,
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
};
|
||||
|
||||
let notif = thread_started_notification(thread);
|
||||
|
||||
@@ -776,7 +776,6 @@ mod thread_processor_behavior_tests {
|
||||
developer_instructions: None,
|
||||
},
|
||||
},
|
||||
multi_agent_mode: Default::default(),
|
||||
session_source: SessionSource::Cli,
|
||||
forked_from_thread_id: None,
|
||||
parent_thread_id: None,
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::*;
|
||||
use chrono::DateTime;
|
||||
#[cfg(test)]
|
||||
use chrono::Utc;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn read_summary_from_rollout(
|
||||
@@ -205,7 +206,7 @@ pub(crate) fn thread_settings_from_config_snapshot(
|
||||
effort: config_snapshot.reasoning_effort.clone(),
|
||||
summary: config_snapshot.reasoning_summary,
|
||||
collaboration_mode: config_snapshot.collaboration_mode.clone(),
|
||||
multi_agent_mode: config_snapshot.multi_agent_mode,
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
personality: config_snapshot.personality,
|
||||
}
|
||||
}
|
||||
@@ -226,7 +227,6 @@ pub(crate) fn thread_settings_from_core_snapshot(
|
||||
reasoning_summary,
|
||||
personality,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
} = snapshot;
|
||||
let sandbox_policy = thread_response_sandbox_policy(&permission_profile, cwd.as_path());
|
||||
ThreadSettings {
|
||||
@@ -243,7 +243,7 @@ pub(crate) fn thread_settings_from_core_snapshot(
|
||||
effort: reasoning_effort,
|
||||
summary: reasoning_summary,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
personality,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::*;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::protocol::AdditionalContextEntry as CoreAdditionalContextEntry;
|
||||
@@ -118,7 +117,6 @@ struct ThreadSettingsBuildParams {
|
||||
effort: Option<ReasoningEffort>,
|
||||
summary: Option<ReasoningSummary>,
|
||||
collaboration_mode: Option<CollaborationMode>,
|
||||
multi_agent_mode: Option<MultiAgentMode>,
|
||||
personality: Option<Personality>,
|
||||
}
|
||||
|
||||
@@ -515,7 +513,6 @@ impl TurnRequestProcessor {
|
||||
effort: params.effort,
|
||||
summary: params.summary,
|
||||
collaboration_mode: params.collaboration_mode,
|
||||
multi_agent_mode: params.multi_agent_mode,
|
||||
personality: params.personality,
|
||||
},
|
||||
)
|
||||
@@ -622,7 +619,6 @@ impl TurnRequestProcessor {
|
||||
effort,
|
||||
summary,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
personality,
|
||||
} = params;
|
||||
|
||||
@@ -656,7 +652,6 @@ impl TurnRequestProcessor {
|
||||
|| effort.is_some()
|
||||
|| summary.is_some()
|
||||
|| collaboration_mode.is_some()
|
||||
|| multi_agent_mode.is_some()
|
||||
|| personality.is_some();
|
||||
|
||||
let runtime_workspace_roots =
|
||||
@@ -733,7 +728,6 @@ impl TurnRequestProcessor {
|
||||
summary,
|
||||
service_tier: service_tier.clone(),
|
||||
collaboration_mode: collaboration_mode.clone(),
|
||||
multi_agent_mode,
|
||||
personality,
|
||||
})
|
||||
.await
|
||||
@@ -757,7 +751,6 @@ impl TurnRequestProcessor {
|
||||
summary,
|
||||
service_tier,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
personality,
|
||||
})
|
||||
}
|
||||
@@ -788,7 +781,6 @@ impl TurnRequestProcessor {
|
||||
effort: params.effort,
|
||||
summary: params.summary,
|
||||
collaboration_mode: params.collaboration_mode,
|
||||
multi_agent_mode: params.multi_agent_mode,
|
||||
personality: params.personality,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -10,6 +10,8 @@ use codex_core::CodexThread;
|
||||
use codex_core::ThreadConfigSnapshot;
|
||||
use codex_file_watcher::WatchRegistration;
|
||||
use codex_protocol::ThreadId;
|
||||
#[cfg(test)]
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
use codex_rollout::state_db::StateDbHandle;
|
||||
@@ -241,7 +243,7 @@ mod tests {
|
||||
developer_instructions: None,
|
||||
},
|
||||
},
|
||||
multi_agent_mode: Default::default(),
|
||||
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
|
||||
personality: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_core::test_support::all_model_presets;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||
use core_test_support::responses;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value;
|
||||
@@ -97,109 +94,6 @@ async fn thread_settings_update_emits_notification_and_updates_future_turns() ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_settings_update_multi_agent_mode_applies_to_future_turns() -> Result<()> {
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_mock = responses::mount_sse_sequence(
|
||||
&server,
|
||||
(1..=2)
|
||||
.map(|index| {
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created(&format!("resp-{index}")),
|
||||
responses::ev_assistant_message(&format!("msg-{index}"), "done"),
|
||||
responses::ev_completed(&format!("resp-{index}")),
|
||||
])
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.await;
|
||||
let codex_home = TempDir::new()?;
|
||||
write_mock_responses_config_toml(
|
||||
codex_home.path(),
|
||||
&server.uri(),
|
||||
&BTreeMap::from([(Feature::MultiAgentV2, true)]),
|
||||
/*auto_compact_limit*/ 200_000,
|
||||
/*requires_openai_auth*/ None,
|
||||
"mock_provider",
|
||||
"compact",
|
||||
)?;
|
||||
|
||||
let mut mcp = TestAppServer::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
|
||||
let thread = start_thread(&mut mcp).await?.thread;
|
||||
|
||||
start_text_turn(&mut mcp, thread.id.clone()).await?;
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(response_mock.requests().len(), 1);
|
||||
|
||||
send_thread_settings_update(
|
||||
&mut mcp,
|
||||
ThreadSettingsUpdateParams {
|
||||
thread_id: thread.id.clone(),
|
||||
multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
response_mock.requests().len(),
|
||||
1,
|
||||
"settings-only update should not start a model request"
|
||||
);
|
||||
|
||||
let updated = read_thread_settings_updated(&mut mcp).await?;
|
||||
assert_eq!(updated.thread_id, thread.id);
|
||||
assert_eq!(
|
||||
updated.thread_settings.multi_agent_mode,
|
||||
MultiAgentMode::Proactive
|
||||
);
|
||||
|
||||
start_text_turn(&mut mcp, thread.id).await?;
|
||||
timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
let requests = response_mock.requests();
|
||||
let first_developer_texts = requests[0].message_input_texts("developer");
|
||||
let second_developer_texts = requests[1].message_input_texts("developer");
|
||||
assert_eq!(
|
||||
first_developer_texts
|
||||
.iter()
|
||||
.filter(|text| text.contains(MULTI_AGENT_MODE_OPEN_TAG))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
second_developer_texts
|
||||
.iter()
|
||||
.filter(|text| text.contains(MULTI_AGENT_MODE_OPEN_TAG))
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
second_developer_texts
|
||||
.iter()
|
||||
.filter(|text| text.contains("Proactive multi-agent delegation is active."))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
second_developer_texts
|
||||
.iter()
|
||||
.filter(|text| text
|
||||
.contains("Do not spawn sub-agents unless the user explicitly asks for sub-agents"))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_settings_update_cwd_retargets_default_environment() -> Result<()> {
|
||||
let server = responses::start_mock_server().await;
|
||||
|
||||
@@ -1754,7 +1754,7 @@ async fn turn_start_accepts_personality_override_v2() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_start_accepts_multi_agent_mode_v2() -> Result<()> {
|
||||
async fn turn_start_ignores_deprecated_multi_agent_mode() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
@@ -1817,18 +1817,19 @@ async fn turn_start_accepts_multi_agent_mode_v2() -> Result<()> {
|
||||
.single_request()
|
||||
.message_input_texts("developer");
|
||||
assert!(developer_texts.iter().any(|text| {
|
||||
text.contains("<multi_agent_mode>")
|
||||
&& text.contains("Proactive multi-agent delegation is active.")
|
||||
}));
|
||||
assert!(!developer_texts.iter().any(|text| {
|
||||
text.contains("Do not spawn sub-agents unless the user explicitly asks for sub-agents")
|
||||
}));
|
||||
assert!(
|
||||
!developer_texts
|
||||
.iter()
|
||||
.any(|text| text.contains("Proactive multi-agent delegation is active."))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_multi_agent_mode_initializes_first_turn() -> Result<()> {
|
||||
async fn thread_start_ignores_deprecated_multi_agent_mode() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
@@ -1867,7 +1868,7 @@ async fn thread_start_multi_agent_mode_initializes_first_turn() -> Result<()> {
|
||||
multi_agent_mode,
|
||||
..
|
||||
} = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||
assert_eq!(multi_agent_mode, MultiAgentMode::Proactive);
|
||||
assert_eq!(multi_agent_mode, MultiAgentMode::ExplicitRequestOnly);
|
||||
|
||||
let turn_req = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
@@ -1895,71 +1896,20 @@ async fn thread_start_multi_agent_mode_initializes_first_turn() -> Result<()> {
|
||||
let developer_texts = response_mock
|
||||
.single_request()
|
||||
.message_input_texts("developer");
|
||||
assert!(developer_texts.iter().any(|text| {
|
||||
text.contains(MULTI_AGENT_MODE_OPEN_TAG)
|
||||
&& text
|
||||
.contains("Do not spawn sub-agents unless the user explicitly asks for sub-agents")
|
||||
}));
|
||||
assert!(
|
||||
developer_texts.iter().any(|text| {
|
||||
text.contains(MULTI_AGENT_MODE_OPEN_TAG)
|
||||
&& text.contains("Proactive multi-agent delegation is active.")
|
||||
}),
|
||||
"expected proactive multi-agent mode instructions in developer input, got {developer_texts:?}"
|
||||
!developer_texts
|
||||
.iter()
|
||||
.any(|text| text.contains("Proactive multi-agent delegation is active."))
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_reports_multi_agent_mode() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let cases = [
|
||||
(
|
||||
BTreeMap::from([(Feature::MultiAgentV2, true)]),
|
||||
Some(MultiAgentMode::Proactive),
|
||||
MultiAgentMode::Proactive,
|
||||
),
|
||||
(
|
||||
BTreeMap::from([(Feature::MultiAgentV2, true)]),
|
||||
Some(MultiAgentMode::None),
|
||||
MultiAgentMode::None,
|
||||
),
|
||||
(
|
||||
BTreeMap::new(),
|
||||
Some(MultiAgentMode::Proactive),
|
||||
MultiAgentMode::Proactive,
|
||||
),
|
||||
(
|
||||
BTreeMap::from([(Feature::MultiAgentV2, true)]),
|
||||
None,
|
||||
MultiAgentMode::ExplicitRequestOnly,
|
||||
),
|
||||
];
|
||||
|
||||
for (features, requested_multi_agent_mode, expected_multi_agent_mode) in cases {
|
||||
let server = responses::start_mock_server().await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri(), "never", &features)?;
|
||||
|
||||
let mut mcp = TestAppServer::new_with_auto_env(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
let thread_req = mcp
|
||||
.send_thread_start_request_with_auto_env(ThreadStartParams {
|
||||
model: Some("mock-model".to_string()),
|
||||
multi_agent_mode: requested_multi_agent_mode,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let thread_resp: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(thread_req)),
|
||||
)
|
||||
.await??;
|
||||
let response = to_response::<ThreadStartResponse>(thread_resp)?;
|
||||
|
||||
assert_eq!(response.multi_agent_mode, expected_multi_agent_mode);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_start_change_personality_mid_thread_v2() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::thread_rollout_truncation::truncate_rollout_to_last_n_fork_turns;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::SessionId;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::models::ContentItem;
|
||||
@@ -68,7 +67,6 @@ pub(crate) struct SpawnAgentOptions {
|
||||
pub(crate) fork_mode: Option<SpawnAgentForkMode>,
|
||||
pub(crate) parent_thread_id: Option<ThreadId>,
|
||||
pub(crate) environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
pub(crate) initial_multi_agent_mode: Option<MultiAgentMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -142,7 +142,6 @@ async fn spawn_v2_subagent(
|
||||
/*forked_from_thread_id*/ None,
|
||||
Some(ThreadSource::Subagent),
|
||||
/*metrics_service_name*/ None,
|
||||
/*initial_multi_agent_mode*/ None,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
/*environments*/ None,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use super::residency::is_v2_resident_session_source;
|
||||
use super::*;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
|
||||
const AGENT_NAMES: &str = include_str!("../agent_names.txt");
|
||||
|
||||
struct SpawnAgentThreadInheritance {
|
||||
environments: Option<TurnEnvironmentSnapshot>,
|
||||
exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
|
||||
inherited_multi_agent_mode: Option<MultiAgentMode>,
|
||||
}
|
||||
|
||||
fn default_agent_nickname_list() -> Vec<&'static str> {
|
||||
@@ -241,7 +239,6 @@ impl AgentControl {
|
||||
exec_policy: self
|
||||
.inherited_exec_policy_for_source(&state, session_source.as_ref(), &config)
|
||||
.await,
|
||||
inherited_multi_agent_mode: options.initial_multi_agent_mode,
|
||||
};
|
||||
let (session_source, mut agent_metadata) = match session_source {
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
@@ -288,7 +285,6 @@ impl AgentControl {
|
||||
/*forked_from_thread_id*/ None,
|
||||
/*thread_source*/ Some(ThreadSource::Subagent),
|
||||
/*metrics_service_name*/ None,
|
||||
inheritance.inherited_multi_agent_mode,
|
||||
inheritance.environments,
|
||||
inheritance.exec_policy,
|
||||
options.environments.clone(),
|
||||
@@ -394,7 +390,6 @@ impl AgentControl {
|
||||
let SpawnAgentThreadInheritance {
|
||||
environments: inherited_environments,
|
||||
exec_policy: inherited_exec_policy,
|
||||
inherited_multi_agent_mode,
|
||||
} = inheritance;
|
||||
if options.fork_parent_spawn_call_id.is_none() {
|
||||
return Err(CodexErr::Fatal(
|
||||
@@ -519,7 +514,6 @@ impl AgentControl {
|
||||
/*thread_source*/ Some(ThreadSource::Subagent),
|
||||
/*parent_thread_id*/ Some(parent_thread_id),
|
||||
/*forked_from_thread_id*/ Some(parent_thread_id),
|
||||
inherited_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
options.environments.clone(),
|
||||
|
||||
@@ -18,7 +18,6 @@ use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::config_types::ModeKind;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::MessagePhase;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
@@ -889,42 +888,6 @@ async fn ephemeral_spawn_does_not_persist_agent_graph_edge() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_thread_subagent_uses_supplied_initial_multi_agent_mode_without_history() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let (parent_thread_id, _parent_thread) = harness.start_thread().await;
|
||||
|
||||
let child_thread_id = harness
|
||||
.control
|
||||
.spawn_agent_with_metadata(
|
||||
harness.config.clone(),
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
SpawnAgentOptions {
|
||||
initial_multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("spawn child without parent history")
|
||||
.thread_id;
|
||||
let child_snapshot = harness
|
||||
.manager
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should be registered")
|
||||
.config_snapshot()
|
||||
.await;
|
||||
|
||||
assert_eq!(child_snapshot.multi_agent_mode, MultiAgentMode::Proactive);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
@@ -947,15 +910,6 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
.expect("start parent thread");
|
||||
let parent_thread_id = new_thread.thread_id;
|
||||
let parent_thread = new_thread.thread;
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.update_settings(crate::session::SessionSettingsUpdate {
|
||||
multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("update parent multi-agent mode");
|
||||
parent_thread
|
||||
.inject_user_message_without_turn("parent seed context".to_string())
|
||||
.await;
|
||||
@@ -1050,7 +1004,6 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
initial_multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -1063,10 +1016,6 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should be registered");
|
||||
assert_eq!(
|
||||
child_thread.config_snapshot().await.multi_agent_mode,
|
||||
MultiAgentMode::Proactive
|
||||
);
|
||||
assert_ne!(child_thread_id, parent_thread_id);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
let mut expected_final_answer =
|
||||
@@ -1359,15 +1308,6 @@ async fn spawn_agent_fork_flushes_parent_rollout_before_loading_history() {
|
||||
async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
let (parent_thread_id, parent_thread) = harness.start_thread().await;
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.update_settings(crate::session::SessionSettingsUpdate {
|
||||
multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("update parent multi-agent mode");
|
||||
|
||||
parent_thread
|
||||
.inject_user_message_without_turn("old parent context".to_string())
|
||||
@@ -1452,7 +1392,6 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
|
||||
initial_multi_agent_mode: Some(MultiAgentMode::Proactive),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -1465,10 +1404,6 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should be registered");
|
||||
assert_eq!(
|
||||
child_thread.config_snapshot().await.multi_agent_mode,
|
||||
MultiAgentMode::Proactive
|
||||
);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
|
||||
assert!(
|
||||
@@ -2283,7 +2218,6 @@ async fn spawn_thread_subagents_persist_parent_originator_across_new_and_truncat
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: Some("codex_work_desktop".to_string()),
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: ExtensionDataInit::default(),
|
||||
@@ -2393,7 +2327,7 @@ async fn spawn_thread_subagent_uses_role_specific_nickname_candidates() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_thread_subagent_restores_stored_metadata_and_effective_multi_agent_mode() {
|
||||
async fn resume_thread_subagent_restores_stored_metadata() {
|
||||
let (home, config) = test_config().await;
|
||||
let thread_store = Arc::new(InMemoryThreadStore::default());
|
||||
let manager = ThreadManager::new(
|
||||
@@ -2418,7 +2352,7 @@ async fn resume_thread_subagent_restores_stored_metadata_and_effective_multi_age
|
||||
manager,
|
||||
control,
|
||||
};
|
||||
let (parent_thread_id, parent_thread) = harness.start_thread().await;
|
||||
let (parent_thread_id, _parent_thread) = harness.start_thread().await;
|
||||
let agent_path = AgentPath::from_string("/root/explorer".to_string())
|
||||
.expect("test agent path should be valid");
|
||||
|
||||
@@ -2443,18 +2377,6 @@ async fn resume_thread_subagent_restores_stored_metadata_and_effective_multi_age
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should exist");
|
||||
let mut child_turn_context = child_thread
|
||||
.codex
|
||||
.session
|
||||
.new_default_turn()
|
||||
.await
|
||||
.to_turn_context_item();
|
||||
child_turn_context.multi_agent_mode = Some(MultiAgentMode::Proactive);
|
||||
child_thread
|
||||
.codex
|
||||
.session
|
||||
.persist_rollout_items(&[RolloutItem::TurnContext(child_turn_context)])
|
||||
.await;
|
||||
child_thread
|
||||
.codex
|
||||
.session
|
||||
@@ -2465,16 +2387,7 @@ async fn resume_thread_subagent_restores_stored_metadata_and_effective_multi_age
|
||||
.session
|
||||
.flush_rollout()
|
||||
.await
|
||||
.expect("flush child effective multi-agent mode");
|
||||
parent_thread
|
||||
.codex
|
||||
.session
|
||||
.update_settings(crate::session::SessionSettingsUpdate {
|
||||
multi_agent_mode: Some(MultiAgentMode::ExplicitRequestOnly),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.expect("change parent multi-agent mode before child resume");
|
||||
.expect("flush child rollout");
|
||||
let mut status_rx = harness
|
||||
.control
|
||||
.subscribe_status(child_thread_id)
|
||||
@@ -2567,7 +2480,6 @@ 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, MultiAgentMode::Proactive);
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
|
||||
@@ -163,6 +163,13 @@ pub(crate) struct CompactConversationRequestSettings {
|
||||
pub(crate) service_tier: Option<String>,
|
||||
}
|
||||
|
||||
fn reasoning_effort_for_request(effort: ReasoningEffortConfig) -> ReasoningEffortConfig {
|
||||
match effort {
|
||||
ReasoningEffortConfig::Ultra => ReasoningEffortConfig::Custom("max".to_string()),
|
||||
effort => effort,
|
||||
}
|
||||
}
|
||||
|
||||
fn session_telemetry_for_request(
|
||||
session_telemetry: &SessionTelemetry,
|
||||
request: &ResponsesApiRequest,
|
||||
@@ -665,11 +672,13 @@ impl ModelClient {
|
||||
let payload = ApiMemorySummarizeInput {
|
||||
model: model_info.slug.clone(),
|
||||
raw_memories,
|
||||
reasoning: effort.map(|effort| Reasoning {
|
||||
effort: Some(effort),
|
||||
summary: None,
|
||||
context: None,
|
||||
}),
|
||||
reasoning: effort
|
||||
.map(reasoning_effort_for_request)
|
||||
.map(|effort| Reasoning {
|
||||
effort: Some(effort),
|
||||
summary: None,
|
||||
context: None,
|
||||
}),
|
||||
};
|
||||
|
||||
client
|
||||
@@ -768,7 +777,9 @@ impl ModelClient {
|
||||
) -> Option<Reasoning> {
|
||||
if model_info.supports_reasoning_summaries {
|
||||
Some(Reasoning {
|
||||
effort: effort.or_else(|| model_info.default_reasoning_level.clone()),
|
||||
effort: effort
|
||||
.or_else(|| model_info.default_reasoning_level.clone())
|
||||
.map(reasoning_effort_for_request),
|
||||
summary: if summary == ReasoningSummaryConfig::None {
|
||||
None
|
||||
} else {
|
||||
|
||||
@@ -31,6 +31,7 @@ use codex_protocol::auth::AuthMode;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::InternalSessionSource;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
@@ -154,6 +155,20 @@ fn test_session_telemetry() -> SessionTelemetry {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ultra_reasoning_uses_max_for_requests() {
|
||||
assert_eq!(
|
||||
(
|
||||
super::reasoning_effort_for_request(ReasoningEffort::Ultra),
|
||||
super::reasoning_effort_for_request(ReasoningEffort::High),
|
||||
),
|
||||
(
|
||||
ReasoningEffort::Custom("max".to_string()),
|
||||
ReasoningEffort::High,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TagCollectorVisitor {
|
||||
tags: BTreeMap<String, String>,
|
||||
|
||||
@@ -123,7 +123,6 @@ pub(crate) async fn run_codex_thread_interactive(
|
||||
attestation_provider: parent_session.services.attestation_provider.clone(),
|
||||
external_time_provider: Some(Arc::clone(&parent_session.services.time_provider)),
|
||||
inherited_multi_agent_version: Some(MultiAgentVersion::Disabled),
|
||||
initial_multi_agent_mode: None,
|
||||
}))
|
||||
.or_cancel(&cancel_token)
|
||||
.await??;
|
||||
|
||||
@@ -8,7 +8,6 @@ use codex_otel::SessionTelemetry;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
@@ -72,7 +71,6 @@ pub struct ThreadConfigSnapshot {
|
||||
pub reasoning_summary: Option<ReasoningSummary>,
|
||||
pub personality: Option<Personality>,
|
||||
pub collaboration_mode: CollaborationMode,
|
||||
pub multi_agent_mode: MultiAgentMode,
|
||||
pub session_source: SessionSource,
|
||||
pub forked_from_thread_id: Option<ThreadId>,
|
||||
pub parent_thread_id: Option<ThreadId>,
|
||||
@@ -154,7 +152,6 @@ pub struct CodexThreadSettingsOverrides {
|
||||
pub summary: Option<ReasoningSummary>,
|
||||
pub service_tier: Option<Option<String>>,
|
||||
pub collaboration_mode: Option<CollaborationMode>,
|
||||
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||
pub personality: Option<Personality>,
|
||||
}
|
||||
|
||||
@@ -375,7 +372,6 @@ impl CodexThread {
|
||||
summary,
|
||||
service_tier,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
personality,
|
||||
} = overrides;
|
||||
let collaboration_mode = if let Some(collaboration_mode) = collaboration_mode {
|
||||
@@ -399,7 +395,6 @@ impl CodexThread {
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
multi_agent_mode,
|
||||
reasoning_summary: summary,
|
||||
service_tier,
|
||||
personality,
|
||||
|
||||
@@ -78,11 +78,7 @@ fn build_multi_agent_mode_update_item(
|
||||
previous: Option<&TurnContextItem>,
|
||||
next: &TurnContext,
|
||||
) -> Option<String> {
|
||||
let effective_multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode(
|
||||
next.multi_agent_version,
|
||||
&next.session_source,
|
||||
next.multi_agent_mode,
|
||||
);
|
||||
let effective_multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode(next);
|
||||
let previous = previous?;
|
||||
if previous.multi_agent_mode == effective_multi_agent_mode {
|
||||
return None;
|
||||
|
||||
@@ -124,7 +124,6 @@ async fn thread_settings_update(
|
||||
summary,
|
||||
service_tier,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
personality,
|
||||
} = thread_settings;
|
||||
let collaboration_mode = match collaboration_mode {
|
||||
@@ -150,7 +149,6 @@ async fn thread_settings_update(
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
multi_agent_mode,
|
||||
reasoning_summary: summary,
|
||||
service_tier,
|
||||
personality,
|
||||
@@ -178,7 +176,6 @@ async fn thread_settings_applied_event(sess: &Session) -> EventMsg {
|
||||
reasoning_summary: snapshot.reasoning_summary,
|
||||
personality: snapshot.personality,
|
||||
collaboration_mode: snapshot.collaboration_mode,
|
||||
multi_agent_mode: snapshot.multi_agent_mode,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -453,7 +453,6 @@ pub(crate) struct CodexSpawnArgs {
|
||||
pub(crate) attestation_provider: Option<Arc<dyn AttestationProvider>>,
|
||||
pub(crate) external_time_provider: Option<Arc<dyn TimeProvider>>,
|
||||
pub(crate) inherited_multi_agent_version: Option<MultiAgentVersion>,
|
||||
pub(crate) initial_multi_agent_mode: Option<MultiAgentMode>,
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_multi_agent_version(
|
||||
@@ -539,7 +538,6 @@ impl Codex {
|
||||
attestation_provider,
|
||||
external_time_provider,
|
||||
inherited_multi_agent_version,
|
||||
initial_multi_agent_mode,
|
||||
} = args;
|
||||
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
|
||||
let (tx_event, rx_event) = async_channel::unbounded();
|
||||
@@ -594,7 +592,6 @@ 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.unwrap_or_default();
|
||||
config
|
||||
.validate_multi_agent_v2_config()
|
||||
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
|
||||
@@ -628,7 +625,6 @@ impl Codex {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
service_tier,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
@@ -3402,11 +3398,7 @@ impl Session {
|
||||
{
|
||||
items.push(usage_hint_message);
|
||||
}
|
||||
match multi_agents::effective_multi_agent_mode(
|
||||
turn_context.multi_agent_version,
|
||||
&session_source,
|
||||
turn_context.multi_agent_mode,
|
||||
) {
|
||||
match multi_agents::effective_multi_agent_mode(turn_context) {
|
||||
Some(
|
||||
multi_agent_mode
|
||||
@ (MultiAgentMode::ExplicitRequestOnly | MultiAgentMode::Proactive),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::config::MultiAgentV2Config;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
@@ -35,16 +36,17 @@ fn configured_usage_hint_text_for_source<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn effective_multi_agent_mode(
|
||||
multi_agent_version: MultiAgentVersion,
|
||||
session_source: &SessionSource,
|
||||
multi_agent_mode: MultiAgentMode,
|
||||
) -> Option<MultiAgentMode> {
|
||||
if multi_agent_version != MultiAgentVersion::V2 {
|
||||
pub(crate) fn effective_multi_agent_mode(turn_context: &TurnContext) -> Option<MultiAgentMode> {
|
||||
if turn_context.multi_agent_version != MultiAgentVersion::V2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
match session_source {
|
||||
let multi_agent_mode = match turn_context.effective_reasoning_effort() {
|
||||
Some(ReasoningEffort::Ultra) => MultiAgentMode::Proactive,
|
||||
_ => MultiAgentMode::ExplicitRequestOnly,
|
||||
};
|
||||
|
||||
match &turn_context.session_source {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { .. })
|
||||
| SessionSource::Cli
|
||||
| SessionSource::VSCode
|
||||
|
||||
@@ -128,7 +128,6 @@ pub(super) async fn spawn_review_thread(
|
||||
developer_instructions: None,
|
||||
user_instructions: None,
|
||||
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
|
||||
multi_agent_mode: parent_turn_context.multi_agent_mode,
|
||||
multi_agent_version: MultiAgentVersion::Disabled,
|
||||
personality: parent_turn_context.personality,
|
||||
approval_policy: parent_turn_context.approval_policy.clone(),
|
||||
|
||||
@@ -52,7 +52,6 @@ pub(crate) struct SessionConfiguration {
|
||||
pub(super) provider: ModelProviderInfo,
|
||||
|
||||
pub(super) collaboration_mode: CollaborationMode,
|
||||
pub(super) multi_agent_mode: MultiAgentMode,
|
||||
pub(super) model_reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
pub(super) service_tier: Option<String>,
|
||||
|
||||
@@ -195,7 +194,6 @@ impl SessionConfiguration {
|
||||
reasoning_summary: self.model_reasoning_summary,
|
||||
personality: self.personality,
|
||||
collaboration_mode: self.collaboration_mode.clone(),
|
||||
multi_agent_mode: self.multi_agent_mode,
|
||||
session_source: self.session_source.clone(),
|
||||
forked_from_thread_id: self.forked_from_thread_id,
|
||||
parent_thread_id: self.parent_thread_id,
|
||||
@@ -233,9 +231,6 @@ impl SessionConfiguration {
|
||||
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
|
||||
next_configuration.collaboration_mode = collaboration_mode;
|
||||
}
|
||||
if let Some(multi_agent_mode) = updates.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);
|
||||
}
|
||||
@@ -430,7 +425,6 @@ pub(crate) struct SessionSettingsUpdate {
|
||||
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
pub(crate) collaboration_mode: Option<CollaborationMode>,
|
||||
pub(crate) multi_agent_mode: Option<MultiAgentMode>,
|
||||
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
pub(crate) service_tier: Option<Option<String>>,
|
||||
pub(crate) final_output_json_schema: Option<Option<Value>>,
|
||||
|
||||
@@ -3651,7 +3651,6 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -3759,7 +3758,6 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -4291,7 +4289,6 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -5163,7 +5160,6 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -5293,7 +5289,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -5541,7 +5536,6 @@ async fn make_session_with_config_and_rx(
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -5649,7 +5643,6 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
@@ -7374,7 +7367,6 @@ where
|
||||
let session_configuration = SessionConfiguration {
|
||||
provider: config.model_provider.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: Default::default(),
|
||||
model_reasoning_summary: config.model_reasoning_summary,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
loaded_agents_md: None,
|
||||
|
||||
@@ -750,7 +750,6 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
|
||||
attestation_provider: None,
|
||||
external_time_provider: None,
|
||||
inherited_multi_agent_version: None,
|
||||
initial_multi_agent_mode: None,
|
||||
})
|
||||
.await
|
||||
.expect("spawn guardian subagent");
|
||||
|
||||
@@ -126,7 +126,6 @@ pub struct TurnContext {
|
||||
pub(crate) developer_instructions: Option<String>,
|
||||
pub(crate) user_instructions: Option<String>,
|
||||
pub(crate) collaboration_mode: CollaborationMode,
|
||||
pub(crate) multi_agent_mode: MultiAgentMode,
|
||||
pub(crate) multi_agent_version: MultiAgentVersion,
|
||||
pub(crate) personality: Option<Personality>,
|
||||
pub(crate) approval_policy: Constrained<AskForApproval>,
|
||||
@@ -276,7 +275,6 @@ impl TurnContext {
|
||||
developer_instructions: self.developer_instructions.clone(),
|
||||
user_instructions: self.user_instructions.clone(),
|
||||
collaboration_mode,
|
||||
multi_agent_mode: self.multi_agent_mode,
|
||||
multi_agent_version: self.multi_agent_version,
|
||||
personality: self.personality,
|
||||
approval_policy: self.approval_policy.clone(),
|
||||
@@ -375,11 +373,7 @@ impl TurnContext {
|
||||
personality: self.personality,
|
||||
collaboration_mode: Some(self.collaboration_mode.clone()),
|
||||
multi_agent_version: Some(self.multi_agent_version),
|
||||
multi_agent_mode: super::multi_agents::effective_multi_agent_mode(
|
||||
self.multi_agent_version,
|
||||
&self.session_source,
|
||||
self.multi_agent_mode,
|
||||
),
|
||||
multi_agent_mode: super::multi_agents::effective_multi_agent_mode(self),
|
||||
realtime_active: Some(self.realtime_active),
|
||||
effort: self.reasoning_effort.clone(),
|
||||
summary: ReasoningSummaryConfig::Auto,
|
||||
@@ -563,7 +557,6 @@ impl Session {
|
||||
.as_ref()
|
||||
.map(LoadedAgentsMd::render),
|
||||
collaboration_mode: session_configuration.collaboration_mode.clone(),
|
||||
multi_agent_mode: session_configuration.multi_agent_mode,
|
||||
multi_agent_version,
|
||||
personality: session_configuration.personality,
|
||||
approval_policy: session_configuration.approval_policy.clone(),
|
||||
|
||||
@@ -40,7 +40,6 @@ use codex_models_manager::manager::RefreshStrategy;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::CollaborationModeMask;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::openai_models::ModelPreset;
|
||||
@@ -188,7 +187,6 @@ pub struct StartThreadOptions {
|
||||
pub thread_source: Option<ThreadSource>,
|
||||
pub dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
|
||||
pub metrics_service_name: Option<String>,
|
||||
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||
pub parent_trace: Option<W3cTraceContext>,
|
||||
pub environments: Vec<TurnEnvironmentSelection>,
|
||||
pub thread_extension_init: ExtensionDataInit,
|
||||
@@ -639,7 +637,6 @@ impl ThreadManager {
|
||||
thread_source: None,
|
||||
dynamic_tools,
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments,
|
||||
thread_extension_init: ExtensionDataInit::default(),
|
||||
@@ -679,7 +676,6 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
options.dynamic_tools,
|
||||
options.metrics_service_name,
|
||||
options.multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
options.parent_trace,
|
||||
@@ -764,7 +760,6 @@ impl ThreadManager {
|
||||
let (session_source, thread_source) = initial_history
|
||||
.get_resumed_session_sources()
|
||||
.unwrap_or_else(|| (self.state.session_source.clone(), None));
|
||||
let initial_multi_agent_mode = initial_history.get_latest_effective_multi_agent_mode();
|
||||
Box::pin(self.state.spawn_thread_with_source(
|
||||
config,
|
||||
initial_history,
|
||||
@@ -776,7 +771,6 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
parent_trace,
|
||||
@@ -809,7 +803,6 @@ impl ThreadManager {
|
||||
/*thread_source*/ None,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
/*initial_multi_agent_mode*/ None,
|
||||
/*parent_trace*/ None,
|
||||
environments,
|
||||
/*thread_extension_init*/ ExtensionDataInit::default(),
|
||||
@@ -836,7 +829,6 @@ impl ThreadManager {
|
||||
let (session_source, thread_source) = initial_history
|
||||
.get_resumed_session_sources()
|
||||
.unwrap_or_else(|| (self.state.session_source.clone(), None));
|
||||
let initial_multi_agent_mode = initial_history.get_latest_effective_multi_agent_mode();
|
||||
Box::pin(self.state.spawn_thread_with_source(
|
||||
config,
|
||||
initial_history,
|
||||
@@ -848,7 +840,6 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1004,13 +995,6 @@ impl ThreadManager {
|
||||
InitialHistory::Forked(_) => history.forked_from_id(),
|
||||
InitialHistory::New | InitialHistory::Cleared => None,
|
||||
};
|
||||
let initial_multi_agent_mode = match source_thread_id {
|
||||
Some(thread_id) => match self.get_thread(thread_id).await {
|
||||
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(),
|
||||
};
|
||||
let multi_agent_version = self
|
||||
.state
|
||||
.effective_multi_agent_version_for_spawn(
|
||||
@@ -1039,7 +1023,6 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
parent_trace,
|
||||
environments,
|
||||
/*thread_extension_init*/ ExtensionDataInit::default(),
|
||||
@@ -1322,7 +1305,6 @@ impl ThreadManagerState {
|
||||
/*forked_from_thread_id*/ None,
|
||||
/*thread_source*/ None,
|
||||
/*metrics_service_name*/ None,
|
||||
/*initial_multi_agent_mode*/ None,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
/*environments*/ None,
|
||||
@@ -1340,7 +1322,6 @@ impl ThreadManagerState {
|
||||
forked_from_thread_id: Option<ThreadId>,
|
||||
thread_source: Option<ThreadSource>,
|
||||
metrics_service_name: Option<String>,
|
||||
initial_multi_agent_mode: Option<MultiAgentMode>,
|
||||
inherited_environments: Option<TurnEnvironmentSnapshot>,
|
||||
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
|
||||
environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
@@ -1359,7 +1340,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
metrics_service_name,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1387,7 +1367,6 @@ impl ThreadManagerState {
|
||||
let environments =
|
||||
default_thread_environment_selections(self.environment_manager.as_ref(), &config.cwd);
|
||||
let thread_source = initial_history.get_resumed_thread_source();
|
||||
let initial_multi_agent_mode = initial_history.get_latest_effective_multi_agent_mode();
|
||||
Box::pin(self.spawn_thread_with_source(
|
||||
config,
|
||||
initial_history,
|
||||
@@ -1399,7 +1378,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1421,7 +1399,6 @@ impl ThreadManagerState {
|
||||
thread_source: Option<ThreadSource>,
|
||||
parent_thread_id: Option<ThreadId>,
|
||||
forked_from_thread_id: Option<ThreadId>,
|
||||
initial_multi_agent_mode: Option<MultiAgentMode>,
|
||||
inherited_environments: Option<TurnEnvironmentSnapshot>,
|
||||
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
|
||||
environments: Option<Vec<TurnEnvironmentSelection>>,
|
||||
@@ -1440,7 +1417,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1465,7 +1441,6 @@ impl ThreadManagerState {
|
||||
thread_source: Option<ThreadSource>,
|
||||
dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
|
||||
metrics_service_name: Option<String>,
|
||||
initial_multi_agent_mode: Option<MultiAgentMode>,
|
||||
parent_trace: Option<W3cTraceContext>,
|
||||
environments: Vec<TurnEnvironmentSelection>,
|
||||
thread_extension_init: ExtensionDataInit,
|
||||
@@ -1483,7 +1458,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
dynamic_tools,
|
||||
metrics_service_name,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
parent_trace,
|
||||
@@ -1508,7 +1482,6 @@ impl ThreadManagerState {
|
||||
thread_source: Option<ThreadSource>,
|
||||
dynamic_tools: Vec<codex_protocol::dynamic_tools::DynamicToolSpec>,
|
||||
metrics_service_name: Option<String>,
|
||||
initial_multi_agent_mode: Option<MultiAgentMode>,
|
||||
inherited_environments: Option<TurnEnvironmentSnapshot>,
|
||||
inherited_exec_policy: Option<Arc<crate::exec_policy::ExecPolicyManager>>,
|
||||
parent_trace: Option<W3cTraceContext>,
|
||||
@@ -1598,7 +1571,6 @@ impl ThreadManagerState {
|
||||
attestation_provider: self.attestation_provider.clone(),
|
||||
external_time_provider: self.external_time_provider.clone(),
|
||||
inherited_multi_agent_version: multi_agent_version,
|
||||
initial_multi_agent_mode,
|
||||
}))
|
||||
.await?;
|
||||
let new_thread = self
|
||||
|
||||
@@ -404,7 +404,6 @@ async fn start_thread_keeps_internal_threads_hidden_from_normal_lookups() {
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: Default::default(),
|
||||
@@ -545,7 +544,6 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: selected_root_init("selected-a", "env-a"),
|
||||
@@ -561,7 +559,6 @@ async fn start_thread_seeds_extension_data_for_mcp_and_lifecycle_contributors()
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: selected_root_init("selected-b", "env-b"),
|
||||
@@ -654,7 +651,6 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: environments.clone(),
|
||||
thread_extension_init: Default::default(),
|
||||
@@ -938,7 +934,6 @@ async fn resume_stopped_thread_from_rollout_preserves_thread_source() {
|
||||
thread_source: Some(ThreadSource::User),
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: Default::default(),
|
||||
|
||||
@@ -132,7 +132,6 @@ async fn handle_spawn_agent(
|
||||
fork_mode: args.fork_context.then_some(SpawnAgentForkMode::FullHistory),
|
||||
parent_thread_id: Some(session.thread_id),
|
||||
environments: Some(turn.environments.to_selections()),
|
||||
initial_multi_agent_mode: None,
|
||||
},
|
||||
))
|
||||
.await
|
||||
|
||||
@@ -26,7 +26,6 @@ use codex_model_provider_info::built_in_model_providers;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
@@ -1152,7 +1151,6 @@ 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");
|
||||
turn.multi_agent_mode = MultiAgentMode::Proactive;
|
||||
set_turn_config(&mut turn, config);
|
||||
|
||||
let session = Arc::new(session);
|
||||
@@ -1191,7 +1189,6 @@ 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, MultiAgentMode::Proactive);
|
||||
assert!(manager.captured_ops().iter().any(|(id, op)| {
|
||||
*id == child_thread_id
|
||||
&& matches!(
|
||||
|
||||
@@ -49,11 +49,6 @@ async fn handle_spawn_agent(
|
||||
let arguments = function_arguments(payload)?;
|
||||
let args: SpawnAgentArgs = parse_arguments(&arguments)?;
|
||||
let fork_mode = args.fork_mode()?;
|
||||
let multi_agent_mode = crate::session::multi_agents::effective_multi_agent_mode(
|
||||
turn.multi_agent_version,
|
||||
&turn.session_source,
|
||||
turn.multi_agent_mode,
|
||||
);
|
||||
let role_name = args
|
||||
.agent_type
|
||||
.as_deref()
|
||||
@@ -134,7 +129,6 @@ async fn handle_spawn_agent(
|
||||
fork_mode,
|
||||
parent_thread_id: Some(session.thread_id),
|
||||
environments: Some(turn.environments.to_selections()),
|
||||
initial_multi_agent_mode: multi_agent_mode,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -663,7 +663,6 @@ impl TestCodexBuilder {
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments,
|
||||
thread_extension_init: Default::default(),
|
||||
|
||||
@@ -442,7 +442,6 @@ async fn loads_user_instructions_without_a_primary_environment() -> Result<()> {
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: Default::default(),
|
||||
@@ -648,7 +647,6 @@ async fn multi_environment_thread_loads_every_project_and_keeps_creation_snapsho
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: vec![
|
||||
TurnEnvironmentSelection {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use codex_core::config::Config;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::MultiAgentMode;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::openai_models::ReasoningEffortPreset;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::MULTI_AGENT_MODE_OPEN_TAG;
|
||||
use codex_protocol::protocol::Op;
|
||||
@@ -19,9 +22,30 @@ 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 add_ultra_reasoning(model_info: &mut ModelInfo) {
|
||||
model_info.supports_reasoning_summaries = true;
|
||||
model_info
|
||||
.supported_reasoning_levels
|
||||
.push(ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Ultra,
|
||||
description: "Ultra".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
fn configure_multi_agent_v2(config: &mut Config) {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.expect("test config should allow feature update");
|
||||
}
|
||||
|
||||
fn configure_ultra(config: &mut Config) {
|
||||
configure_multi_agent_v2(config);
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::Ultra);
|
||||
}
|
||||
|
||||
fn developer_texts(input: &[Value]) -> Vec<&str> {
|
||||
input
|
||||
.iter()
|
||||
@@ -39,7 +63,7 @@ fn count_containing(texts: &[&str], target: &str) -> usize {
|
||||
async fn submit_turn(
|
||||
codex: &codex_core::CodexThread,
|
||||
prompt: &str,
|
||||
mode: Option<MultiAgentMode>,
|
||||
effort: Option<ReasoningEffort>,
|
||||
) -> Result<()> {
|
||||
codex
|
||||
.submit(Op::UserInput {
|
||||
@@ -51,7 +75,7 @@ async fn submit_turn(
|
||||
responsesapi_client_metadata: None,
|
||||
additional_context: Default::default(),
|
||||
thread_settings: ThreadSettingsOverrides {
|
||||
multi_agent_mode: mode,
|
||||
effort: effort.map(Some),
|
||||
..Default::default()
|
||||
},
|
||||
})
|
||||
@@ -61,214 +85,43 @@ async fn submit_turn(
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn multi_agent_mode_is_sticky_and_emits_only_on_change() -> Result<()> {
|
||||
async fn ultra_reasoning_uses_max_and_proactive_mode() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let responses = mount_sse_sequence(
|
||||
&server,
|
||||
(1..=5)
|
||||
.map(|index| {
|
||||
sse(vec![
|
||||
ev_response_created(&format!("resp-{index}")),
|
||||
ev_completed(&format!("resp-{index}")),
|
||||
])
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.await;
|
||||
let test = test_codex()
|
||||
.with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.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,
|
||||
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
|
||||
.iter()
|
||||
.map(core_test_support::responses::ResponsesRequest::input)
|
||||
.collect::<Vec<_>>();
|
||||
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!(
|
||||
(
|
||||
count_containing(&first, MULTI_AGENT_MODE_OPEN_TAG),
|
||||
count_containing(&first, NO_SPAWN_TEXT),
|
||||
count_containing(&first, PROACTIVE_TEXT),
|
||||
),
|
||||
(1, 1, 0)
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
count_containing(&second, MULTI_AGENT_MODE_OPEN_TAG),
|
||||
count_containing(&second, NO_SPAWN_TEXT),
|
||||
count_containing(&second, PROACTIVE_TEXT),
|
||||
),
|
||||
(2, 1, 1)
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
count_containing(&third, MULTI_AGENT_MODE_OPEN_TAG),
|
||||
count_containing(&third, NO_SPAWN_TEXT),
|
||||
count_containing(&third, PROACTIVE_TEXT),
|
||||
),
|
||||
(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_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;
|
||||
let responses = mount_sse_once(
|
||||
let response = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
|
||||
)
|
||||
.await;
|
||||
let test = test_codex()
|
||||
.with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.expect("test config should allow feature update");
|
||||
config.multi_agent_v2.root_agent_usage_hint_text = None;
|
||||
})
|
||||
.with_model_info_override("gpt-5.4", add_ultra_reasoning)
|
||||
.with_config(configure_ultra)
|
||||
.build(&server)
|
||||
.await?;
|
||||
|
||||
submit_turn(&test.codex, "hello", Some(MultiAgentMode::Proactive)).await?;
|
||||
submit_turn(&test.codex, "hello", /*effort*/ None).await?;
|
||||
|
||||
let input = responses.single_request().input();
|
||||
let request = response.single_request();
|
||||
assert_eq!(
|
||||
request.body_json()["reasoning"]["effort"].as_str(),
|
||||
Some("max")
|
||||
);
|
||||
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),
|
||||
),
|
||||
(1, 1)
|
||||
(0, 1)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result<()> {
|
||||
async fn leaving_ultra_after_cold_resume_emits_explicit_mode() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -285,12 +138,8 @@ async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result
|
||||
)
|
||||
.await;
|
||||
let initial = test_codex()
|
||||
.with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.expect("test config should allow feature update");
|
||||
})
|
||||
.with_model_info_override("gpt-5.4", add_ultra_reasoning)
|
||||
.with_config(configure_ultra)
|
||||
.build(&server)
|
||||
.await?;
|
||||
let home = initial.home.clone();
|
||||
@@ -300,29 +149,27 @@ async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result
|
||||
.clone()
|
||||
.expect("rollout path");
|
||||
|
||||
submit_turn(
|
||||
&initial.codex,
|
||||
"before resume",
|
||||
Some(MultiAgentMode::Proactive),
|
||||
)
|
||||
.await?;
|
||||
submit_turn(&initial.codex, "before resume", /*effort*/ None).await?;
|
||||
drop(initial);
|
||||
|
||||
let mut resume_builder = test_codex().with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.expect("test config should allow feature update");
|
||||
});
|
||||
let mut resume_builder = test_codex()
|
||||
.with_model_info_override("gpt-5.4", add_ultra_reasoning)
|
||||
.with_config(configure_ultra);
|
||||
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::Proactive
|
||||
);
|
||||
submit_turn(&resumed.codex, "after resume", Some(ReasoningEffort::High)).await?;
|
||||
|
||||
let requests = responses.requests();
|
||||
assert_eq!(
|
||||
(
|
||||
requests[0].body_json()["reasoning"]["effort"]
|
||||
.as_str()
|
||||
.map(str::to_string),
|
||||
requests[1].body_json()["reasoning"]["effort"]
|
||||
.as_str()
|
||||
.map(str::to_string),
|
||||
),
|
||||
(Some("max".to_string()), Some("high".to_string()))
|
||||
);
|
||||
let resumed_input = requests[1].input();
|
||||
let texts = developer_texts(&resumed_input);
|
||||
assert_eq!(
|
||||
@@ -331,39 +178,40 @@ async fn resume_compares_against_previous_effective_multi_agent_mode() -> Result
|
||||
count_containing(&texts, NO_SPAWN_TEXT),
|
||||
count_containing(&texts, PROACTIVE_TEXT),
|
||||
),
|
||||
(1, 0, 1)
|
||||
(2, 1, 1)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn multi_agent_mode_is_retained_without_multi_agent_v2() -> Result<()> {
|
||||
async fn ultra_on_multi_agent_v1_uses_max_without_mode_instructions() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let responses = mount_sse_once(
|
||||
let response = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![ev_response_created("resp-1"), ev_completed("resp-1")]),
|
||||
)
|
||||
.await;
|
||||
let test = test_codex().build(&server).await?;
|
||||
let test = test_codex()
|
||||
.with_model_info_override("gpt-5.4", add_ultra_reasoning)
|
||||
.with_config(|config| {
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::Ultra);
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
|
||||
submit_turn(&test.codex, "hello", Some(MultiAgentMode::Proactive)).await?;
|
||||
submit_turn(&test.codex, "hello", /*effort*/ None).await?;
|
||||
|
||||
let request = response.single_request();
|
||||
assert_eq!(
|
||||
test.codex.config_snapshot().await.multi_agent_mode,
|
||||
MultiAgentMode::Proactive
|
||||
request.body_json()["reasoning"]["effort"].as_str(),
|
||||
Some("max")
|
||||
);
|
||||
let input = responses.single_request().input();
|
||||
let input = request.input();
|
||||
let texts = developer_texts(&input);
|
||||
assert_eq!(
|
||||
(
|
||||
count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG),
|
||||
count_containing(&texts, PROACTIVE_TEXT),
|
||||
),
|
||||
(0, 0)
|
||||
);
|
||||
assert_eq!(count_containing(&texts, MULTI_AGENT_MODE_OPEN_TAG), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -766,7 +766,6 @@ async fn subagent_stop_replaces_stop_and_skips_internal_subagents() -> Result<()
|
||||
thread_source: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments: Vec::new(),
|
||||
thread_extension_init: Default::default(),
|
||||
|
||||
@@ -334,7 +334,6 @@ impl MemoryStartupContext {
|
||||
thread_source: Some(ThreadSource::MemoryConsolidation),
|
||||
dynamic_tools: Vec::new(),
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments,
|
||||
thread_extension_init: Default::default(),
|
||||
|
||||
@@ -45,6 +45,7 @@ pub enum ReasoningEffort {
|
||||
Medium,
|
||||
High,
|
||||
XHigh,
|
||||
Ultra,
|
||||
/// A model-defined effort value that this client does not know yet.
|
||||
Custom(String),
|
||||
}
|
||||
@@ -59,6 +60,7 @@ impl ReasoningEffort {
|
||||
Self::Medium => "medium",
|
||||
Self::High => "high",
|
||||
Self::XHigh => "xhigh",
|
||||
Self::Ultra => "ultra",
|
||||
Self::Custom(effort) => effort,
|
||||
}
|
||||
}
|
||||
@@ -123,6 +125,7 @@ impl FromStr for ReasoningEffort {
|
||||
"medium" => Ok(Self::Medium),
|
||||
"high" => Ok(Self::High),
|
||||
"xhigh" => Ok(Self::XHigh),
|
||||
"ultra" => Ok(Self::Ultra),
|
||||
"" => Err("reasoning_effort must not be empty".to_string()),
|
||||
effort => Ok(Self::Custom(effort.to_string())),
|
||||
}
|
||||
@@ -701,20 +704,25 @@ mod tests {
|
||||
let deserialized = from_str::<ReasoningEffort>(r#""max""#)
|
||||
.expect("custom reasoning effort should deserialize");
|
||||
let serialized = to_string(&custom).expect("custom reasoning effort should serialize");
|
||||
let serialized_ultra = to_string(&ReasoningEffort::Ultra).expect("Ultra should serialize");
|
||||
|
||||
assert_eq!(
|
||||
(
|
||||
"high".parse(),
|
||||
"ultra".parse(),
|
||||
"max".parse(),
|
||||
deserialized,
|
||||
serialized,
|
||||
serialized_ultra,
|
||||
custom.to_string(),
|
||||
),
|
||||
(
|
||||
Ok(ReasoningEffort::High),
|
||||
Ok(ReasoningEffort::Ultra),
|
||||
Ok(custom.clone()),
|
||||
custom,
|
||||
r#""max""#.to_string(),
|
||||
r#""ultra""#.to_string(),
|
||||
"max".to_string(),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -489,9 +489,6 @@ pub struct ThreadSettingsOverrides {
|
||||
/// Takes precedence over model, effort, and developer instructions if set.
|
||||
pub collaboration_mode: Option<CollaborationMode>,
|
||||
|
||||
/// Updated multi-agent mode for this turn and subsequent turns.
|
||||
pub multi_agent_mode: Option<MultiAgentMode>,
|
||||
|
||||
/// Updated personality preference.
|
||||
pub personality: Option<Personality>,
|
||||
}
|
||||
@@ -2023,8 +2020,6 @@ pub struct ThreadSettingsSnapshot {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub personality: Option<Personality>,
|
||||
pub collaboration_mode: CollaborationMode,
|
||||
#[serde(default)]
|
||||
pub multi_agent_mode: MultiAgentMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
@@ -505,6 +505,7 @@ impl ChatWidget {
|
||||
ReasoningEffortConfig::Medium => "Medium".to_string(),
|
||||
ReasoningEffortConfig::High => "High".to_string(),
|
||||
ReasoningEffortConfig::XHigh => "Extra high".to_string(),
|
||||
ReasoningEffortConfig::Ultra => "Ultra".to_string(),
|
||||
ReasoningEffortConfig::Custom(value) => value.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user