Preserve multi-agent settings across config representations (#35656)

## Why

`features.multi_agent_v2` can be represented as either a legacy boolean toggle
or a table with an `enabled` field and nested settings. Layering or editing
configs that mix these forms could replace one form with the other and discard
the enabled state or nested settings.

## What changed

- Normalize boolean toggles to the table's `enabled` field when merging config
  layers, applying CLI overrides, and editing user or profile config.
- Preserve nested multi-agent settings when toggling the feature, while keeping
  ordinary replacement semantics for unrelated paths.
- Attribute normalized `enabled` values to the layer that supplied the boolean
  toggle so config write results report overrides correctly.

## Testing

Added coverage for layered config, CLI overrides, config edits, app-server
writes, and origin metadata using both root and profile feature paths.

GitOrigin-RevId: 38b248c949b9ea5d6340a73d754f91c1834ac486
This commit is contained in:
Adam Perry @ OpenAI authored and copyberry committed 2026-07-27 18:41:09 +00:00
1 parent 6b23635a7e
commit 2f19a57704
9 files changed
+560 -8

No files matched your search

+7
View File
@@ -1,4 +1,5 @@
use crate::ConfigLayerMetadata;
use crate::merge::is_multi_agent_v2_feature_path;
use serde_json::Value as JsonValue;
use sha2::Digest;
use sha2::Sha256;
@@ -28,6 +29,12 @@ pub(super) fn record_origins(
}
_ => {
if !path.is_empty() {
if matches!(value, TomlValue::Boolean(_)) && is_multi_agent_v2_feature_path(path) {
path.push("enabled".to_string());
origins.insert(path.join("."), meta.clone());
path.pop();
return;
}
origins.insert(path.join("."), meta.clone());
}
}
+30
View File
@@ -58,9 +58,39 @@ pub fn merge_toml_values(base: &mut TomlValue, overlay: &TomlValue) {
merge_toml_values_at_path(base, overlay, &mut Vec::new());
}
pub(crate) fn is_multi_agent_v2_feature_path<S: AsRef<str>>(path: &[S]) -> bool {
match path {
[features, feature] => {
features.as_ref() == "features" && feature.as_ref() == "multi_agent_v2"
}
[profiles, _, features, feature] => {
profiles.as_ref() == "profiles"
&& features.as_ref() == "features"
&& feature.as_ref() == "multi_agent_v2"
}
_ => false,
}
}
fn merge_toml_values_at_path(base: &mut TomlValue, overlay: &TomlValue, path: &mut Vec<String>) {
replace_shell_environment_policy_filter_representation(base, overlay, path);
if is_multi_agent_v2_feature_path(path) {
if let TomlValue::Boolean(enabled) = base
&& overlay.is_table()
{
*base = TomlValue::Table(toml::map::Map::from_iter([(
"enabled".to_string(),
TomlValue::Boolean(*enabled),
)]));
} else if let TomlValue::Table(table) = base
&& let TomlValue::Boolean(enabled) = overlay
{
table.insert("enabled".to_string(), TomlValue::Boolean(*enabled));
return;
}
}
if let TomlValue::Table(overlay_table) = overlay
&& let TomlValue::Table(base_table) = base
{
+111
View File
@@ -161,6 +161,117 @@ max_concurrent_threads_per_session = 7
assert_eq!(base, expected);
}
/// Feature tables added above legacy toggles retain the lower layer's enabled state.
#[test]
fn merge_multi_agent_v2_table_preserves_legacy_boolean_toggle() {
for feature_path in ["features", "profiles.work.features"] {
let mut base = parse_toml(&format!("[{feature_path}]\nmulti_agent_v2 = true\n"));
let overlay = parse_toml(&format!(
"[{feature_path}.multi_agent_v2]\nsubagent_usage_hint_text = \"Delegate carefully.\"\n",
));
merge_toml_values(&mut base, &overlay);
assert_eq!(
base,
parse_toml(&format!(
"[{feature_path}.multi_agent_v2]\nenabled = true\nsubagent_usage_hint_text = \"Delegate carefully.\"\n",
))
);
}
}
/// Legacy feature toggles update enabled state without discarding nested configuration.
#[test]
fn merge_multi_agent_v2_boolean_preserves_existing_feature_table() {
for feature_path in ["features", "profiles.work.features"] {
let mut base = parse_toml(&format!(
"[{feature_path}.multi_agent_v2]\nenabled = true\nsubagent_usage_hint_text = \"Delegate carefully.\"\n",
));
let overlay = parse_toml(&format!("[{feature_path}]\nmulti_agent_v2 = false\n"));
merge_toml_values(&mut base, &overlay);
assert_eq!(
base,
parse_toml(&format!(
"[{feature_path}.multi_agent_v2]\nenabled = false\nsubagent_usage_hint_text = \"Delegate carefully.\"\n",
))
);
}
}
/// Opaque desktop settings retain ordinary scalar/table replacement semantics.
#[test]
fn merge_multi_agent_v2_compatibility_excludes_opaque_desktop_paths() {
let cases = [
(
"[desktop.features.multi_agent_v2]\nenabled = true\n",
"[desktop.features]\nmulti_agent_v2 = false\n",
"[desktop.features]\nmulti_agent_v2 = false\n",
),
(
"[desktop.features]\nmulti_agent_v2 = true\n",
"[desktop.features.multi_agent_v2]\ncustom = true\n",
"[desktop.features.multi_agent_v2]\ncustom = true\n",
),
];
for (base, overlay, expected) in cases {
let mut base = parse_toml(base);
merge_toml_values(&mut base, &parse_toml(overlay));
assert_eq!(base, parse_toml(expected));
}
}
/// CLI overrides preserve the multi-agent toggle and nested options in either ordering.
#[test]
fn multi_agent_v2_cli_overrides_preserve_boolean_and_nested_configuration() {
for feature_path in ["features", "profiles.work.features"] {
let instructions = (
format!("{feature_path}.multi_agent_v2.subagent_usage_hint_text"),
TomlValue::String("Delegate carefully.".to_string()),
);
let enabled = (
format!("{feature_path}.multi_agent_v2"),
TomlValue::Boolean(true),
);
let feature_table = (
format!("{feature_path}.multi_agent_v2"),
parse_toml("subagent_usage_hint_text = \"Delegate carefully.\"\n"),
);
let expected = parse_toml(&format!(
"[{feature_path}.multi_agent_v2]\nenabled = true\nsubagent_usage_hint_text = \"Delegate carefully.\"\n",
));
for overrides in [
vec![enabled.clone(), instructions.clone()],
vec![instructions, enabled.clone()],
vec![enabled.clone(), feature_table.clone()],
vec![feature_table, enabled],
] {
assert_eq!(crate::build_cli_overrides_layer(&overrides), expected);
}
}
}
/// Repeated opaque desktop overrides continue to replace their previous value.
#[test]
fn multi_agent_v2_cli_compatibility_excludes_opaque_desktop_paths() {
let path = "desktop.features.multi_agent_v2".to_string();
let enabled = (path.clone(), TomlValue::Boolean(true));
let feature_table = (path, parse_toml("custom = true\n"));
assert_eq!(
crate::build_cli_overrides_layer(&[enabled.clone(), feature_table.clone()]),
parse_toml("[desktop.features.multi_agent_v2]\ncustom = true\n")
);
assert_eq!(
crate::build_cli_overrides_layer(&[feature_table, enabled]),
parse_toml("[desktop.features]\nmulti_agent_v2 = true\n")
);
}
#[test]
fn merge_toml_values_normalizes_permission_network_domains_before_overlaying() {
let mut base = parse_toml(
+44
View File
@@ -1,3 +1,5 @@
use crate::merge::is_multi_agent_v2_feature_path;
use crate::merge::merge_toml_values;
use toml::Value as TomlValue;
pub(crate) fn default_empty_table() -> TomlValue {
@@ -18,13 +20,47 @@ fn apply_toml_override(root: &mut TomlValue, path: &str, value: TomlValue) {
let mut current = root;
let mut segments_iter = path.split('.').peekable();
let mut traversed_segments = Vec::new();
while let Some(segment) = segments_iter.next() {
traversed_segments.push(segment);
let is_last = segments_iter.peek().is_none();
if is_last {
match current {
TomlValue::Table(table) => {
if is_multi_agent_v2_feature_path(&traversed_segments)
&& let Some(existing) = table.get_mut(segment)
{
match (&mut *existing, &value) {
(TomlValue::Table(feature), TomlValue::Boolean(enabled)) => {
feature.insert("enabled".to_string(), TomlValue::Boolean(*enabled));
return;
}
(TomlValue::Boolean(enabled), TomlValue::Table(_)) => {
*existing = TomlValue::Table(Table::from_iter([(
"enabled".to_string(),
TomlValue::Boolean(*enabled),
)]));
merge_toml_values(existing, &value);
return;
}
(TomlValue::Table(_), TomlValue::Table(_)) => {
merge_toml_values(existing, &value);
return;
}
(
TomlValue::String(_)
| TomlValue::Integer(_)
| TomlValue::Float(_)
| TomlValue::Boolean(_)
| TomlValue::Datetime(_)
| TomlValue::Array(_)
| TomlValue::Table(_),
_,
) => {}
}
}
table.insert(segment.to_string(), value);
}
_ => {
@@ -41,6 +77,14 @@ fn apply_toml_override(root: &mut TomlValue, path: &str, value: TomlValue) {
current = table
.entry(segment.to_string())
.or_insert_with(|| TomlValue::Table(Table::new()));
if is_multi_agent_v2_feature_path(&traversed_segments)
&& let TomlValue::Boolean(enabled) = current
{
*current = TomlValue::Table(Table::from_iter([(
"enabled".to_string(),
TomlValue::Boolean(*enabled),
)]));
}
}
_ => {
*current = TomlValue::Table(Table::new());
+39
View File
@@ -39,6 +39,45 @@ no_memories_if_mcp_or_web_search = true
);
}
/// Legacy feature toggles own the semantic enabled leaf after layered merging.
#[test]
fn origins_attribute_multi_agent_v2_enabled_to_overriding_boolean_layer() {
let temp_dir = TempDir::new().expect("tempdir");
let user_layer = ConfigLayerEntry::new(
ConfigLayerSource::User {
file: test_user_config_path(&temp_dir, "config.toml"),
profile: None,
},
toml::from_str(
"[features.multi_agent_v2]\nenabled = true\nsubagent_usage_hint_text = \"keep\"\n",
)
.expect("user config"),
);
let user_metadata = user_layer.metadata();
let session_layer = ConfigLayerEntry::new(
ConfigLayerSource::SessionFlags,
toml::from_str("[features]\nmulti_agent_v2 = false\n").expect("session config"),
);
let session_metadata = session_layer.metadata();
let stack = ConfigLayerStack::new(
vec![user_layer, session_layer],
ConfigRequirements::default(),
ConfigRequirementsToml::default(),
)
.expect("layer stack should be valid");
let origins = stack.origins();
assert_eq!(
origins.get("features.multi_agent_v2.enabled"),
Some(&session_metadata)
);
assert_eq!(
origins.get("features.multi_agent_v2.subagent_usage_hint_text"),
Some(&user_metadata)
);
}
#[test]
fn enabled_layers_validate_shell_environment_policy() {
let layer = ConfigLayerEntry::new(