Synthetic-session harness: marker scorer denominator, vocabulary-gap severity, hotwords #506

Open
opened 2026-09-05 06:56:33 +00:00 by claude-bot · 3 comments
Contributor

Follow-ups surfaced by the vocabulary-bias measurement (PR #505, #355, #360). None of them change the numbers already reported, but each should be fixed before the harness is used for a cross-assembly or cross-provider comparison.

  1. score_markers uses the wrong denominator for the in-character-only assembly. The scorer divides by the full marker count (54) for both assemblies, but only 50 of the markers occur in the ic_only script, so that assembly's detection rate is understated by ~7 % relative. Around evals/synthetic_session/scorers.py:308. Count the markers that actually occur in the scored script instead.
  2. GAP_NO_VOCABULARY is probably under-severe. The capability gap for "this provider ignores the vocabulary list" is currently low severity. The measurement shows the list is worth 9–24 pp of proper-noun recall, which is the largest single effect measured in v4.2.0, so SEVERITY_MEDIUM fits better. Also check the copy on the Admin capability panel says what the GM loses.
  3. hotwords is unexplored. WhisperX API v2 honours both initial_prompt (what the vocabulary list becomes today) and hotwords. Run the same three-condition matrix with hotwords, and with both, and record which one the adapter should prefer.
  4. Leg 3 (LLM on the ASR transcript) is still run by hand. The harness automates the audio and ASR legs and the LLM-on-golden-transcript leg, but scoring the summary produced from the ASR transcript still needs a manual step. Automate it so a provider swap can be scored end to end in one command.
Follow-ups surfaced by the vocabulary-bias measurement (PR #505, #355, #360). None of them change the numbers already reported, but each should be fixed before the harness is used for a cross-assembly or cross-provider comparison. 1. **`score_markers` uses the wrong denominator for the in-character-only assembly.** The scorer divides by the full marker count (54) for both assemblies, but only 50 of the markers occur in the `ic_only` script, so that assembly's detection rate is understated by ~7 % relative. Around `evals/synthetic_session/scorers.py:308`. Count the markers that actually occur in the scored script instead. 2. **`GAP_NO_VOCABULARY` is probably under-severe.** The capability gap for "this provider ignores the vocabulary list" is currently low severity. The measurement shows the list is worth 9–24 pp of proper-noun recall, which is the largest single effect measured in v4.2.0, so `SEVERITY_MEDIUM` fits better. Also check the copy on the Admin capability panel says what the GM loses. 3. **`hotwords` is unexplored.** WhisperX API v2 honours both `initial_prompt` (what the vocabulary list becomes today) and `hotwords`. Run the same three-condition matrix with hotwords, and with both, and record which one the adapter should prefer. 4. **Leg 3 (LLM on the ASR transcript) is still run by hand.** The harness automates the audio and ASR legs and the LLM-on-golden-transcript leg, but scoring the summary produced from the *ASR* transcript still needs a manual step. Automate it so a provider swap can be scored end to end in one command.
Author
Contributor

5. seed_db.py creates PlatformLink rows without verified_at, so the attendance path is never exercised. Found validating v4.2.0 on dev: the end-to-end run logged attendance autofill {'spoke': 0, 'in_channel_silent': 0, 'unmatched': 5, 'auto_applied': 0} and wrote zero attendance_proposals. Root cause is that _resolve_discord_ids_to_members (attendance_service.py, around line 150) deliberately requires verified_at IS NOT NULL — an unclaimed link must never drive attendance — while seed_db.py (around line 216) never sets it. Speaker resolution does not require verification, which is why the transcript labels were still right; only attendance went blind. The product behaviour is correct, the fixture is wrong.

Fix: set verified_at on the seeded links, then assert in the harness's check that the presence-only member (Kesh) and the silent-track owner (Dunmar) are reported as uncaptured, and that the five speakers match. That turns the #114 uncaptured-member path into something the harness actually tests.

**5. `seed_db.py` creates `PlatformLink` rows without `verified_at`, so the attendance path is never exercised.** Found validating v4.2.0 on dev: the end-to-end run logged `attendance autofill {'spoke': 0, 'in_channel_silent': 0, 'unmatched': 5, 'auto_applied': 0}` and wrote zero `attendance_proposals`. Root cause is that `_resolve_discord_ids_to_members` (`attendance_service.py`, around line 150) deliberately requires `verified_at IS NOT NULL` — an unclaimed link must never drive attendance — while `seed_db.py` (around line 216) never sets it. Speaker resolution does not require verification, which is why the transcript labels were still right; only attendance went blind. The product behaviour is correct, the fixture is wrong. Fix: set `verified_at` on the seeded links, then assert in the harness's check that the presence-only member (Kesh) and the silent-track owner (Dunmar) are reported as uncaptured, and that the five speakers match. That turns the #114 uncaptured-member path into something the harness actually tests.
Author
Contributor

Picking up items 1, 2 and 5 for v4.2.1 (the scorer denominator, the GAP_NO_VOCABULARY severity, and seed_db setting verified_at). Items 3 (hotwords) and 4 (leg 3 automation) are measurement and harness work rather than fixes, and stay open here.

Picking up items **1, 2 and 5** for v4.2.1 (the scorer denominator, the `GAP_NO_VOCABULARY` severity, and `seed_db` setting `verified_at`). Items 3 (hotwords) and 4 (leg 3 automation) are measurement and harness work rather than fixes, and stay open here.
rbrooks referenced this issue from a commit 2026-09-05 16:03:00 +00:00
Author
Contributor

Items 1, 2 and 5 are merged in PR #510 and ship in v4.2.1. Items 3 (hotwords) and 4 (leg 3 automation) remain open here.

1. score_markers denominator. ic_only renders only the ic and proposed registers, and four of the manifest's 54 marker occurrences (all 24 distinct markers still appear in IC lines) are spoken exclusively in out-of-character lines: astrid_040 Silverbeck, bryn_114 Ravenstoke, bryn_176 Nettleford, wren_196 Blackwillow. They were being scored as markers the transcriber lost, so a flawless ic_only transcript could not score above 50/54. score_markers now takes the assembly and filters expected occurrences by its registers, exactly as score_wer does, and returns the excluded occurrences alongside numerator and denominator so a denominator that moves is visible rather than inferred. full keeps every register and is unchanged at 54. Foreign markers are deliberately still checked across the whole lexicon; the order check's expected side is filtered with everything else.

The recorded figures in calibration.json and the harness README are annotated, not restated: the numerator is not re-derivable from the record (it stores counts, not segments) and re-measuring needs the GPU box. What can be said without a rerun is bounded. Detection per marker is min(occurrences, hits), so the correction can only lower a numerator, which puts the control's ic_only between 31/50 and 35/50 (62.0–70.0 %) and the worst speaker between 4/11 and 5/11. Both bands hold at the pessimistic end, so the floors stay where the measurement put them. On the next GPU rerun, rewrite the tables from the new run rather than patching; calibration.json carries a note saying so.

2. GAP_NO_VOCABULARY severity. Raised low → medium. The #355 A/B measured +9 to +24 points of proper-noun recall with WER flat, the largest single effect in v4.2.0; ranking that below "the provider was guessed from the URL" told operators the wrong thing to fix first. The consequence copy now says what is lost (character and place names coming back mis-heard, unrepairable downstream because everything after transcription reads the transcript and not the audio) with the measured range in plain words. The OPERATIONS gap table is updated; the frontend renders severity generically, so nothing there changed. Runs already stamped low keep it, which is what a provenance record is for.

5. seed_db never set verified_at. attendance_service resolves Discord ids through verified links only, on purpose, so the fixture seeded a roster the attendance path could not see and the uncaptured-member check (#114) was unreachable through it. Links are now seeded with a fixed timestamp, --print-sql renders it, and --verify grew a cheap second check that every seeded link is verified.

Confirmed against a real database rather than argued: seeded → labels and links both clean → {'spoke': 4, 'in_channel_silent': 2, 'unmatched': 1, 'auto_applied': 0} with unmatched: ['Corvin']. That is 4 spoke rather than 5 because Corvin is the deliberately unlinked guest (#344's third label path); Dunmar reaches in_channel_silent via the dropped silent track and Marn via presence. Nulling verified_at on the same rows reproduces the old {'spoke': 0, 'in_channel_silent': 0, 'unmatched': 5} exactly, so this was the cause and not a symptom. The expected counts are tabulated in the harness README's live-run section.

Backend suite 2323 passed / 13 skipped, ruff clean.

Items 1, 2 and 5 are merged in PR #510 and ship in v4.2.1. Items 3 (hotwords) and 4 (leg 3 automation) remain open here. **1. `score_markers` denominator.** `ic_only` renders only the `ic` and `proposed` registers, and four of the manifest's 54 marker *occurrences* (all 24 distinct markers still appear in IC lines) are spoken exclusively in out-of-character lines: `astrid_040` Silverbeck, `bryn_114` Ravenstoke, `bryn_176` Nettleford, `wren_196` Blackwillow. They were being scored as markers the transcriber lost, so a flawless `ic_only` transcript could not score above 50/54. `score_markers` now takes the assembly and filters expected occurrences by its registers, exactly as `score_wer` does, and returns the excluded occurrences alongside numerator and denominator so a denominator that moves is visible rather than inferred. `full` keeps every register and is unchanged at 54. Foreign markers are deliberately still checked across the whole lexicon; the order check's expected side is filtered with everything else. The recorded figures in `calibration.json` and the harness README are **annotated, not restated**: the numerator is not re-derivable from the record (it stores counts, not segments) and re-measuring needs the GPU box. What can be said without a rerun is bounded. Detection per marker is `min(occurrences, hits)`, so the correction can only lower a numerator, which puts the control's `ic_only` between 31/50 and 35/50 (62.0–70.0 %) and the worst speaker between 4/11 and 5/11. Both bands hold at the pessimistic end, so the floors stay where the measurement put them. On the next GPU rerun, rewrite the tables from the new run rather than patching; `calibration.json` carries a note saying so. **2. `GAP_NO_VOCABULARY` severity.** Raised low → medium. The #355 A/B measured +9 to +24 points of proper-noun recall with WER flat, the largest single effect in v4.2.0; ranking that below "the provider was guessed from the URL" told operators the wrong thing to fix first. The consequence copy now says what is lost (character and place names coming back mis-heard, unrepairable downstream because everything after transcription reads the transcript and not the audio) with the measured range in plain words. The OPERATIONS gap table is updated; the frontend renders severity generically, so nothing there changed. Runs already stamped `low` keep it, which is what a provenance record is for. **5. `seed_db` never set `verified_at`.** `attendance_service` resolves Discord ids through verified links only, on purpose, so the fixture seeded a roster the attendance path could not see and the uncaptured-member check (#114) was unreachable through it. Links are now seeded with a fixed timestamp, `--print-sql` renders it, and `--verify` grew a cheap second check that every seeded link is verified. Confirmed against a real database rather than argued: seeded → labels and links both clean → `{'spoke': 4, 'in_channel_silent': 2, 'unmatched': 1, 'auto_applied': 0}` with `unmatched: ['Corvin']`. That is 4 spoke rather than 5 because Corvin is the deliberately unlinked guest (#344's third label path); Dunmar reaches `in_channel_silent` via the dropped silent track and Marn via presence. Nulling `verified_at` on the same rows reproduces the old `{'spoke': 0, 'in_channel_silent': 0, 'unmatched': 5}` exactly, so this was the cause and not a symptom. The expected counts are tabulated in the harness README's live-run section. Backend suite 2323 passed / 13 skipped, ruff clean.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#506
No description provided.