Add per-turn multi-agent mode (#28685)

## Why

Multi-agent v2 currently carries an explicit-request-only delegation
rule in its static usage hint. That provides a safe default, but it
prevents clients from selecting proactive delegation per turn without
changing static guidance or rewriting prior model context.

This change makes delegation mode a session selection that can be
updated through `turn/start`, while deriving the effective model-visible
mode separately for each turn. Eligible multi-agent v2 turns remain
explicit-request-only unless proactive mode is both selected and
enabled.

## What changed

- Add the experimental `turn/start.multiAgentMode` parameter with
`explicitRequestOnly` and `proactive` values. Omission retains the
loaded session's current optional selection.
- Add the default-off `features.multi_agent_mode` feature gate. Eligible
multi-agent v2 turns use the selected mode when enabled; an unset
selection or disabled gate resolves to `explicitRequestOnly`.
- Treat mode prompting as inapplicable for multi-agent v1 and other
unsupported session configurations, producing no multi-agent mode
developer message rather than rejecting the turn.
- Move the explicit-request-only rule out of the static v2 usage hint
and into a bounded, tagged developer context fragment.
- Emit the effective mode in initial context and only when that
effective mode changes on later turns.
- Persist the effective mode in `TurnContextItem` as the durable
baseline for resume and context-update comparisons.

Historical rollout items are not rewritten. Later mode developer
messages establish the current rule incrementally.

## Not covered

- Initial selection through `thread/start` and selected-mode reporting
from thread lifecycle/settings APIs; those are isolated in the stacked
#28792.
- A TUI control or slash command for selecting the mode.
- Persisting a preferred mode to `config.toml`; selection remains
session/turn scoped.
- Changes to multi-agent concurrency limits, tool availability, or model
catalog capability declarations.
- Rewriting historical rollout prompt items. Cold resume restores the
latest persisted effective mode when available while leaving historical
developer messages intact.

## Verification

- `CARGO_INCREMENTAL=0 just test -p codex-core multi_agent_mode`
- Focused app-server coverage verifies that `turn/start.multiAgentMode`
produces proactive developer instructions for an eligible v2 turn.

## Stack

Followed by #28792, which adds `thread/start` initialization and
lifecycle/settings observability.
This commit is contained in:
Shijie Rao
2026-06-18 22:47:51 -07:00
committed by GitHub
parent f886e33e5a
commit fc8c6b7384
41 changed files with 779 additions and 8 deletions
@@ -3774,6 +3774,7 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
summary: None,
output_schema: None,
collaboration_mode: None,
multi_agent_mode: None,
personality: None,
};
let serialized_without_override =
@@ -3781,6 +3782,29 @@ fn turn_start_params_preserve_explicit_null_service_tier() {
assert_eq!(serialized_without_override.get("serviceTier"), None);
}
#[test]
fn turn_start_params_round_trip_multi_agent_mode() {
let params: TurnStartParams = serde_json::from_value(json!({
"threadId": "thread_123",
"input": [],
"multiAgentMode": "proactive"
}))
.expect("params should deserialize");
assert_eq!(
params.multi_agent_mode,
Some(codex_protocol::config_types::MultiAgentMode::Proactive)
);
assert_eq!(
crate::experimental_api::ExperimentalApi::experimental_reason(&params),
Some("turn/start.multiAgentMode")
);
assert_eq!(
serde_json::to_value(params).expect("params should serialize")["multiAgentMode"],
"proactive"
);
}
#[test]
fn thread_settings_update_params_preserve_explicit_null_service_tier() {
let params: ThreadSettingsUpdateParams = serde_json::from_value(json!({
@@ -4,6 +4,7 @@ use super::SandboxPolicy;
use super::Turn;
use codex_experimental_api_macros::ExperimentalApi;
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::models::ImageDetail;
@@ -149,6 +150,12 @@ pub struct TurnStartParams {
#[experimental("turn/start.collaborationMode")]
#[ts(optional = nullable)]
pub collaboration_mode: Option<CollaborationMode>,
/// Controls whether multi-agent v2 delegation requires an explicit user request.
/// Omitted keeps the loaded session's current mode.
#[experimental("turn/start.multiAgentMode")]
#[ts(optional = nullable)]
pub multi_agent_mode: Option<MultiAgentMode>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]