Support leaf models in multi-agent v2 (#36892)

## What changed

- Allow multi-agent v2 parents to spawn any visible model that has not explicitly disabled multi-agent support.
- Expose collaboration tools to child agents only when their selected model supports multi-agent v2, keeping legacy models as leaf workers.
- Preserve a worker's selected model when reloading it into residency.
- Propagate multi-agent capability metadata to Amazon Bedrock model entries so delegation is gated consistently.

## Testing

- Cover model selection, leaf-worker tool visibility, Bedrock capability handling, and model preservation after reload.

GitOrigin-RevId: 1457adb1a09806d1f0621311d5a42a6815b9dd4e
This commit is contained in:
jif authored and copyberry committed 2026-08-04 12:36:14 +00:00
1 parent 1669c2403f
commit 6d4d9442c7
11 files changed
+131 -61

No files matched your search

+13 -11
View File
@@ -100,17 +100,19 @@ async fn v2_nested_spawn_checks_shared_active_execution_capacity() -> Result<()>
)
.await;
let mut builder = test_codex().with_model("koffing").with_config(|config| {
config
.features
.enable(Feature::Collab)
.expect("test config should allow feature update");
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.max_concurrent_threads_per_session = 2;
});
let mut builder = test_codex()
.with_model("gpt-5.6-sol")
.with_config(|config| {
config
.features
.enable(Feature::Collab)
.expect("test config should allow feature update");
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
config.multi_agent_v2.max_concurrent_threads_per_session = 2;
});
let test = builder.build(&server).await?;
test.submit_turn(FIRST_PROMPT).await?;
@@ -47,7 +47,7 @@ const SIBLING_FOLLOWUP_TASK: &str = "verify the surviving worker";
const INTERRUPT_PROMPT: &str = "release the interrupted worker";
const SIBLING_NAME: &str = "survivor";
const ROLE_NAME: &str = "durable_worker";
const ROLE_MODEL: &str = "gpt-5.4";
const ROLE_MODEL: &str = "gpt-5.6-sol";
const ROLE_MODEL_PROVIDER_ID: &str = "mock";
const ROLE_DEVELOPER_INSTRUCTIONS: &str = "Keep the durable worker role configuration.";
const SUBAGENT_DEVELOPER_INSTRUCTIONS: &str = "Use the default durable worker instructions.";
@@ -1400,10 +1400,15 @@ async fn spawned_multi_agent_v2_child_inherits_parent_developer_context() -> Res
Ok(())
}
#[test_case(false; "encrypted")]
#[test_case(true; "plaintext")]
#[test_case(None, false; "encrypted")]
#[test_case(None, true; "plaintext")]
#[test_case(Some("gpt-5.6-luna"), false; "luna encrypted leaf")]
#[test_case(Some("gpt-5.5"), false; "legacy encrypted leaf")]
#[tokio::test]
async fn multi_agent_v2_spawn_sends_agent_message_to_child(plaintext: bool) -> Result<()> {
async fn multi_agent_v2_spawn_sends_agent_message_to_child(
model: Option<&str>,
plaintext: bool,
) -> Result<()> {
let output: &'static Mutex<Vec<u8>> = Box::leak(Box::new(Mutex::new(Vec::new())));
let subscriber = tracing_subscriber::fmt()
.with_ansi(false)
@@ -1418,10 +1423,17 @@ async fn multi_agent_v2_spawn_sends_agent_message_to_child(plaintext: bool) -> R
} else {
"opaque-encrypted-message"
};
let spawn_args = serde_json::to_string(&json!({
let mut spawn_args = json!({
"message": message,
"task_name": "worker",
}))?;
});
if let Some(model) = model {
spawn_args["model"] = json!(model);
if model == "gpt-5.5" {
spawn_args["fork_turns"] = json!("none");
}
}
let spawn_args = serde_json::to_string(&spawn_args)?;
let mut spawn_event = ev_function_call_with_namespace(
SPAWN_CALL_ID,
MULTI_AGENT_V2_NAMESPACE,
@@ -1463,7 +1475,12 @@ async fn multi_agent_v2_spawn_sends_agent_message_to_child(plaintext: bool) -> R
)
.await;
let mut builder = test_codex().with_model("koffing").with_config(|config| {
let parent_model = if model.is_some() {
"gpt-5.6-sol"
} else {
"koffing"
};
let mut builder = test_codex().with_model(parent_model).with_config(|config| {
config
.features
.enable(Feature::Collab)
@@ -1524,6 +1541,16 @@ async fn multi_agent_v2_spawn_sends_agent_message_to_child(plaintext: bool) -> R
"content": content,
})])
);
if let Some(model) = model {
assert_eq!(child_request.body_json()["model"], json!(model));
assert!(
!child_request
.body_json()
.to_string()
.contains("\"name\":\"collaboration\""),
"leaf workers must not receive collaboration tools",
);
}
if plaintext {
assert!(
parent_request_log.requests().into_iter().any(|request| {