mirror of
https://github.com/openai/codex.git
synced 2026-11-02 00:18:52 +00:00
## 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`
264 lines
8.7 KiB
Rust
264 lines
8.7 KiB
Rust
use crate::context::CollaborationModeInstructions;
|
|
use crate::context::ContextualUserFragment;
|
|
use crate::context::ModelSwitchInstructions;
|
|
use crate::context::MultiAgentModeInstructions;
|
|
use crate::context::PermissionsInstructions;
|
|
use crate::context::PersonalitySpecInstructions;
|
|
use crate::context::RealtimeEndInstructions;
|
|
use crate::context::RealtimeStartInstructions;
|
|
use crate::context::RealtimeStartWithInstructions;
|
|
use crate::session::PreviousTurnSettings;
|
|
use crate::session::turn_context::TurnContext;
|
|
use codex_execpolicy::Policy;
|
|
use codex_features::Feature;
|
|
use codex_protocol::config_types::MultiAgentMode;
|
|
use codex_protocol::config_types::Personality;
|
|
use codex_protocol::models::ContentItem;
|
|
use codex_protocol::models::ResponseItem;
|
|
use codex_protocol::openai_models::ModelInfo;
|
|
use codex_protocol::protocol::TurnContextItem;
|
|
|
|
fn build_permissions_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
exec_policy: &Policy,
|
|
) -> Option<String> {
|
|
if !next.config.include_permissions_instructions {
|
|
return None;
|
|
}
|
|
|
|
let prev = previous?;
|
|
if prev.permission_profile() == next.permission_profile()
|
|
&& prev.approval_policy == next.approval_policy.value()
|
|
{
|
|
return None;
|
|
}
|
|
|
|
Some(
|
|
PermissionsInstructions::from_permission_profile(
|
|
&next.permission_profile,
|
|
next.approval_policy.value(),
|
|
next.config.approvals_reviewer,
|
|
exec_policy,
|
|
#[allow(deprecated)]
|
|
&next.cwd,
|
|
next.config
|
|
.features
|
|
.enabled(Feature::ExecPermissionApprovals),
|
|
next.config
|
|
.features
|
|
.enabled(Feature::RequestPermissionsTool),
|
|
)
|
|
.render(),
|
|
)
|
|
}
|
|
|
|
fn build_collaboration_mode_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
) -> Option<String> {
|
|
if !next.config.include_collaboration_mode_instructions {
|
|
return None;
|
|
}
|
|
|
|
let prev = previous?;
|
|
if prev.collaboration_mode.as_ref() != Some(&next.collaboration_mode) {
|
|
// If the next mode has empty developer instructions, this returns None and we emit no
|
|
// update, so prior collaboration instructions remain in the prompt history.
|
|
Some(
|
|
CollaborationModeInstructions::from_collaboration_mode(&next.collaboration_mode)?
|
|
.render(),
|
|
)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
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);
|
|
let previous = previous?;
|
|
if previous.multi_agent_mode == effective_multi_agent_mode {
|
|
return None;
|
|
}
|
|
|
|
match effective_multi_agent_mode {
|
|
Some(MultiAgentMode::None) => {
|
|
Some(MultiAgentModeInstructions::new(MultiAgentMode::None).render())
|
|
}
|
|
Some(multi_agent_mode) => Some(MultiAgentModeInstructions::new(multi_agent_mode).render()),
|
|
None if previous.multi_agent_mode == Some(MultiAgentMode::Proactive) => {
|
|
Some(MultiAgentModeInstructions::new(MultiAgentMode::ExplicitRequestOnly).render())
|
|
}
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn build_realtime_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
previous_turn_settings: Option<&PreviousTurnSettings>,
|
|
next: &TurnContext,
|
|
) -> Option<String> {
|
|
match (
|
|
previous.and_then(|item| item.realtime_active),
|
|
next.realtime_active,
|
|
) {
|
|
(Some(true), false) => Some(RealtimeEndInstructions::new("inactive").render()),
|
|
(Some(false), true) | (None, true) => Some(
|
|
if let Some(instructions) = next
|
|
.config
|
|
.experimental_realtime_start_instructions
|
|
.as_deref()
|
|
{
|
|
RealtimeStartWithInstructions::new(instructions).render()
|
|
} else {
|
|
RealtimeStartInstructions.render()
|
|
},
|
|
),
|
|
(Some(true), true) | (Some(false), false) => None,
|
|
(None, false) => previous_turn_settings
|
|
.and_then(|settings| settings.realtime_active)
|
|
.filter(|realtime_active| *realtime_active)
|
|
.map(|_| RealtimeEndInstructions::new("inactive").render()),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn build_initial_realtime_item(
|
|
previous: Option<&TurnContextItem>,
|
|
previous_turn_settings: Option<&PreviousTurnSettings>,
|
|
next: &TurnContext,
|
|
) -> Option<String> {
|
|
build_realtime_update_item(previous, previous_turn_settings, next)
|
|
}
|
|
|
|
fn build_personality_update_item(
|
|
previous: Option<&TurnContextItem>,
|
|
next: &TurnContext,
|
|
personality_feature_enabled: bool,
|
|
) -> Option<String> {
|
|
if !personality_feature_enabled {
|
|
return None;
|
|
}
|
|
let previous = previous?;
|
|
if next.model_info.slug != previous.model {
|
|
return None;
|
|
}
|
|
|
|
if let Some(personality) = next.personality
|
|
&& next.personality != previous.personality
|
|
{
|
|
let model_info = &next.model_info;
|
|
let personality_message = personality_message_for(model_info, personality);
|
|
personality_message.map(|message| PersonalitySpecInstructions::new(message).render())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) fn personality_message_for(
|
|
model_info: &ModelInfo,
|
|
personality: Personality,
|
|
) -> Option<String> {
|
|
model_info
|
|
.model_messages
|
|
.as_ref()
|
|
.and_then(|spec| spec.get_personality_message(Some(personality)))
|
|
.filter(|message| !message.is_empty())
|
|
}
|
|
|
|
pub(crate) fn build_model_instructions_update_item(
|
|
previous_turn_settings: Option<&PreviousTurnSettings>,
|
|
next: &TurnContext,
|
|
) -> Option<String> {
|
|
let previous_turn_settings = previous_turn_settings?;
|
|
if previous_turn_settings.model == next.model_info.slug {
|
|
return None;
|
|
}
|
|
|
|
let model_instructions = next.model_info.get_model_instructions(next.personality);
|
|
if model_instructions.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(ModelSwitchInstructions::new(model_instructions).render())
|
|
}
|
|
|
|
pub(crate) fn build_developer_update_item(text_sections: Vec<String>) -> Option<ResponseItem> {
|
|
build_text_message("developer", text_sections)
|
|
}
|
|
|
|
pub(crate) fn build_contextual_user_message(text_sections: Vec<String>) -> Option<ResponseItem> {
|
|
build_text_message("user", text_sections)
|
|
}
|
|
|
|
pub(crate) fn merge_contextual_fragments(
|
|
fragments: Vec<Box<dyn ContextualUserFragment>>,
|
|
) -> Vec<ResponseItem> {
|
|
let mut messages: Vec<(&str, Vec<String>)> = Vec::with_capacity(fragments.len());
|
|
for fragment in fragments {
|
|
let role = fragment.role();
|
|
let text = fragment.render();
|
|
match messages.last_mut() {
|
|
Some((previous_role, text_sections)) if *previous_role == role => {
|
|
text_sections.push(text);
|
|
}
|
|
_ => messages.push((role, vec![text])),
|
|
}
|
|
}
|
|
messages
|
|
.into_iter()
|
|
.filter_map(|(role, text_sections)| build_text_message(role, text_sections))
|
|
.collect()
|
|
}
|
|
|
|
fn build_text_message(role: &str, text_sections: Vec<String>) -> Option<ResponseItem> {
|
|
if text_sections.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let content = text_sections
|
|
.into_iter()
|
|
.map(|text| ContentItem::InputText { text })
|
|
.collect();
|
|
|
|
Some(ResponseItem::Message {
|
|
id: None,
|
|
role: role.to_string(),
|
|
content,
|
|
phase: None,
|
|
internal_chat_message_metadata_passthrough: None,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn build_settings_update_items(
|
|
previous: Option<&TurnContextItem>,
|
|
previous_turn_settings: Option<&PreviousTurnSettings>,
|
|
next: &TurnContext,
|
|
exec_policy: &Policy,
|
|
personality_feature_enabled: bool,
|
|
) -> Vec<ResponseItem> {
|
|
// TODO(ccunningham): build_settings_update_items still does not cover every
|
|
// model-visible item emitted by build_initial_context. Persist the remaining
|
|
// inputs or add explicit replay events so fork/resume can diff everything
|
|
// deterministically.
|
|
let developer_update_sections = [
|
|
// Keep model-switch instructions first so model-specific guidance is read before
|
|
// any other context diffs on this turn.
|
|
build_model_instructions_update_item(previous_turn_settings, next),
|
|
build_permissions_update_item(previous, next, exec_policy),
|
|
build_collaboration_mode_update_item(previous, next),
|
|
build_multi_agent_mode_update_item(previous, next),
|
|
build_realtime_update_item(previous, previous_turn_settings, next),
|
|
build_personality_update_item(previous, next, personality_feature_enabled),
|
|
]
|
|
.into_iter()
|
|
.flatten()
|
|
.collect();
|
|
|
|
build_developer_update_item(developer_update_sections)
|
|
.into_iter()
|
|
.collect()
|
|
}
|