mirror of
https://github.com/openai/codex.git
synced 2026-11-05 08:28:29 +00:00
tui: warn on Ultra with high multi-agent concurrency (#31621)
## Why Ultra reasoning may proactively use multiple agents. When `features.multi_agent_v2.max_concurrent_threads_per_session` is configured at 8 or higher, explicitly selecting Ultra can allow up to `N - 1` subagents to work concurrently and increase usage quickly. Showing the configured limits at selection time makes that tradeoff visible and points users to the setting that controls it. ## What changed - Show a warning history cell after the user explicitly selects Ultra reasoning with a concurrent-thread limit of at least 8. - Include the configured concurrent-thread count and maximum subagent count in the warning. - Apply the warning consistently across model and reasoning pickers, Plan-mode scope selection, and reasoning shortcuts. - Keep the trigger limited to the selected reasoning effort and configured thread limit, independent of how multi-agent v2 is activated. - Add focused threshold coverage and an `insta` snapshot for the rendered warning. ## User impact Users with high multi-agent concurrency receive a concrete warning immediately after selecting Ultra. Other reasoning efforts, limits below 8, and startup behavior are unchanged. https://github.com/user-attachments/assets/5795bcb9-432e-42bc-bde3-ee363e9aeb69 ## Test plan - `just test -p codex-tui ultra_reasoning_selection` - Built the full debug CLI with `cargo build -p codex-cli`. - Manually verified the debug TUI with a 10-thread limit: switching from Max to Ultra displayed the warning with 10 concurrent threads and up to 9 subagents.
This commit is contained in:
1 parent
1d14221af2
commit
927004c06d
4 files changed
+141
-1
No files matched your search
@@ -5,6 +5,8 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
const ULTRA_REASONING_CONCURRENCY_WARNING_THRESHOLD: usize = 8;
|
||||
|
||||
impl ChatWidget {
|
||||
/// Open a popup to choose a quick auto model. Selecting "All models"
|
||||
/// opens the full picker with every available preset.
|
||||
@@ -102,7 +104,7 @@ impl ChatWidget {
|
||||
model.as_str(),
|
||||
Some(preset.default_reasoning_effort.clone()),
|
||||
);
|
||||
let actions = Self::model_selection_actions(
|
||||
let actions = self.model_selection_actions(
|
||||
model.clone(),
|
||||
Some(preset.default_reasoning_effort.clone()),
|
||||
should_prompt_plan_mode_scope,
|
||||
@@ -214,10 +216,14 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
fn model_selection_actions(
|
||||
&self,
|
||||
model_for_action: String,
|
||||
effort_for_action: Option<ReasoningEffortConfig>,
|
||||
should_prompt_plan_mode_scope: bool,
|
||||
) -> Vec<SelectionAction> {
|
||||
let warning = effort_for_action
|
||||
.as_ref()
|
||||
.and_then(|effort| self.ultra_reasoning_concurrency_warning(effort));
|
||||
vec![Box::new(move |tx| {
|
||||
if should_prompt_plan_mode_scope {
|
||||
tx.send(AppEvent::OpenPlanReasoningScopePrompt {
|
||||
@@ -233,6 +239,11 @@ impl ChatWidget {
|
||||
model: model_for_action.clone(),
|
||||
effort: effort_for_action.clone(),
|
||||
});
|
||||
if let Some(warning) = warning.clone() {
|
||||
tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_warning_event(warning),
|
||||
)));
|
||||
}
|
||||
})]
|
||||
}
|
||||
|
||||
@@ -299,14 +310,23 @@ impl ChatWidget {
|
||||
"Set the global default reasoning level and the Plan mode override. This replaces the current {plan_reasoning_source}."
|
||||
);
|
||||
let subtitle = format!("Choose where to apply {reasoning_phrase}.");
|
||||
let warning = effort
|
||||
.as_ref()
|
||||
.and_then(|effort| self.ultra_reasoning_concurrency_warning(effort));
|
||||
|
||||
let plan_only_actions: Vec<SelectionAction> = vec![Box::new({
|
||||
let model = model.clone();
|
||||
let effort = effort.clone();
|
||||
let warning = warning.clone();
|
||||
move |tx| {
|
||||
tx.send(AppEvent::UpdateModel(model.clone()));
|
||||
tx.send(AppEvent::UpdatePlanModeReasoningEffort(effort.clone()));
|
||||
tx.send(AppEvent::PersistPlanModeReasoningEffort(effort.clone()));
|
||||
if let Some(warning) = warning.clone() {
|
||||
tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_warning_event(warning),
|
||||
)));
|
||||
}
|
||||
}
|
||||
})];
|
||||
let all_modes_actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
|
||||
@@ -318,6 +338,11 @@ impl ChatWidget {
|
||||
model: model.clone(),
|
||||
effort: effort.clone(),
|
||||
});
|
||||
if let Some(warning) = warning.clone() {
|
||||
tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_warning_event(warning),
|
||||
)));
|
||||
}
|
||||
})];
|
||||
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
@@ -427,6 +452,7 @@ impl ChatWidget {
|
||||
let mut items: Vec<SelectionItem> = Vec::new();
|
||||
for choice in choices.iter() {
|
||||
let effort = choice.clone();
|
||||
let warning = self.ultra_reasoning_concurrency_warning(&effort);
|
||||
let mut effort_label = Self::reasoning_effort_label(&effort);
|
||||
if Some(choice) == default_choice.as_ref() {
|
||||
effort_label.push_str(" (default)");
|
||||
@@ -469,6 +495,11 @@ impl ChatWidget {
|
||||
model: model_for_action.clone(),
|
||||
effort: choice_effort.clone(),
|
||||
});
|
||||
if let Some(warning) = warning.clone() {
|
||||
tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_warning_event(warning),
|
||||
)));
|
||||
}
|
||||
}
|
||||
})];
|
||||
|
||||
@@ -518,14 +549,47 @@ impl ChatWidget {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ultra_reasoning_concurrency_warning(
|
||||
&self,
|
||||
effort: &ReasoningEffortConfig,
|
||||
) -> Option<String> {
|
||||
if effort != &ReasoningEffortConfig::Ultra {
|
||||
return None;
|
||||
}
|
||||
|
||||
let max_threads = self
|
||||
.config
|
||||
.multi_agent_v2
|
||||
.max_concurrent_threads_per_session;
|
||||
if max_threads < ULTRA_REASONING_CONCURRENCY_WARNING_THRESHOLD {
|
||||
return None;
|
||||
}
|
||||
|
||||
let max_subagents = max_threads.saturating_sub(1);
|
||||
Some(format!(
|
||||
"Ultra reasoning may proactively use multiple agents. This session is configured for \
|
||||
{max_threads} concurrent threads with up to {max_subagents} subagents which can \
|
||||
increase usage quickly. Consider setting \
|
||||
features.multi_agent_v2.max_concurrent_threads_per_session below 8."
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn apply_model_and_effort_without_persist(
|
||||
&self,
|
||||
model: String,
|
||||
effort: Option<ReasoningEffortConfig>,
|
||||
) {
|
||||
let warning = effort
|
||||
.as_ref()
|
||||
.and_then(|effort| self.ultra_reasoning_concurrency_warning(effort));
|
||||
self.app_event_tx.send(AppEvent::UpdateModel(model));
|
||||
self.app_event_tx
|
||||
.send(AppEvent::UpdateReasoningEffort(effort));
|
||||
if let Some(warning) = warning {
|
||||
self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
history_cell::new_warning_event(warning),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_model_and_effort(&self, model: String, effort: Option<ReasoningEffortConfig>) {
|
||||
|
||||
@@ -110,8 +110,14 @@ impl ChatWidget {
|
||||
};
|
||||
|
||||
if self.collaboration_modes_enabled() && self.active_mode_kind() == ModeKind::Plan {
|
||||
let warning = self.ultra_reasoning_concurrency_warning(&next_effort);
|
||||
self.app_event_tx
|
||||
.send(AppEvent::UpdatePlanModeReasoningEffort(Some(next_effort)));
|
||||
if let Some(warning) = warning {
|
||||
self.app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
|
||||
crate::history_cell::new_warning_event(warning),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
self.apply_model_and_effort_without_persist(current_model, Some(next_effort));
|
||||
}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/popups_and_settings.rs
|
||||
expression: "&warnings[0]"
|
||||
---
|
||||
⚠ Ultra reasoning may proactively use multiple agents. This session is
|
||||
configured for 8 concurrent threads with up to 7 subagents which can increase
|
||||
usage quickly. Consider setting
|
||||
features.multi_agent_v2.max_concurrent_threads_per_session below 8.
|
||||
@@ -3236,6 +3236,68 @@ async fn model_reasoning_selection_popup_applies_custom_effort() {
|
||||
);
|
||||
}
|
||||
|
||||
async fn select_ultra_with_multi_agent_thread_limit(max_threads: usize) -> (bool, Vec<String>) {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
chat.config
|
||||
.multi_agent_v2
|
||||
.max_concurrent_threads_per_session = max_threads;
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
|
||||
|
||||
let mut preset = get_available_model(&chat, "gpt-5.4");
|
||||
preset.default_reasoning_effort = ReasoningEffortConfig::High;
|
||||
preset.supported_reasoning_efforts = vec![
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffortConfig::High,
|
||||
description: "High reasoning".to_string(),
|
||||
},
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffortConfig::Ultra,
|
||||
description: "Ultra reasoning".to_string(),
|
||||
},
|
||||
];
|
||||
chat.open_reasoning_popup(preset);
|
||||
while rx.try_recv().is_ok() {}
|
||||
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Down));
|
||||
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
|
||||
|
||||
let mut selected_ultra = false;
|
||||
let mut warnings = Vec::new();
|
||||
while let Ok(event) = rx.try_recv() {
|
||||
match event {
|
||||
AppEvent::UpdateReasoningEffort(Some(ReasoningEffortConfig::Ultra)) => {
|
||||
selected_ultra = true;
|
||||
}
|
||||
AppEvent::InsertHistoryCell(cell) => {
|
||||
warnings.push(lines_to_single_string(&cell.display_lines(/*width*/ 80)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
(selected_ultra, warnings)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ultra_reasoning_selection_warns_for_high_multi_agent_concurrency() {
|
||||
let (selected_ultra, warnings) =
|
||||
select_ultra_with_multi_agent_thread_limit(/*max_threads*/ 8).await;
|
||||
|
||||
assert!(selected_ultra);
|
||||
assert_eq!(warnings.len(), 1);
|
||||
assert_chatwidget_snapshot!(
|
||||
"ultra_reasoning_selection_high_multi_agent_concurrency_warning",
|
||||
&warnings[0]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn ultra_reasoning_selection_skips_warning_below_threshold() {
|
||||
let below_threshold = select_ultra_with_multi_agent_thread_limit(/*max_threads*/ 7).await;
|
||||
|
||||
assert_eq!(below_threshold, (true, Vec::new()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn model_reasoning_selection_popup_extra_high_warning_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.2")).await;
|
||||
|
||||
Reference in new issue
Block a user