Skip to content

feat(keywords): Custom Keywords backend — glossary-driven transcript + summary correction (draft) - #367

Draft
Optic00 wants to merge 1 commit into
stenolabs:mainfrom
Optic00:feat/custom-keywords-backend
Draft

feat(keywords): Custom Keywords backend — glossary-driven transcript + summary correction (draft)#367
Optic00 wants to merge 1 commit into
stenolabs:mainfrom
Optic00:feat/custom-keywords-backend

Conversation

@Optic00

@Optic00 Optic00 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Draft — backend slice of Custom Keywords. Needs a hardening pass before it is production-ready (see checklist). Part of #365.

What this is

A user-maintained Custom Keywords glossary (preferred spellings + aliases / common mis-hearings). Backend-only slice:

  • src/keywords.py (new) — parse/normalize the glossary (defensive against malformed config), boundary-aware alias replacement (diarised-safe), and the summary reference-block builder.
  • src/config.pyget_custom_keywords / set_custom_keywords (persist in config.json, replace-not-merge, validated).
  • src/summarizer.py — inject a reference block into the permissive, markdown, template-report, title and query prompts. Empty glossary = byte-for-byte unchanged prompts (zero behavior change when unused).
  • simple_recorder.py — apply the glossary to the transcript at transcription time and on reprocess (retroactive heal, no re-ASR), plus get-custom-keywords / set-custom-keywords CLI.
  • Unit tests: test_keywords, test_keywords_cli, test_keywords_pipeline, test_reprocess_keywords, test_summarizer_keywords, test_config additions.

Out of scope here (follow-up PR): the Settings UI (re-integrated against the #344 nav-rail redesign), the IPC bridge, and the custom-keywords.t2 e2e spec. This slice is independently verifiable via ruff + unittest.

Reconstruction note

The original prototype branch was ~82 commits behind main; this is a clean re-application onto current main (semantic merge into the diverged summarizer.py / config.py / simple_recorder.py), not a cherry-pick.

Review round 1 — real bugs fixed (with regression tests)

An independent cross-family review found and this branch fixes:

  • CRITICAL — keyword healing ran before silence detection, so an alias like speech -> Speechly could mutate the "No speech detected in audio" sentinel, defeat the silence/live-rescue check, and get a fake-empty transcript summarised/saved (audio possibly deleted). Fixed: the sentinel is never healed.
  • Diarised label protection only covered the leading [timestamp], leaving [You]/[Others] exposed (real format is [MM:SS] [You] text). Fixed to protect every leading [...] label.
  • The live-transcript fallback text was stored un-healed. Fixed.
  • normalize_keywords didn't validate persisted shape — a hand-edited "aliases": "foo" (string) was char-iterated into aliases f,o. Fixed with defensive coercion + control/newline stripping.

Hardening checklist before un-drafting (why this stays a draft)

A second adversarial review surfaced a bigger design issue than the individual deferrals — ordered by severity:

  • Reversibility (release blocker). Healing overwrites the canonical persisted transcript, and reprocess runs on already-healed text (and audio may later be deleted), so a wrong glossary rule is irreversible — the original wording cannot be recovered. Before production: keep an immutable raw transcript and derive the healed one from it (or persist replacement provenance and reprocess from the raw source). The most likely silent corruption is a short common-word alias, e.g. United States: US turning "let us know" into "let United States know" — word boundaries do not resolve semantic ambiguity, and today that mistake is permanent.
  • Prompt term filtering. Every preferred term is injected into every prompt regardless of relevance; a large glossary (up to 200 entries) can pollute summaries with unrelated names and consume context that _needs_chunking does not budget for. Filter to terms actually present in the transcript, or hard-cap the injected set.
  • Enforce case-only aliases. An alias equal to its preferred term case-insensitively (openai -> OpenAI) is currently discarded, but capitalization enforcement is a central "preferred spelling" use case. Replacement is single-pass, so it is safe and idempotent — keep it, don't drop it.
  • Long-meeting reference block. Map/reduce summary prompts don't carry the reference block. (Deterministic alias healing already reaches them via the healed transcript; what's missing is preferred-only guidance for unlisted near-misses.) Thread the block into the map + reduce prompts, mindful of num_ctx.
  • Unicode normalization. NFC-normalize / grapheme-aware boundaries so a decomposed grapheme isn't half-matched. Lower probability; deserves fixing before broad release given the storage is currently destructive.
  • Real-meeting quality pass — does the reference block improve canonical spelling without the model inventing unsaid terms?
  • e2e + UI — the follow-up PR (Settings UI + IPC bridge + custom-keywords.t2).

Verification

  • ruff check . — zero new errors vs origin/main baseline.
  • python -m unittest discover tests515 passed (skipped 1), incl. the round-1 regression tests.

Refs #365.

…+ summary correction

Backend-only slice of Custom Keywords (RFC-adjacent, issue stenolabs#365): a user
glossary of preferred spellings + aliases that (a) persists in config.json,
(b) heals the transcript at transcription time and on reprocess, and (c)
injects a reference block into the summariser prompts. Empty glossary is a
no-op (zero behavior change).

- src/keywords.py: parse/normalize (defensive against malformed config),
  boundary-aware + diarised-safe alias replacement, reference-block builder.
- src/config.py: get/set_custom_keywords (validated, replace-not-merge).
- src/summarizer.py: reference block injected into permissive/markdown/
  template-report/title/query prompts.
- simple_recorder.py: guarded transcript heal in the pipeline + live-fallback
  + reprocess; get/set-custom-keywords CLI. Never heals the silence sentinel.
- Unit tests incl. silence-sentinel safety, diarised timestamped labels, and
  malformed-config hardening. Test fixtures use fictional placeholders only.

UI/IPC bridge + e2e spec land in a follow-up PR (re-integrated against stenolabs#344).

Refs stenolabs#365.
@Optic00
Optic00 force-pushed the feat/custom-keywords-backend branch from b9fb5da to cbebb93 Compare July 22, 2026 08:49
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