Skip to content

fix(stt): don't clear the primary speaker on an unattributed segment#6460

Open
chuenchen309 wants to merge 1 commit into
livekit:mainfrom
chuenchen309:fix/primary-speaker-reset
Open

fix(stt): don't clear the primary speaker on an unattributed segment#6460
chuenchen309 wants to merge 1 commit into
livekit:mainfrom
chuenchen309:fix/primary-speaker-reset

Conversation

@chuenchen309

Copy link
Copy Markdown

Summary

_PrimarySpeakerDetector._update_primary_speaker clears _primary_speaker whenever a final transcript arrives without a speaker_id, because that case is folded in with detect_primary_speaker=False:

if sd.speaker_id is None or not self._detect_primary:
    self._primary_speaker = None
    return

Those are different situations. The flag being off means "never track a primary"; a missing speaker_id just means the diarizer couldn't attribute this one segment.

Clearing matters because None is also the "no primary yet" sentinel — the next attributed segment takes the it's the first speaker branch and is crowned with no loudness comparison. One unattributed segment is enough to hand primary to a quiet background speaker:

primary
A speaks (amplitude 3000) A
unattributed segment (no speaker_id) None
B speaks (amplitude 50, 60x quieter) B — skipped the RMS threshold

Without the unattributed segment in the middle, B is correctly rejected and A stays primary.

on_stt_event already guards speaker_id is None before touching _primary_speaker, so the unattributed segment was never going to be labelled either way — the only effect of clearing was losing the tracked state.

Scope

The detect_primary_speaker=False branch keeps its current behaviour; only the missing-speaker_id path changes. This is reachable through MultiSpeakerAdapter (exported from livekit.agents.stt), which needs a diarization-capable STT — it has no in-repo callers, so nothing inside the framework changes.

Testing

New tests/test_multi_speaker_primary_speaker.py, three unit tests: the quiet-speaker control, the bug case, and the detect_primary_speaker=False behaviour. The bug test fails on main and passes with this change; the other two pass on both. Existing stt tests (test_speaker_id_grouping, test_stt*) — 49 passed. ruff check/ruff format clean; mypy reports the same 14 pre-existing errors in other files with and without this diff, none in the file I touched.

Disclosure: written with Claude Code. I verified the reproduction, the reachability, and the test red/green myself.

`_update_primary_speaker` cleared `_primary_speaker` whenever a final
transcript arrived without a `speaker_id`, folding that case in with the
`detect_primary_speaker=False` case. Those are different situations: the
flag being off means "never track a primary", while a missing speaker_id
just means the diarizer couldn't attribute this one segment.

Clearing the primary matters because `None` is also the "no primary yet"
sentinel: the next attributed segment hits the `is it the first speaker?`
branch and is crowned without any loudness comparison. So a single
unattributed segment lets a quiet background speaker take over:

    A speaks (loud)          -> primary = A
    unattributed segment     -> primary = None
    B speaks (60x quieter)   -> primary = B      # skips the RMS threshold

`on_stt_event` already guards `speaker_id is None` before using
`_primary_speaker`, so the segment itself was never going to be labelled
either way - the only effect of clearing was to lose the tracked state.

Leave the primary in place instead. The `detect_primary_speaker=False`
branch keeps its existing behaviour.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chuenchen309
chuenchen309 requested a review from a team as a code owner July 17, 2026 04:11

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

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.

1 participant