mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
[codex] Add Ultra reasoning effort (#29899)
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. - 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`. - `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
shijie-openai
parent
27f22b54ae
commit
aedb8f345a
@@ -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;
|
||||
@@ -69,7 +68,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> {
|
||||
@@ -239,7 +237,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 {
|
||||
@@ -286,7 +283,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(),
|
||||
@@ -392,7 +388,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(
|
||||
@@ -517,7 +512,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(),
|
||||
|
||||
@@ -14,7 +14,6 @@ use codex_features::Feature;
|
||||
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;
|
||||
@@ -817,42 +816,6 @@ async fn spawn_agent_creates_thread_and_sends_prompt() {
|
||||
assert_eq!(captured, Some(expected));
|
||||
}
|
||||
|
||||
#[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;
|
||||
@@ -875,15 +838,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;
|
||||
@@ -978,7 +932,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()
|
||||
},
|
||||
)
|
||||
@@ -991,10 +944,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 =
|
||||
@@ -1287,15 +1236,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())
|
||||
@@ -1380,7 +1320,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()
|
||||
},
|
||||
)
|
||||
@@ -1393,10 +1332,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!(
|
||||
@@ -2244,7 +2179,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, mut config) = test_config().await;
|
||||
config
|
||||
.features
|
||||
@@ -2266,7 +2201,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");
|
||||
|
||||
@@ -2291,18 +2226,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
|
||||
@@ -2313,16 +2236,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)
|
||||
@@ -2411,7 +2325,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
|
||||
|
||||
@@ -162,6 +162,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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Session-scoped state shared by all [`ModelClient`] clones.
|
||||
///
|
||||
/// This is intentionally kept minimal so `ModelClient` does not need to hold a full `Config`. Most
|
||||
@@ -647,11 +654,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
|
||||
@@ -749,7 +758,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::ThreadId;
|
||||
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;
|
||||
@@ -153,6 +154,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>,
|
||||
|
||||
@@ -122,7 +122,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>,
|
||||
@@ -153,7 +151,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>,
|
||||
}
|
||||
|
||||
@@ -374,7 +371,6 @@ impl CodexThread {
|
||||
summary,
|
||||
service_tier,
|
||||
collaboration_mode,
|
||||
multi_agent_mode,
|
||||
personality,
|
||||
} = overrides;
|
||||
let collaboration_mode = if let Some(collaboration_mode) = collaboration_mode {
|
||||
@@ -398,7 +394,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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -454,7 +454,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(),
|
||||
@@ -3330,11 +3326,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
|
||||
|
||||
@@ -127,7 +127,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>,
|
||||
|
||||
@@ -193,7 +192,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,
|
||||
@@ -230,9 +228,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);
|
||||
}
|
||||
@@ -427,7 +422,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>>,
|
||||
|
||||
@@ -3436,7 +3436,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,
|
||||
@@ -3543,7 +3542,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,
|
||||
@@ -4072,7 +4070,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,
|
||||
@@ -4939,7 +4936,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,
|
||||
@@ -5052,7 +5048,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,
|
||||
@@ -5298,7 +5293,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,
|
||||
@@ -5405,7 +5399,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,
|
||||
@@ -7121,7 +7114,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,
|
||||
|
||||
@@ -738,7 +738,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");
|
||||
|
||||
@@ -125,7 +125,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>,
|
||||
@@ -278,7 +277,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(),
|
||||
@@ -384,11 +382,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,
|
||||
@@ -571,7 +565,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(),
|
||||
|
||||
@@ -36,7 +36,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;
|
||||
@@ -185,7 +184,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,
|
||||
@@ -609,7 +607,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(),
|
||||
@@ -649,7 +646,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,
|
||||
@@ -734,7 +730,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,
|
||||
@@ -746,7 +741,6 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
parent_trace,
|
||||
@@ -779,7 +773,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(),
|
||||
@@ -806,7 +799,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,
|
||||
@@ -818,7 +810,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,
|
||||
@@ -974,13 +965,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(
|
||||
@@ -1009,7 +993,6 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
parent_trace,
|
||||
environments,
|
||||
/*thread_extension_init*/ ExtensionDataInit::default(),
|
||||
@@ -1234,7 +1217,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,
|
||||
@@ -1252,7 +1234,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>>,
|
||||
@@ -1271,7 +1252,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
metrics_service_name,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1299,7 +1279,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,
|
||||
@@ -1311,7 +1290,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1333,7 +1311,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>>,
|
||||
@@ -1352,7 +1329,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1377,7 +1353,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,
|
||||
@@ -1395,7 +1370,6 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
dynamic_tools,
|
||||
metrics_service_name,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
parent_trace,
|
||||
@@ -1420,7 +1394,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>,
|
||||
@@ -1500,7 +1473,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
|
||||
|
||||
@@ -322,7 +322,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(),
|
||||
@@ -463,7 +462,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"),
|
||||
@@ -479,7 +477,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"),
|
||||
@@ -572,7 +569,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(),
|
||||
@@ -856,7 +852,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
|
||||
|
||||
@@ -24,7 +24,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;
|
||||
@@ -1148,7 +1147,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);
|
||||
@@ -1187,7 +1185,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,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -641,7 +641,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(),
|
||||
@@ -650,7 +649,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(())
|
||||
}
|
||||
|
||||
@@ -752,7 +752,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(),
|
||||
|
||||
Reference in New Issue
Block a user