From ca3246f77a5eca14f3424d786ce8855fe8811dbc Mon Sep 17 00:00:00 2001 From: guinness-oai Date: Mon, 20 Apr 2026 16:46:15 -0700 Subject: [PATCH] [codex] Send realtime transcript deltas on handoff (#18761) ## Summary - Track how many realtime transcript entries have already been attached to a background-agent handoff. - Attach only entries added since the previous handoff as `` instead of resending the accumulated transcript snapshot. - Update the realtime integration test so the second delegation carries only the second transcript delta. ## Validation - `just fmt` - `cargo test -p codex-api` - `cargo test -p codex-core inbound_handoff_request_sends_transcript_delta_after_each_handoff` - `cargo build -p codex-cli -p codex-app-server` ## Manual testing Built local debug binaries at: - `codex-rs/target/debug/codex` - `codex-rs/target/debug/codex-app-server` --- .../codex-api/src/endpoint/realtime_websocket/methods.rs | 6 +++++- codex-rs/core/tests/suite/realtime_conversation.rs | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs b/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs index 0a8bac005..3c1fd7bd9 100644 --- a/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs +++ b/codex-rs/codex-api/src/endpoint/realtime_websocket/methods.rs @@ -217,6 +217,7 @@ pub struct RealtimeWebsocketEvents { #[derive(Default)] struct ActiveTranscriptState { entries: Vec, + last_handoff_entry_count: usize, new_input_entry: bool, new_output_entry: bool, } @@ -459,7 +460,10 @@ impl RealtimeWebsocketEvents { } RealtimeEvent::HandoffRequested(handoff) => { append_handoff_input(&mut active_transcript.entries, &handoff.input_transcript); - handoff.active_transcript = active_transcript.entries.clone(); + handoff.active_transcript = active_transcript.entries + [active_transcript.last_handoff_entry_count..] + .to_vec(); + active_transcript.last_handoff_entry_count = active_transcript.entries.len(); active_transcript.new_input_entry = true; active_transcript.new_output_entry = true; } diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index dcf5e1f95..60167a155 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -2661,7 +2661,7 @@ async fn inbound_handoff_request_uses_active_transcript() -> Result<()> { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn inbound_handoff_request_keeps_transcript_history_after_each_handoff() -> Result<()> { +async fn inbound_handoff_request_sends_transcript_delta_after_each_handoff() -> Result<()> { skip_if_no_network!(Ok(())); let api_server = start_mock_server().await; @@ -2773,9 +2773,9 @@ async fn inbound_handoff_request_keeps_transcript_history_after_each_handoff() - let second_user_texts = requests[1].message_input_texts("user"); assert!(second_user_texts.iter().any(|text| text - == "\n second question\n user: first question\nuser: second question\n")); + == "\n second question\n user: second question\n")); assert!(!second_user_texts.iter().any(|text| text - == "\n second question\n user: first questionsecond question\n")); + == "\n second question\n user: first question\nuser: second question\n")); realtime_server.shutdown().await; Ok(())