From 8758db5d5bf9f3d98bff595f38f6124d0174a2a8 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Tue, 24 Feb 2026 11:33:00 +0000 Subject: [PATCH] feat: mutli agents persist config overrides (#12667) Fix propagation of runtime config changes and `--yolo` --- .../core/src/tools/handlers/multi_agents.rs | 94 ++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/tools/handlers/multi_agents.rs b/codex-rs/core/src/tools/handlers/multi_agents.rs index 20be209fb..090810d9f 100644 --- a/codex-rs/core/src/tools/handlers/multi_agents.rs +++ b/codex-rs/core/src/tools/handlers/multi_agents.rs @@ -153,6 +153,7 @@ mod spawn { apply_role_to_config(&mut config, role_name) .await .map_err(FunctionCallError::RespondToModel)?; + apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?; apply_spawn_agent_overrides(&mut config, child_depth); let result = session @@ -922,6 +923,16 @@ fn build_agent_shared_config( config.model_reasoning_summary = turn.reasoning_summary; config.developer_instructions = turn.developer_instructions.clone(); config.compact_prompt = turn.compact_prompt.clone(); + apply_spawn_agent_runtime_overrides(&mut config, turn)?; + apply_spawn_agent_overrides(&mut config, child_depth); + + Ok(config) +} + +fn apply_spawn_agent_runtime_overrides( + config: &mut Config, + turn: &TurnContext, +) -> Result<(), FunctionCallError> { config.permissions.shell_environment_policy = turn.shell_environment_policy.clone(); config.codex_linux_sandbox_exe = turn.codex_linux_sandbox_exe.clone(); config.cwd = turn.cwd.clone(); @@ -932,9 +943,7 @@ fn build_agent_shared_config( .map_err(|err| { FunctionCallError::RespondToModel(format!("sandbox_policy is invalid: {err}")) })?; - apply_spawn_agent_overrides(&mut config, child_depth); - - Ok(config) + Ok(()) } fn apply_spawn_agent_overrides(config: &mut Config, child_depth: i32) { @@ -1165,6 +1174,85 @@ mod tests { ); } + #[tokio::test] + async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() { + fn pick_allowed_sandbox_policy( + constraint: &crate::config::Constrained, + base: SandboxPolicy, + ) -> SandboxPolicy { + let candidates = [ + SandboxPolicy::DangerFullAccess, + SandboxPolicy::new_workspace_write_policy(), + SandboxPolicy::new_read_only_policy(), + ]; + candidates + .into_iter() + .find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok()) + .unwrap_or(base) + } + + #[derive(Debug, Deserialize)] + struct SpawnAgentResult { + agent_id: String, + nickname: Option, + } + + let (mut session, mut turn) = make_session_and_context().await; + let manager = thread_manager(); + session.services.agent_control = manager.agent_control(); + let expected_sandbox = pick_allowed_sandbox_policy( + &turn.config.permissions.sandbox_policy, + turn.config.permissions.sandbox_policy.get().clone(), + ); + turn.sandbox_policy + .set(expected_sandbox.clone()) + .expect("sandbox policy should be set"); + assert_ne!( + expected_sandbox, + turn.config.permissions.sandbox_policy.get().clone(), + "test requires a runtime sandbox override that differs from base config" + ); + + let invocation = invocation( + Arc::new(session), + Arc::new(turn), + "spawn_agent", + function_payload(json!({ + "message": "await this command", + "agent_type": "awaiter" + })), + ); + let output = MultiAgentHandler + .handle(invocation) + .await + .expect("spawn_agent should succeed"); + let ToolOutput::Function { + body: FunctionCallOutputBody::Text(content), + .. + } = output + else { + panic!("expected function output"); + }; + let result: SpawnAgentResult = + serde_json::from_str(&content).expect("spawn_agent result should be json"); + let agent_id = agent_id(&result.agent_id).expect("agent_id should be valid"); + assert!( + result + .nickname + .as_deref() + .is_some_and(|nickname| !nickname.is_empty()) + ); + + let snapshot = manager + .get_thread(agent_id) + .await + .expect("spawned agent thread should exist") + .config_snapshot() + .await; + assert_eq!(snapshot.sandbox_policy, expected_sandbox); + assert_eq!(snapshot.approval_policy, AskForApproval::Never); + } + #[tokio::test] async fn spawn_agent_rejects_when_depth_limit_exceeded() { let (mut session, mut turn) = make_session_and_context().await;