Skip to content

fix(speakers): defer short live epoch labels and gate owner evidence - #23533

Open
Git-on-my-level wants to merge 59 commits into
mainfrom
lane/live-speaker-epochs
Open

Git-on-my-level wants to merge 59 commits into
mainfrom
lane/live-speaker-epochs

Conversation

@Git-on-my-level

@Git-on-my-level Git-on-my-level commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Short Soniox epochs produced consecutive Speaker 4–8 labels because provider/socket scopes each reserve a new conversation voice. This PR ships the bounded safe repair: live unknown voices spend no visible ordinal until five seconds of non-overlapping placed speech. Names still come from existing manual/acoustic authority. Raw identities stay distinct and available for correction.

This stacked PR depends on #23462 (fix/speaker-identity-guardrails, integrated head d544d262e02b9be09682235b340ab98340a04224) and must merge after #23462. #23404 is already merged to main as 6243071d9efeff253570749da4cece26ffcc7943. This branch merges the reconciled prerequisite and main, with no rebase or force push, and targets main as requested. It extends their shared display authorities rather than introducing another labeler.

  • Flutter live transcript and episode naming use SpeakerNames' live qualification floor; qualified voices number in qualification order. Short/unplaced voices show the localized Speaker, with owner and person names preserved.
  • Live legacy moment labels defer ordinals and voice-order ledger entries. Once a scoped voice qualifies, its saved ledger token preserves the ordinal on later short or unplaced moment deltas; a different conversation or scope cannot borrow it. Consecutive unnamed turns join only when their identity keys agree.
  • Existing live cross-epoch owner-competition grouping requires five seconds of extracted acoustic evidence on each voice, the shared strict CROSS_EPOCH threshold, MATCH_MARGIN, complete-link agreement and different scopes. Weak/ambiguous evidence cannot remove an owner competitor. This grouping remains internal to owner arbitration; it does not rewrite transcript identities.

Scope and remaining work

This is the authorized safe subset, not complete anonymous live linking. Anonymous voices with substantial evidence in multiple epochs can still acquire separate ordinals. Cross-listen anonymous identity needs a bounded encrypted, model/version/provenance-bound evidence handoff and reversible display aliases fenced by manual receipt generation, replica concurrency and finalization. The implementation/qualification checklist is in backend/docs/listen_pusher_pipeline.mdx, “Remaining live anonymous linking”. Unplaced audio still bypasses matching; capture placement belongs to the timeline lane. No provider-label equality or weak short-turn vectors force an owner match.

Prerequisite review follow-ups

The refreshed prerequisite fixes catalog-only ordinary-word contact scrubbing. One review finding remains in files unchanged from #23462: partially named/owner voices can be counted twice in the transcript heading. It belongs to the prerequisite's review; this lane does not edit that branch. The live ledger finding is fixed here with an incremental-renderer regression.

Failure-Class: FC-generated-presentation-contract-bypass

Line-Count-Exception: app/lib/widgets/transcript.dart | 1589 -> 1590 | Pass the live qualification option into the existing shared speaker naming primitive; no new widget behavior or duplicated labeler.

Product invariants affected

  • INV-UI-3: SpeakerNames remains the Flutter naming authority. Live short-voice deferral extends that primitive and is documented in app/docs/ux-contract.md; distinct wire/correction identities and named-participant counts remain intact.

Trusted manual person_name projections remain visible, while a name bound to one excerpt never labels an unbound excerpt from the same provider voice. Focused regressions cover both boundaries.

Validation

  • Final publication source: fe4adf5a57797e9173f774df4bdaf8039ca73405; merged main 18fc1d6e114ff5a509b91c533949e24f6e2691c6 and fix(speakers): add typed identity, count and presentation guardrails #23462 d544d262e02b9be09682235b340ab98340a04224.

  • Backend: 477 tests across 19 focused files passed after the final main/prerequisite reconciliation, including all 13 live epoch/moment cases. The ledger regression covers short and unplaced replies, a new epoch, and conversation isolation.

  • Flutter: 61 focused naming/rendered transcript tests passed; Dart analysis of six sources/tests found no issues.

  • Web: 154 files / 872 tests passed in the full suite. The web task-row regression now waits for Motion's post-render hover callback before double-clicking the snooze control. Its assertions and product behavior are unchanged; the focused two-test file passed.

  • Local make preflight passed all 58 checks after main/prerequisite reconciliation; PR metadata passed all five checks. Normal push hooks run before publication. A final main/prerequisite refresh is followed by focused backend/Flutter rechecks and frozen score replay; publication stops if the refreshed metrics differ.

  • Remote CI: current published-head status is shown in the PR checks.

The acceptance fixture drives the real Soniox idle close/reopen adapter, epoch stamper and conversation allocator, deterministic short-turn embeddings through the live matcher, and a fresh listen matcher hydrated from serialized rows. After reconnect it swaps provider labels between owner and guest. Both enrolled and unenrolled guest cases must re-acquire evidence; an enrolled guest keeps the same name, an unenrolled guest stays separate and unnamed until qualification. Owner recognition never follows provider-number equality. These tests qualify the bounded safe repair; anonymous identity continuity across replicas remains deferred. No device or production acceptance is claimed.

Frozen local-quality scorecard

Executed the frozen #23435 scorer (e7a2144f63ed tooling) with the unchanged private corpus/word clocks, SHA-256 b2d293306c74e1423ae41c70d0432ad5eea012a60224cf6f7dc88284105309cc. Historical inference source/audio/crop/payload hashes were verified, then this branch's real Projector.assign_parts, projection builder and conversation allocator ran with filesystem store/assignment boundaries. The split diagnostic uses the frozen split validator and the candidate's production split/build/allocation code. No raw identity mapping was fitted and no display collapse was counted as diarization improvement.

Recording Reference This branch Earlier comparison
1 2 6 6
2 2 10 10
3 2 27 28
4 5 25 28
5 3 51 54

Engineering gate: FAIL. All 10,541 scored words remain anonymous in this batch path: owner/enrolled accuracy is 0/4,415 (0%); wrong-name/false-owner rate is 0%. The live matcher is not exercised by this batch scorecard. This branch does not include the separate #23436 naming or #23437 batch voice-link changes that produced the supplied earlier 6/10/28/28/54 comparison, so the count differences (0/0/-1/-3/-3) are not like-for-like improvements attributable to this live repair. The batch identity producer files are byte-identical to main at evaluation.

The frozen five-speaker split case produced 27 unsplit speakers and 37/30 after the two cuts. Identity loss is 0.0/0.0 percentage points, versus the earlier approximately 3.6–3.8 points. Both split and unsplit owner accuracy are zero here: this is an all-anonymous floor, not a continuity gain, and the split gate remains FAIL. Anonymous cross-listen linking remains deferred as described above.

This was a current-projection replay with zero new model calls, zero production reads and no Firestore transaction qualification. Original inference receipts are retained; seven historical worker switches remain UNKNOWN, so full current-worker inference compatibility is INCOMPLETE. Non-English and real-device/production acoustic acceptance were not evaluated. Private persisted outputs, source hashes and aggregate reports remain outside Git.

UI-Evidence: none -- Naming policy changes only; focused naming and rendered transcript tests cover the visible labels. No layout or styling change.

Delivery

Backend/listen image deployment and a Flutter build are required for the corresponding changes. No merge, deploy, production data read, api.omi.me call, production config flip, or GCP tier used. No language-gate or firmware file touched. Rollback: revert the live-epoch feature changes while retaining the prerequisite PRs.

🤖 Generated with Claude Code

Git-on-my-level and others added 30 commits October 23, 2026 20:27
…diarization ids

A live episode's "So far" bullets read "Speaker 379 mentions..." because the
legacy live-moment renderer wrote the raw per-conversation diarization counter
into the prompt ("Speaker 379: ...") and the summarizer copied the label
verbatim into user-visible notes (#23381). The finished-notes v2 path already
renders cluster keys, so it never had the problem.

Add utils/speaker_display.SpeakerLabeler and route every LLM-facing or
user-facing transcript renderer through it: the owner renders as "You" or the
owner's name (live moments now resolve the account's display name), named
people keep their names, and every unnamed voice gets a dense per-transcript
"Speaker A/B/..." label in first-appearance order, keyed by
(speaker_id_scope, speaker_id) so merged multi-source transcripts stay
distinct. Raw diarization ids no longer reach any prompt or payload:

- live moments: incremental.speaker_label/_legacy_moment (+ moment rules now
  forbid copying voice markers or placeholder headings like "New moment")
- TranscriptSegment.segments_as_string (titles, memories, summaries, chat)
- transcript_for_llm action-item speaker labels
- shared-chat bounded transcripts, smart-merge question excerpts, retrieval
  chunks and MCP match snippets, review quotes
- conversation analytics and populate_speaker_names (MCP/integration/webhook
  payloads)

Bump smart-merge QUESTION_VERSION: cached adjacent-merge answers were keyed to
the old raw-id excerpts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two guards so "Speaker 379" cannot come back:

- unit: run each LLM-facing formatter with the incident's raw ids (378/379,
  across scopes) and assert the digits never survive a render; covers
  segments_as_string, live and v2 moment renderers, cluster transcripts,
  smart-merge lines, shared-chat bounded transcripts, MCP snippets, retrieval
  chunks, action-item labels, populate_speaker_names and analytics.
- static: check_speaker_label_interpolation.py rejects any new interpolated
  "Speaker {speaker_id}" (f-string, .format, %, concatenation) outside the
  sanctioned SpeakerLabeler; registered in checks-manifest with a self-test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n's evidence

A People record, lexicon spelling, profile, memory or old meeting can no longer
authorize a participant name. Calendar attendees overlapping the conversation,
this conversation's roster/tile names and affirmative owner corrections still do.
Enforced in the persisted notes contract independent of packet rollout; agent
output is repaired, unsupported names become "a speaker" or hedged heard names.

Failure-Class: notes-identity-leak

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load the real speaker presentation modules in the isolated date-validation harness, expect canonical c0 keys in wake-word rendering, and remove the duplicate Dart test import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ping

Failure-Class: FC-generated-presentation-contract-bypass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Failure-Class: FC-generated-presentation-contract-bypass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve trusted manual episode name projections without using a bound
excerpt name as the fallback label for unbound speech of that provider voice.

Failure-Class: FC-generated-presentation-contract-bypass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y light

notes_packet imported known_spellings at module scope, pulling the dream
store and People database into every conversation_processing import; the
isolated action-item test then failed at collection. Import it at the call,
and pre-load the speaker display modules the models now import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load the real known-spelling helper with explicit empty database adapters so notes-packet imports do not pull the dream-agent runtime into action-item date tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deduplicate named participants by People record ID so repeated diarization IDs count once while different people with the same display name retain separate participant counts. Preserve the existing transcript heading regression assertion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The published notes packet repair now defers its database-dependent spelling import until it is used. Keep the date harness minimal instead of eagerly loading the helper with extra database mocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the identity-aware display authority at overlapping renderers; preserve main boundary regression coverage in its own suite, both static scan interfaces and the legacy scoped-key helper. Remove stale labeler scaffolding and keep analytics compatible with lightweight segment objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep the active worker count in the parent shell and update it with the existing PID arrays. Every 20ms polling iteration previously forked twice for command substitution merely to count workers. File isolation, status handoff, crash detection and timing budgets remain intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canonical identity authority passes the full raw-speaker scan without an extra exemption. Keep main full-scan coverage unchanged alongside the broader changed-file identity ratchet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtion

Main changed this regression to render all segments in one transcript. Retain its stronger A/B and six-distinct-output assertions; the old per-segment A/A expectation was kept incorrectly during the speaker-authority merge. Digest and attribution checks remain intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git-on-my-level and others added 9 commits October 25, 2026 00:05
Exercise enrolled and unenrolled guests through real idle reopen and a fresh listen matcher. Hydrate persisted wire rows, swap provider numbers, and require fresh acoustic evidence before names reappear. Anonymous cross-replica aliases remain an explicit follow-up.

Failure-Class: FC-generated-presentation-contract-bypass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keep unplaced live rows unnamed and retain dense-label coverage for five-second voices after integrating the reconciled prerequisite regression suite.

Failure-Class: FC-generated-presentation-contract-bypass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xture

Keep all four error-sanitization assertions and the unchanged CPU limit. Measure the status behavior in the call phase rather than charging its cold dependency import to one test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endencies normally

Keep the input guards and output contract, remove production file-loader fallbacks added for the isolated test, and register the real pure modules in that fixture so dataclass annotations resolve correctly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ture

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full preflight suite exposed an existing immediate getByTitle race. Framer Motion schedules onHoverStart through frame.postRender, so await the rendered snooze control before exercising the double-click contract. Product behavior and assertions are unchanged.

Failure-Class: none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain main notes and header-chip fixes. On overlapping retrieval/Wrapped guards, keep the validated accurate annotations, canonical imports and normalized text rather than reintroducing type ignores or test-only production loaders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve fresh per-test isolation and every proration/402/no-write assertion. Keep endpoint calls under the unchanged one-second CPU guard without charging cold router imports and mock construction to the call phase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
omi 🟢 Ready View Preview Oct 24, 2026, 9:14 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T21:19:08.790409Z fe4adf5 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ae989f5e6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backend/utils/llm/notes_identity.py Outdated
Comment on lines +112 to +114
blocked = participant_names | {name for name in identity.contact_names if not identity.supported(name)}
blocked.discard('')
blocked = {name for name in blocked if not re.fullmatch(r'(?i)(?:a speaker|speaker [A-Z])', name)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Limit contact-name scrubbing to actual identity claims

When an unrelated contact has a name that is also an ordinary word, such as Will or May, adding every unsupported contact spelling to blocked corrupts otherwise grounded notes. The case-insensitive replacement runs across all visible prose even when participants is empty: with those contacts, We will launch in May. becomes We “will” (as heard) launch in “May” (as heard)., and the title May launch plan loses its month. Restrict this repair to actual unsupported identity claims, rather than treating every occurrence of an account contact's name as a speaker attribution.

Useful? React with 👍 / 👎.

Comment on lines +267 to +269
defer_short_voices
and not name
and (cluster.speech_seconds if cluster else display_seconds.get(key, 0.0)) < MIN_EVIDENCE_SECONDS

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve qualification for voices already in the episode ledger

For successive episode moments, _record_moment_once supplies only segments newer than applied_until while carrying forward speaker_display_order. This duration check nevertheless requires five seconds again in each new delta, before consulting that ledger. A voice rendered as Speaker A after six seconds therefore becomes Unknown speaker when its next moment contains a one-second reply, despite retaining the same ledger token. Honor previously qualified tokens before applying the short-voice floor so established identities remain stable across incremental updates.

Useful? React with 👍 / 👎.

Comment on lines +537 to +540
final participants = ConversationDetailMeta.participants(conversation.transcriptSegments,
you: you, personName: (id) => personById(people, id)?.name, speakers: conversation.speakerResolution);
if (participants.uncounted) return null;
final count = participants.named.length + participants.unnamed;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Deduplicate named voices before adding anonymous speaker counts

When only some segments of a resolved voice have been named, such as after assigning a person to selected transcript lines, the new helper counts that voice twice: EpisodePresentation.participants includes the person's name, while ConversationDetailMeta.participants also includes the same speaker ID in unnamedIds for its remaining unbound segments. Adding these totals makes a single resolved voice appear as two speakers in the transcript heading; partially owner-labeled voices have the same problem. The previous implementation excluded owner IDs and resolved each remaining speaker ID through personIdsBySpeaker, avoiding this double count. Deduplicate the anonymous IDs against named and owner voices before calculating the heading count.

Useful? React with 👍 / 👎.

Git-on-my-level and others added 3 commits October 25, 2026 02:04
Treat an existing conversation-scoped voice ledger token as prior display qualification. Incremental moments contain only new speech, so a short reply must not erase an established ordinal. Regressions cover short and unplaced continuations, a new epoch, and a different conversation.

Failure-Class: FC-generated-presentation-contract-bypass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git-on-my-level and others added 2 commits October 25, 2026 02:46
Restrict identity repair to unsupported participant bindings, exempt owner
names and protect owner aliases. Add a default-on identity repair kill
switch across notes paths and remove repair-only People catalog reads.
Preserve developer API participant IDs, wire shape and speaker names;
retain MCP opaque display identities and the owner's real speaker name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use deterministic timestamps in serialized presentation fixtures so clock microseconds cannot look like leaked raw speaker IDs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff289bfbec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +129 to +131
display = display_participants(
segments, owner_name=owner, voice_order=voice_order, display_scope=conversation_id, defer_short_voices=True
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accumulate qualification evidence across live moment deltas

On the legacy live-moment path, a voice that has never qualified must now contribute five seconds within a single moment to receive a label. _record_moment_once filters out segments through applied_until, and the persisted ledger stores only already-qualified voices, so earlier sub-threshold speech is forgotten. For example, the same scoped voice contributing three seconds in each of three successfully applied moments remains Unknown speaker after nine seconds, while Flutter's whole-conversation resolver qualifies it. Calculate qualification from the conversation's accumulated placed speech, or retain bounded pending-duration evidence across moments, without replaying previously applied text.

Useful? React with 👍 / 👎.

Git-on-my-level and others added 4 commits October 25, 2026 03:18
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve real helper imports and the isolated router cleanup fixture across the independent CI repairs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the same real-module imports, isolated IO setup and behavioral assertions while removing textual conflicts with the current main fixtures. No production code change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git-on-my-level and others added 2 commits October 25, 2026 03:56
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 972cefe8b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +186 to +189
kept = [
person for person in people if person.get('is_ai_agent') or str(person.get('name') or '').strip() not in blocked
]
repaired = scrub({**submission, 'participants': kept})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve supported bindings when rejecting another participant

When the same name appears in both a supported and an unsupported participant binding, blocked contains the name globally, so this filter removes both participants and the subsequent scrub erases even grounded references. For example, with resolved bindings c0 → Sarah and c1 → Phong, submitting Sarah for both speakers removes the valid c0 participant too and changes Sarah approved deployment to a speaker approved deployment. Reject participants by their individual binding rather than name alone, and preserve prose references to independently supported identities.

Useful? React with 👍 / 👎.

Comment on lines +276 to +278
if token not in order and len(order) <= MAX_UNNAMED_VOICES:
order.append(token)
labels[key] = labeler.label(token) if token in order[:MAX_UNNAMED_VOICES] else 'Others'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allocate live labels in qualification order

When multiple voices qualify within one moment, this loop allocates labels by first appearance after computing each voice's total duration, rather than by when it reaches five seconds. For turns A at 0–1 seconds, B at 1–6, and A at 6–10, the backend assigns A Speaker A and B Speaker B; Flutter's _qualifiedLiveOrdinals assigns B the first ordinal because B qualifies first. Consequently, the episode prompt and persisted ledger identify different voices than the live transcript. Order newly qualifying voices by their qualification timestamp before appending their ledger tokens.

Useful? React with 👍 / 👎.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
staging - docs — fe4adf5a Deployed Oct 24, 2026 by mintlify[bot]
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