[Evals] Synthetic-audio end-to-end harness for the per-speaker transcription pipeline #433
Closed
opened 2026-08-28 22:45:09 +00:00 by claude-bot
·
3 comments
No Branch/Tag specified
main
feat/1328-fill-it-in
feat/1328-fill-frontend
feat/1328-fill-backend
feat/9-foundry-push
fix/9-foundry-live
feat/9-foundry-push-frontend
renovate/celery-5.x
feat/5-foundry-connect
feat/9-foundry-push-backend
feat/9-foundry-push-module
feat/5-foundry-frontend
feat/5-foundry-backend
feat/5-foundry-module
release/v4.8.0-rc1
docs/d341-convert-draft
feat/958-update-to-version
feat/958-convert-sheet
feat/958-pathbuilder-import-frontend
fix/1360-heading-order
feat/958-pathbuilder-import-backend
feat/11-weekly-digest
feat/1341-auto-update
docs/d332-superseded
chore/1345-names-cleanup
feat/1342-mentioned-pages
feat/1328-recipes
fix/1346-pgdg-key
perf/1247-shared-recording-stream
chore/132-gitleaks-allowlist
release/v4.7.1
spike/foundry-module
release/v4.7.1-rc1
docs/decisions-d332-d337
fix/bot-certifi
chore/release-4.7.0
chore/release-4.7.0-rc4
chore/deps-4.7.0
chore/release-4.7.0-rc3
fix/1280-quote-must-say-it
fix/1257-names-row-squeeze
chore/release-4.7.0-rc2
fix/1245-pg-upgrade-count
chore/release-4.7.0-rc1
feat/1257-names-in-prep
feat/1257-be
feat/1257-fe
feat/1257-rating-fixes
design/1328-fill-it-in
feat/1257-name-splits
fix/1326-redis-503
feat/1280-fact-suggestions
feat/1280-be
feat/1280-fe
design/1280-fact-suggestions
feat/1279-infobox
feat/1279-be
feat/1279-fe
feat/1321-shelf-copies
design/1279-infobox
feat/1243-table-find
feat/1257-who-rated
feat/1243-prep-drawer
feat/1243-sheet-pickers
design/1243-browse-in-place
feat/1315-hotwords-default
feat/1242-system-filters
feat/1257-more-name-styles
feat/506-leg3-hotwords
design/1242-system-filters
fix/1302-packs-polish
feat/1241-rules-library
design/1241-rules-library
feat/1245-postgres-18
integ/4.6.2
fix/1263-map-ui-2
fix/1263-map-server
fix/1263-map-ui-1
fix/1261-editor-link-menu
fix/1229-axe-4.14-name-mismatch
renovate/axllent-mailpit-1.x
renovate/eslint-monorepo
renovate/authlib-1.x
chore/release-4.6.0
chore/release-4.6.0-rc2
fix/460-rc2-screens
fix/460-rc2-server
fix/release-prerelease-exit
chore/release-4.6.0-rc1
feat/125-campaign-templates
feat/126-calendar
feat/1181-pictures-and-files
feat/1190-event-people
integ/4.6.0
feat/1191-line-of-the-night
feat/1200-starter-packs
chore/bot-multidict-and-playwright-group
fix/1202-this-week-answered-count
fix/122-124-leftovers
fix/1178-get-routes-that-write
fix/1203-journeys-check-names
design/125-campaign-templates
feat/126-calendar-engine
design/126-in-world-calendar
fix/1189-cited-stamp-in-range
fix/1189-v4-looser-check
fix/1189-richer-beats
fix/1113-export-bind-lists
fix/1154-release-notes-by-file
chore/release-4.5.0
chore/release-4.5.0-rc12
fix/1135-wiki-markup-as-text
fix/demo-usage-cost-in-window
chore/release-4.5.0-rc11
fix/1121-sheet-consistency
fix/1121-sheet-5e-terms-and-card-hp
copy/1122-busiest-pages
fix/1121-relevant-choices
fix/1121-wiki-create-npc-save-sheet-links
fix/1029-menu-stays-on-screen
docs/prototype-transcript-bin-wording
fix/1023-editor-save-row-back-scroll
fix/1020-search-exact-first
fix/1020-table-prep
fix/1020-shell-wordmark-footer-bell
fix/1020-wiki-article-party
fix/872-admin-kept-until-phone-tables
feat/l13-milestone-level-waiting
fix/1000-1001-bell-older-setup-context
fix/999-pronouns-in-prompts
fix/l19-other-class-feats
fix/1010-transcript-window-ordinary-confirm
docs/d254-d269-decisions
fix/702-member-search
fix/680-transcript-bin
fix/993-unidentified-prompts
fix/841-redis-timeouts
fix/990-binned-not-exported
fix/830-export-prep-shelf
fix/deps-audit-2026-10-01
fix/877-title-reads
fix/798-audio-received-at
build/lane11-17-j7
build/lane11-16-levelup
build/lane11-14-seams
build/lane11-14-seed
build/lane11-15-start
build/lane11-13-old-block-out
fix/980-review-r3
build/lane11-12-block
build/lane11-8-packs
fix/demo-seed-canonical-names
build/lane11-11-hot-half
docs/l11-decisions
build/lane11-7-convert
build/lane11-10-editing
fix/npm-audit-brace-expansion
build/lane11-9-sheet-page
fix/bot-pyjwt
build/lane11-6-content
fix/lane11-rank-names
build/lane11-2b-language
build/lane11-4-js-evaluator
build/lane11-3-server
build/lane11-5a-pf2e
build/lane11-5b-5e
feat/design-admin
build/lane11-2-evaluator
build/lane11-1-sheet-pieces
build/lane11-0-drawing
fix/943-quest-objectives-fk
fix/prepsheet-shelf-flake
fix/945-leftovers
build/demo-gm-copy
fix/901-email-landings
build/lane10-8-journeys
build/demo-seed-lanes-8-10
fix/887-gm-own-confirmation
build/lane9-5-j1-admin
fix/918-audience-click
fix/useasync-overtaken
ci/939-critical-path
test/937-backend-suite-speed
chore/claude-orchestration
fix/924-xp-gm-only
fix/session-stale-poll
docs/decisions-d239-d242
fix/930-pypdf
build/lane8-journeys
build/925-quest-editor
fix/894-private-page-pictures
build/lane8-p4-drafts
build/lane8-p7-map
build/lane8-p6-handouts-rules
fix/wiki-fixture-names
build/lane10-d227-test-usage
fix/rsvp-horizon-test-tuesday
fix/media-volume-owner
build/lane8-p3-editor
fix/912-unidentified-rename
build/lane8-p5-party-quests
build/lane8-handout-takeback
build/lane10-pickers
build/lane8-p2-article
build/lane8-6-map
build/lane10-7b-email-pages
build/lane8-5-handouts
build/lane10-7a-test-the-bot-pages
build/lane8-p1-pages
build/lane8-4-quests
build/lane8-2b-feeds
build/lane8-3-parties
build/lane8-2a-links
build/lane10-5-commands
build/lane9-6-seed
build/lane10-4b-reactions
build/lane9-4d-pages
build/lane9-4c-pages
build/lane10-4a-posts
build/lane10-3-email
fix/891-web-fonts
build/lane9-4b-pages
build/lane10-6-test-the-bot
build/lane10-2-notify-out
build/lane8-1-wiki-text
build/lane9-4a-frame
design/lane10-drawings
fix/j6-budget
fix/j5-phone-flake
fix/869-test-leaks
fix/843-record-start-followups
build/lane7-all
build/lane6-all
build/lane9-b2-server
chore/changelog-fragments
docs/decisions-d218
build/lane9-a-owner-overview
fix/rule-card-level-label
fix/834-retention-semantics
fix/o3c-record-without-announcement
docs/decisions-d179
docs/decisions-d131-d170
fix/851-commit-before-response
build/lane6-j3-seed
build/lane9-b1-d67-identity
fix/841-blocking-loop
build/lane9-c-backups-audit
fix/vote-race-and-start-commit
fix/recording-announcement
fix/proposal-read-leak
fix/last-admin-guard
build/lane6-p4c-planning
build/lane6-p4b-margin
build/lane6-p4a-sheet
build/lane5-j4-seed
build/lane6-base
build/lane7-pages-talk
build/lane5-pages
build/lane7-pages-safety
build/lane7-p3-safety
build/lane7-p2-notify
build/lane6-p3-planning
build/lane6-p2-history
build/lane6-p1b-items
build/lane7-p1-threads
build/lane5-p6-faces
build/lane6-p1a-sheet
build/lane5-p5-find
build/lane5-p4a-board
build/lane5-p4b-scenes
build/lane5-p3-live
build/lane5-base
build/lane5-p2-cards
docs/lane5-decisions
build/review-r2-wide
fix/review-r1
build/lane5-p1-scenes
build/demo-seed
build/lane4-journeys-j5
build/lane4-page-review-b
build/lane4-page-review-a
build/lane4-page-read
build/lane4-journeys
build/lane4-page-sess2
build/lane4-page-sess1
build/lane4-notes
build/lane4-discord-on-approval
build/lane4-scheduling
build/lane4-versions
build/760-session-number
build/lane4-decisions
build/797-erase-no-regenerate
build/test-race-audit
build/lane3-completions
build/794-dark-only
build/796-titles-scroll
build/791-retention-anchor
build/a11y-sweep
build/shell-followups
build/retention-preview
build/753-backup-readback
docs/d91-d93
build/lane3-j1
build/ci-harness-paths-selftest
fix/useauth-test-race
build/ci-harness-paths
build/777-ai-settings-read
build/lane3-settings-c
build/lane3-settings-b
build/lane3-j6
build/lane3-sessionform
build/lane3-settings-a
build/lane3-members
build/lane3-thisweek
build/lane3-story
chore/87-align-python-3.12
v4.8.0-rc1
v4.7.1
v4.7.1-rc1
v4.7.0
v4.7.0-rc4
v4.7.0-rc3
v4.7.0-rc2
v4.7.0-rc1
v4.6.2
v4.6.2-rc4
v4.6.2-rc3
v4.6.2-rc2
v4.6.2-rc1
v4.6.1
v4.6.1-rc1
v4.6.0
v4.6.0-rc2
v4.6.0-rc1
v4.5.3
v4.5.3-rc1
v4.5.2
v4.5.2-rc1
v4.5.1
v4.5.1-rc2
v4.5.1-rc1
v4.5.0
v4.5.0-rc12
v4.5.0-rc11
v4.5.0-rc10
v4.5.0-rc9
v4.5.0-rc8
v4.5.0-rc7
v4.5.0-rc6
v4.5.0-rc5
v4.5.0-rc4
v4.5.0-rc3
v4.5.0-rc2
v4.5.0-rc1
v4.4.1
v4.4.0
v4.3.0
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.2
v4.1.1
v4.1.0
v4.0.1
v4.0.0
v3.11.4
v3.11.3
v3.11.2
v3.11.1
v3.11.0
v3.10.0
v3.9.1
v3.9.0
v3.8.0
v3.7.0
v3.6.1
v3.6.0
v3.5.0
v3.4.1
v3.4.0
v3.3.0
v3.2.0
v3.1.0
v3.0.0
v2.1.0
v2.0.0
v1.1.0
v1.0.0
v0.12.0
v0.11.0
v0.10.0
v0.9.1
v0.3.3
v0.9.0
v0.8.0
v0.3.2
v0.3.1
v0.3.0
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.2.1
v0.2.0
v0.1.1
v0.1.0
Labels
Clear labels
backend
Issue related to the backend
bot
Issue related to the bot
bug
Something is not working
duplicate
This issue or pull request already exists
enhancement
New feature
frontend
Issue related to the frontend
help wanted
Need some help
invalid
Something is wrong
question
More information is needed
recording
Issue related to the recording system
security
Security hardening / vulnerability work
wontfix
This won't be fixed
No labels
backend
bot
bug
duplicate
enhancement
frontend
help wanted
invalid
question
recording
security
wontfix
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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#433
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
The per-speaker transcription change (#342) is the riskiest thing in v4.0.0 and has never run against a real Whisper server outside unit mocks. Neither has speaker resolution against real member rows, nor beats → validation → compose → anchors against a real LLM. The eval harness starts from a transcript, so nothing in it exercises transcription at all.
Every real recording has been destroyed and is unrecoverable, so this is built from invented audio: no real voices, no real content, committable, re-runnable against any provider indefinitely.
Design
Full plan and rationale: https://claude.ai/code/artifact/d8bd7647-9fc2-4c25-a3ad-375c977cdb2f
Settled:
CLAUDE.md's "do not commit audio files" rule.POST /api/bot/sessions/{id}/audiowithforce=true— the production intake path. Admin retry (admin.py:155) 409s unless the session is alreadyfailed, so it cannot be used for a fresh synthetic session.ic/ooc/meta/proposed.fullandic_only— so the cost of the table talk is measurable rather than assumed.What this measures that nothing currently does
Three prompts already instruct against table talk (
beat_service.py:456,speaker_service.py:381,audio_service.py:154) and nothing tests any of them.scoring.pyhas coverage, attribution, chronology and beat-validation — all recall or correctness. There is no precision metric in the harness at all, so nothing can penalise a summary for containing material that shouldn't be there. This adds the first one.Key design constraints
transcribe_tracksetsspeaker_labelandtrack_owner_idfrom the sameTrackobject, so asserting they agree tests nothing. Replaced with a per-speaker marker lexicon, asserting foreign-marker count is exactly zero — licensed by the asymmetry that ASR error can only miss a marker, never manufacture a foreign one. Markers are sequence-numbered so the assertion also catches timestamp corruption with attribution intact (the #320 shape).keptseconds, not speech seconds.compute_speech_spanspads each span 300 ms per side and folds gaps under 2 s, so fragmented speech inflateskeptwell above real speech time. Measure and assert it; do not infer it from the script.Acceptance criteria
CLAUDE.mdcarve-out for synthetic TTS clips, written before the first audio commit.track_sha256asserted.resolve_speakerslabel paths (GM, linked player with character, unlinked guest).Related
process_audiopath run in CI with no GPU.Spike complete — the premise holds, one design change needed
Ten utterances, three piper voices, eight invented proper nouns of varied difficulty. Rendered, put through the full Opus chain (
libopus -b:a {32,64,96}k -vbr on -application voip -frame_duration 20, decoded, then the bot's own resample command verbatim), assembled onto one 72-second clock, transcribed by the production Whisper server —large-v3-turboon CUDA.Results
The asymmetry the exact attribution assertion rests on — ASR error can only lose a marker, never manufacture a foreign one — is now measured rather than argued. Five of eight markers were mangled and not one appeared on another speaker's track.
Design change: markers must be screened, not chosen
Only 3/8 survived exactly. The failures follow no clean rule:
Quillfeather,Thistledown,BramblewickVarnholtAshgroveMarrowgateCopperkettleZhaltirmekSo: screen marker candidates empirically before committing them (render → transcribe → keep what survives, using the spike as the rig), and lean the assertion on a per-speaker aggregate floor rather than any individual marker. At ~2 markers/minute over 30 minutes there is ample redundancy for a ~40% loss rate, provided no single marker is load-bearing.
Match on word boundaries, not substrings. Substring matching passed
Varnholtinside varnholtz andAshgroveinside ashgrover — overstating survival by 2/8. Worse, it drifts upward as ASR gets worse, since mangled words keep the marker as a prefix.Five things for the build
--seed. Re-rendering is not reproducible. This independently settles the commit-the-clips decision: the clip must be the artifact, because it cannot be regenerated.--download-dirbelongs topiper.download_voices, not the synthesiser; passed to the synthesiser it was swallowed as input text and spoken aloud ("Download ire slash work slash voices"). Caught only by transcribing the clip. The assembler must verify clip content, not just existence — this is the repo's recurring failure mode appearing inside its own test harness.Sizing
Committed Opus totalled ~354 KB for ~48 s of speech, extrapolating to ~10 MB for the 30-minute scene. In line with the estimate in the plan.
Environment notes
POST /transcribewithaudio+speaker+ optionallanguage; also exposes/healthand/transcribe/session.Plan updated: https://claude.ai/code/artifact/d8bd7647-9fc2-4c25-a3ad-375c977cdb2f (§7 revised, §11 added)
Landed in PR #497 (merged 2026-09-05, CI green including the new harness job).
seed_db --i-know-this-writes --verifyseeds all three label paths and proves each resolves.Design change made on the way to green CI, worth knowing: the exact per-track PCM hash turned out to be a property of the CPU, not the audio — both the Opus float decode and the resample choose SIMD code paths at runtime, and a Ryzen with AVX-512 and a QEMU vCPU without AVX2 differ by at most ±1 LSB per sample. The contract is now: exact sample count, exact silence, exact all-zero track, exact golden transcripts, and a banded per-window RMS fingerprint (250 ms windows, 4 LSB / 0.5 % tolerance calibrated against the no-AVX box, where only 2 of 8 673 windows moved and by exactly 1 LSB); a determinism test proves the assembler is bit-exact within one environment. Recorded in the README with the numbers.
Known gaps (README): the full live run through
POST /api/bot/sessions/{id}/audiowith a real LLM and thefullvsic_onlysummary comparison run during dev validation of v4.2.0; #432 is exercised directly, not pinned end to end; per-carrier marker screening over-predicts survival by ~28 points in a full session.First live end-to-end run of the harness through a deployed stack (dev, v4.2.0, 2026-09-05). Both assemblies were seeded with
seed_db, submitted throughPOST /api/bot/sessions/{id}/audioand processed by the realprocess_audioagainst the bundled WhisperX v2 server (large-v3-turbo, cuda) and llama.cppqwen3.5.fullic_onlyleak_rules_numbers)This is the comparison the harness exists to make, and it came out the way the design predicts: removing the out-of-character register drops table-talk leakage to zero and lifts in-character coverage to 5/5. The one leak in
fullwas dice/difficulty talk rendered as narrative; the one lost required beat was the quiet player's second contribution (required_wren_objection), which fired and was correctly attributed inic_only.Also confirmed live: all three speaker-resolution paths (
label_mismatches: []), the silent track dropped with zero segments, both structural EXACT_ZERO guards (segments_before_late_join,segments_in_scripted_silence) empty, the provider queue holding oneqb:slot:asr:…lease during ASR and none after, and twosession_usagerows with the right provider and host.One fixture defect found and filed on #506 (item 5): seeded platform links have no
verified_at, so the attendance-autofill path resolves nobody and the uncaptured-member check is not actually exercised yet.