Skip to content

Commit df1199f

Browse files
authored
[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`
1 parent fa036d3 commit df1199f

41 files changed

Lines changed: 169 additions & 648 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codex-rs/app-server-protocol/src/protocol/v2/thread.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ pub struct ThreadStartParams {
9494
pub developer_instructions: Option<String>,
9595
#[ts(optional = nullable)]
9696
pub personality: Option<Personality>,
97-
/// Set the initial multi-agent mode for this thread. `none` leaves the
98-
/// multi-agent tools available without injecting mode instructions.
99-
/// Omitted defaults to `explicitRequestOnly`.
97+
/// @deprecated Ignored. Use Ultra reasoning effort for proactive multi-agent behavior.
10098
#[experimental("thread/start.multiAgentMode")]
10199
#[ts(optional = nullable)]
102100
pub multi_agent_mode: Option<MultiAgentMode>,
@@ -186,7 +184,7 @@ pub struct ThreadStartResponse {
186184
#[serde(default)]
187185
pub active_permission_profile: Option<ActivePermissionProfile>,
188186
pub reasoning_effort: Option<ReasoningEffort>,
189-
/// Current multi-agent mode for this thread.
187+
/// @deprecated Always `explicitRequestOnly`. Use `reasoningEffort` for Ultra behavior.
190188
#[experimental("thread/start.multiAgentMode")]
191189
#[serde(default)]
192190
pub multi_agent_mode: MultiAgentMode,
@@ -250,7 +248,7 @@ pub struct ThreadSettingsUpdateParams {
250248
#[experimental("thread/settings/update.collaborationMode")]
251249
#[ts(optional = nullable)]
252250
pub collaboration_mode: Option<CollaborationMode>,
253-
/// Select the multi-agent mode for subsequent turns.
251+
/// @deprecated Ignored. Use `effort: "ultra"` for proactive multi-agent behavior.
254252
#[experimental("thread/settings/update.multiAgentMode")]
255253
#[ts(optional = nullable)]
256254
pub multi_agent_mode: Option<MultiAgentMode>,
@@ -279,7 +277,7 @@ pub struct ThreadSettings {
279277
pub effort: Option<ReasoningEffort>,
280278
pub summary: Option<ReasoningSummary>,
281279
pub collaboration_mode: CollaborationMode,
282-
/// Current multi-agent mode for this thread.
280+
/// @deprecated Always `explicitRequestOnly`. Use `effort` for Ultra behavior.
283281
#[experimental("thread/settings.multiAgentMode")]
284282
#[serde(default)]
285283
pub multi_agent_mode: MultiAgentMode,
@@ -419,7 +417,7 @@ pub struct ThreadResumeResponse {
419417
#[serde(default)]
420418
pub active_permission_profile: Option<ActivePermissionProfile>,
421419
pub reasoning_effort: Option<ReasoningEffort>,
422-
/// Current multi-agent mode for this thread.
420+
/// @deprecated Always `explicitRequestOnly`. Use `reasoningEffort` for Ultra behavior.
423421
#[experimental("thread/resume.multiAgentMode")]
424422
#[serde(default)]
425423
pub multi_agent_mode: MultiAgentMode,
@@ -578,7 +576,7 @@ pub struct ThreadForkResponse {
578576
#[serde(default)]
579577
pub active_permission_profile: Option<ActivePermissionProfile>,
580578
pub reasoning_effort: Option<ReasoningEffort>,
581-
/// Current multi-agent mode for this thread.
579+
/// @deprecated Always `explicitRequestOnly`. Use `reasoningEffort` for Ultra behavior.
582580
#[experimental("thread/fork.multiAgentMode")]
583581
#[serde(default)]
584582
pub multi_agent_mode: MultiAgentMode,

codex-rs/app-server-protocol/src/protocol/v2/turn.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,7 @@ pub struct TurnStartParams {
151151
#[ts(optional = nullable)]
152152
pub collaboration_mode: Option<CollaborationMode>,
153153

154-
/// Controls multi-agent v2 delegation instructions. `none` leaves the
155-
/// multi-agent tools available without injecting mode instructions. Omitted
156-
/// keeps the loaded session's current mode.
154+
/// @deprecated Ignored. Use `effort: "ultra"` for proactive multi-agent behavior.
157155
#[experimental("turn/start.multiAgentMode")]
158156
#[ts(optional = nullable)]
159157
pub multi_agent_mode: Option<MultiAgentMode>,

codex-rs/app-server/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,17 @@ Example with notification opt-out:
137137

138138
## API Overview
139139

140-
- `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.
141-
- `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`.
140+
- `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.
141+
- `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`.
142142
- `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`.
143-
- `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.
143+
- `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.
144144
- `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.
145145
- `thread/loaded/list` — list the thread ids currently loaded in memory.
146146
- `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.
147147
- `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`.
148148
- `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.
149149
- `thread/metadata/update` — patch stored thread metadata in sqlite; currently supports updating persisted `gitInfo` fields and returns the refreshed `thread`.
150-
- `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.
150+
- `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.
151151
- `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.
152152
- `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.
153153
- `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:
168168
- `thread/backgroundTerminals/list` — list running background terminals for a loaded thread (experimental; requires `capabilities.experimentalApi`); returns `data` with the running terminal ids.
169169
- `thread/backgroundTerminals/terminate` — terminate one running background terminal by app-server `processId` (experimental; requires `capabilities.experimentalApi`); returns whether a process was terminated.
170170
- `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.
171-
- `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.
171+
- `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.
172172
- `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success.
173173
- `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`.
174174
- `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"`.

codex-rs/app-server/src/request_processors/thread_lifecycle.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::*;
2+
use codex_protocol::config_types::MultiAgentMode;
23

34
pub(super) const THREAD_UNLOADING_DELAY: Duration = Duration::from_secs(30 * 60);
45

@@ -639,7 +640,6 @@ pub(super) async fn handle_pending_thread_resume_request(
639640
active_permission_profile,
640641
workspace_roots,
641642
reasoning_effort,
642-
multi_agent_mode,
643643
..
644644
} = config_snapshot;
645645
let instruction_sources = pending.instruction_sources;
@@ -662,7 +662,7 @@ pub(super) async fn handle_pending_thread_resume_request(
662662
sandbox,
663663
active_permission_profile,
664664
reasoning_effort,
665-
multi_agent_mode,
665+
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
666666
initial_turns_page,
667667
};
668668
outgoing.send_response(request_id, response).await;

codex-rs/app-server/src/request_processors/thread_processor.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ impl ThreadRequestProcessor {
903903
mock_experimental_field: _mock_experimental_field,
904904
experimental_raw_events,
905905
personality,
906-
multi_agent_mode,
906+
multi_agent_mode: _multi_agent_mode,
907907
ephemeral,
908908
session_start_source,
909909
thread_source,
@@ -957,7 +957,6 @@ impl ThreadRequestProcessor {
957957
supports_openai_form_elicitation,
958958
config,
959959
typesafe_overrides,
960-
multi_agent_mode,
961960
dynamic_tools,
962961
selected_capability_roots.unwrap_or_default(),
963962
session_start_source,
@@ -1032,7 +1031,6 @@ impl ThreadRequestProcessor {
10321031
supports_openai_form_elicitation: bool,
10331032
config_overrides: Option<HashMap<String, serde_json::Value>>,
10341033
typesafe_overrides: ConfigOverrides,
1035-
multi_agent_mode: Option<MultiAgentMode>,
10361034
dynamic_tools: Option<Vec<DynamicToolSpec>>,
10371035
selected_capability_roots: Vec<SelectedCapabilityRoot>,
10381036
session_start_source: Option<codex_app_server_protocol::ThreadStartSource>,
@@ -1156,7 +1154,6 @@ impl ThreadRequestProcessor {
11561154
thread_source,
11571155
dynamic_tools,
11581156
metrics_service_name: service_name,
1159-
multi_agent_mode,
11601157
parent_trace: request_trace,
11611158
environments,
11621159
thread_extension_init,
@@ -1262,7 +1259,7 @@ impl ThreadRequestProcessor {
12621259
sandbox,
12631260
active_permission_profile,
12641261
reasoning_effort: config_snapshot.reasoning_effort,
1265-
multi_agent_mode: config_snapshot.multi_agent_mode,
1262+
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
12661263
};
12671264
let notif = thread_started_notification(thread);
12681265
listener_task_context
@@ -2856,7 +2853,7 @@ impl ThreadRequestProcessor {
28562853
sandbox,
28572854
active_permission_profile,
28582855
reasoning_effort: session_configured.reasoning_effort,
2859-
multi_agent_mode: config_snapshot.multi_agent_mode,
2856+
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
28602857
initial_turns_page,
28612858
};
28622859

@@ -3576,7 +3573,7 @@ impl ThreadRequestProcessor {
35763573
sandbox,
35773574
active_permission_profile,
35783575
reasoning_effort: session_configured.reasoning_effort,
3579-
multi_agent_mode: config_snapshot.multi_agent_mode,
3576+
multi_agent_mode: MultiAgentMode::ExplicitRequestOnly,
35803577
};
35813578

35823579
let notif = thread_started_notification(thread);

codex-rs/app-server/src/request_processors/thread_processor_tests.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,6 @@ mod thread_processor_behavior_tests {
776776
developer_instructions: None,
777777
},
778778
},
779-
multi_agent_mode: Default::default(),
780779
session_source: SessionSource::Cli,
781780
forked_from_thread_id: None,
782781
parent_thread_id: None,

0 commit comments

Comments
 (0)