[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:
Shijie Rao
2026-06-24 20:13:52 -07:00
committed by GitHub
Unverified
parent fa036d39aa
commit df1199fddb
41 changed files with 172 additions and 651 deletions
@@ -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,
},
)
+3 -1
View File
@@ -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,
}
}