Track multi-agent v2 spawn calls in analytics (#39722)

## What changed

- Emit started and completed collaboration tool events for multi-agent v2
  `spawn_agent` calls, including failed invocations.
- Record execution duration and successful child-agent configuration metadata
  without including the spawn prompt.
- Deduplicate collaboration and subagent activity items by call ID when
  calculating per-turn subagent tool counts.

## Testing

- Extend the app-server multi-agent v2 integration test to cover successful
  and failed spawn telemetry, duration, prompt omission, and turn counts.

GitOrigin-RevId: c1c2fb9cc2a1a9b0f63ef68509ad36d11a92d1e5
This commit is contained in:
jif authored and copyberry committed 2026-08-20 14:33:55 +00:00
1 parent 1674b0a130
commit 9894a14c81
4 files changed
+182 -11

No files matched your search

@@ -5,11 +5,15 @@ use crate::agent::next_thread_spawn_depth;
use crate::agent::role::DEFAULT_ROLE_NAME;
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;
use crate::tools::handlers::multi_agents_spec::SpawnAgentToolOptions;
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_protocol::AgentPath;
use codex_protocol::ThreadId;
use codex_protocol::protocol::MultiAgentVersion;
use codex_tools::ToolSpec;
@@ -34,13 +38,66 @@ impl ToolExecutor<ToolInvocation> for Handler {
}
fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> {
Box::pin(async move { handle_spawn_agent(invocation).await.map(boxed_tool_output) })
Box::pin(async move {
let analytics = invocation.session.services.analytics_events_client.clone();
let sender_thread_id = invocation.session.thread_id;
let turn_id = invocation.step_context.turn.sub_id.clone();
let call_id = invocation.call_id.clone();
let started_at_ms = now_unix_timestamp_ms();
let result = handle_spawn_agent(invocation).await;
let completed_at_ms = now_unix_timestamp_ms();
let (status, receiver_thread_ids, agents_states) = match &result {
Ok((_, thread_id, agent_status, _)) => (
collab_tool_call_status(agent_status, Some(*thread_id)),
vec![*thread_id],
[(*thread_id, agent_status.clone())].into_iter().collect(),
),
Err(_) => (
CollabAgentToolCallStatus::Failed,
Vec::new(),
Default::default(),
),
};
let agent_snapshot = result
.as_ref()
.ok()
.and_then(|(_, _, _, snapshot)| snapshot.as_ref());
analytics.track_collab_tool_call(
turn_id,
CollabAgentToolCallItem {
id: call_id,
tool: CollabAgentTool::SpawnAgent,
status,
sender_thread_id,
receiver_thread_ids,
receiver_agents: Vec::new(),
prompt: None,
model: agent_snapshot.map(|snapshot| snapshot.model.clone()),
reasoning_effort: agent_snapshot
.and_then(|snapshot| snapshot.reasoning_effort.clone()),
agents_states,
},
started_at_ms,
completed_at_ms,
);
result.map(|(output, _, _, _)| boxed_tool_output(output))
})
}
}
async fn handle_spawn_agent(
invocation: ToolInvocation,
) -> Result<SpawnAgentResult, FunctionCallError> {
) -> Result<
(
SpawnAgentResult,
ThreadId,
AgentStatus,
Option<ThreadConfigSnapshot>,
),
FunctionCallError,
> {
let ToolInvocation {
session,
step_context,
@@ -163,6 +220,7 @@ async fn handle_spawn_agent(
.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
@@ -192,14 +250,15 @@ async fn handle_spawn_agent(
let task_name = String::from(new_agent_path);
let hide_agent_metadata = turn.config.multi_agent_v2.hide_spawn_agent_metadata;
if hide_agent_metadata {
Ok(SpawnAgentResult::HiddenMetadata { task_name })
let output = if hide_agent_metadata {
SpawnAgentResult::HiddenMetadata { task_name }
} else {
Ok(SpawnAgentResult::WithNickname {
SpawnAgentResult::WithNickname {
task_name,
nickname,
})
}
}
};
Ok((output, new_thread_id, agent_status, agent_snapshot))
}
impl CoreToolRuntime for Handler {