mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
Inherit parent config for forked agent spawns
Keep context-forked child agents on the parent turn config by restoring the fork config after role validation. Non-forked spawns still honor requested model/reasoning and role TOML. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -61,17 +61,23 @@ impl ToolHandler for Handler {
|
||||
.await;
|
||||
let mut config =
|
||||
build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?;
|
||||
apply_requested_spawn_agent_model_overrides(
|
||||
&session,
|
||||
turn.as_ref(),
|
||||
&mut config,
|
||||
args.model.as_deref(),
|
||||
args.reasoning_effort,
|
||||
)
|
||||
.await?;
|
||||
let forked_config = args.fork_context.then(|| config.clone());
|
||||
if !args.fork_context {
|
||||
apply_requested_spawn_agent_model_overrides(
|
||||
&session,
|
||||
turn.as_ref(),
|
||||
&mut config,
|
||||
args.model.as_deref(),
|
||||
args.reasoning_effort,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
apply_role_to_config(&mut config, role_name)
|
||||
.await
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
if let Some(forked_config) = forked_config {
|
||||
config = forked_config;
|
||||
}
|
||||
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
|
||||
apply_spawn_agent_overrides(&mut config, child_depth);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::*;
|
||||
use crate::CodexThread;
|
||||
use crate::ThreadManager;
|
||||
use crate::codex::make_session_and_context;
|
||||
use crate::config::AgentRoleConfig;
|
||||
use crate::config::DEFAULT_AGENT_MAX_DEPTH;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::session_prefix::format_subagent_notification_message;
|
||||
@@ -28,6 +29,7 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -1638,6 +1640,93 @@ async fn multi_agent_v2_spawn_omits_agent_id_when_named() {
|
||||
assert_eq!(success, Some(true));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multi_agent_v2_forked_spawn_inherits_parent_config_instead_of_spawn_overrides() {
|
||||
let (mut session, mut turn) = make_session_and_context().await;
|
||||
let manager = thread_manager();
|
||||
let root = manager
|
||||
.start_thread((*turn.config).clone())
|
||||
.await
|
||||
.expect("root thread should start");
|
||||
session.services.agent_control = manager.agent_control();
|
||||
session.conversation_id = root.thread_id;
|
||||
|
||||
let parent_developer_instructions = "parent developer instructions".to_string();
|
||||
let role_developer_instructions = "role developer instructions";
|
||||
let role_name = "fork_config_override_role".to_string();
|
||||
tokio::fs::create_dir_all(&turn.config.codex_home)
|
||||
.await
|
||||
.expect("codex home should be created");
|
||||
let role_config_path = turn.config.codex_home.join("fork-role.toml");
|
||||
tokio::fs::write(
|
||||
&role_config_path,
|
||||
format!(
|
||||
"model_reasoning_effort = \"low\"\ndeveloper_instructions = \"{role_developer_instructions}\"\n",
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("role config should be written");
|
||||
|
||||
let mut config = (*turn.config).clone();
|
||||
config
|
||||
.features
|
||||
.enable(Feature::MultiAgentV2)
|
||||
.expect("test config should allow feature update");
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::High);
|
||||
config.developer_instructions = Some(parent_developer_instructions.clone());
|
||||
config.agent_roles.insert(
|
||||
role_name.clone(),
|
||||
AgentRoleConfig {
|
||||
description: Some("role that would override forked parent config".to_string()),
|
||||
config_file: Some(role_config_path),
|
||||
nickname_candidates: None,
|
||||
},
|
||||
);
|
||||
turn.reasoning_effort = Some(ReasoningEffort::High);
|
||||
turn.developer_instructions = Some(parent_developer_instructions.clone());
|
||||
turn.config = Arc::new(config);
|
||||
|
||||
SpawnAgentHandlerV2
|
||||
.handle(invocation(
|
||||
Arc::new(session),
|
||||
Arc::new(turn),
|
||||
"spawn_agent",
|
||||
function_payload(json!({
|
||||
"message": "inspect this repo",
|
||||
"task_name": "worker",
|
||||
"agent_type": role_name,
|
||||
"model": "not-a-real-model",
|
||||
"reasoning_effort": "low",
|
||||
"fork_turns": "all"
|
||||
})),
|
||||
))
|
||||
.await
|
||||
.expect("forked spawn should ignore requested model config");
|
||||
|
||||
let child_thread_id = manager
|
||||
.captured_ops()
|
||||
.into_iter()
|
||||
.map(|(thread_id, _)| thread_id)
|
||||
.find(|thread_id| *thread_id != root.thread_id)
|
||||
.expect("spawned agent should receive an op");
|
||||
let child_thread = manager
|
||||
.get_thread(child_thread_id)
|
||||
.await
|
||||
.expect("child thread should exist");
|
||||
let child_config = child_thread.codex.session.get_config().await;
|
||||
let child_developer_instructions = child_config
|
||||
.developer_instructions
|
||||
.as_deref()
|
||||
.expect("forked child should have developer instructions");
|
||||
|
||||
assert_eq!(
|
||||
child_config.model_reasoning_effort,
|
||||
Some(ReasoningEffort::High)
|
||||
);
|
||||
assert!(child_developer_instructions.contains(&parent_developer_instructions));
|
||||
assert!(!child_developer_instructions.contains(role_developer_instructions));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors() {
|
||||
let (mut session, mut turn) = make_session_and_context().await;
|
||||
|
||||
@@ -70,17 +70,23 @@ impl ToolHandler for Handler {
|
||||
.await;
|
||||
let mut config =
|
||||
build_agent_spawn_config(&session.get_base_instructions().await, turn.as_ref())?;
|
||||
apply_requested_spawn_agent_model_overrides(
|
||||
&session,
|
||||
turn.as_ref(),
|
||||
&mut config,
|
||||
args.model.as_deref(),
|
||||
args.reasoning_effort,
|
||||
)
|
||||
.await?;
|
||||
let forked_config = fork_mode.is_some().then(|| config.clone());
|
||||
if fork_mode.is_none() {
|
||||
apply_requested_spawn_agent_model_overrides(
|
||||
&session,
|
||||
turn.as_ref(),
|
||||
&mut config,
|
||||
args.model.as_deref(),
|
||||
args.reasoning_effort,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
apply_role_to_config(&mut config, role_name)
|
||||
.await
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
if let Some(forked_config) = forked_config {
|
||||
config = forked_config;
|
||||
}
|
||||
apply_spawn_agent_runtime_overrides(&mut config, turn.as_ref())?;
|
||||
apply_spawn_agent_overrides(&mut config, child_depth);
|
||||
config.developer_instructions = Some(
|
||||
|
||||
Reference in New Issue
Block a user