Skip to content

Commit 4c07dd4

Browse files
authored
Configure multi_agent_v2 spawn agent hints (#17071)
Allow multi_agent_v2 features to have its own temporary configuration under `[features.multi_agent_v2]` ``` [features.multi_agent_v2] enabled = true usage_hint_enabled = false usage_hint_text = "Custom delegation guidance." hide_spawn_agent_metadata = true ``` Absent `usage_hint_text` means use the default hint. ``` [features] multi_agent_v2 = true ``` still works as the boolean shorthand.
1 parent 2250fdd commit 4c07dd4

File tree

18 files changed

+501
-62
lines changed

18 files changed

+501
-62
lines changed

codex-rs/config/src/schema.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
2525
if feature.id == codex_features::Feature::Artifact {
2626
continue;
2727
}
28+
if feature.id == codex_features::Feature::MultiAgentV2 {
29+
validation.properties.insert(
30+
feature.key.to_string(),
31+
schema_gen.subschema_for::<codex_features::FeatureToml<
32+
codex_features::MultiAgentV2ConfigToml,
33+
>>(),
34+
);
35+
continue;
36+
}
2837
validation
2938
.properties
3039
.insert(feature.key.to_string(), schema_gen.subschema_for::<bool>());

codex-rs/core/config.schema.json

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,6 @@
362362
"connectors": {
363363
"type": "boolean"
364364
},
365-
"debug_hide_spawn_agent_metadata": {
366-
"type": "boolean"
367-
},
368365
"default_mode_request_user_input": {
369366
"type": "boolean"
370367
},
@@ -426,7 +423,7 @@
426423
"type": "boolean"
427424
},
428425
"multi_agent_v2": {
429-
"type": "boolean"
426+
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
430427
},
431428
"personality": {
432429
"type": "boolean"
@@ -621,6 +618,16 @@
621618
},
622619
"type": "object"
623620
},
621+
"FeatureToml_for_MultiAgentV2ConfigToml": {
622+
"anyOf": [
623+
{
624+
"type": "boolean"
625+
},
626+
{
627+
"$ref": "#/definitions/MultiAgentV2ConfigToml"
628+
}
629+
]
630+
},
624631
"FeedbackConfigToml": {
625632
"additionalProperties": false,
626633
"properties": {
@@ -980,6 +987,24 @@
980987
],
981988
"type": "object"
982989
},
990+
"MultiAgentV2ConfigToml": {
991+
"additionalProperties": false,
992+
"properties": {
993+
"enabled": {
994+
"type": "boolean"
995+
},
996+
"hide_spawn_agent_metadata": {
997+
"type": "boolean"
998+
},
999+
"usage_hint_enabled": {
1000+
"type": "boolean"
1001+
},
1002+
"usage_hint_text": {
1003+
"type": "string"
1004+
}
1005+
},
1006+
"type": "object"
1007+
},
9831008
"NetworkDomainPermissionToml": {
9841009
"enum": [
9851010
"allow",
@@ -2075,9 +2100,6 @@
20752100
"connectors": {
20762101
"type": "boolean"
20772102
},
2078-
"debug_hide_spawn_agent_metadata": {
2079-
"type": "boolean"
2080-
},
20812103
"default_mode_request_user_input": {
20822104
"type": "boolean"
20832105
},
@@ -2139,7 +2161,7 @@
21392161
"type": "boolean"
21402162
},
21412163
"multi_agent_v2": {
2142-
"type": "boolean"
2164+
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
21432165
},
21442166
"personality": {
21452167
"type": "boolean"

codex-rs/core/src/codex.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,9 @@ impl TurnContext {
964964
.with_web_search_config(self.tools_config.web_search_config.clone())
965965
.with_allow_login_shell(self.tools_config.allow_login_shell)
966966
.with_has_environment(self.tools_config.has_environment)
967+
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
968+
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
969+
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
967970
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
968971
&config.agent_roles,
969972
));
@@ -1488,6 +1491,9 @@ impl Session {
14881491
.with_web_search_config(per_turn_config.web_search_config.clone())
14891492
.with_allow_login_shell(per_turn_config.permissions.allow_login_shell)
14901493
.with_has_environment(environment.is_some())
1494+
.with_spawn_agent_usage_hint(per_turn_config.multi_agent_v2.usage_hint_enabled)
1495+
.with_spawn_agent_usage_hint_text(per_turn_config.multi_agent_v2.usage_hint_text.clone())
1496+
.with_hide_spawn_agent_metadata(per_turn_config.multi_agent_v2.hide_spawn_agent_metadata)
14911497
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
14921498
&per_turn_config.agent_roles,
14931499
));
@@ -5676,6 +5682,9 @@ async fn spawn_review_thread(
56765682
.with_web_search_config(/*web_search_config*/ None)
56775683
.with_allow_login_shell(config.permissions.allow_login_shell)
56785684
.with_has_environment(parent_turn_context.environment.is_some())
5685+
.with_spawn_agent_usage_hint(config.multi_agent_v2.usage_hint_enabled)
5686+
.with_spawn_agent_usage_hint_text(config.multi_agent_v2.usage_hint_text.clone())
5687+
.with_hide_spawn_agent_metadata(config.multi_agent_v2.hide_spawn_agent_metadata)
56795688
.with_agent_type_description(crate::agent::role::spawn_tool_spec::build(
56805689
&config.agent_roles,
56815690
));

codex-rs/core/src/config/config_tests.rs

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,7 @@ fn feature_table_overrides_legacy_flags() -> std::io::Result<()> {
17461746
let mut entries = BTreeMap::new();
17471747
entries.insert("apply_patch_freeform".to_string(), false);
17481748
let cfg = ConfigToml {
1749-
features: Some(FeaturesToml { entries }),
1749+
features: Some(FeaturesToml::from(entries)),
17501750
..Default::default()
17511751
};
17521752

@@ -1794,7 +1794,7 @@ fn responses_websocket_features_do_not_change_wire_api() -> std::io::Result<()>
17941794
let mut entries = BTreeMap::new();
17951795
entries.insert(feature_key.to_string(), true);
17961796
let cfg = ConfigToml {
1797-
features: Some(FeaturesToml { entries }),
1797+
features: Some(FeaturesToml::from(entries)),
17981798
..Default::default()
17991799
};
18001800

@@ -3005,14 +3005,14 @@ async fn set_feature_enabled_updates_profile() -> anyhow::Result<()> {
30053005
profile
30063006
.features
30073007
.as_ref()
3008-
.and_then(|features| features.entries.get("guardian_approval")),
3009-
Some(&true),
3008+
.and_then(|features| features.entries().get("guardian_approval").copied()),
3009+
Some(true),
30103010
);
30113011
assert_eq!(
30123012
parsed
30133013
.features
30143014
.as_ref()
3015-
.and_then(|features| features.entries.get("guardian_approval")),
3015+
.and_then(|features| features.entries().get("guardian_approval").copied()),
30163016
None,
30173017
);
30183018

@@ -3047,14 +3047,14 @@ async fn set_feature_enabled_persists_default_false_feature_disable_in_profile()
30473047
profile
30483048
.features
30493049
.as_ref()
3050-
.and_then(|features| features.entries.get("guardian_approval")),
3051-
Some(&false),
3050+
.and_then(|features| features.entries().get("guardian_approval").copied()),
3051+
Some(false),
30523052
);
30533053
assert_eq!(
30543054
parsed
30553055
.features
30563056
.as_ref()
3057-
.and_then(|features| features.entries.get("guardian_approval")),
3057+
.and_then(|features| features.entries().get("guardian_approval").copied()),
30583058
None,
30593059
);
30603060

@@ -3087,15 +3087,15 @@ async fn set_feature_enabled_profile_disable_overrides_root_enable() -> anyhow::
30873087
parsed
30883088
.features
30893089
.as_ref()
3090-
.and_then(|features| features.entries.get("guardian_approval")),
3091-
Some(&true),
3090+
.and_then(|features| features.entries().get("guardian_approval").copied()),
3091+
Some(true),
30923092
);
30933093
assert_eq!(
30943094
profile
30953095
.features
30963096
.as_ref()
3097-
.and_then(|features| features.entries.get("guardian_approval")),
3098-
Some(&false),
3097+
.and_then(|features| features.entries().get("guardian_approval").copied()),
3098+
Some(false),
30993099
);
31003100

31013101
Ok(())
@@ -4520,6 +4520,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
45204520
use_experimental_unified_exec_tool: !cfg!(windows),
45214521
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
45224522
ghost_snapshot: GhostSnapshotConfig::default(),
4523+
multi_agent_v2: MultiAgentV2Config::default(),
45234524
features: Features::with_defaults().into(),
45244525
suppress_unstable_features_warning: false,
45254526
active_profile: Some("o3".to_string()),
@@ -4665,6 +4666,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
46654666
use_experimental_unified_exec_tool: !cfg!(windows),
46664667
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
46674668
ghost_snapshot: GhostSnapshotConfig::default(),
4669+
multi_agent_v2: MultiAgentV2Config::default(),
46684670
features: Features::with_defaults().into(),
46694671
suppress_unstable_features_warning: false,
46704672
active_profile: Some("gpt3".to_string()),
@@ -4808,6 +4810,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
48084810
use_experimental_unified_exec_tool: !cfg!(windows),
48094811
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
48104812
ghost_snapshot: GhostSnapshotConfig::default(),
4813+
multi_agent_v2: MultiAgentV2Config::default(),
48114814
features: Features::with_defaults().into(),
48124815
suppress_unstable_features_warning: false,
48134816
active_profile: Some("zdr".to_string()),
@@ -4937,6 +4940,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
49374940
use_experimental_unified_exec_tool: !cfg!(windows),
49384941
background_terminal_max_timeout: DEFAULT_MAX_BACKGROUND_TERMINAL_TIMEOUT_MS,
49394942
ghost_snapshot: GhostSnapshotConfig::default(),
4943+
multi_agent_v2: MultiAgentV2Config::default(),
49404944
features: Features::with_defaults().into(),
49414945
suppress_unstable_features_warning: false,
49424946
active_profile: Some("gpt5".to_string()),
@@ -6096,6 +6100,71 @@ smart_approvals = true
60966100
Ok(())
60976101
}
60986102

6103+
#[tokio::test]
6104+
async fn multi_agent_v2_config_from_feature_table() -> std::io::Result<()> {
6105+
let codex_home = TempDir::new()?;
6106+
std::fs::write(
6107+
codex_home.path().join(CONFIG_TOML_FILE),
6108+
r#"[features.multi_agent_v2]
6109+
enabled = true
6110+
usage_hint_enabled = false
6111+
usage_hint_text = "Custom delegation guidance."
6112+
hide_spawn_agent_metadata = true
6113+
"#,
6114+
)?;
6115+
6116+
let config = ConfigBuilder::without_managed_config_for_tests()
6117+
.codex_home(codex_home.path().to_path_buf())
6118+
.fallback_cwd(Some(codex_home.path().to_path_buf()))
6119+
.build()
6120+
.await?;
6121+
6122+
assert!(config.features.enabled(Feature::MultiAgentV2));
6123+
assert!(!config.multi_agent_v2.usage_hint_enabled);
6124+
assert_eq!(
6125+
config.multi_agent_v2.usage_hint_text.as_deref(),
6126+
Some("Custom delegation guidance.")
6127+
);
6128+
assert!(config.multi_agent_v2.hide_spawn_agent_metadata);
6129+
6130+
Ok(())
6131+
}
6132+
6133+
#[tokio::test]
6134+
async fn profile_multi_agent_v2_config_overrides_base() -> std::io::Result<()> {
6135+
let codex_home = TempDir::new()?;
6136+
std::fs::write(
6137+
codex_home.path().join(CONFIG_TOML_FILE),
6138+
r#"profile = "no_hint"
6139+
6140+
[features.multi_agent_v2]
6141+
usage_hint_enabled = true
6142+
usage_hint_text = "base hint"
6143+
hide_spawn_agent_metadata = true
6144+
6145+
[profiles.no_hint.features.multi_agent_v2]
6146+
usage_hint_enabled = false
6147+
usage_hint_text = "profile hint"
6148+
hide_spawn_agent_metadata = false
6149+
"#,
6150+
)?;
6151+
6152+
let config = ConfigBuilder::without_managed_config_for_tests()
6153+
.codex_home(codex_home.path().to_path_buf())
6154+
.fallback_cwd(Some(codex_home.path().to_path_buf()))
6155+
.build()
6156+
.await?;
6157+
6158+
assert!(!config.multi_agent_v2.usage_hint_enabled);
6159+
assert_eq!(
6160+
config.multi_agent_v2.usage_hint_text.as_deref(),
6161+
Some("profile hint")
6162+
);
6163+
assert!(!config.multi_agent_v2.hide_spawn_agent_metadata);
6164+
6165+
Ok(())
6166+
}
6167+
60996168
#[tokio::test]
61006169
async fn feature_requirements_normalize_runtime_feature_mutations() -> std::io::Result<()> {
61016170
let codex_home = TempDir::new()?;

codex-rs/core/src/config/managed_features.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,9 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
202202
let mut explicit_settings = Vec::new();
203203

204204
if let Some(features) = cfg.features.as_ref() {
205-
for (key, enabled) in &features.entries {
206-
if let Some(feature) = feature_for_key(key) {
207-
explicit_settings.push((format!("features.{key}"), feature, *enabled));
205+
for (key, enabled) in features.entries() {
206+
if let Some(feature) = feature_for_key(&key) {
207+
explicit_settings.push((format!("features.{key}"), feature, enabled));
208208
}
209209
}
210210
}
@@ -224,12 +224,12 @@ fn explicit_feature_settings_in_config(cfg: &ConfigToml) -> Vec<(String, Feature
224224
}
225225
for (profile_name, profile) in &cfg.profiles {
226226
if let Some(features) = profile.features.as_ref() {
227-
for (key, enabled) in &features.entries {
228-
if let Some(feature) = feature_for_key(key) {
227+
for (key, enabled) in features.entries() {
228+
if let Some(feature) = feature_for_key(&key) {
229229
explicit_settings.push((
230230
format!("profiles.{profile_name}.features.{key}"),
231231
feature,
232-
*enabled,
232+
enabled,
233233
));
234234
}
235235
}

0 commit comments

Comments
 (0)