Codex Handbook
rollout-trace/src/reducer/tool/agents.rs 809 lines
use anyhow::Context;use anyhow::Result;use anyhow::bail;use codex_protocol::protocol::CollabAgentInteractionBeginEvent;use codex_protocol::protocol::CollabAgentInteractionEndEvent;use codex_protocol::protocol::CollabAgentSpawnEndEvent;use codex_protocol::protocol::CollabCloseBeginEvent;use codex_protocol::protocol::CollabCloseEndEvent;use codex_protocol::protocol::InterAgentCommunication;use codex_protocol::protocol::SubAgentActivityEvent;use codex_protocol::protocol::SubAgentActivityKind;use serde::Deserialize;use serde_json::Value;use super::super::TraceReducer;use crate::model::ConversationItem;use crate::model::ConversationItemKind;use crate::model::ConversationPart;use crate::model::ConversationRole;use crate::model::InteractionEdge;use crate::model::InteractionEdgeKind;use crate::model::ToolCallKind;use crate::model::TraceAnchor;use crate::payload::RawPayloadRef;/// Agent delivery edge waiting for the recipient-side conversation item.////// Multi-agent v2 records the sender tool before the target thread necessarily/// includes the delivered mailbox message in a model-visible request. The edge/// stays pending so it can target that exact conversation item when possible.pub(in crate::reducer) struct PendingAgentInteractionEdge {    pub(in crate::reducer) edge_id: String,    pub(in crate::reducer) kind: InteractionEdgeKind,    pub(in crate::reducer) source: TraceAnchor,    pub(in crate::reducer) target_thread_id: String,    pub(in crate::reducer) message_author: String,    pub(in crate::reducer) message_content: String,    /// Spawn-only fallback for children that fail before their task message is model-visible.    pub(in crate::reducer) unresolved_spawn_thread_id: Option<String>,    pub(in crate::reducer) started_at_unix_ms: i64,    pub(in crate::reducer) ended_at_unix_ms: Option<i64>,    pub(in crate::reducer) carried_raw_payload_ids: Vec<String>,}/// Typed reducer input for a multi-agent v2 child completion notification.////// Child results are observed outside the normal tool lifecycle, but they still/// carry a parent-thread notification. This wrapper keeps the dispatcher from/// passing a positional bundle of thread and turn ids.pub(in crate::reducer) struct ObservedAgentResultEdge {    pub(in crate::reducer) wall_time_unix_ms: i64,    pub(in crate::reducer) edge_id: String,    pub(in crate::reducer) child_thread_id: String,    pub(in crate::reducer) child_codex_turn_id: String,    pub(in crate::reducer) parent_thread_id: String,    pub(in crate::reducer) message: String,    pub(in crate::reducer) carried_payload: Option<RawPayloadRef>,}#[derive(Deserialize)]struct AgentMessageInvocationArgs {    message: String,}/// Builds the stable edge id for the spawn relationship between two threads.pub(in crate::reducer) fn spawn_edge_id(parent_thread_id: &str, child_thread_id: &str) -> String {    format!("edge:spawn:{parent_thread_id}:{child_thread_id}")}impl TraceReducer {    /// Starts a multi-agent edge from a runtime begin payload, when the tool kind supports one.    pub(super) fn start_agent_interaction_from_runtime(        &mut self,        tool_call_id: &str,        runtime_payload: &RawPayloadRef,    ) -> Result<()> {        let kind = self            .rollout            .tool_calls            .get(tool_call_id)            .with_context(|| format!("agent edge referenced unknown tool call {tool_call_id}"))?            .kind            .clone();        match kind {            ToolCallKind::AssignAgentTask => {                let payload: CollabAgentInteractionBeginEvent =                    serde_json::from_value(self.read_payload_json(runtime_payload)?)?;                self.queue_message_agent_interaction(                    tool_call_id,                    InteractionEdgeKind::AssignAgentTask,                    payload.receiver_thread_id.to_string(),                    payload.prompt,                    /*ended_at_unix_ms*/ None,                )            }            ToolCallKind::SendMessage => {                let payload: CollabAgentInteractionBeginEvent =                    serde_json::from_value(self.read_payload_json(runtime_payload)?)?;                self.queue_message_agent_interaction(                    tool_call_id,                    InteractionEdgeKind::SendMessage,                    payload.receiver_thread_id.to_string(),                    payload.prompt,                    /*ended_at_unix_ms*/ None,                )            }            ToolCallKind::CloseAgent => {                let payload: CollabCloseBeginEvent =                    serde_json::from_value(self.read_payload_json(runtime_payload)?)?;                self.upsert_close_agent_interaction(                    tool_call_id,                    payload.receiver_thread_id.to_string(),                    /*ended_at_unix_ms*/ None,                )            }            ToolCallKind::ExecCommand            | ToolCallKind::WriteStdin            | ToolCallKind::ApplyPatch            | ToolCallKind::Mcp { .. }            | ToolCallKind::Web            | ToolCallKind::ImageGeneration            | ToolCallKind::SpawnAgent            | ToolCallKind::WaitAgent            | ToolCallKind::Other { .. } => Ok(()),        }    }    /// Ends or enriches a multi-agent edge from a runtime end payload.    pub(super) fn end_agent_interaction_from_runtime(        &mut self,        wall_time_unix_ms: i64,        tool_call_id: &str,        runtime_payload: &RawPayloadRef,    ) -> Result<()> {        let kind = self.rollout.tool_calls[tool_call_id].kind.clone();        let runtime_payload_json = self.read_payload_json(runtime_payload)?;        if runtime_payload_json.get("agent_thread_id").is_some() {            let payload: SubAgentActivityEvent = serde_json::from_value(runtime_payload_json)?;            return self.end_sub_agent_activity(wall_time_unix_ms, tool_call_id, &kind, &payload);        }        match kind {            ToolCallKind::SpawnAgent => {                let payload: CollabAgentSpawnEndEvent =                    serde_json::from_value(runtime_payload_json)?;                self.end_spawn_agent_interaction(wall_time_unix_ms, tool_call_id, &payload)            }            ToolCallKind::AssignAgentTask => {                let payload: CollabAgentInteractionEndEvent =                    serde_json::from_value(runtime_payload_json)?;                self.end_message_agent_interaction(                    wall_time_unix_ms,                    tool_call_id,                    InteractionEdgeKind::AssignAgentTask,                    &payload,                )            }            ToolCallKind::SendMessage => {                let payload: CollabAgentInteractionEndEvent =                    serde_json::from_value(runtime_payload_json)?;                self.end_message_agent_interaction(                    wall_time_unix_ms,                    tool_call_id,                    InteractionEdgeKind::SendMessage,                    &payload,                )            }            ToolCallKind::CloseAgent => {                let payload: CollabCloseEndEvent = serde_json::from_value(runtime_payload_json)?;                self.upsert_close_agent_interaction(                    tool_call_id,                    payload.receiver_thread_id.to_string(),                    Some(wall_time_unix_ms),                )            }            ToolCallKind::ExecCommand            | ToolCallKind::WriteStdin            | ToolCallKind::ApplyPatch            | ToolCallKind::Mcp { .. }            | ToolCallKind::Web            | ToolCallKind::ImageGeneration            | ToolCallKind::WaitAgent            | ToolCallKind::Other { .. } => Ok(()),        }    }    fn end_sub_agent_activity(        &mut self,        wall_time_unix_ms: i64,        tool_call_id: &str,        tool_kind: &ToolCallKind,        payload: &SubAgentActivityEvent,    ) -> Result<()> {        let target_thread_id = payload.agent_thread_id.to_string();        match (tool_kind, &payload.kind) {            (ToolCallKind::SpawnAgent, SubAgentActivityKind::Started) => {                let parent_thread_id = self                    .rollout                    .tool_calls                    .get(tool_call_id)                    .with_context(|| {                        format!("agent activity referenced unknown tool call {tool_call_id}")                    })?                    .thread_id                    .clone();                self.queue_sub_agent_activity_message_edge(                    wall_time_unix_ms,                    tool_call_id,                    spawn_edge_id(&parent_thread_id, &target_thread_id),                    InteractionEdgeKind::SpawnAgent,                    target_thread_id.clone(),                    Some(target_thread_id),                )            }            (ToolCallKind::AssignAgentTask, SubAgentActivityKind::Interacted) => self                .queue_sub_agent_activity_message_edge(                    wall_time_unix_ms,                    tool_call_id,                    tool_edge_id(tool_call_id),                    InteractionEdgeKind::AssignAgentTask,                    target_thread_id,                    /*unresolved_spawn_thread_id*/ None,                ),            (ToolCallKind::SendMessage, SubAgentActivityKind::Interacted) => self                .queue_sub_agent_activity_message_edge(                    wall_time_unix_ms,                    tool_call_id,                    tool_edge_id(tool_call_id),                    InteractionEdgeKind::SendMessage,                    target_thread_id,                    /*unresolved_spawn_thread_id*/ None,                ),            (ToolCallKind::CloseAgent, SubAgentActivityKind::Interrupted) => self                .upsert_close_agent_interaction(                    tool_call_id,                    target_thread_id,                    Some(wall_time_unix_ms),                ),            _ => bail!(                "sub-agent activity {:?} does not match tool call kind {tool_kind:?}",                payload.kind            ),        }    }    fn queue_sub_agent_activity_message_edge(        &mut self,        wall_time_unix_ms: i64,        tool_call_id: &str,        edge_id: String,        edge_kind: InteractionEdgeKind,        target_thread_id: String,        unresolved_spawn_thread_id: Option<String>,    ) -> Result<()> {        let tool_call = self.rollout.tool_calls.get(tool_call_id).with_context(|| {            format!("agent activity referenced unknown tool call {tool_call_id}")        })?;        let started_at_unix_ms = tool_call.execution.started_at_unix_ms;        let message_author = self.agent_path_for_thread(&tool_call.thread_id)?;        let message_content = self.agent_message_content_from_invocation(tool_call_id)?;        let carried_raw_payload_ids = self.agent_tool_payload_ids(tool_call_id)?;        self.queue_or_resolve_agent_interaction_edge(PendingAgentInteractionEdge {            edge_id,            kind: edge_kind,            source: TraceAnchor::ToolCall {                tool_call_id: tool_call_id.to_string(),            },            target_thread_id,            message_author,            message_content,            unresolved_spawn_thread_id,            started_at_unix_ms,            ended_at_unix_ms: Some(wall_time_unix_ms),            carried_raw_payload_ids,        })    }    fn agent_message_content_from_invocation(&self, tool_call_id: &str) -> Result<String> {        let tool_call = self.rollout.tool_calls.get(tool_call_id).with_context(|| {            format!("agent activity referenced unknown tool call {tool_call_id}")        })?;        let invocation_payload_id = tool_call            .raw_invocation_payload_id            .as_deref()            .with_context(|| {                format!("agent activity tool call {tool_call_id} missing invocation payload")            })?;        let invocation_payload = self            .rollout            .raw_payloads            .get(invocation_payload_id)            .with_context(|| {                format!(                    "agent activity tool call {tool_call_id} referenced missing invocation payload {invocation_payload_id}"                )            })?;        let invocation = self.read_payload_json(invocation_payload)?;        let arguments = invocation            .get("payload")            .and_then(|payload| payload.get("arguments"))            .and_then(Value::as_str)            .with_context(|| {                format!("agent activity tool call {tool_call_id} missing function arguments")            })?;        let args: AgentMessageInvocationArgs = serde_json::from_str(arguments)            .with_context(|| format!("parse agent activity tool call {tool_call_id} arguments"))?;        Ok(args.message)    }    /// Adds the canonical tool result payload to an already reduced multi-agent edge.    pub(super) fn attach_agent_interaction_tool_result(        &mut self,        tool_call_id: &str,        result_payload: Option<&RawPayloadRef>,    ) -> Result<()> {        let Some(result_payload) = result_payload else {            return Ok(());        };        if let Some(edge) = self            .rollout            .interaction_edges            .values_mut()            .find(|edge| tool_call_source_matches(&edge.source, tool_call_id))        {            push_unique(                &mut edge.carried_raw_payload_ids,                &result_payload.raw_payload_id,            );            return Ok(());        }        // Agent delivery edges intentionally wait for the recipient-side        // conversation item. Tool end can arrive before that item is        // reduced, so preserve the response payload on the pending edge rather        // than dropping evidence until the delivery materializes.        if let Some(pending) = self            .pending_agent_interaction_edges            .iter_mut()            .find(|pending| tool_call_source_matches(&pending.source, tool_call_id))        {            push_unique(                &mut pending.carried_raw_payload_ids,                &result_payload.raw_payload_id,            );        }        Ok(())    }    fn end_spawn_agent_interaction(        &mut self,        wall_time_unix_ms: i64,        tool_call_id: &str,        payload: &CollabAgentSpawnEndEvent,    ) -> Result<()> {        let Some(child_thread_id) = payload.new_thread_id else {            return Ok(());        };        let tool_call = &self.rollout.tool_calls[tool_call_id];        let child_thread_id = child_thread_id.to_string();        let edge_id = spawn_edge_id(&payload.sender_thread_id.to_string(), &child_thread_id);        let message_author = self.agent_path_for_thread(&tool_call.thread_id)?;        self.queue_or_resolve_agent_interaction_edge(PendingAgentInteractionEdge {            edge_id,            kind: InteractionEdgeKind::SpawnAgent,            source: TraceAnchor::ToolCall {                tool_call_id: tool_call_id.to_string(),            },            target_thread_id: child_thread_id.clone(),            message_author,            message_content: payload.prompt.clone(),            unresolved_spawn_thread_id: Some(child_thread_id),            started_at_unix_ms: tool_call.execution.started_at_unix_ms,            ended_at_unix_ms: Some(wall_time_unix_ms),            carried_raw_payload_ids: self.agent_tool_payload_ids(tool_call_id)?,        })    }    fn end_message_agent_interaction(        &mut self,        wall_time_unix_ms: i64,        tool_call_id: &str,        edge_kind: InteractionEdgeKind,        payload: &CollabAgentInteractionEndEvent,    ) -> Result<()> {        self.queue_message_agent_interaction(            tool_call_id,            edge_kind,            payload.receiver_thread_id.to_string(),            payload.prompt.clone(),            Some(wall_time_unix_ms),        )    }    fn queue_message_agent_interaction(        &mut self,        tool_call_id: &str,        kind: InteractionEdgeKind,        target_thread_id: String,        message_content: String,        ended_at_unix_ms: Option<i64>,    ) -> Result<()> {        let tool_call = &self.rollout.tool_calls[tool_call_id];        let message_author = self.agent_path_for_thread(&tool_call.thread_id)?;        self.queue_or_resolve_agent_interaction_edge(PendingAgentInteractionEdge {            edge_id: tool_edge_id(tool_call_id),            kind,            source: TraceAnchor::ToolCall {                tool_call_id: tool_call_id.to_string(),            },            target_thread_id,            message_author,            message_content,            unresolved_spawn_thread_id: None,            started_at_unix_ms: tool_call.execution.started_at_unix_ms,            ended_at_unix_ms,            carried_raw_payload_ids: self.agent_tool_payload_ids(tool_call_id)?,        })    }    fn agent_tool_payload_ids(&self, tool_call_id: &str) -> Result<Vec<String>> {        let tool_call =            self.rollout.tool_calls.get(tool_call_id).with_context(|| {                format!("agent edge referenced unknown tool call {tool_call_id}")            })?;        let mut payload_ids = Vec::new();        if let Some(payload_id) = &tool_call.raw_invocation_payload_id {            push_unique(&mut payload_ids, payload_id);        }        for payload_id in &tool_call.raw_runtime_payload_ids {            push_unique(&mut payload_ids, payload_id);        }        if let Some(payload_id) = &tool_call.raw_result_payload_id {            push_unique(&mut payload_ids, payload_id);        }        Ok(payload_ids)    }    fn upsert_close_agent_interaction(        &mut self,        tool_call_id: &str,        target_thread_id: String,        ended_at_unix_ms: Option<i64>,    ) -> Result<()> {        if !self.rollout.threads.contains_key(&target_thread_id) {            // A failed close can name a thread that never participated in this            // trace. Keep that evidence on the ToolCall raw payloads rather            // than creating an anchor to a non-existent reduced object.            return Ok(());        }        let started_at_unix_ms = self            .rollout            .tool_calls            .get(tool_call_id)            .with_context(|| format!("close edge referenced unknown tool call {tool_call_id}"))?            .execution            .started_at_unix_ms;        let carried_raw_payload_ids = self.agent_tool_payload_ids(tool_call_id)?;        self.upsert_interaction_edge(InteractionEdge {            edge_id: tool_edge_id(tool_call_id),            kind: InteractionEdgeKind::CloseAgent,            source: TraceAnchor::ToolCall {                tool_call_id: tool_call_id.to_string(),            },            target: TraceAnchor::Thread {                thread_id: target_thread_id,            },            started_at_unix_ms,            ended_at_unix_ms,            carried_item_ids: Vec::new(),            carried_raw_payload_ids,        })    }    /// Queues or resolves the edge from a child completion to its parent notification.    pub(in crate::reducer) fn queue_agent_result_interaction_edge(        &mut self,        observed: ObservedAgentResultEdge,    ) -> Result<()> {        let message_author = self.agent_path_for_thread(&observed.child_thread_id)?;        let source = if let Some(source_item_id) = self.latest_assistant_message_item_for_turn(            &observed.child_thread_id,            &observed.child_codex_turn_id,        ) {            TraceAnchor::ConversationItem {                item_id: source_item_id,            }        } else {            // Child completion is delivered from AgentStatus, not from transcript            // content. Failed or cancelled children can therefore notify the parent            // without producing a final assistant message. Anchor those edges to            // the child thread so the trace keeps the valid delivery instead of            // inventing a missing conversation item.            TraceAnchor::Thread {                thread_id: observed.child_thread_id,            }        };        self.queue_or_resolve_agent_interaction_edge(PendingAgentInteractionEdge {            edge_id: observed.edge_id,            kind: InteractionEdgeKind::AgentResult,            source,            target_thread_id: observed.parent_thread_id,            message_author,            message_content: observed.message,            unresolved_spawn_thread_id: None,            started_at_unix_ms: observed.wall_time_unix_ms,            ended_at_unix_ms: Some(observed.wall_time_unix_ms),            carried_raw_payload_ids: observed                .carried_payload                .map(|payload| vec![payload.raw_payload_id])                .unwrap_or_default(),        })    }    /// Resolves pending agent edges whose target is the newly reduced conversation item.    pub(in crate::reducer) fn resolve_pending_agent_edges_for_item(        &mut self,        item_id: &str,    ) -> Result<()> {        if self.is_interaction_edge_target_item(item_id) {            return Ok(());        }        let Some((thread_id, message_author, message_content)) =            self.inter_agent_message_item(item_id)        else {            return Ok(());        };        let Some(pending_index) = self            .pending_agent_interaction_edges            .iter()            .position(|pending| {                pending.target_thread_id == thread_id                    && pending.message_author == message_author                    && pending.message_content == message_content            })        else {            return Ok(());        };        let pending = self.pending_agent_interaction_edges.remove(pending_index);        self.upsert_agent_interaction_edge_for_item(pending, item_id.to_string())    }    fn queue_or_resolve_agent_interaction_edge(        &mut self,        pending: PendingAgentInteractionEdge,    ) -> Result<()> {        if let Some(item_id) = self.find_unlinked_inter_agent_message_item(            &pending.target_thread_id,            &pending.message_author,            &pending.message_content,        ) {            return self.upsert_agent_interaction_edge_for_item(pending, item_id);        }        if let Some(existing) = self            .pending_agent_interaction_edges            .iter_mut()            .find(|existing| existing.edge_id == pending.edge_id)        {            if existing.kind != pending.kind                || existing.source != pending.source                || existing.target_thread_id != pending.target_thread_id                || existing.message_author != pending.message_author                || existing.message_content != pending.message_content                || existing.unresolved_spawn_thread_id != pending.unresolved_spawn_thread_id            {                bail!(                    "pending interaction edge {} was observed with conflicting delivery data",                    pending.edge_id                );            }            existing.started_at_unix_ms =                existing.started_at_unix_ms.min(pending.started_at_unix_ms);            existing.ended_at_unix_ms = match (existing.ended_at_unix_ms, pending.ended_at_unix_ms)            {                (Some(existing_ended), Some(pending_ended)) => {                    Some(existing_ended.max(pending_ended))                }                (None, ended) | (ended, None) => ended,            };            extend_unique(                &mut existing.carried_raw_payload_ids,                pending.carried_raw_payload_ids,            );            return Ok(());        }        self.pending_agent_interaction_edges.push(pending);        Ok(())    }    /// Materializes unresolved spawn edges that have a valid child-thread fallback target.    pub(in crate::reducer) fn resolve_pending_spawn_edge_fallbacks(&mut self) -> Result<()> {        let pending_edges = std::mem::take(&mut self.pending_agent_interaction_edges);        for pending in pending_edges {            let Some(child_thread_id) = pending.unresolved_spawn_thread_id else {                continue;            };            if pending.kind != InteractionEdgeKind::SpawnAgent {                bail!(                    "non-spawn interaction edge {} carried a spawn fallback target",                    pending.edge_id                );            }            if !self.rollout.threads.contains_key(&child_thread_id) {                continue;            }            // Spawn normally resolves to the child task message because that is            // where the delegated work first becomes model-visible. A child can            // fail before that transcript item exists, but the spawned thread is            // still real and the spawning tool still created it. Preserve that            // relationship with the thread fallback instead of dropping the edge.            self.upsert_interaction_edge(InteractionEdge {                edge_id: pending.edge_id,                kind: pending.kind,                source: pending.source,                target: TraceAnchor::Thread {                    thread_id: child_thread_id,                },                started_at_unix_ms: pending.started_at_unix_ms,                ended_at_unix_ms: pending.ended_at_unix_ms,                carried_item_ids: Vec::new(),                carried_raw_payload_ids: pending.carried_raw_payload_ids,            })?;        }        Ok(())    }    fn upsert_agent_interaction_edge_for_item(        &mut self,        pending: PendingAgentInteractionEdge,        target_item_id: String,    ) -> Result<()> {        self.upsert_interaction_edge(InteractionEdge {            edge_id: pending.edge_id,            kind: pending.kind,            source: pending.source,            target: TraceAnchor::ConversationItem {                item_id: target_item_id.clone(),            },            started_at_unix_ms: pending.started_at_unix_ms,            ended_at_unix_ms: pending.ended_at_unix_ms,            carried_item_ids: vec![target_item_id],            carried_raw_payload_ids: pending.carried_raw_payload_ids,        })    }    fn upsert_interaction_edge(&mut self, edge: InteractionEdge) -> Result<()> {        if let Some(existing) = self.rollout.interaction_edges.get_mut(&edge.edge_id) {            if existing.kind != edge.kind                || existing.source != edge.source                || existing.target != edge.target            {                bail!(                    "interaction edge {} was observed with conflicting endpoints",                    edge.edge_id                );            }            existing.started_at_unix_ms = existing.started_at_unix_ms.min(edge.started_at_unix_ms);            existing.ended_at_unix_ms = match (existing.ended_at_unix_ms, edge.ended_at_unix_ms) {                (Some(existing_ended), Some(edge_ended)) => Some(existing_ended.max(edge_ended)),                (None, ended) | (ended, None) => ended,            };            extend_unique(&mut existing.carried_item_ids, edge.carried_item_ids);            extend_unique(                &mut existing.carried_raw_payload_ids,                edge.carried_raw_payload_ids,            );            return Ok(());        }        self.rollout            .interaction_edges            .insert(edge.edge_id.clone(), edge);        Ok(())    }    fn find_unlinked_inter_agent_message_item(        &self,        thread_id: &str,        message_author: &str,        message_content: &str,    ) -> Option<String> {        self.rollout            .threads            .get(thread_id)?            .conversation_item_ids            .iter()            .find(|item_id| {                !self.is_interaction_edge_target_item(item_id)                    && self                        .inter_agent_message_item(item_id)                        .is_some_and(|(_, author, content)| {                            author == message_author && content == message_content                        })            })            .cloned()    }    fn inter_agent_message_item(&self, item_id: &str) -> Option<(String, String, String)> {        let item = self.rollout.conversation_items.get(item_id)?;        let (author_agent_path, recipient_agent_path, message_content) =            inter_agent_message_fields(item)?;        let thread = self.rollout.threads.get(&item.thread_id)?;        if recipient_agent_path != thread.agent_path {            return None;        }        Some((item.thread_id.clone(), author_agent_path, message_content))    }    fn agent_path_for_thread(&self, thread_id: &str) -> Result<String> {        self.rollout            .threads            .get(thread_id)            .map(|thread| thread.agent_path.clone())            .with_context(|| format!("agent edge referenced unknown thread {thread_id}"))    }    fn is_interaction_edge_target_item(&self, item_id: &str) -> bool {        self.rollout            .interaction_edges            .values()            .any(|edge| matches!(&edge.target, TraceAnchor::ConversationItem { item_id: target } if target == item_id))    }    fn latest_assistant_message_item_for_turn(        &self,        thread_id: &str,        codex_turn_id: &str,    ) -> Option<String> {        self.rollout            .conversation_items            .values()            .filter(|item| {                item.thread_id == thread_id                    && item.codex_turn_id.as_deref() == Some(codex_turn_id)                    && item.role == ConversationRole::Assistant                    && item.kind == ConversationItemKind::Message                    && item.agent_message.is_none()            })            .max_by_key(|item| item.first_seen_at_unix_ms)            .map(|item| item.item_id.clone())    }}fn extend_unique(items: &mut Vec<String>, new_items: Vec<String>) {    for item in new_items {        if !items.iter().any(|existing| existing == &item) {            items.push(item);        }    }}fn tool_edge_id(tool_call_id: &str) -> String {    format!("edge:tool:{tool_call_id}")}fn tool_call_source_matches(anchor: &TraceAnchor, tool_call_id: &str) -> bool {    matches!(anchor, TraceAnchor::ToolCall { tool_call_id: source } if source == tool_call_id)}fn push_unique(items: &mut Vec<String>, item: &str) {    if !items.iter().any(|existing| existing == item) {        items.push(item.to_string());    }}fn inter_agent_message_fields(item: &ConversationItem) -> Option<(String, String, String)> {    if item.role != ConversationRole::Assistant || item.kind != ConversationItemKind::Message {        return None;    }    if let Some(agent_message) = &item.agent_message {        let message_content = match item.body.parts.as_slice() {            [ConversationPart::Text { text }] => text,            [ConversationPart::Encoded { label, value }] if label == "encrypted_content" => value,            [                ConversationPart::Text { .. },                ConversationPart::Encoded { label, value },            ] if label == "encrypted_content" => value,            _ => return None,        };        return Some((            agent_message.author.clone(),            agent_message.recipient.clone(),            message_content.clone(),        ));    }    // Older traces store multi-agent v2 deliveries as assistant messages whose    // text is serialized `InterAgentCommunication`. Treat only that exact    // transport shape as an edge target; ordinary assistant JSON must not be    // mistaken for cross-thread delivery.    let [ConversationPart::Text { text }] = item.body.parts.as_slice() else {        return None;    };    let communication = serde_json::from_str::<InterAgentCommunication>(text).ok()?;    Some((        communication.author.to_string(),        communication.recipient.to_string(),        communication            .encrypted_content            .unwrap_or(communication.content),    ))}#[cfg(test)]#[path = "agents_tests.rs"]mod tests;