mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Expose thread-level multi-agent mode (#28792)
## Why Once multi-agent mode can be selected per turn, clients also need to choose the initial selection when creating a thread and observe that selection through lifecycle and settings APIs. The selected value is intentionally distinct from the effective model-visible value: no client selection is represented as `null`, even though an eligible multi-agent v2 turn derives `explicitRequestOnly` as its effective default. ## What changed - Add the optional experimental `thread/start.multiAgentMode` parameter and pass it through thread creation. - Preserve an omitted initial value as an unset selection rather than eagerly storing `explicitRequestOnly`. - Apply an explicit `thread/start` selection to the first turn through the session configuration established at thread creation. - Restore the latest persisted effective mode as the selected baseline on cold resume when rollout history contains one. - Inherit the optional selected mode from a loaded parent when creating related runtime threads. - Return the current selected `multiAgentMode` from `thread/start`, `thread/resume`, `thread/fork`, and thread settings, using `null` when no mode is selected. - Keep lifecycle reporting independent from model capability and feature eligibility; core turn construction remains responsible for calculating and persisting the effective mode. ## Not covered - Clearing an existing loaded-session selection back to unset through `turn/start`; omitted or `null` currently retains the session's selection. - A TUI control, slash command, or `config.toml` preference. ## Verification - `CARGO_INCREMENTAL=0 just test -p codex-app-server-protocol` - `CARGO_INCREMENTAL=0 just test -p codex-app-server multi_agent_mode` The focused app-server coverage verifies explicit `thread/start` initialization, first-turn prompting, nullable reporting for an omitted selection, and retention of selections that are not currently runtime-eligible. ## Stack Stacked on #28685. This PR contains only the thread initialization and lifecycle/settings API layer.
This commit is contained in:
committed by
GitHub
Unverified
parent
fc8c6b7384
commit
7abfcf220b
@@ -19,6 +19,7 @@ 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;
|
||||
@@ -68,6 +69,7 @@ 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,6 +142,7 @@ 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,11 +1,13 @@
|
||||
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> {
|
||||
@@ -237,6 +239,7 @@ 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 {
|
||||
@@ -283,6 +286,7 @@ 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(),
|
||||
@@ -388,6 +392,7 @@ 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(
|
||||
@@ -513,6 +518,7 @@ 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,6 +14,7 @@ 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;
|
||||
@@ -816,6 +817,45 @@ 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,
|
||||
Some(MultiAgentMode::Proactive)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
let harness = AgentControlHarness::new().await;
|
||||
@@ -838,6 +878,15 @@ 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;
|
||||
@@ -923,6 +972,7 @@ 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()
|
||||
},
|
||||
)
|
||||
@@ -935,6 +985,10 @@ 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,
|
||||
Some(MultiAgentMode::Proactive)
|
||||
);
|
||||
assert_ne!(child_thread_id, parent_thread_id);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
let expected_history = [
|
||||
@@ -1242,6 +1296,15 @@ 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())
|
||||
@@ -1326,6 +1389,7 @@ 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()
|
||||
},
|
||||
)
|
||||
@@ -1338,6 +1402,10 @@ 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,
|
||||
Some(MultiAgentMode::Proactive)
|
||||
);
|
||||
let history = child_thread.codex.session.clone_history().await;
|
||||
|
||||
assert!(
|
||||
@@ -2185,7 +2253,7 @@ async fn spawn_thread_subagent_uses_role_specific_nickname_candidates() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resume_thread_subagent_restores_stored_nickname_and_role() {
|
||||
async fn resume_thread_subagent_restores_stored_metadata_and_effective_multi_agent_mode() {
|
||||
let (home, mut config) = test_config().await;
|
||||
config
|
||||
.features
|
||||
@@ -2207,7 +2275,7 @@ async fn resume_thread_subagent_restores_stored_nickname_and_role() {
|
||||
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");
|
||||
|
||||
@@ -2232,6 +2300,38 @@ async fn resume_thread_subagent_restores_stored_nickname_and_role() {
|
||||
.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
|
||||
.ensure_rollout_materialized()
|
||||
.await;
|
||||
child_thread
|
||||
.codex
|
||||
.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");
|
||||
let mut status_rx = harness
|
||||
.control
|
||||
.subscribe_status(child_thread_id)
|
||||
@@ -2320,6 +2420,10 @@ async fn resume_thread_subagent_restores_stored_nickname_and_role() {
|
||||
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,
|
||||
Some(MultiAgentMode::Proactive)
|
||||
);
|
||||
|
||||
let _ = harness
|
||||
.control
|
||||
|
||||
@@ -122,6 +122,7 @@ 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??;
|
||||
|
||||
@@ -106,7 +106,7 @@ fn build_multi_agent_mode_update_item(
|
||||
&next.config.multi_agent_v2,
|
||||
&next.session_source,
|
||||
next.multi_agent_mode,
|
||||
next.features.enabled(Feature::MultiAgentMode),
|
||||
next.config.features.enabled(Feature::MultiAgentMode),
|
||||
);
|
||||
let previous = previous?;
|
||||
if previous.multi_agent_mode == effective_multi_agent_mode {
|
||||
|
||||
@@ -178,6 +178,7 @@ 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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -445,6 +445,7 @@ 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(
|
||||
@@ -529,6 +530,7 @@ 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();
|
||||
@@ -583,7 +585,7 @@ impl Codex {
|
||||
.await;
|
||||
let multi_agent_version =
|
||||
resolve_multi_agent_version(&conversation_history, inherited_multi_agent_version);
|
||||
let multi_agent_mode = conversation_history.get_multi_agent_mode();
|
||||
let multi_agent_mode = initial_multi_agent_mode;
|
||||
config
|
||||
.validate_multi_agent_v2_config()
|
||||
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
|
||||
@@ -3249,7 +3251,10 @@ impl Session {
|
||||
&turn_context.config.multi_agent_v2,
|
||||
&session_source,
|
||||
turn_context.multi_agent_mode,
|
||||
turn_context.features.enabled(Feature::MultiAgentMode),
|
||||
turn_context
|
||||
.config
|
||||
.features
|
||||
.enabled(Feature::MultiAgentMode),
|
||||
) {
|
||||
items.push(ContextualUserFragment::into(
|
||||
MultiAgentModeInstructions::new(multi_agent_mode),
|
||||
|
||||
@@ -738,6 +738,7 @@ 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");
|
||||
|
||||
@@ -388,7 +388,7 @@ impl TurnContext {
|
||||
&self.config.multi_agent_v2,
|
||||
&self.session_source,
|
||||
self.multi_agent_mode,
|
||||
self.features.enabled(Feature::MultiAgentMode),
|
||||
self.config.features.enabled(Feature::MultiAgentMode),
|
||||
),
|
||||
realtime_active: Some(self.realtime_active),
|
||||
effort: self.reasoning_effort.clone(),
|
||||
|
||||
@@ -36,6 +36,7 @@ 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;
|
||||
@@ -184,6 +185,7 @@ 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,
|
||||
@@ -607,6 +609,7 @@ impl ThreadManager {
|
||||
thread_source: None,
|
||||
dynamic_tools,
|
||||
metrics_service_name: None,
|
||||
multi_agent_mode: None,
|
||||
parent_trace: None,
|
||||
environments,
|
||||
thread_extension_init: ExtensionDataInit::default(),
|
||||
@@ -646,6 +649,7 @@ 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,
|
||||
@@ -730,6 +734,7 @@ 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,
|
||||
@@ -741,6 +746,7 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
parent_trace,
|
||||
@@ -773,6 +779,7 @@ 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(),
|
||||
@@ -799,6 +806,7 @@ 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,
|
||||
@@ -810,6 +818,7 @@ impl ThreadManager {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
/*parent_trace*/ None,
|
||||
@@ -960,18 +969,25 @@ impl ThreadManager {
|
||||
) -> CodexResult<NewThread> {
|
||||
// `forked_from_id()` describes this history's existing lineage. When
|
||||
// forking a resumed thread, the child copies the resumed thread itself.
|
||||
let forked_from_thread_id = match &history {
|
||||
let source_thread_id = match &history {
|
||||
InitialHistory::Resumed(resumed) => Some(resumed.conversation_id),
|
||||
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) => 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(
|
||||
&history,
|
||||
/*session_source*/ None,
|
||||
/*parent_thread_id*/ None,
|
||||
forked_from_thread_id,
|
||||
source_thread_id,
|
||||
&config,
|
||||
)
|
||||
.await;
|
||||
@@ -989,10 +1005,11 @@ impl ThreadManager {
|
||||
Arc::clone(&self.state.auth_manager),
|
||||
agent_control,
|
||||
/*parent_thread_id*/ None,
|
||||
forked_from_thread_id,
|
||||
source_thread_id,
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
parent_trace,
|
||||
environments,
|
||||
/*thread_extension_init*/ ExtensionDataInit::default(),
|
||||
@@ -1217,6 +1234,7 @@ 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,
|
||||
@@ -1234,6 +1252,7 @@ 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>>,
|
||||
@@ -1252,6 +1271,7 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
metrics_service_name,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1279,6 +1299,7 @@ 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,
|
||||
@@ -1290,6 +1311,7 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1311,6 +1333,7 @@ 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>>,
|
||||
@@ -1329,6 +1352,7 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
Vec::new(),
|
||||
/*metrics_service_name*/ None,
|
||||
initial_multi_agent_mode,
|
||||
inherited_environments,
|
||||
inherited_exec_policy,
|
||||
/*parent_trace*/ None,
|
||||
@@ -1353,6 +1377,7 @@ 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,
|
||||
@@ -1370,6 +1395,7 @@ impl ThreadManagerState {
|
||||
thread_source,
|
||||
dynamic_tools,
|
||||
metrics_service_name,
|
||||
initial_multi_agent_mode,
|
||||
/*inherited_environments*/ None,
|
||||
/*inherited_exec_policy*/ None,
|
||||
parent_trace,
|
||||
@@ -1394,6 +1420,7 @@ 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>,
|
||||
@@ -1473,6 +1500,7 @@ 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,6 +322,7 @@ 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(),
|
||||
@@ -462,6 +463,7 @@ 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"),
|
||||
@@ -477,6 +479,7 @@ 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"),
|
||||
@@ -569,6 +572,7 @@ 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(),
|
||||
@@ -852,6 +856,7 @@ 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,6 +132,7 @@ 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,6 +24,7 @@ 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;
|
||||
@@ -1147,6 +1148,11 @@ 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");
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentMode)
|
||||
.expect("test config should allow feature update");
|
||||
turn.multi_agent_mode = Some(MultiAgentMode::Proactive);
|
||||
set_turn_config(&mut turn, config);
|
||||
|
||||
let session = Arc::new(session);
|
||||
@@ -1185,6 +1191,10 @@ 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,
|
||||
Some(MultiAgentMode::Proactive)
|
||||
);
|
||||
assert!(manager.captured_ops().iter().any(|(id, op)| {
|
||||
*id == child_thread_id
|
||||
&& matches!(
|
||||
|
||||
@@ -50,6 +50,15 @@ 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.config.multi_agent_v2,
|
||||
&turn.session_source,
|
||||
turn.multi_agent_mode,
|
||||
turn.config
|
||||
.features
|
||||
.enabled(codex_features::Feature::MultiAgentMode),
|
||||
);
|
||||
let role_name = args
|
||||
.agent_type
|
||||
.as_deref()
|
||||
@@ -134,6 +143,7 @@ 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,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user