mirror of
https://github.com/openai/codex.git
synced 2026-10-21 12:10:06 +00:00
Route multi-agent spawning through SpawnRequest (#47235)
## What changed - Route both multi-agent spawn handlers through `LocalAgentControl::spawn`, accepting user input or an agent message. - Centralize spawn message attribution and canonical path validation, and require message input to use `TriggerTurn`. - Return the child's configuration snapshot alongside `LiveAgent`, so handlers obtain effective settings directly from spawning without a separate lookup. - Update child test fixtures to use the shared spawn entry point. GitOrigin-RevId: 9f9278df08041a62215f9ec3d38688c78fc5e0e2
This commit is contained in:
@@ -10,7 +10,6 @@ use crate::agent::types::AgentMetadata;
|
||||
use crate::agent::types::LiveAgent;
|
||||
use crate::agent_communication::AgentCommunicationContext;
|
||||
use crate::agent_communication::AgentCommunicationKind;
|
||||
use crate::codex_thread::ThreadConfigSnapshot;
|
||||
use crate::config::Config;
|
||||
use crate::config::RolloutBudgetConfig;
|
||||
use crate::context::SubagentNotification;
|
||||
@@ -80,6 +79,7 @@ mod residency;
|
||||
mod sender_context;
|
||||
mod service_tier;
|
||||
mod spawn;
|
||||
mod spawn_request;
|
||||
mod target;
|
||||
mod user_authorization;
|
||||
|
||||
@@ -425,16 +425,6 @@ impl LocalAgentControl {
|
||||
Ok(thread_ids)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_agent_config_snapshot(
|
||||
&self,
|
||||
agent_id: ThreadId,
|
||||
) -> Option<ThreadConfigSnapshot> {
|
||||
match self.inspect_agent(agent_id).await.ok()? {
|
||||
crate::agent::api::AgentInfo::Loaded { config, .. } => Some(*config),
|
||||
crate::agent::api::AgentInfo::Unloaded(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe to status updates for `agent_id`, yielding the latest value and changes.
|
||||
pub(crate) async fn subscribe_status(
|
||||
&self,
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::agent::types::SpawnAgentForkMode;
|
||||
use crate::agent::types::SpawnAgentOptions;
|
||||
use crate::agents_md_manager::SessionInstructions;
|
||||
use crate::codex_thread::CodexThread;
|
||||
use crate::codex_thread::ThreadConfigSnapshot;
|
||||
use crate::config::PermissionProfileSnapshot;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::CurrentTimeReminder;
|
||||
@@ -42,7 +43,7 @@ struct SpawnAgentThreadInheritance {
|
||||
/// provide user input directly, making an uncontextualized inter-agent communication
|
||||
/// unrepresentable.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum SpawnInitialInput {
|
||||
pub(super) enum SpawnInitialInput {
|
||||
UserInput(Vec<UserInput>),
|
||||
InterAgentCommunication(InterAgentCommunication, AgentCommunicationContext),
|
||||
}
|
||||
@@ -257,7 +258,7 @@ impl LocalAgentControl {
|
||||
initial_input: Vec<UserInput>,
|
||||
session_source: Option<SessionSource>,
|
||||
) -> CodexResult<ThreadId> {
|
||||
let spawned_agent = Box::pin(self.spawn_agent_internal(
|
||||
let (spawned_agent, _) = Box::pin(self.spawn_agent_internal(
|
||||
config,
|
||||
SpawnInitialInput::UserInput(initial_input),
|
||||
session_source,
|
||||
@@ -267,40 +268,6 @@ impl LocalAgentControl {
|
||||
Ok(spawned_agent.thread_id)
|
||||
}
|
||||
|
||||
/// Spawn an agent thread with some metadata.
|
||||
pub(crate) async fn spawn_agent_with_metadata(
|
||||
&self,
|
||||
config: Config,
|
||||
initial_input: Vec<UserInput>,
|
||||
session_source: Option<SessionSource>,
|
||||
options: SpawnAgentOptions, // TODO(jif) drop with new fork.
|
||||
) -> CodexResult<LiveAgent> {
|
||||
Box::pin(self.spawn_agent_internal(
|
||||
config,
|
||||
SpawnInitialInput::UserInput(initial_input),
|
||||
session_source,
|
||||
options,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn spawn_agent_with_communication(
|
||||
&self,
|
||||
config: Config,
|
||||
communication: InterAgentCommunication,
|
||||
context: AgentCommunicationContext,
|
||||
session_source: Option<SessionSource>,
|
||||
options: SpawnAgentOptions,
|
||||
) -> CodexResult<LiveAgent> {
|
||||
Box::pin(self.spawn_agent_internal(
|
||||
config,
|
||||
SpawnInitialInput::InterAgentCommunication(communication, context),
|
||||
session_source,
|
||||
options,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
fn validate_loaded_v2_child(
|
||||
&self,
|
||||
thread: &CodexThread,
|
||||
@@ -629,13 +596,13 @@ impl LocalAgentControl {
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_agent_internal(
|
||||
pub(super) async fn spawn_agent_internal(
|
||||
&self,
|
||||
config: Config,
|
||||
initial_input: SpawnInitialInput,
|
||||
session_source: Option<SessionSource>,
|
||||
options: SpawnAgentOptions,
|
||||
) -> CodexResult<LiveAgent> {
|
||||
) -> CodexResult<(LiveAgent, ThreadConfigSnapshot)> {
|
||||
let state = self.upgrade()?;
|
||||
let multi_agent_version = state
|
||||
.effective_multi_agent_version_for_spawn(
|
||||
@@ -829,11 +796,13 @@ impl LocalAgentControl {
|
||||
);
|
||||
}
|
||||
|
||||
Ok(LiveAgent {
|
||||
let agent = LiveAgent {
|
||||
thread_id: new_thread.thread_id,
|
||||
metadata: agent_metadata,
|
||||
status: self.get_status(new_thread.thread_id).await,
|
||||
})
|
||||
};
|
||||
let config = new_thread.thread.config_snapshot().await;
|
||||
Ok((agent, config))
|
||||
}
|
||||
|
||||
async fn spawn_forked_thread(
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//! Converts captured spawn input into the existing local startup operation.
|
||||
//! Agent messages keep their canonical paths and spawn attribution.
|
||||
|
||||
use super::LocalAgentControl;
|
||||
use super::spawn::SpawnInitialInput;
|
||||
use crate::agent::api::AgentInput;
|
||||
use crate::agent::api::SpawnRequest;
|
||||
use crate::agent::types::LiveAgent;
|
||||
use crate::agent::types::MessageDeliveryMode;
|
||||
use crate::agent_communication::AgentCommunicationContext;
|
||||
use crate::agent_communication::AgentCommunicationKind;
|
||||
use crate::codex_thread::ThreadConfigSnapshot;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
|
||||
impl LocalAgentControl {
|
||||
pub(crate) async fn spawn(
|
||||
&self,
|
||||
request: SpawnRequest,
|
||||
) -> CodexResult<(LiveAgent, ThreadConfigSnapshot)> {
|
||||
let SpawnRequest {
|
||||
caller,
|
||||
config,
|
||||
input,
|
||||
source,
|
||||
options,
|
||||
} = request;
|
||||
let input = match input {
|
||||
AgentInput::UserInput(input) => SpawnInitialInput::UserInput(input),
|
||||
AgentInput::Message { message, mode } => {
|
||||
if mode != MessageDeliveryMode::TriggerTurn {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"spawn input must start the child turn".to_string(),
|
||||
));
|
||||
}
|
||||
let recipient = source.get_agent_path().ok_or_else(|| {
|
||||
CodexErr::InvalidRequest(
|
||||
"spawned agent is missing a canonical task name".to_string(),
|
||||
)
|
||||
})?;
|
||||
let author = recipient
|
||||
.as_str()
|
||||
.rsplit_once('/')
|
||||
.and_then(|(parent, _)| AgentPath::try_from(parent).ok())
|
||||
.ok_or_else(|| {
|
||||
CodexErr::InvalidRequest("spawn input needs a child path".to_string())
|
||||
})?;
|
||||
SpawnInitialInput::InterAgentCommunication(
|
||||
message.into_communication(author, recipient, mode),
|
||||
AgentCommunicationContext::new(AgentCommunicationKind::Spawn, caller),
|
||||
)
|
||||
}
|
||||
};
|
||||
Box::pin(self.spawn_agent_internal(config, input, Some(source), options)).await
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ use crate::StateDbHandle;
|
||||
use crate::ThreadManager;
|
||||
use crate::agent::agent_status_from_event;
|
||||
use crate::agent::api::AgentInfo;
|
||||
use crate::agent::api::AgentInput;
|
||||
use crate::agent::api::AgentTarget;
|
||||
use crate::agent::api::SpawnRequest;
|
||||
use crate::agent::next_thread_spawn_depth;
|
||||
use crate::agent::types::AgentMessage;
|
||||
use crate::agent::types::LiveAgent;
|
||||
@@ -101,6 +103,30 @@ use tokio::time::timeout;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
impl LocalAgentControl {
|
||||
/// Create a child fixture through the production spawn entry point.
|
||||
pub(crate) async fn spawn_agent_with_metadata(
|
||||
&self,
|
||||
config: Config,
|
||||
initial_input: Vec<UserInput>,
|
||||
source: SessionSource,
|
||||
options: SpawnAgentOptions,
|
||||
) -> CodexResult<LiveAgent> {
|
||||
let caller = source
|
||||
.parent_thread_id()
|
||||
.expect("child fixture has a parent");
|
||||
self.spawn(SpawnRequest {
|
||||
caller,
|
||||
config,
|
||||
input: AgentInput::UserInput(initial_input),
|
||||
source,
|
||||
options,
|
||||
})
|
||||
.await
|
||||
.map(|(agent, _)| agent)
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_config_with_cli_overrides(
|
||||
mut cli_overrides: Vec<(String, TomlValue)>,
|
||||
) -> (TempDir, Config) {
|
||||
@@ -261,13 +287,13 @@ impl AgentControlHarness {
|
||||
.spawn_agent_with_metadata(
|
||||
self.config.clone(),
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
options,
|
||||
)
|
||||
.await
|
||||
@@ -826,7 +852,7 @@ async fn spawn_v2_reload_test_child(
|
||||
.spawn_agent_with_metadata(
|
||||
config,
|
||||
text_input("hello child"),
|
||||
Some(source),
|
||||
source,
|
||||
SpawnAgentOptions {
|
||||
parent_thread_id: Some(parent.session.thread_id),
|
||||
..Default::default()
|
||||
@@ -2243,13 +2269,13 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
.spawn_agent_with_metadata(
|
||||
child_config,
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
@@ -2368,13 +2394,13 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() {
|
||||
.spawn_agent_with_metadata(
|
||||
no_hint_child_config,
|
||||
text_input("child task without hints"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
@@ -2605,13 +2631,13 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(
|
||||
.spawn_agent_with_metadata(
|
||||
child_config,
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
@@ -2808,13 +2834,13 @@ async fn spawn_agent_full_fork_restores_instructions_after_compaction_discards_p
|
||||
.spawn_agent_with_metadata(
|
||||
child_config,
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
@@ -2992,13 +3018,13 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc
|
||||
.spawn_agent_with_metadata(
|
||||
child_config,
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.to_string()),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
@@ -3178,13 +3204,13 @@ async fn spawn_agent_fork_flushes_parent_rollout_before_loading_history() {
|
||||
.spawn_agent_with_metadata(
|
||||
harness.config.clone(),
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_mode: Some(SpawnAgentForkMode::FullHistory),
|
||||
@@ -3318,13 +3344,13 @@ async fn spawn_agent_fork_last_n_turns_keeps_only_recent_turns() {
|
||||
.spawn_agent_with_metadata(
|
||||
harness.config.clone(),
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id.clone()),
|
||||
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
|
||||
@@ -3458,13 +3484,13 @@ async fn spawn_agent_fork_last_n_turns_drops_parent_startup_prefix_when_under_li
|
||||
.spawn_agent_with_metadata(
|
||||
harness.config.clone(),
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
|
||||
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
|
||||
@@ -3594,13 +3620,13 @@ async fn spawn_agent_fork_last_n_turns_strips_parent_usage_hints() {
|
||||
.spawn_agent_with_metadata(
|
||||
child_config,
|
||||
text_input("child task"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some(parent_spawn_call_id),
|
||||
fork_mode: Some(SpawnAgentForkMode::LastNTurns(2)),
|
||||
@@ -4240,13 +4266,13 @@ async fn spawn_thread_subagents_persist_parent_originator_across_new_and_truncat
|
||||
.spawn_agent_with_metadata(
|
||||
harness.config.clone(),
|
||||
text_input("hello forked child"),
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: parent.thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: Some("explorer".to_string()),
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: Some("spawn-call-last-n".to_string()),
|
||||
fork_mode: Some(SpawnAgentForkMode::LastNTurns(1)),
|
||||
|
||||
@@ -385,13 +385,13 @@ async fn thread_id_generator_applies_to_roots_children_and_forks() {
|
||||
text: "child task".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 1,
|
||||
agent_path: None,
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
SpawnAgentOptions {
|
||||
parent_thread_id: Some(root.thread_id),
|
||||
..Default::default()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use crate::agent::api::AgentInput;
|
||||
use crate::agent::api::SpawnRequest;
|
||||
use crate::agent::child_config::SpawnConfigOptions;
|
||||
use crate::agent::child_config::SpawnConfigVersion;
|
||||
use crate::agent::child_config::prepare_agent_spawn_config;
|
||||
@@ -106,17 +108,18 @@ async fn handle_spawn_agent(
|
||||
.await
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
let config = prepared.config;
|
||||
let result = Box::pin(session.services.agent_control.spawn_agent_with_metadata(
|
||||
let result = Box::pin(session.services.agent_control.spawn(SpawnRequest {
|
||||
caller: session.thread_id,
|
||||
config,
|
||||
input_items,
|
||||
Some(thread_spawn_source(
|
||||
input: AgentInput::UserInput(input_items),
|
||||
source: thread_spawn_source(
|
||||
session.thread_id,
|
||||
&turn.session_source,
|
||||
child_depth,
|
||||
prepared.role_name.as_deref(),
|
||||
/*task_name*/ None,
|
||||
)?),
|
||||
SpawnAgentOptions {
|
||||
)?,
|
||||
options: SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: args.fork_context.then(|| call_id.clone()),
|
||||
fork_mode: args.fork_context.then_some(SpawnAgentForkMode::FullHistory),
|
||||
parent_thread_id: Some(session.thread_id),
|
||||
@@ -127,47 +130,22 @@ async fn handle_spawn_agent(
|
||||
multi_agent_v2_usage_hints: None,
|
||||
cyber_access_program: turn.cyber_access_program,
|
||||
},
|
||||
))
|
||||
}))
|
||||
.await
|
||||
.map_err(collab_spawn_error);
|
||||
let (new_thread_id, new_agent_metadata, status) = match &result {
|
||||
Ok(spawned_agent) => (
|
||||
Some(spawned_agent.thread_id),
|
||||
Some(spawned_agent.metadata.clone()),
|
||||
spawned_agent.status.clone(),
|
||||
),
|
||||
Err(_) => (None, None, AgentStatus::NotFound),
|
||||
let (new_thread_id, status) = match &result {
|
||||
Ok((spawned_agent, _)) => (Some(spawned_agent.thread_id), spawned_agent.status.clone()),
|
||||
Err(_) => (None, AgentStatus::NotFound),
|
||||
};
|
||||
let agent_snapshot = match new_thread_id {
|
||||
Some(thread_id) => {
|
||||
session
|
||||
.services
|
||||
.agent_control
|
||||
.get_agent_config_snapshot(thread_id)
|
||||
.await
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let (_new_agent_path, new_agent_nickname, new_agent_role) =
|
||||
match (&agent_snapshot, new_agent_metadata) {
|
||||
(Some(snapshot), _) => (
|
||||
snapshot.session_source.get_agent_path().map(String::from),
|
||||
snapshot.session_source.get_nickname(),
|
||||
snapshot.session_source.get_agent_role(),
|
||||
),
|
||||
(None, Some(metadata)) => (
|
||||
metadata.agent_path.map(String::from),
|
||||
metadata.agent_nickname,
|
||||
metadata.agent_role,
|
||||
),
|
||||
(None, None) => (None, None, None),
|
||||
};
|
||||
let agent_snapshot = result.as_ref().ok().map(|(_, config)| config);
|
||||
let new_agent_nickname =
|
||||
agent_snapshot.and_then(|snapshot| snapshot.session_source.get_nickname());
|
||||
let new_agent_role =
|
||||
agent_snapshot.and_then(|snapshot| snapshot.session_source.get_agent_role());
|
||||
let effective_model = agent_snapshot
|
||||
.as_ref()
|
||||
.map(|snapshot| snapshot.model.clone())
|
||||
.unwrap_or_else(|| args.model.clone().unwrap_or_default());
|
||||
let effective_reasoning_effort = agent_snapshot
|
||||
.as_ref()
|
||||
.and_then(|snapshot| snapshot.reasoning_effort.clone())
|
||||
.unwrap_or(args.reasoning_effort.unwrap_or_default());
|
||||
let nickname = new_agent_nickname.clone();
|
||||
@@ -200,7 +178,8 @@ async fn handle_spawn_agent(
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let new_thread_id = result?.thread_id;
|
||||
let (spawned_agent, _) = result?;
|
||||
let new_thread_id = spawned_agent.thread_id;
|
||||
let role_tag = role_name.unwrap_or(DEFAULT_ROLE_NAME);
|
||||
turn.session_telemetry.counter(
|
||||
"codex.multi_agent.spawn",
|
||||
|
||||
@@ -1225,13 +1225,13 @@ async fn multi_agent_v2_send_message_accepts_root_target_from_child() {
|
||||
text: "inspect this repo".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 1,
|
||||
agent_path: Some(child_path.clone()),
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
crate::agent::types::SpawnAgentOptions::default(),
|
||||
)
|
||||
.await
|
||||
@@ -1301,13 +1301,13 @@ async fn multi_agent_v2_followup_task_rejects_root_target_from_child() {
|
||||
text: "inspect this repo".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 1,
|
||||
agent_path: Some(child_path.clone()),
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
crate::agent::types::SpawnAgentOptions::default(),
|
||||
)
|
||||
.await
|
||||
@@ -1466,13 +1466,13 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() {
|
||||
text: "research".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 1,
|
||||
agent_path: Some(researcher_path.clone()),
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
crate::agent::types::SpawnAgentOptions::default(),
|
||||
)
|
||||
.await
|
||||
@@ -1486,13 +1486,13 @@ async fn multi_agent_v2_list_agents_filters_by_relative_path_prefix() {
|
||||
text: "build".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 2,
|
||||
agent_path: Some(worker_path.clone()),
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
crate::agent::types::SpawnAgentOptions::default(),
|
||||
)
|
||||
.await
|
||||
@@ -4016,13 +4016,13 @@ async fn multi_agent_v2_interrupt_agent_rejects_self_target_by_id() {
|
||||
text: "inspect this repo".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 1,
|
||||
agent_path: Some(child_path.clone()),
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
crate::agent::types::SpawnAgentOptions::default(),
|
||||
)
|
||||
.await
|
||||
@@ -4083,13 +4083,13 @@ async fn multi_agent_v2_interrupt_agent_rejects_self_target_by_task_name() {
|
||||
text: "inspect this repo".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
Some(SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
|
||||
parent_thread_id: root.thread_id,
|
||||
depth: 1,
|
||||
agent_path: Some(child_path.clone()),
|
||||
agent_nickname: None,
|
||||
agent_role: None,
|
||||
})),
|
||||
}),
|
||||
crate::agent::types::SpawnAgentOptions::default(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use crate::agent::api::AgentInput;
|
||||
use crate::agent::api::SpawnRequest;
|
||||
use crate::agent::child_config::SpawnConfigOptions;
|
||||
use crate::agent::child_config::SpawnConfigVersion;
|
||||
use crate::agent::child_config::prepare_agent_spawn_config;
|
||||
@@ -7,8 +9,6 @@ use crate::agent::role::DEFAULT_ROLE_NAME;
|
||||
use crate::agent::types::MessageDeliveryMode;
|
||||
use crate::agent::types::SpawnAgentForkMode;
|
||||
use crate::agent::types::SpawnAgentOptions;
|
||||
use crate::agent_communication::AgentCommunicationContext;
|
||||
use crate::agent_communication::AgentCommunicationKind;
|
||||
use crate::codex_thread::ThreadConfigSnapshot;
|
||||
use crate::session::multi_agents::resolve_usage_hints;
|
||||
use crate::tools::handlers::multi_agents::collab_tool_call_status;
|
||||
@@ -17,7 +17,6 @@ use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v2;
|
||||
use crate::tools::handlers::multi_agents_v2::message_tool::message_content;
|
||||
use crate::turn_timing::now_unix_timestamp_ms;
|
||||
use codex_prompts::ResolvedModelMessages;
|
||||
use codex_protocol::AgentPath;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_tools::ToolSpec;
|
||||
@@ -73,10 +72,7 @@ impl ToolExecutor<ToolInvocation> for Handler {
|
||||
Default::default(),
|
||||
),
|
||||
};
|
||||
let agent_snapshot = result
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|(_, _, _, snapshot)| snapshot.as_ref());
|
||||
let agent_snapshot = result.as_ref().ok().map(|(_, _, _, snapshot)| snapshot);
|
||||
|
||||
analytics.track_collab_tool_call(
|
||||
turn_id,
|
||||
@@ -109,7 +105,7 @@ async fn handle_spawn_agent(
|
||||
SpawnAgentResult,
|
||||
ThreadId,
|
||||
AgentStatus,
|
||||
Option<ThreadConfigSnapshot>,
|
||||
ThreadConfigSnapshot,
|
||||
),
|
||||
FunctionCallError,
|
||||
> {
|
||||
@@ -161,16 +157,6 @@ async fn handle_spawn_agent(
|
||||
"spawned agent is missing a canonical task name".to_string(),
|
||||
)
|
||||
})?;
|
||||
let author = turn
|
||||
.session_source
|
||||
.get_agent_path()
|
||||
.unwrap_or_else(AgentPath::root);
|
||||
let communication = agent_message_from_tool(message, &source).into_communication(
|
||||
author,
|
||||
new_agent_path.clone(),
|
||||
MessageDeliveryMode::TriggerTurn,
|
||||
);
|
||||
let context = AgentCommunicationContext::new(AgentCommunicationKind::Spawn, session.thread_id);
|
||||
let multi_agent_v2_usage_hints =
|
||||
if is_full_history_fork && turn.multi_agent_version == MultiAgentVersion::V2 {
|
||||
let child_model_info = match config.model.as_deref() {
|
||||
@@ -195,40 +181,34 @@ async fn handle_spawn_agent(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let spawned_agent = Box::pin(
|
||||
session
|
||||
.services
|
||||
.agent_control
|
||||
.spawn_agent_with_communication(
|
||||
config,
|
||||
communication,
|
||||
context,
|
||||
Some(spawn_source),
|
||||
SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: fork_mode.as_ref().map(|_| call_id.clone()),
|
||||
fork_mode,
|
||||
parent_thread_id: Some(session.thread_id),
|
||||
parent_turn_id: Some(turn.sub_id.clone()),
|
||||
root_turn_id: turn.turn_metadata_state.root_turn_id(),
|
||||
turn_trigger: turn.turn_metadata_state.current_turn_trigger(),
|
||||
environments: Some(step_context.environments.to_selections()),
|
||||
multi_agent_v2_usage_hints,
|
||||
cyber_access_program: turn.cyber_access_program,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await
|
||||
.map_err(collab_spawn_error)?;
|
||||
let (spawned_agent, agent_snapshot) =
|
||||
Box::pin(session.services.agent_control.spawn(SpawnRequest {
|
||||
caller: session.thread_id,
|
||||
config,
|
||||
input: AgentInput::Message {
|
||||
message: agent_message_from_tool(message, &source),
|
||||
mode: MessageDeliveryMode::TriggerTurn,
|
||||
},
|
||||
source: spawn_source,
|
||||
options: SpawnAgentOptions {
|
||||
fork_parent_spawn_call_id: fork_mode.as_ref().map(|_| call_id.clone()),
|
||||
fork_mode,
|
||||
parent_thread_id: Some(session.thread_id),
|
||||
parent_turn_id: Some(turn.sub_id.clone()),
|
||||
root_turn_id: turn.turn_metadata_state.root_turn_id(),
|
||||
turn_trigger: turn.turn_metadata_state.current_turn_trigger(),
|
||||
environments: Some(step_context.environments.to_selections()),
|
||||
multi_agent_v2_usage_hints,
|
||||
cyber_access_program: turn.cyber_access_program,
|
||||
},
|
||||
}))
|
||||
.await
|
||||
.map_err(collab_spawn_error)?;
|
||||
let new_thread_id = spawned_agent.thread_id;
|
||||
let agent_status = spawned_agent.status;
|
||||
let agent_snapshot = session
|
||||
.services
|
||||
.agent_control
|
||||
.get_agent_config_snapshot(new_thread_id)
|
||||
.await;
|
||||
let nickname = agent_snapshot
|
||||
.as_ref()
|
||||
.and_then(|snapshot| snapshot.session_source.get_nickname())
|
||||
.session_source
|
||||
.get_nickname()
|
||||
.or(spawned_agent.metadata.agent_nickname);
|
||||
emit_sub_agent_activity(
|
||||
&session,
|
||||
|
||||
Reference in New Issue
Block a user