Thanks — this is a great, detailed report. I’m responding to the four specific questions you asked and suggesting a small set of targeted checks so we can confirm whether this is a start-up race or something else.
Short answers to your four questions
1. on_enter() awaiting an event while session.start() completes — could leave audio pipeline inconsistent?
• Possibly. session.start() completes and marks the session as started before the on_enter() task finishes, so if on_enter() blocks on an external event and that delays participant wiring, there is a window where room audio may arrive before the per-participant audio stream is fully bound.
1. Is the forward_audio_task → ParticipantAudioInputStream → push_audio() → _rt_session.push_audio() chain guaranteed ready by the time on_enter() unblocks?
• Not strictly guaranteed. session.start() creates the forward task and sets _started = True once the Realtime socket is connected, but some per-participant initialization (setting the participant on the ParticipantAudioInputStream) may still be pending and depends on RoomIO/participant subscription wiring which can complete asynchronously after start.
1. Race between RoomIO._init_task and SIP participant joining that drops frames?
• Yes — if the SIP participant joins and their audio begins before RoomIO has finished setting the participant identity/subscriptions, incoming frames may be ignored or dropped until the stream is associated. That matches your observation that push_audio drops frames when not self._started and the timing of participant set calls.
1. Known issues with outbound SIP + RealtimeModel (semantic_vad) after first generate_reply()?
• There are no confirmed platform-level guarantees that semantic_vad will always emit events after the agent TTS finishes in a mixed-timing scenario; if audio frames are delayed/dropped while the VAD is initializing or while per-participant setup occurs, the VAD may not produce input_speech_started events. This manifests exactly as you described (no segmentation, silence detection fires).
Recommended next checks (minimal, reproduceable, and targeted)
1. Capture precise ordering timestamps (logs) for these events in a single failing call:
◦ Realtime WS connected completion time
◦ session._started set time
◦ forward_audio_task started time
◦ ParticipantAudioInputStream.set_participant() time
◦ SIP participant join time / first RTP frame arrival
◦ Start of agent TTS playback and end of playback
◦ Any push_audio() calls and whether they return early / drop frames
1. Add brief delay test
◦ As a quick diagnostic, after session.start() completes but before placing the SIP call, add a short delay (e.g., 250–500 ms) to see if the issue disappears. If it does, that strongly indicates a race in participant wiring.
1. Instrument push_audio path
◦ Log inside push_audio() whether frames are accepted or dropped and whether the ParticipantAudioInputStream has a set participant at that moment.
1. Try reversing the wait
◦ In your broken flow, try starting the session fully (await session.start()) before creating the SIP participant (you already have the prod working case — confirm that behavior consistently works). This is the practical workaround while we narrow the root cause.
If you can share the minimal logs/timestamps from step 1 (the six timestamps above), I’ll map them to the internal lifecycle and confirm exactly which handoff is missing. If you want, I can also provide a short patch snippet to log participant binding and push_audio acceptance points to make the timing gaps explicit. Which would you prefer?
, with interactive elements