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:
Shijie Rao
2026-06-19 10:50:44 +02:00
committed by GitHub
parent fc8c6b7384
commit 7abfcf220b
46 changed files with 620 additions and 36 deletions
@@ -2,6 +2,7 @@ use super::*;
use crate::error_code::method_not_found;
use codex_app_server_protocol::SelectedCapabilityRoot;
use codex_extension_api::ExtensionDataInit;
use codex_protocol::config_types::MultiAgentMode;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
@@ -902,6 +903,7 @@ impl ThreadRequestProcessor {
mock_experimental_field: _mock_experimental_field,
experimental_raw_events,
personality,
multi_agent_mode,
ephemeral,
session_start_source,
thread_source,
@@ -955,6 +957,7 @@ impl ThreadRequestProcessor {
supports_openai_form_elicitation,
config,
typesafe_overrides,
multi_agent_mode,
dynamic_tools,
selected_capability_roots.unwrap_or_default(),
session_start_source,
@@ -1029,6 +1032,7 @@ impl ThreadRequestProcessor {
supports_openai_form_elicitation: bool,
config_overrides: Option<HashMap<String, serde_json::Value>>,
typesafe_overrides: ConfigOverrides,
multi_agent_mode: Option<MultiAgentMode>,
dynamic_tools: Option<Vec<DynamicToolSpec>>,
selected_capability_roots: Vec<SelectedCapabilityRoot>,
session_start_source: Option<codex_app_server_protocol::ThreadStartSource>,
@@ -1152,6 +1156,7 @@ impl ThreadRequestProcessor {
thread_source,
dynamic_tools,
metrics_service_name: service_name,
multi_agent_mode,
parent_trace: request_trace,
environments,
thread_extension_init,
@@ -1257,6 +1262,7 @@ impl ThreadRequestProcessor {
sandbox,
active_permission_profile,
reasoning_effort: config_snapshot.reasoning_effort,
multi_agent_mode: config_snapshot.multi_agent_mode,
};
let notif = thread_started_notification(thread);
listener_task_context
@@ -2787,6 +2793,7 @@ impl ThreadRequestProcessor {
sandbox,
active_permission_profile,
reasoning_effort: session_configured.reasoning_effort,
multi_agent_mode: config_snapshot.multi_agent_mode,
initial_turns_page,
};
@@ -3508,6 +3515,7 @@ impl ThreadRequestProcessor {
sandbox,
active_permission_profile,
reasoning_effort: session_configured.reasoning_effort,
multi_agent_mode: config_snapshot.multi_agent_mode,
};
let notif = thread_started_notification(thread);