Skip to content

fix: preserve adaptive interruption across tool calls - #2290

Open
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
gals-aseptic-hanks
Open

fix: preserve adaptive interruption across tool calls#2290
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
gals-aseptic-hanks

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Ports livekit/agents#6564 to preserve adaptive interruption when audible playout pauses for tool execution or resumes during active user speech.

  • Treat playout pauses and tool-call thinking gaps as real speech boundaries.
  • Close inconclusive overlaps before resetting the detector and exclude them from backchannel metrics.
  • Restart and serialize overlap inference boundaries when paused playout resumes.
  • Add a patch changeset for @livekit/agents.
Source diff coverage
  • Adapted: livekit-agents/livekit/agents/inference/interruption.py -> agents/src/inference/interruption/interruption_stream.ts. Excludes agentEnded overlaps from backchannel metrics; target event types already carry the marker.
  • Adapted: livekit-agents/livekit/agents/voice/agent_activity.py -> agents/src/voice/agent_activity.ts. Removes pause/resume lifecycle exceptions, ends active speech at tool-call thinking gaps, rearms interruption handling, and removes duplicate pause-cancel teardown. The equivalent target realtime tool path is updated because JS separates pipeline and realtime execution.
  • Adapted: livekit-agents/livekit/agents/voice/audio_recognition.py -> agents/src/voice/audio_recognition.ts. Closes overlap before detector reset, restarts inference when playout resumes over active user speech, preserves endpointing state, and serializes JS async sentinel batches to match Python send_nowait ordering.
  • Adapted: livekit-agents/livekit/agents/voice/remote_session.py -> agents/src/voice/remote_session.ts. Mirrors the source TODO because the remote-session protobuf cannot yet carry agentEnded. No unsupported wire field is invented.
  • Adapted: tests/test_agent_session.py -> agents/src/voice/agent_activity_tool_output_commit.test.ts. Verifies speech end is observed after entering the tool-call thinking state and later ends occur while listening.
  • Adapted: tests/test_false_interruption_resume.py -> agents/src/voice/agent_activity.test.ts. Verifies cancelling already-paused speech does not report a duplicate speech end.
  • Adapted in part: tests/test_interruption/test_overlapping_speech_event.py -> agents/src/inference/interruption/interruption_stream.test.ts. Ports the added agent-ended metric assertion. The existing Python/Pydantic serialization assertion is not applicable to a compile-time TypeScript interface.
  • Adapted: tests/test_realtime_adaptive_interruption.py -> agents/src/voice/realtime_adaptive_interruption.test.ts. Ports overlap-before-reset ordering, idempotent user end, resumed detector restart, and real speech-end assertions.

Protocol gap

agentEnded still cannot be forwarded by remote sessions because the shared AgentSessionEvent protobuf has no field for it. This matches the authoritative source PR, which documents the same limitation with TODO(AGT-3180). Local adaptive interruption behavior and metrics preserve the marker.

Validation

  • pnpm test agents (2120 passed, 5 skipped)
  • pnpm build
  • pnpm build:agents after final refinement
  • pnpm --filter @livekit/agents typecheck
  • pnpm --filter @livekit/agents lint
  • pnpm format:check
  • cue-cli voice run: post-tool playout was adaptively interrupted, getWeather executed for the replacement turn, and a fresh assistant response was committed

Source: livekit/agents#6564


Ported from livekit/agents#6564

Original PR description

Bug: Adaptive interruption could silently drop a user overlap when agent playout resumed after a tool call because the interruption stream received mismatched speech boundaries.

Treat thinking gaps as real speech boundaries and restart overlap inference when playout resumes mid-utterance. Agent-ended overlaps remain inconclusive and no longer count as backchannels; remote forwarding is noted until the protocol carries that marker.

Fixes livekit/agents#6548. Fixes livekit/agents#6548.

@rosetta-livekit-bot
rosetta-livekit-bot Bot requested a review from a team as a code owner August 14, 2026 13:12
@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 12829f9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines 1696 to 1699
this.audioRecognition.onEndOfAgentSpeech(
options?.ignoreUserTranscriptUntil ?? Date.now(),
{ paused: true },
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Pausing the agent's voice now reports a bogus "user talked over the agent" event every time

An overlap is opened and then immediately closed as agent-ended (onStartOfOverlapSpeech at agents/src/voice/agent_activity.ts:1684-1689 followed by onEndOfAgentSpeech at agents/src/voice/agent_activity.ts:1696) whenever agent playout is paused, so listeners receive an extra overlapping-speech notification and a zero-valued measurement for a pause that produced no real judgement.
Impact: Applications and dashboards subscribed to overlapping-speech events see spurious entries (and zeroed interruption metrics) each time the agent's voice is paused.

Mechanism: overlap opened right before the boundary that closes it

Before this PR the pause path passed { paused: true }, so no agent-speech-ended sentinel was written and the overlap opened at agents/src/voice/agent_activity.ts:1684 stayed open across the pause (an event was produced only on a real verdict or user-speech end).

Now onEndOfAgentSpeech (agents/src/voice/audio_recognition.ts:784-831) always closes any open overlap via closeOverlap(now, undefined, true) and then sends agent-speech-ended. Since the pause path opens the overlap synchronously one statement earlier, the queued sentinel pair is overlap-speech-started + overlap-speech-ended(agentEnded: true), and the transform at agents/src/inference/interruption/interruption_stream.ts:274-300 enqueues an OverlappingSpeechEvent built from InterruptionCacheEntry.default() (all zeros, isInterruption: false). That event is emitted publicly (this.model.emit('overlapping_speech', chunk) at agents/src/inference/interruption/interruption_stream.ts:331) and forwarded to the session, plus an interruption_metrics event with numInterruptions: 0 / numBackchannels: 0.

If the intent is that pausing is a plain speech boundary, the overlap open at agents/src/voice/agent_activity.ts:1683-1689 is now redundant and should be dropped (the overlap is re-opened by onStartOfAgentSpeech when playout resumes while the user is still speaking, agents/src/voice/audio_recognition.ts:770-775).

Prompt for agents
In AgentActivity.interruptByAudioActivity (agents/src/voice/agent_activity.ts, pause branch), an overlap inference is started via audioRecognition.onStartOfOverlapSpeech(...) immediately before audioRecognition.onEndOfAgentSpeech(...) is called for the same pause. With the new semantics in AudioRecognition.onEndOfAgentSpeech (which now always closes an open overlap with agentEnded=true and then resets the detector), this pair produces an immediate, meaningless overlapping_speech event with all-zero timings plus a zeroed interruption_metrics event on every pause. Decide whether the overlap should still be opened at pause time at all: since AudioRecognition.onStartOfAgentSpeech now re-opens the overlap when playout resumes while the user is still speaking, the pause-time open appears redundant. Verify against the upstream Python implementation and remove the redundant open (or suppress emitting an event for an overlap that is closed with no inference requests).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adaptive interruption: a second agent speech segment (e.g. after a tool call) disarms an open overlap and silently swallows the interruption

0 participants