Codex Handbook
rollout-trace/src/reducer/tool/agents_tests.rs 991 lines
use pretty_assertions::assert_eq;use serde_json::json;use tempfile::TempDir;use crate::model::AgentOrigin;use crate::model::ExecutionStatus;use crate::model::InteractionEdgeKind;use crate::model::RolloutStatus;use crate::model::ToolCallKind;use crate::model::ToolCallSummary;use crate::model::TraceAnchor;use crate::payload::RawPayloadKind;use crate::payload::RawPayloadRef;use crate::raw_event::RawToolCallRequester;use crate::raw_event::RawTraceEventPayload;use crate::reducer::test_support::append_completed_inference;use crate::reducer::test_support::append_inference_request;use crate::reducer::test_support::create_started_agent_writer;use crate::reducer::test_support::message;use crate::reducer::test_support::start_agent_turn;use crate::reducer::test_support::start_thread;use crate::reducer::test_support::start_turn_for_thread;use crate::reducer::test_support::trace_context_for_agent;use crate::reducer::test_support::trace_context_for_thread;use crate::replay_bundle;use crate::writer::TraceWriter;#[test]fn child_thread_metadata_creates_spawn_origin_without_delivery_edge() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = TraceWriter::create(        temp.path(),        "trace-1".to_string(),        "rollout-1".to_string(),        "019d0000-0000-7000-8000-000000000002".to_string(),    )?;    let metadata = writer.write_json_payload(        RawPayloadKind::SessionMetadata,        &json!({            "nickname": "James",            "agent_role": "explorer",            "task_name": "repo_file_counter",            "model": "gpt-test",            "session_source": {                "subagent": {                    "thread_spawn": {                        "parent_thread_id": "019d0000-0000-7000-8000-000000000001",                        "agent_path": "/root/repo_file_counter",                        "agent_nickname": "James",                        "agent_role": "explorer"                    }                }            }        }),    )?;    writer.append(RawTraceEventPayload::ThreadStarted {        thread_id: "019d0000-0000-7000-8000-000000000002".to_string(),        agent_path: "/root/repo_file_counter".to_string(),        metadata_payload: Some(metadata),    })?;    let replayed = replay_bundle(temp.path())?;    let thread = &replayed.threads["019d0000-0000-7000-8000-000000000002"];    assert_eq!(thread.nickname, Some("James".to_string()));    assert_eq!(thread.default_model, Some("gpt-test".to_string()));    assert_eq!(        thread.origin,        AgentOrigin::Spawned {            parent_thread_id: "019d0000-0000-7000-8000-000000000001".to_string(),            spawn_edge_id: "edge:spawn:019d0000-0000-7000-8000-000000000001:019d0000-0000-7000-8000-000000000002".to_string(),            task_name: "repo_file_counter".to_string(),            agent_role: "explorer".to_string(),        }    );    assert!(        !replayed.interaction_edges.contains_key(            "edge:spawn:019d0000-0000-7000-8000-000000000001:019d0000-0000-7000-8000-000000000002"        ),        "spawn metadata identifies the child, but the delivery edge waits for the recipient \         conversation item"    );    Ok(())}#[test]fn spawn_runtime_payload_targets_delivered_child_message() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_agent_turn(&writer, "turn-1")?;    let spawn_payloads = append_spawn_agent_tool_lifecycle(&writer, "turn-1")?;    // Then record the child-side model-visible task message. This is the    // preferred target because it pinpoints where the delegated work entered    // the child timeline.    start_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "/root/repo_file_counter",    )?;    start_turn_for_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",    )?;    let delivered = inter_agent_message(        "/root",        "/root/repo_file_counter",        "count",        /*trigger_turn*/ true,    );    append_inference_request(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",        "inference-child-1",        vec![message("assistant", &delivered)],    )?;    let replayed = replay_bundle(temp.path())?;    let edge = &replayed.interaction_edges["edge:spawn:019d0000-0000-7000-8000-000000000001:019d0000-0000-7000-8000-000000000002"];    assert_eq!(edge.kind, InteractionEdgeKind::SpawnAgent);    assert_eq!(        edge.source,        TraceAnchor::ToolCall {            tool_call_id: "call-spawn".to_string()        }    );    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        "019d0000-0000-7000-8000-000000000002"    );    assert_eq!(        edge.carried_raw_payload_ids,        vec![            spawn_payloads.invocation.raw_payload_id,            spawn_payloads.begin.raw_payload_id,            spawn_payloads.end.raw_payload_id,            spawn_payloads.result.raw_payload_id,        ]    );    Ok(())}#[test]fn spawn_runtime_payload_falls_back_to_child_thread_without_delivery_item() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_agent_turn(&writer, "turn-1")?;    let spawn_payloads = append_spawn_agent_tool_lifecycle(&writer, "turn-1")?;    // Deliberately start the child thread without appending an inference    // request containing the inter-agent task message. This reproduces the    // failure path where the child aborts before the reducer can target the    // precise child-side ConversationItem.    start_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "/root/repo_file_counter",    )?;    let replayed = replay_bundle(temp.path())?;    let edge = &replayed.interaction_edges["edge:spawn:019d0000-0000-7000-8000-000000000001:019d0000-0000-7000-8000-000000000002"];    assert_eq!(edge.kind, InteractionEdgeKind::SpawnAgent);    assert_eq!(        edge.source,        TraceAnchor::ToolCall {            tool_call_id: "call-spawn".to_string()        }    );    assert_eq!(        edge.target,        TraceAnchor::Thread {            thread_id: "019d0000-0000-7000-8000-000000000002".to_string()        }    );    // No transcript item carried the task, so the fallback edge should not    // claim one. The raw payloads still preserve the tool evidence.    assert!(edge.carried_item_ids.is_empty());    assert_eq!(        edge.carried_raw_payload_ids,        vec![            spawn_payloads.invocation.raw_payload_id,            spawn_payloads.begin.raw_payload_id,            spawn_payloads.end.raw_payload_id,            spawn_payloads.result.raw_payload_id,        ]    );    Ok(())}#[test]fn sub_agent_started_activity_creates_spawn_edge() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_agent_turn(&writer, "turn-1")?;    let child_thread_id = "019d0000-0000-7000-8000-000000000002";    let invocation_payload = writer.write_json_payload(        RawPayloadKind::ToolInvocation,        &json!({            "tool_name": "spawn_agent",            "payload": {                "type": "function",                "arguments": "{\"message\":\"review this\",\"task_name\":\"reviewer\"}"            }        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallStarted {            tool_call_id: "call-spawn-v2".to_string(),            model_visible_call_id: Some("call-spawn-v2".to_string()),            code_mode_runtime_tool_id: None,            requester: RawToolCallRequester::Model,            kind: ToolCallKind::SpawnAgent,            summary: ToolCallSummary::Generic {                label: "spawn_agent".to_string(),                input_preview: None,                output_preview: None,            },            invocation_payload: Some(invocation_payload.clone()),        },    )?;    let activity_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "event_id": "call-spawn-v2",            "occurred_at_ms": 1234,            "agent_thread_id": child_thread_id,            "agent_path": "/root/reviewer",            "kind": "started"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeEnded {            tool_call_id: "call-spawn-v2".to_string(),            status: ExecutionStatus::Completed,            runtime_payload: activity_payload.clone(),        },    )?;    start_thread(&writer, child_thread_id, "/root/reviewer")?;    start_turn_for_thread(&writer, child_thread_id, "turn-child-1")?;    append_inference_request(        &writer,        child_thread_id,        "turn-child-1",        "inference-child-1",        vec![json!({            "type": "agent_message",            "author": "/root",            "recipient": "/root/reviewer",            "content": [                {                    "type": "input_text",                    "text": "Message Type: NEW_TASK\nTask name: /root/reviewer\nSender: /root\nPayload:\n"                },                {"type": "encrypted_content", "encrypted_content": "review this"}            ]        })],    )?;    let replayed = replay_bundle(temp.path())?;    let edge_id = format!("edge:spawn:019d0000-0000-7000-8000-000000000001:{child_thread_id}");    let edge = &replayed.interaction_edges[&edge_id];    assert_eq!(edge.kind, InteractionEdgeKind::SpawnAgent);    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        child_thread_id    );    assert_eq!(        edge.carried_raw_payload_ids,        vec![            invocation_payload.raw_payload_id,            activity_payload.raw_payload_id,        ]    );    Ok(())}#[test]fn send_message_runtime_payload_targets_delivered_child_message() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_agent_turn(&writer, "turn-1")?;    let invocation_payload = writer.write_json_payload(        RawPayloadKind::ToolInvocation,        &json!({            "tool_name": "send_message",            "payload": {                "type": "function",                "arguments": "{\"target\":\"/root/child\",\"message\":\"hello\"}"            }        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallStarted {            tool_call_id: "call-send".to_string(),            model_visible_call_id: Some("call-send".to_string()),            code_mode_runtime_tool_id: None,            requester: RawToolCallRequester::Model,            kind: ToolCallKind::SendMessage,            summary: ToolCallSummary::Generic {                label: "send_message".to_string(),                input_preview: None,                output_preview: None,            },            invocation_payload: Some(invocation_payload),        },    )?;    let begin_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "call_id": "call-send",            "sender_thread_id": "019d0000-0000-7000-8000-000000000001",            "receiver_thread_id": "019d0000-0000-7000-8000-000000000002",            "prompt": "hello",            "status": "running"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeStarted {            tool_call_id: "call-send".to_string(),            runtime_payload: begin_payload,        },    )?;    let end_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "call_id": "call-send",            "sender_thread_id": "019d0000-0000-7000-8000-000000000001",            "receiver_thread_id": "019d0000-0000-7000-8000-000000000002",            "prompt": "hello",            "status": "running"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeEnded {            tool_call_id: "call-send".to_string(),            status: ExecutionStatus::Completed,            runtime_payload: end_payload,        },    )?;    start_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "/root/child",    )?;    start_turn_for_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",    )?;    let delivered =        inter_agent_message("/root", "/root/child", "hello", /*trigger_turn*/ false);    append_inference_request(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",        "inference-child-1",        vec![message("assistant", &delivered)],    )?;    let replayed = replay_bundle(temp.path())?;    let edge = &replayed.interaction_edges["edge:tool:call-send"];    assert_eq!(edge.kind, InteractionEdgeKind::SendMessage);    assert_eq!(        edge.source,        TraceAnchor::ToolCall {            tool_call_id: "call-send".to_string()        }    );    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        "019d0000-0000-7000-8000-000000000002"    );    assert!(edge.ended_at_unix_ms.is_some());    Ok(())}#[test]fn send_message_activity_targets_delivered_child_message() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_agent_turn(&writer, "turn-1")?;    let child_thread_id = "019d0000-0000-7000-8000-000000000002";    let invocation_payload = writer.write_json_payload(        RawPayloadKind::ToolInvocation,        &json!({            "tool_name": "send_message",            "payload": {                "type": "function",                "arguments": "{\"target\":\"/root/child\",\"message\":\"hello again\"}"            }        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallStarted {            tool_call_id: "call-send-v2".to_string(),            model_visible_call_id: Some("call-send-v2".to_string()),            code_mode_runtime_tool_id: None,            requester: RawToolCallRequester::Model,            kind: ToolCallKind::SendMessage,            summary: ToolCallSummary::Generic {                label: "send_message".to_string(),                input_preview: None,                output_preview: None,            },            invocation_payload: Some(invocation_payload.clone()),        },    )?;    let activity_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "event_id": "call-send-v2",            "occurred_at_ms": 1234,            "agent_thread_id": child_thread_id,            "agent_path": "/root/child",            "kind": "interacted"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeEnded {            tool_call_id: "call-send-v2".to_string(),            status: ExecutionStatus::Completed,            runtime_payload: activity_payload.clone(),        },    )?;    start_thread(&writer, child_thread_id, "/root/child")?;    start_turn_for_thread(&writer, child_thread_id, "turn-child-1")?;    let delivered = inter_agent_message(        "/root",        "/root/child",        "hello again",        /*trigger_turn*/ false,    );    append_inference_request(        &writer,        child_thread_id,        "turn-child-1",        "inference-child-1",        vec![message("assistant", &delivered)],    )?;    let replayed = replay_bundle(temp.path())?;    let edge = &replayed.interaction_edges["edge:tool:call-send-v2"];    assert_eq!(edge.kind, InteractionEdgeKind::SendMessage);    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        child_thread_id    );    assert_eq!(        edge.carried_raw_payload_ids,        vec![            invocation_payload.raw_payload_id,            activity_payload.raw_payload_id,        ]    );    Ok(())}#[test]fn followup_activity_targets_delivered_child_message() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_agent_turn(&writer, "turn-1")?;    let child_thread_id = "019d0000-0000-7000-8000-000000000002";    let invocation_payload = writer.write_json_payload(        RawPayloadKind::ToolInvocation,        &json!({            "tool_name": "followup_task",            "payload": {                "type": "function",                "arguments": "{\"target\":\"/root/child\",\"message\":\"continue\"}"            }        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallStarted {            tool_call_id: "call-followup-v2".to_string(),            model_visible_call_id: Some("call-followup-v2".to_string()),            code_mode_runtime_tool_id: None,            requester: RawToolCallRequester::Model,            kind: ToolCallKind::AssignAgentTask,            summary: ToolCallSummary::Generic {                label: "followup_task".to_string(),                input_preview: None,                output_preview: None,            },            invocation_payload: Some(invocation_payload.clone()),        },    )?;    let activity_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "event_id": "call-followup-v2",            "occurred_at_ms": 1234,            "agent_thread_id": child_thread_id,            "agent_path": "/root/child",            "kind": "interacted"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeEnded {            tool_call_id: "call-followup-v2".to_string(),            status: ExecutionStatus::Completed,            runtime_payload: activity_payload.clone(),        },    )?;    start_thread(&writer, child_thread_id, "/root/child")?;    start_turn_for_thread(&writer, child_thread_id, "turn-child-1")?;    let delivered = inter_agent_message(        "/root",        "/root/child",        "continue",        /*trigger_turn*/ true,    );    append_inference_request(        &writer,        child_thread_id,        "turn-child-1",        "inference-child-1",        vec![message("assistant", &delivered)],    )?;    let replayed = replay_bundle(temp.path())?;    let edge = &replayed.interaction_edges["edge:tool:call-followup-v2"];    assert_eq!(edge.kind, InteractionEdgeKind::AssignAgentTask);    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        child_thread_id    );    assert_eq!(        edge.carried_raw_payload_ids,        vec![            invocation_payload.raw_payload_id,            activity_payload.raw_payload_id,        ]    );    Ok(())}#[test]fn close_agent_runtime_payload_targets_thread() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "/root/child",    )?;    start_agent_turn(&writer, "turn-1")?;    let invocation_payload = writer.write_json_payload(        RawPayloadKind::ToolInvocation,        &json!({            "tool_name": "close_agent",            "payload": {                "type": "function",                "arguments": r#"{"target":"/root/child"}"#            }        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallStarted {            tool_call_id: "call-close".to_string(),            model_visible_call_id: Some("call-close".to_string()),            code_mode_runtime_tool_id: None,            requester: RawToolCallRequester::Model,            kind: ToolCallKind::CloseAgent,            summary: ToolCallSummary::Generic {                label: "close_agent".to_string(),                input_preview: None,                output_preview: None,            },            invocation_payload: Some(invocation_payload.clone()),        },    )?;    let begin_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "call_id": "call-close",            "sender_thread_id": "019d0000-0000-7000-8000-000000000001",            "receiver_thread_id": "019d0000-0000-7000-8000-000000000002"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeStarted {            tool_call_id: "call-close".to_string(),            runtime_payload: begin_payload.clone(),        },    )?;    let end_payload = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "call_id": "call-close",            "sender_thread_id": "019d0000-0000-7000-8000-000000000001",            "receiver_thread_id": "019d0000-0000-7000-8000-000000000002",            "receiver_agent_nickname": "Scout",            "receiver_agent_role": "explorer",            "status": "running"        }),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallRuntimeEnded {            tool_call_id: "call-close".to_string(),            status: ExecutionStatus::Completed,            runtime_payload: end_payload.clone(),        },    )?;    let result_payload = writer.write_json_payload(        RawPayloadKind::ToolResult,        &json!({"previous_status": "running"}),    )?;    writer.append_with_context(        trace_context_for_agent("turn-1"),        RawTraceEventPayload::ToolCallEnded {            tool_call_id: "call-close".to_string(),            status: ExecutionStatus::Completed,            result_payload: Some(result_payload.clone()),        },    )?;    writer.append(RawTraceEventPayload::ThreadEnded {        thread_id: "019d0000-0000-7000-8000-000000000002".to_string(),        status: RolloutStatus::Completed,    })?;    let replayed = replay_bundle(temp.path())?;    let edge = &replayed.interaction_edges["edge:tool:call-close"];    assert_eq!(edge.kind, InteractionEdgeKind::CloseAgent);    assert_eq!(        edge.source,        TraceAnchor::ToolCall {            tool_call_id: "call-close".to_string()        }    );    assert_eq!(        edge.target,        TraceAnchor::Thread {            thread_id: "019d0000-0000-7000-8000-000000000002".to_string()        }    );    assert!(edge.carried_item_ids.is_empty());    assert_eq!(        edge.carried_raw_payload_ids,        vec![            invocation_payload.raw_payload_id,            begin_payload.raw_payload_id,            end_payload.raw_payload_id,            result_payload.raw_payload_id,        ]    );    assert_eq!(        replayed.threads["019d0000-0000-7000-8000-000000000002"]            .execution            .status,        ExecutionStatus::Completed    );    assert_eq!(replayed.status, RolloutStatus::Running);    Ok(())}#[test]fn agent_result_edge_links_child_result_to_parent_notification() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    start_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "/root/child",    )?;    start_turn_for_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",    )?;    append_completed_inference(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",        "inference-child-1",        vec![message("assistant", "task")],        vec![message("assistant", "done")],    )?;    let notification = "<subagent_notification>{\"agent_path\":\"/root/child\",\"status\":{\"completed\":\"done\"}}</subagent_notification>";    let carried_payload = writer.write_json_payload(        RawPayloadKind::AgentResult,        &json!({            "child_agent_path": "/root/child",            "message": notification,            "status": {"completed": "done"}        }),    )?;    writer.append_with_context(        trace_context_for_thread("019d0000-0000-7000-8000-000000000002", "turn-child-1"),        RawTraceEventPayload::AgentResultObserved {            edge_id: "edge:agent_result:thread-child:turn-child-1:thread-root".to_string(),            child_thread_id: "019d0000-0000-7000-8000-000000000002".to_string(),            child_codex_turn_id: "turn-child-1".to_string(),            parent_thread_id: "019d0000-0000-7000-8000-000000000001".to_string(),            message: notification.to_string(),            carried_payload: Some(carried_payload.clone()),        },    )?;    start_agent_turn(&writer, "turn-root-1")?;    let delivered = inter_agent_message(        "/root/child",        "/root",        notification,        /*trigger_turn*/ false,    );    append_inference_request(        &writer,        "019d0000-0000-7000-8000-000000000001",        "turn-root-1",        "inference-root-1",        vec![message("assistant", &delivered)],    )?;    let replayed = replay_bundle(temp.path())?;    let edge =        &replayed.interaction_edges["edge:agent_result:thread-child:turn-child-1:thread-root"];    assert_eq!(edge.kind, InteractionEdgeKind::AgentResult);    let TraceAnchor::ConversationItem {        item_id: source_item_id,    } = &edge.source    else {        panic!("expected child result conversation item source");    };    assert_eq!(        text_body(&replayed.conversation_items[source_item_id]),        "done"    );    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        "019d0000-0000-7000-8000-000000000001"    );    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        edge.carried_raw_payload_ids,        vec![carried_payload.raw_payload_id]    );    Ok(())}#[test]fn agent_result_edge_falls_back_to_child_thread_without_result_message() -> anyhow::Result<()> {    let temp = TempDir::new()?;    let writer = create_started_agent_writer(&temp)?;    // The child received its task but produced no assistant output. Failed    // child tasks can still notify the parent through AgentStatus, so the    // inbound task must not be mistaken for the child's result.    start_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "/root/child",    )?;    start_turn_for_thread(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",    )?;    append_inference_request(        &writer,        "019d0000-0000-7000-8000-000000000002",        "turn-child-1",        "inference-child-1",        vec![json!({            "type": "agent_message",            "author": "/root",            "recipient": "/root/child",            "content": [{"type": "input_text", "text": "do the task"}]        })],    )?;    let notification = r#"<subagent_notification>{"agent_path":"/root/child","status":{"failed":"boom"}}</subagent_notification>"#;    let carried_payload = writer.write_json_payload(        RawPayloadKind::AgentResult,        &json!({            "child_agent_path": "/root/child",            "message": notification,            "status": {"failed": "boom"}        }),    )?;    writer.append_with_context(        trace_context_for_thread("019d0000-0000-7000-8000-000000000002", "turn-child-1"),        RawTraceEventPayload::AgentResultObserved {            edge_id: "edge:agent_result:thread-child:turn-child-1:thread-root".to_string(),            child_thread_id: "019d0000-0000-7000-8000-000000000002".to_string(),            child_codex_turn_id: "turn-child-1".to_string(),            parent_thread_id: "019d0000-0000-7000-8000-000000000001".to_string(),            message: notification.to_string(),            carried_payload: Some(carried_payload.clone()),        },    )?;    // The parent does receive the failure notification as a model-visible    // mailbox item. The target should remain that precise parent-side    // ConversationItem even though the source falls back to the child thread.    start_agent_turn(&writer, "turn-root-1")?;    let delivered = inter_agent_message(        "/root/child",        "/root",        notification,        /*trigger_turn*/ false,    );    append_inference_request(        &writer,        "019d0000-0000-7000-8000-000000000001",        "turn-root-1",        "inference-root-1",        vec![message("assistant", &delivered)],    )?;    let replayed = replay_bundle(temp.path())?;    let edge =        &replayed.interaction_edges["edge:agent_result:thread-child:turn-child-1:thread-root"];    assert_eq!(edge.kind, InteractionEdgeKind::AgentResult);    assert_eq!(        edge.source,        TraceAnchor::Thread {            thread_id: "019d0000-0000-7000-8000-000000000002".to_string(),        }    );    let target_item_id = target_conversation_item_id(&edge.target);    assert_eq!(        replayed.conversation_items[target_item_id].thread_id,        "019d0000-0000-7000-8000-000000000001"    );    assert_eq!(edge.carried_item_ids, vec![target_item_id.clone()]);    assert_eq!(        edge.carried_raw_payload_ids,        vec![carried_payload.raw_payload_id]    );    Ok(())}struct SpawnAgentToolPayloads {    invocation: RawPayloadRef,    begin: RawPayloadRef,    end: RawPayloadRef,    result: RawPayloadRef,}fn append_spawn_agent_tool_lifecycle(    writer: &TraceWriter,    turn_id: &str,) -> anyhow::Result<SpawnAgentToolPayloads> {    // Keep the parent-side tool lifecycle in one place so the spawn tests can    // focus on the child-side event that decides the edge target.    let invocation = writer.write_json_payload(        RawPayloadKind::ToolInvocation,        &json!({            "tool_name": "spawn_agent",            "payload": {                "type": "function",                "arguments": r#"{"task_name":"repo_file_counter","message":"count"}"#            }        }),    )?;    writer.append_with_context(        trace_context_for_agent(turn_id),        RawTraceEventPayload::ToolCallStarted {            tool_call_id: "call-spawn".to_string(),            model_visible_call_id: Some("call-spawn".to_string()),            code_mode_runtime_tool_id: None,            requester: RawToolCallRequester::Model,            kind: ToolCallKind::SpawnAgent,            summary: ToolCallSummary::Generic {                label: "spawn_agent".to_string(),                input_preview: None,                output_preview: None,            },            invocation_payload: Some(invocation.clone()),        },    )?;    let begin = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "call_id": "call-spawn",            "sender_thread_id": "019d0000-0000-7000-8000-000000000001",            "prompt": "count"        }),    )?;    writer.append_with_context(        trace_context_for_agent(turn_id),        RawTraceEventPayload::ToolCallRuntimeStarted {            tool_call_id: "call-spawn".to_string(),            runtime_payload: begin.clone(),        },    )?;    let end = writer.write_json_payload(        RawPayloadKind::ToolRuntimeEvent,        &json!({            "call_id": "call-spawn",            "sender_thread_id": "019d0000-0000-7000-8000-000000000001",            "new_thread_id": "019d0000-0000-7000-8000-000000000002",            "prompt": "count",            "model": "gpt-test",            "reasoning_effort": "medium",            "status": "running"        }),    )?;    writer.append_with_context(        trace_context_for_agent(turn_id),        RawTraceEventPayload::ToolCallRuntimeEnded {            tool_call_id: "call-spawn".to_string(),            status: ExecutionStatus::Completed,            runtime_payload: end.clone(),        },    )?;    let result = writer.write_json_payload(        RawPayloadKind::ToolResult,        &json!({"task_name": "/root/repo_file_counter"}),    )?;    writer.append_with_context(        trace_context_for_agent(turn_id),        RawTraceEventPayload::ToolCallEnded {            tool_call_id: "call-spawn".to_string(),            status: ExecutionStatus::Completed,            result_payload: Some(result.clone()),        },    )?;    Ok(SpawnAgentToolPayloads {        invocation,        begin,        end,        result,    })}fn inter_agent_message(author: &str, recipient: &str, content: &str, trigger_turn: bool) -> String {    json!({        "author": author,        "recipient": recipient,        "other_recipients": [],        "content": content,        "trigger_turn": trigger_turn,    })    .to_string()}fn target_conversation_item_id(anchor: &TraceAnchor) -> &String {    let TraceAnchor::ConversationItem { item_id } = anchor else {        panic!("expected conversation item target");    };    item_id}fn text_body(item: &crate::model::ConversationItem) -> &str {    let [crate::model::ConversationPart::Text { text }] = item.body.parts.as_slice() else {        panic!("expected single text part");    };    text}