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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user