allow_interruptions does not work when used like t...
# ask-ai
b
allow_interruptions does not work when used like this. Why is this the case? @sentry_trace(operation="agent", description="Conversation start") async def *on_enter*(self) -> None: """ For outbound calls, we intentionally do NOT greet first. Instead, we wait for the user to speak (e.g., "Hello?") which confirms they've actually picked up the phone. The agent will then respond naturally. For inbound calls, we greet immediately as the caller expects. This solves the issue where sip.callStatus becomes "active" before the person actually answers (outbound), which would cause the agent to start talking while the phone is still ringing. """ if self.call_type == "outbound": logger.info( f"Outbound call - agent ready, waiting for user to speak first. " f"Model: {self.llm_model}" ) self.session.on("user_input_transcribed", self._on_user_input_transcribed) if self.enter_phrase_said is False: await self.session.say( self.agent_constructor.get_enter_phrase(), _allow_interruptions_=False, ) self.enter_phrase_said = True
t
Thanks for reaching out, Amir. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about why
allow_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 | LiveKit
b
I'm using a pipeline agent. How can I ensure interruptions are disabled globally so that the message is said by the agent uninterrupted - and then I will enable interruptions after the message is complete
t
Thanks — I’m responding to your question about making the agent speak uninterrupted in a pipeline agent, then re-enabling interruptions once that message finishes. Short answer: there isn’t a single persistent “global” session flag you can flip. The right, reliable pattern is to disable interruptions for the specific speech action, await playback to finish, then continue with interruption-enabled behavior. You can wrap that in a small helper to get the “global” effect at the application level. Why:
allow_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):
Copy code
# 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:
Copy code
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:
Copy code
async def play_uninterrupted(session, text):
    handle = session.say(text, allow_interruptions=False)
    await handle  # wait for full playout
Usage:
Copy code
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:
Copy code
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 Documentation
b
Im already awaiting this session.say inside the on_enter function. It doesn't work
t
Thanks — I hear your frustration. You’re awaiting
session.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?
b
The interruption is happening when the user tries to speak over the agent. I want to completely disable this behaviour. Why doesn't your library have this functionality
t
Thanks — I hear you: you want the agent to be completely uninterruptible when it speaks (so user speech never cuts it off), and you’d like that enforced globally without relying on per-call flags. I know that’s critical for some outbound flows, so here’s a clear explanation of what’s supported, why it behaves that way, and exactly how to guarantee uninterruptible playback in your pipeline agent. Short summary • There is no single persistent session flag that disables interruptions for every future message. • You can guarantee fully uninterruptible playback by (A) calling speech with allow_interruptions=False and awaiting completion, and (B) preventing transcription/VAD-driven handlers from acting while that playback is happening. Optionally, disable audio input to make interruption impossible. • I’ll give you two robust patterns you can implement immediately (one non-invasive, one that physically prevents VAD). Why allow_interruptions is per-message • The system intentionally makes interruption a property of each speech action so you can have fine-grained UX (some prompts should be interruptible, some shouldn’t). That’s why a global boolean isn’t provided. Two reliable ways to make the agent completely uninterruptible 1) Preferred, minimal-change approach — per-message uninterruptible + transient guard • Use allow_interruptions=False on the speech call and set a transient in-memory flag so your transcription handler ignores events while playback is in progress. Example pattern (Python-like):
Copy code
# 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:
Copy code
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