Deprecate the multi_agent feature flag (#59479)

## What changed

Mark `[features].multi_agent` as deprecated while keeping it enabled by default and preserving its control of the V1 fallback.

Emit a startup warning when a user or project configuration explicitly sets the flag. The warning points to `[agents].enabled = false` for disabling subagent tools, with an exception when `[features].multi_agent_v2` is enabled. Session-only overrides do not trigger the warning.

## Testing

Add configuration-loader coverage for explicit user and project settings, including a project setting overridden by a session flag, and for session-only overrides. Update the default-enabled feature check to allow deprecated features.

GitOrigin-RevId: 8bcac6ce5eac27528a2b362755e25d6a6e811c59
This commit is contained in:
jif
2026-11-04 19:57:59 +00:00
committed by copyberry
parent bd7cfa3f69
commit c9296fa1a9
4 changed files with 79 additions and 4 deletions
+15
View File
@@ -506,6 +506,21 @@ pub async fn load_config_layers_state(
ignore_user_and_project_exec_policy_rules,
);
config_layer_stack.is_projectless = is_projectless;
if config_layer_stack.layers_low_to_high().any(|layer| {
matches!(
layer.name,
ConfigLayerSource::User { .. } | ConfigLayerSource::Project { .. }
) && layer
.config
.get("features")
.and_then(|features| features.get("multi_agent"))
.and_then(TomlValue::as_bool)
.is_some()
}) {
startup_warnings.push(
"`[features].multi_agent` is deprecated. It still controls the V1 fallback. `[agents].enabled = false` disables subagent tools even when the model selects a subagent version, unless `[features].multi_agent_v2` is enabled.".to_string(),
);
}
startup_warnings.extend(ignored_config_warning(
&config_layer_stack,
&requirements_layers,
+57
View File
@@ -718,6 +718,63 @@ async fn missing_packaged_defaults_file_returns_an_error() {
);
}
#[tokio::test]
async fn multi_agent_warns_for_explicit_user_and_project_settings() -> std::io::Result<()> {
let home = tempdir()?;
let workspace = tempdir()?;
let cwd = AbsolutePathBuf::try_from(workspace.path())?;
let trust_key = TomlValue::String(project_trust_key(workspace.path()));
let project_config_dir = workspace.path().join(".codex");
std::fs::create_dir(&project_config_dir)?;
let session_flags = [(
"features.multi_agent".to_string(),
TomlValue::Boolean(false),
)];
for (user, project, flags, warn) in [
("", "", session_flags.as_slice(), false),
("[features]\nmulti_agent = false", "", &[], true),
(
"",
"[features]\nmulti_agent = true",
session_flags.as_slice(),
true,
),
] {
std::fs::write(
home.path().join(CONFIG_TOML_FILE),
format!("[projects.{trust_key}]\ntrust_level = 'trusted'\n{user}"),
)?;
std::fs::write(project_config_dir.join(CONFIG_TOML_FILE), project)?;
let stack = load_config_layers_state(
&TestFileSystem,
home.path(),
Some(cwd.clone()),
flags,
LoaderOverrides::without_managed_config_for_tests(),
&crate::NoopThreadConfigLoader,
)
.await?;
let warned = stack
.startup_warnings()
.unwrap_or_default()
.iter()
.any(|warning| {
warning.contains(
"`[features].multi_agent` is deprecated. It still controls the V1 fallback.",
)
});
assert_eq!(
(
stack.effective_config()["features"]["multi_agent"].as_bool(),
warned
),
(Some(false), warn),
"user={user:?}, project={project:?}"
);
}
Ok(())
}
#[cfg(windows)]
#[tokio::test]
async fn default_windows_managed_config_is_ignored_with_warning() {
+1 -1
View File
@@ -1630,7 +1630,7 @@ pub const FEATURES: &[FeatureSpec] = &[
FeatureSpec {
id: Feature::Collab,
key: "multi_agent",
stage: Stage::Stable,
stage: Stage::Deprecated,
default_enabled: true,
},
FeatureSpec {
+6 -3
View File
@@ -142,13 +142,16 @@ fn codex_apps_mcp_protocol_can_be_enabled_independently_of_generic_mcp() {
}
#[test]
fn default_enabled_features_are_stable() {
fn default_enabled_features_have_shipped() {
for spec in crate::FEATURES {
// Instant interrupts are enabled by default while retaining their development stage.
if spec.default_enabled && spec.id != Feature::InstantInterrupt {
assert!(
matches!(spec.stage, Stage::Stable | Stage::Removed),
"feature `{}` is enabled by default but is not stable/removed ({:?})",
matches!(
spec.stage,
Stage::Stable | Stage::Deprecated | Stage::Removed
),
"feature `{}` is enabled by default at stage {:?}",
spec.key,
spec.stage
);