brief-dress-9950
03/03/2026, 12:59 PMtall-airline-16756
03/03/2026, 12:59 PMtall-airline-16756
03/03/2026, 1:00 PMallow_interruptions=False appears not to work in your outbound-call snippet (you showed session.say(..., allow_interruptions=False) and explained you wait for the user to speak on outbound calls).
Short answer: allow_interruptions=False prevents automatic cancellation of the agent’s speech by the VAD/transcription system, but it does not stop other code paths or upstream events from causing the agent to be interrupted or to start speaking. In outbound calls there are a few common reasons this can look like the flag is ignored.
Below I’ll highlight the most likely causes and practical checks/fixes you can try.
1) Event handler or application code is programmatically interrupting
• You attach self.session.on("user_input_transcribed", self._on_user_input_transcribed). If _on_user_input_transcribed calls session.say() or otherwise triggers agent speech, that will start new audio regardless of allow_interruptions=False. The flag protects against automatic VAD-triggered cancellation, not against your own code invoking speech.
• Action: Audit _on_user_input_transcribed and any other callbacks to ensure they do not unconditionally start agent speech. Add guards so the handler only triggers when you actually want the agent to respond.
2) False positives from early media / VAD noise on outbound calls
• Outbound calls can show sip.callStatus as "active" before the callee actually picks up (early media, ringback tones). Ringback or network noise can trigger STT/VAD and produce partial transcripts that fire user_input_transcribed.
• Action: In _on_user_input_transcribed ignore short/noisy transcripts and non-final partial results. Example checks:
◦ require transcript.is_final (or equivalent)
◦ reject very short texts (length < 2 or only punctuation)
◦ filter common noise tokens like single-word “hello” if you treat those as unreliable
This prevents spurious events from causing the agent to act.
3) Timing — speaker plays before media is ready
• If you call session.say() in on_enter() before the participant/media path is fully established, interruptions or odd behavior can occur because audio routing or turn detection isn’t stable yet.
• Action: Wait for a reliable participant/media-connected event (e.g., participant_connected) before playing speech, or detect when audio is actually flowing.
4) Realtime model vs pipeline agent differences
• If you are using a realtime LLM model, server-side turn control may handle interruptions differently; local allow_interruptions semantics can be different or partially overridden.
• Action: Confirm whether you’re using a pipeline agent (STT → LLM → TTS) or a realtime model. If you need strict local interruption control, pipeline agents give more predictable behavior for allow_interruptions.
5) Multiple agent instances or duplicate sessions
• If multiple agents/sessions attach to the same call (retries or duplicate dispatch), overlapping speech can occur and look like interruptions.
• Action: Verify only one agent instance is active per call/room.
What I recommend you try next
1. Add defensive filtering in _on_user_input_transcribed (require final transcripts, length checks).
2. Ensure _on_user_input_transcribed does not immediately call session.say() unless the transcript passes your filters.
3. Delay the greeting until participant/media is confirmed connected for inbound greetings; for outbound, do not greet until you receive a reliable user speech event.
4. Confirm agent type (pipeline vs realtime) and single-instance dispatch.
To pin this down I need one more detail:
• Is your agent a pipeline agent (STT → LLM → TTS) or a realtime model? Also paste the core of _on_user_input_transcribed (or describe what it does) and a short timeline from logs showing when user_input_transcribed fires vs when the agent starts/stops speaking.
With that I can point to the exact cause and suggest a minimal code change that will make allow_interruptions=False behave as you expect.
Sources: Agent speech and audio | LiveKit Documentation | VAD and turn detection configuration guide | LiveKitbrief-dress-9950
03/03/2026, 1:02 PMtall-airline-16756
03/03/2026, 1:02 PMallow_interruptions=False prevents the VAD/STT from cutting the current playback, but it applies per speech/generation call. Programmatic interruptions (your code calling `say()`/`generate_reply()`), false STT events, or media timing issues can still cause unexpected behavior unless you explicitly control the flow.
Recommended implementation (pipeline agent)
1) Play the critical message uninterruptible and wait for completion
• Call session.say(..., allow_interruptions=False) or session.generate_reply(..., allow_interruptions=False).
• Await the returned handle so your code does not proceed until playback finishes.
Example (Python):
# Play a message that must not be interrupted
handle = await session.say(
"Important announcement — please hold.",
allow_interruptions=False
)
# If session.say returns a handle rather than awaiting directly:
# handle = session.say(..., allow_interruptions=False)
# await handle
# At this point playback is finished and normal interruption behavior resumes
2) Optional explicit re-enable (usually not required)
• The default for subsequent calls is interruptible (allow_interruptions=True). If you want to be explicit:
await session.say("You may speak now.", allow_interruptions=True)
3) Encapsulate into a helper for “global” behavior
• Make a helper so callers don’t forget to await and to centralize any extra checks (e.g., filter incoming transcripts while uninterruptible).
Helper example:
async def play_uninterrupted(session, text):
handle = session.say(text, allow_interruptions=False)
await handle # wait for full playout
Usage:
await play_uninterrupted(session, "This message cannot be interrupted.")
# Now resume normal flow
4) Guard your transcription handler while uninterruptible
• Even with allow_interruptions=False, the system may still emit transcription events (noise, ringback). Make sure `_on_user_input_transcribed`:
◦ ignores partial/non-final transcripts,
◦ ignores very short or noisy transcripts,
◦ does not programmatically call say() unless the transcript meets your criteria.
Example minimal guard:
def _on_user_input_transcribed(self, transcript):
if not transcript.is_final:
return
text = transcript.text.strip().lower()
if len(text) < 2: # too short, likely noise
return
# proceed with response logic
5) Ensure playback starts when media is ready
• For outbound calls, don’t play speech before audio routing is stable. Wait for a participant/media-connected event to avoid race conditions where early media or ringback causes false triggers.
Summary
• Use allow_interruptions=False for the specific message and await playback. That ensures the message is spoken uninterrupted.
• Subsequent messages default to interruptible, so interruptions are effectively “re-enabled” after awaiting the handle.
• Wrap this pattern in a helper and add guards in your transcription handler to avoid spurious triggers during the uninterruptible window.
If you want, I can:
• Provide a drop-in helper function that also sets a transient in-memory flag (e.g., self.uninterruptible = True) so your transcript handler can entirely ignore events while the agent is uninterruptible.
• Review your _on_user_input_transcribed implementation and suggest exact guard code based on it.
Which would you prefer?
Sources: Turns overview | LiveKit Documentation | Uninterruptable Agent | LiveKit Documentationbrief-dress-9950
03/03/2026, 1:05 PMtall-airline-16756
03/03/2026, 1:05 PMsession.say(..., allow_interruptions=False) inside on_enter, but the agent is still being interrupted. I can help pinpoint why that’s happening, but I need a few concrete details so we can diagnose the exact cause.
Quick checklist of the most likely causes (so you know what I’m looking for):
• Your _on_user_input_transcribed handler or other callbacks may be programmatically triggering a new say() / generate_reply() when they see any transcript.
• on_enter may be running before media/participant is fully connected, so VAD/STT or early media (ringback) generates transcripts while playback behavior is unstable.
• Partial/non-final transcripts or short noisy transcripts (ringback) are causing your handler to fire; allow_interruptions=False prevents cancellation of playback but does not stop your code from starting new speech.
• There could be another agent/session instance or retry logic creating overlapping speech.
Please provide the following so I can give a precise fix:
1) The core of your _on_user_input_transcribed handler (or a short description of what it does). Specifically: does it call session.say() / generate_reply() immediately on any transcript? 2) The exact code for your on_enter flow (the part where you attach the handler and call session.say) — you posted earlier, but paste the current exact snippet if it changed. 3) A short log snippet (timestamps) showing:
• when on_enter starts,
• when session.say(..., allow_interruptions=False) is called,
• when user_input_transcribed events fire,
• when the agent’s playback starts/stops (or when say() resolves).
If you don’t have logs, brief timing notes are OK (e.g., "user_input_transcribed fires 200ms after say() started"). 4) Do you see partial transcripts (non-final) firing before final? Does your system send is_final on transcripts? 5) Are there multiple agents/sessions that could attach to the same call, or any retry logic that might recreate a session?
Immediate troubleshooting steps you can try now (no code change required beyond logging and small guards):
• Add logging to _on_user_input_transcribed to see whether the event is partial vs final and its timestamp relative to the say() call.
• Temporarily make _on_user_input_transcribed a no-op to confirm whether your handler is the source of the interruption.
• If you can, wait for a reliable media event (e.g., participant_connected) before calling session.say() to avoid early-media race conditions.
• Add guard logic in _on_user_input_transcribed to ignore transcripts while a local uninterruptible flag is set:
◦ set self.uninterruptible = True before await session.say(..., allow_interruptions=False)
◦ clear it after await
◦ in the transcript handler, ignore events while self.uninterruptible is True
If you’d like, paste the items above (1–3) and I’ll:
• Point out exactly where your interrupting call is coming from, or
• Provide the minimal code change (guard or reorder) to ensure the agent’s message plays fully before any new agent speech is started.
Which logs/snippet can you share now?brief-dress-9950
03/03/2026, 1:06 PMtall-airline-16756
03/03/2026, 1:07 PM# in your class
self._uninterruptible = False
# on_enter
self._uninterruptible = True
handle = session.say("Important message...", allow_interruptions=False)
await handle # wait for full playout
self._uninterruptible = False
# transcript handler
def _on_user_input_transcribed(self, transcript):
if self._uninterruptible:
return # ignore any transcription events while playing uninterruptible speech
# otherwise process transcript...
Why this works:
• allow_interruptions=False prevents the VAD/STT pipeline from cancelling playback.
• The in-memory flag prevents your code from programmatically starting new speech in response to transcription events that may still be emitted (e.g., ringback noise, partial transcripts).
• This avoids race conditions and preserves normal flow afterward because you clear the flag after await.
2) Stronger option — disable audio input during critical playback
• Temporarily disable the session’s audio input so VAD/STT cannot detect user speech at all while the agent is speaking.
Example:
session.input.set_audio_enabled(False) # stop capturing/detecting user speech
await session.say("Critical announcement...", allow_interruptions=False)
session.input.set_audio_enabled(True) # re-enable listening
Why this works:
• It physically prevents the system from detecting any user speech so neither VAD nor transcription events can fire. This guarantees no interruptions even in noisy or early-media situations.
• Use this if you want absolute certainty that the user cannot interrupt (but be cautious: you also block legitimate user input).
Extra defensive checks to add
• Require transcripts to be final before acting (ignore partial results).
• Ignore very short transcripts or transcripts matching ringback/noise tokens.
• Log timestamps for say() start, say() completion, and user_input_transcribed events to verify ordering.
Which option to pick
• Use option 1 (guard + allow_interruptions=False) if you want minimal change and still allow quick resumption of listening.
• Use option 2 (disable audio input) if you must 100% prevent any detection of user speech during the announcement (no transcripts, no false positives).
If you’re already awaiting session.say(..., allow_interruptions=False) in on_enter and it still gets interrupted, the most likely causes are:
• Your transcript handler still programmatically starts speech when it receives an event (so use the guard).
• Early media or ringback noise produces transcripts that you treat as real input (use transcript filtering or disable audio).
• Media not fully connected when you start speaking (wait for participant/media-ready events first).
If you want, I can:
• Provide a small helper function that implements the guard pattern and integrates with your existing handlers.
• Review your _on_user_input_transcribed handler and on_enter snippet and show the exact spot to add the guard or audio toggling.
Which would you prefer — the guarded approach (1) or the audio-disable approach (2)? Paste your _on_user_input_transcribed code if you want me to show the exact edit.
Sources: Turns overview | LiveKit Documentation | Agent speech and audio | LiveKit Documentation | Uninterruptable Agent | LiveKit Documentation | Text and transcriptions | LiveKit Documentation