Skip to content

feat(voice): auto-disable realtime server-side turn detection#6495

Open
longcw wants to merge 11 commits into
mainfrom
longc/realtime-auto-disable-td
Open

feat(voice): auto-disable realtime server-side turn detection#6495
longcw wants to merge 11 commits into
mainfrom
longc/realtime-auto-disable-td

Conversation

@longcw

@longcw longcw commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Auto-disables a realtime model's server-side turn detection for the session when the user configured client-side turn-taking (a TurnDetector, vad, manual, or an explicit interruption mode) and the model supports it, so client-side EoT/barge-in works without also passing turn_detection=None to the RealtimeModel.

Adds RealtimeCapabilities.can_disable_turn_detection (OpenAI sets it from the ctor turn_detection given-ness) and a session(turn_detection_disabled=...) seam applied on the initial session.update via the per-session opts copy, so the model is never mutated and stays reusable. AgentActivity resolves the effective state once (_rt_turn_detection_enabled) and routes every server-side turn-detection check through it.

Other realtime plugins accept-and-ignore the flag (can_disable_turn_detection stays False): xAI (client turn-taking not stable in testing) and Gemini/others (no commit_audio/clear_audio; turns are activity-signal based).

Stacked on longc/realtime-barge-in; this PR targets that branch, not main.

@longcw
longcw requested a review from a team as a code owner July 21, 2026 08:57
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 9c2cc63 to 9caf4ba Compare July 21, 2026 09:27
@chenghao-mou chenghao-mou self-assigned this Jul 21, 2026
@longcw
longcw force-pushed the longc/realtime-barge-in branch from a557e26 to 2926544 Compare July 22, 2026 01:49
@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from f9c7088 to b525a7a Compare July 22, 2026 01:49
devin-ai-integration[bot]

This comment was marked as resolved.

@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 48359d0 to 9c988f3 Compare July 22, 2026 02:57
longcw added 11 commits July 23, 2026 14:01
A realtime model transcribes internally and commits turns manually, so barge-in can gatekeep the overlapping user turn by withholding the commit instead of holding and flushing STT transcripts. Relax _resolve_interruption_detection so a realtime model with server-side turn detection disabled no longer requires a separate STT; the STT pipeline path is unchanged.
When a realtime model runs client-side barge-in with server-side turn detection disabled, an overlapping utterance the adaptive detector classifies as a backchannel must not create a user turn. Track the backchannel verdict per turn and skip the end-of-turn commit; clear the model's audio buffer so the backchannel audio doesn't leak into the next turn.
Move the backchannel verdict from an AgentActivity flag reset on VAD start-of-speech to a turn-scoped signal owned by the audio recognition layer and surfaced on _EndOfTurnInfo. A second start-of-speech within a logical turn no longer resets the verdict, and a late verdict can no longer leak into the next turn (it is only recorded while the current turn has an unresolved overlap).

Clear the buffered input audio at the drop point rather than at the verdict, so it also covers the case where the agent is still speaking and the verdict has not arrived, and no longer truncates a turn that escalates from a backchannel into an interruption.
Clear the turn's backchannel verdict when a new speech segment starts without overlapping agent speech, so a real turn following a backchannel in the same logical turn commits instead of being dropped. A stutter while the agent is still speaking keeps the verdict, so pure backchannels are still dropped.
…irst

When the agent finishes speaking while the user is still overlapping, the overlap is force-ended and a synthesized is_interruption=False event is emitted. That isn't a real verdict — the user may still be mid-turn — but it was latched as a backchannel and carried to the end of the turn, dropping genuine speech.

Tag the agent-driven overlap-end via agent_ended on the sentinel and OverlappingSpeechEvent, and skip the backchannel latch for it so the turn falls back to committing.
The backchannel verdict persisted a True from an earlier overlap into a later inconclusive overlap within the same agent turn, so a user turn that continued past the agent could be wrongly dropped. Reset it on every speech onset so each overlap re-derives its own verdict.
A confirmed backchannel over the agent left its audio in the realtime input buffer, so it prefixed the next committed turn. Clear the buffer when the backchannel is confirmed while the user is between segments; deferring when the user is already mid next-segment avoids clipping a real turn. This also applies when an STT is attached, since the realtime session buffers input regardless.
Disable a realtime model's server-side turn detection for the session when the
user configured client-side turn-taking (a TurnDetector, "vad", "manual", or an
explicit interruption mode) and the model supports it, so client-side EoT/barge-in
works without also passing turn_detection=None to the RealtimeModel.

- add RealtimeCapabilities.can_disable_turn_detection; OpenAI sets it from the
  ctor turn_detection given-ness
- add session(turn_detection_disabled=...), applied on the initial session.update
  via the per-session opts copy so the model is never mutated (stays reusable)
- AgentActivity resolves the effective state once (_rt_turn_detection_enabled) and
  routes every server-side turn-detection check through it

Other realtime plugins accept-and-ignore the flag (can_disable_turn_detection stays
False): xAI (client turn-taking not stable in testing) and Gemini/others (no
commit_audio/clear_audio; turns are activity-signal based).
RealtimeModel.update_options force-synced turn_detection=self._opts.turn_detection to
every session unconditionally, so changing an unrelated field (e.g. voice) re-enabled
server VAD on a session that opted out of server-side turn detection. Only propagate
turn_detection when the caller set it; an explicit value still propagates so callers
can re-enable it.
…tection

On an agent handoff the realtime session is reused as-is. If the new agent resolves
server-side turn detection differently (it can override turn_detection, interruption,
or vad), the reused session kept the previous agent's mode, causing duplicate or
missing replies. Refuse reuse on a mismatch so a fresh session is created with the
correct setting.
_resolve_interruption_detection and the on_end_of_turn backchannel-drop guard still read the model's static capabilities.turn_detection instead of the session-resolved _rt_turn_detection_enabled. When a realtime model with built-in turn detection has it auto-disabled for the session (client-side turn-taking configured), can_gatekeep stayed False so the adaptive detector was never created, and the backchannel-drop path was skipped — silently disabling the client-side barge-in this stack is meant to enable.
@longcw
longcw force-pushed the longc/realtime-barge-in branch from 82a241c to 2b70881 Compare July 23, 2026 06:01
@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 9c988f3 to eca4e01 Compare July 23, 2026 06:01
capabilities=llm.RealtimeCapabilities(
message_truncation=True,
turn_detection=turn_detection is not None,
can_disable_turn_detection=not is_given(turn_detection),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: we should update with_azure too

# model to auto-generate a tool reply (auto_tool_reply_generation=True).
self._pending_auto_tool_reply_fut: asyncio.Future[None] | None = None

def _resolve_rt_turn_detection_enabled(self) -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we handle update_options with turn_detection for realtime too?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we should handle with_azure too since it assigns the default turn detection before init.

Base automatically changed from longc/realtime-barge-in to main July 23, 2026 13:18
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.

2 participants