Simplify multi-agent mode controls (#29324)

## Why

Multi-agent delegation policy was split across `multiAgentMode`,
`features.multi_agent_mode`, and `usage_hint_enabled`. These controls
could disagree: a requested mode could be downgraded by the feature
flag, and disabling usage hints also disabled mode instructions.

Some clients also need multi-agent tools without adding
delegation-policy text to model context. The previous two-mode API could
not express that directly.

## What changed

`multiAgentMode` is now the only live delegation-policy control:

| Mode | Behavior |
| --- | --- |
| `none` | Keep multi-agent tools available without adding mode
instructions. |
| `explicitRequestOnly` | Only delegate after an explicit user request.
|
| `proactive` | Delegate when parallel work materially improves speed or
quality. |

- new threads default to `explicitRequestOnly`; omitting the mode on
later turns keeps the current value
- thread start, resume, fork, and settings responses always report the
concrete current mode instead of `null`
- mode selection remains sticky across turns and resume
- usage-hint text no longer controls whether mode instructions apply
- `features.multi_agent_mode` and `usage_hint_enabled` remain accepted
as ignored compatibility settings so existing configs continue to load
- app-server documentation and generated schemas describe the three-mode
API

## Tests

- `just test -p codex-core multi_agent_mode`
- `just test -p codex-core multi_agent_v2_config_from_feature_table`
- `just test -p codex-core spawn_agent_description`
- `just test -p codex-features`
- `just test -p codex-app-server-protocol`
- `just test -p codex-app-server multi_agent_mode`
This commit is contained in:
jif authored and GitHub committed 2026-06-22 10:05:36 +02:00
1 parent 6d15bb3d17
commit c03742ca0a
44 files changed
+253 -350

No files matched your search

-39
View File
@@ -604,45 +604,6 @@ non_code_mode_only = true
);
}
#[test]
fn multi_agent_v2_feature_config_usage_hint_enabled_does_not_enable_feature() {
let features_toml: FeaturesToml = toml::from_str(
r#"
[multi_agent_v2]
usage_hint_enabled = false
"#,
)
.expect("features table should deserialize");
let features = Features::from_sources(
FeatureConfigSource {
features: Some(&features_toml),
..Default::default()
},
FeatureConfigSource::default(),
FeatureOverrides::default(),
);
assert_eq!(features.enabled(Feature::MultiAgentV2), false);
assert_eq!(features_toml.entries(), BTreeMap::new());
assert_eq!(
features_toml.multi_agent_v2,
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
enabled: None,
max_concurrent_threads_per_session: None,
min_wait_timeout_ms: None,
max_wait_timeout_ms: None,
default_wait_timeout_ms: None,
usage_hint_enabled: Some(false),
usage_hint_text: None,
root_agent_usage_hint_text: None,
subagent_usage_hint_text: None,
tool_namespace: None,
hide_spawn_agent_metadata: None,
non_code_mode_only: None,
}))
);
}
#[test]
fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config() {
let mut features = Features::with_defaults();