Interruptions
This recipe shows how to script a user interrupting the agent mid-reply, and explains how the SDK handles the interrupt signal differently depending on whether the adapter supports native barge-in or falls back to VAD-driven detection.
Pattern
Two equivalent forms produce identical runtime behaviour.
Unrolled form
scenario.user("Tell me about my billing"),
scenario.agent(wait=False), # start agent reply in background
scenario.user("Wait — I meant account support"), # interrupt when agent begins speaking
scenario.agent(), # let the agent finish the recovery turnagent(wait=False) / agent({ wait: false }) starts the adapter's reply in the
background without blocking the script. The following steps run while the agent
keeps speaking; the next user() turn fires the interrupt and sends the
replacement user audio. (In TypeScript, scenario.voiceAgent({ wait: false }) is
an exported alias of the same step.)
Sugar form
scenario.user("Tell me about every product feature you offer"),
scenario.interrupt("Sorry — what are your business hours?"),
scenario.agent(),scenario.interrupt(...) is shorthand for the unrolled form above. Prefer it when
you only need to express a single interrupt. In TypeScript, interrupt takes
an options object with three trigger modes:
scenario.interrupt({ after: 2, content: "Wait, that's wrong!" }); // time-based
scenario.interrupt({ afterWords: 5, content: "No, that's not it" }); // word-count (needs streaming transcripts)
scenario.interrupt({ content: "Hold on—" }); // first-chunk barge-inafterWords requires an adapter with streaming transcripts; otherwise it raises
voice.UnsupportedCapabilityError suggesting interrupt({ content }) instead.
Native barge-in vs VAD-driven barge-in
When user() fires the interrupt, the SDK checks whether the adapter reports native
interrupt support (the interruption column in the capability matrix).
| Mode | How it works |
|---|---|
| Native barge-in | The adapter sends a provider-side cancel signal (response.cancel for OpenAI Realtime, clear for Twilio, etc.). The agent's TTS stops immediately and the user turn is queued. |
| VAD barge-in | No provider cancel signal is available. The user audio overlaps with the agent TTS and the SUT's Voice Activity Detection detects barge-in and stops the agent. Requires native_vad support on the adapter side. |
Check the adapter row in the capability matrix to see which mode applies to your deployment.
Random interruptions across a proceed loop
The patterns above script a single deterministic barge-in. For multi-turn
probabilistic interruptions — where any agent turn may be cut off with a given
probability — use voiceProceed with an InterruptionConfig:
import scenario, { voice } from "@langwatch/scenario";
const { InterruptionConfig } = voice;
script: [
scenario.agent(), // capture the opening greeting
scenario.user("I need help with my account"),
scenario.voiceProceed({
turns: 5,
interruptions: new InterruptionConfig({
probability: 0.5, // interrupt ~50% of agent turns
delayRange: [0.5, 2.0], // barge-in 0.5–2s after agent starts speaking
strategy: "random_phrase", // pick from the built-in canned-phrase pool
}),
}),
scenario.judge(),
]InterruptionConfig options:
| Option | Type | Default | Description |
|---|---|---|---|
probability | number | 0.3 | Fraction of agent turns to interrupt. |
delayRange | [number, number] | [0.5, 3.0] | Seconds to wait after agent starts speaking before firing the barge-in. |
strategy | "random_phrase" | "contextual" | "random_phrase" | "random_phrase" picks from the canned phrase pool; "contextual" generates a realistic interjection via an LLM from the running conversation. |
phrases | string[] | built-in pool | Phrase pool for "random_phrase" (and few-shot examples for "contextual"). |
Per-simulator interrupt probability
As an alternative to voiceProceed({ interruptions }), set interruptProbability
directly on the user simulator. This drives barge-ins on any plain scenario.proceed()
loop without needing an explicit InterruptionConfig:
scenario.userSimulatorAgent({
voice: "openai/nova",
interruptProbability: 0.3, // 30% of agent turns get a barge-in
})When both are configured, the voiceProceed({ interruptions }) config wins.
Worked example
Python:interruption_recovery.py
— demonstrates both unrolled and sugar forms back-to-back in a single scenario, with a
JudgeAgent verifying the agent recovered from both interruptions.
Adapter-specific variants:
random_interruptions.py— randomised interrupt timing across turnsgemini_live_interruption.py— Gemini Live adapter with native barge-inelevenlabs_interruption.py— ElevenLabs adapter interrupt path
interruption-recovery.test.ts— unrolled and sugar forms in a multi-turn Pipecat scenario, with a JudgeAgent verifying recoveryrandom-interruptions.test.ts—voiceProceed({ interruptions: new InterruptionConfig({...}) })across 5 turns; asserts barge-ins fired mid-utterance and canned-phrase strategy rangemini-live-interruption.test.ts— Gemini Live adapter with server-side native barge-inelevenlabs-interruption.test.ts— ElevenLabs adapter interrupt path
See also
- Capability matrix —
interruptionandnative_vadcolumns per adapter - Multi-turn recipe — scripting multi-step conversations
- Observability recipe — capture interrupt timing via latency metrics
