Skip to content

Make realtime sideband startup async#20715

Merged
kmeelu-oai merged 7 commits into
mainfrom
kmeelu/async-realtime-sideband-startup
May 4, 2026
Merged

Make realtime sideband startup async#20715
kmeelu-oai merged 7 commits into
mainfrom
kmeelu/async-realtime-sideband-startup

Conversation

@kmeelu-oai
Copy link
Copy Markdown
Contributor

@kmeelu-oai kmeelu-oai commented May 2, 2026

Summary

Moves the WebRTC realtime sideband websocket join out of the voice start critical path. Call creation still posts the SDP offer and session config synchronously so the client gets the SDP answer, but the sideband websocket now connects in the input task async and doesn't block conversation state installation.

This lets the normal realtime input channels buffer text, handoff output, and audio while the WebRTC sideband websocket is connecting. If the sideband join fails while the conversation is still active, the task sends a RealtimeEvent::Error through the existing events_tx / fanout path.

To rephrase this:

  • No longer blocked on sideband: the client can receive the SDP answer earlier, set up the WebRTC peer connection, and let the media leg progress while the sideband websocket joins.
  • Still blocked on sideband: queued text, handoff output, and sideband server events cannot flow until connect_webrtc_sideband(...).await finishes and then run_realtime_input_task(...) starts

Validation

  • env CODEX_SKIP_VENDORED_BWRAP=1 cargo test --manifest-path codex-rs/Cargo.toml -p codex-core --test all conversation_webrtc_start_posts_generated_session

CODEX_SKIP_VENDORED_BWRAP=1 is needed in this local environment because libcap.pc is not installed for the vendored bubblewrap build.

Testing

I tested this locally by running cargo run -p codex-cli --bin codex -- --enable realtime_conversation and invoking /realtime. Then, we get logs emitted in ~/.codex/log/codex-tui.log.

Before the Change

Logging commit (c0299e6)

2026-05-04T16:06:09.251956Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: starting realtime conversation
2026-05-04T16:06:09.251980Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: creating realtime call transport="webrtc"
2026-05-04T16:06:10.365722Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: realtime call created; sdp answer ready transport="webrtc" call_id=rtc_u0_Dbq65nhak5eLjQZ73yhAy elapsed_ms=1113 total_elapsed_ms=1113
2026-05-04T16:06:10.365843Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: connecting realtime sideband websocket call_id=rtc_u0_Dbq65nhak5eLjQZ73yhAy
2026-05-04T16:06:10.784528Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: connected realtime sideband websocket call_id=rtc_u0_Dbq65nhak5eLjQZ73yhAy elapsed_ms=418 total_elapsed_ms=1532
2026-05-04T16:06:10.784665Z  INFO session_loop{thread_id=019df3b9-e3d8-7271-b13a-b880119aa4c2}:submission_dispatch{otel.name="op.dispatch.realtime_conversation_start" submission.id="019df3bd-65df-7ee2-8125-1d6701fe39d2" codex.op="realtime_conversation_start"}: codex_core::realtime_conversation: realtime conversation started

After the Change

Logging commit (c8b00ac)

2026-05-04T15:41:24.080363Z  INFO ... codex_core::realtime_conversation: starting realtime conversation
2026-05-04T15:41:24.080434Z  INFO ... codex_core::realtime_conversation: creating realtime call transport="webrtc"
2026-05-04T15:41:25.106906Z  INFO ... codex_core::realtime_conversation: realtime call created; sdp answer ready transport="webrtc" call_id=rtc_u0_Dbpi8nhak5eLjQZ73yhAy elapsed_ms=1026 total_elapsed_ms=1026
2026-05-04T15:41:25.107067Z  INFO ... codex_core::realtime_conversation: spawned realtime sideband connection task transport="webrtc" total_elapsed_ms=1026
2026-05-04T15:41:25.107160Z  INFO ... codex_core::realtime_conversation: realtime conversation started
2026-05-04T15:41:25.107185Z  INFO codex_core::realtime_conversation: connecting realtime sideband websocket call_id=rtc_u0_Dbpi8nhak5eLjQZ73yhAy
2026-05-04T15:41:25.107352Z  INFO ... codex_core::realtime_conversation: sent realtime sdp answer to client
2026-05-04T15:41:26.076685Z  INFO codex_core::realtime_conversation: connected realtime sideband websocket call_id=rtc_u0_Dbpi8nhak5eLjQZ73yhAy elapsed_ms=969 total_elapsed_ms=1996
2026-05-04T15:41:26.573893Z  INFO codex_core::realtime_conversation: realtime session updated realtime_session_id=sess_u0_Dbpi8nhak5eLjQZ73yhAy
2026-05-04T15:41:26.573970Z  INFO codex_core::realtime_conversation: received realtime conversation event event=SessionUpdated { ... }

Conclusion

Here we see that we saved about a half a second in conversation startup (1532ms -> 969ms). This also checks out with my sanity tests; I was seeing at most a second of saving.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 2, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

kmeelu-oai and others added 2 commits May 1, 2026 21:48
Move the WebRTC sideband websocket connect out of the start critical path. The call-create request still returns the SDP answer synchronously, while the sideband input task connects in the background and uses the existing input channels to queue text, handoff output, and audio until the websocket is ready.

Add coverage that a delayed sideband accepts queued text after the SDP answer has already been emitted.

Co-authored-by: Codex <noreply@openai.com>
@kmeelu-oai kmeelu-oai force-pushed the kmeelu/async-realtime-sideband-startup branch from c5fd3a8 to df4a907 Compare May 4, 2026 15:52
@kmeelu-oai
Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@kmeelu-oai kmeelu-oai force-pushed the kmeelu/async-realtime-sideband-startup branch from 6a7768e to e7207ca Compare May 4, 2026 18:14
.await
.map_err(map_api_error)?;
(connection, Some(call.sdp))
let task = spawn_webrtc_sideband_input_task(RealtimeWebrtcSidebandInputTask {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now also runs run_realtime_input_task, as you'll see below. We can alternatively make that clearer by running a spawn_webrtc_sideband_and_realtime_input_tasks and chain them (we could even put that logic here, if desired).

event_parser,
} = input;

tokio::spawn(async move {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big diff hides the fact that we're just putting this within spawn_realtime_input_task, which calls tokio::spawn.

@kmeelu-oai kmeelu-oai requested a review from guinness-oai May 4, 2026 19:32
@kmeelu-oai kmeelu-oai marked this pull request as ready for review May 4, 2026 19:32
@kmeelu-oai kmeelu-oai requested a review from a team as a code owner May 4, 2026 19:32
@guinness-oai guinness-oai requested a review from aibrahim-oai May 4, 2026 19:51
}

let connection = match client
.connect_webrtc_sideband(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi: defaults to 4 retries.

Wait for the sideband outbound request before asserting on the recorded handshake so async sideband startup cannot race the test on musl.

Co-authored-by: Codex <noreply@openai.com>
@kmeelu-oai kmeelu-oai enabled auto-merge (squash) May 4, 2026 21:58
@kmeelu-oai kmeelu-oai merged commit e7e6267 into main May 4, 2026
26 checks passed
@kmeelu-oai kmeelu-oai deleted the kmeelu/async-realtime-sideband-startup branch May 4, 2026 22:28
@github-actions github-actions Bot locked and limited conversation to collaborators May 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants