Skip to content

bf_alice is documented as an available Kokoro voice but errors: absent from KOKORO_VOICES and the voice pack catalog #3331

Description

@valorengels

Context: tools/tts is this repo's text-to-speech layer. It dispatches between a
local Kokoro ONNX backend and OpenAI's tts-1, and validates the caller's requested
voice name against per-backend catalogs before synthesizing. /do-debrief uses it to
deliver spoken briefs to Telegram.

Problem

bf_alice is documented in two places as an available Kokoro voice, but it does not exist in the code's voice catalog, and requesting it is a hard failure rather than a fallback.

Documented as available:

Absent from the actual catalogs:

  • KOKORO_VOICES (tools/tts/__init__.py:27-38) lists ten voices; bf_alice is not among them. The bf_* entries are bf_emma and bf_isabella.
  • tools/tts/README.md:77, the catalog table the skill-context file points readers to, lists the same ten.
  • _VOICE_FALLBACK_MAP (:47-66) has no bf_alice key.

Current behavior. _resolve_voice (:161-197) checks the target backend set, then the other backend's set, then errors. Since bf_alice is in neither, the function hits :191 and returns:

{"error": "unknown voice: 'bf_alice'. Available kokoro: [...]. Available openai: [...]"}

synthesize returns that dict alone (:409-411) with no path, duration, or backend key. Any caller that reads result["path"] without checking for error first raises KeyError. So a debrief run that follows its own context doc's advice does not get a different voice — it gets a failed synthesis.

Desired outcome. One source of truth. Either bf_alice is a real voice in the Kokoro voice pack and belongs in KOKORO_VOICES, _VOICE_FALLBACK_MAP, and the README table, or it is not and both documentation references should be corrected to name a voice that exists (bf_emma or bf_isabella).

Definitions

Term Meaning
Kokoro Local ONNX TTS model (kokoro-v1.0.onnx) with a bundled voice pack (voices-v1.0.bin), used when available to avoid cloud cost and latency.
_VOICE_FALLBACK_MAP Remap table applied when the caller asks for a voice belonging to the backend that dispatch did not pick.
skill-context file Repo-specific addendum layered onto a generic global skill. See docs/features/skill-context-convention.md.

Solution sketch

First determine which way the mismatch resolves — that is a question, not an assumption:

  1. Check whether bf_alice is actually present in the shipped voices-v1.0.bin voice pack. The Kokoro voice pack is the ground truth; KOKORO_VOICES is a hand-maintained mirror of it, so the mirror could be the thing that is wrong.
  2. If present in the pack: add it to KOKORO_VOICES, give it a _VOICE_FALLBACK_MAP entry (the other bf_* voices map to fable and shimmer), and add it to the README table. Synthesize one clip to confirm it renders.
  3. If absent from the pack: correct .claude/skill-context/do-debrief.md:21 and the :68 comment to name an existing voice.

Whichever way it goes, consider whether KOKORO_VOICES should be derived from the voice pack rather than hand-maintained, since that is the class of drift that produced this. That is a judgment call for the planner, not a requirement here.

Acceptance criteria

  • Ground truth established: bf_alice either is or is not in the shipped voice pack, with the check recorded.
  • KOKORO_VOICES, _VOICE_FALLBACK_MAP, tools/tts/README.md:77, .claude/skill-context/do-debrief.md:21, and the tools/tts/__init__.py:68 comment all agree.
  • Requesting the documented female alternative by name produces a successful synthesis, verified by an actual call.

Downstream context

Surfaced while planning #3330 (pre-recorded question-tree voice interview), which builds a persistent clip library on top of tools/tts and therefore has to know which voice names are real. Deliberately not absorbed into that plan — it is a pre-existing catalog inconsistency with its own blast radius.


Triage 2026-09-15

Status: confirmed — but the doc claims (.claude/skill-context/do-debrief.md:21, tools/tts/__init__.py:68 comment) are actually correct, and the catalog is the bug. Verified against the installed pack: Kokoro('~/.cache/kokoro-onnx/kokoro-v1.0.onnx', '~/.cache/kokoro-onnx/voices-v1.0.bin').get_voices() returns 54 voices including bf_alice (and also bf_lily, likewise undocumented/uncataloged). KOKORO_VOICES at tools/tts/__init__.py:27-38 only lists 10 of the 54, and _resolve_voice() (tools/tts/__init__.py:161-194) treats that set as a hard allow-list, so a genuinely valid, installed voice is rejected outright.

Single-line hotfix: Yes. Add "bf_alice", to the KOKORO_VOICES set at tools/tts/__init__.py (within lines 27-38), and add bf_alice to the catalog table row in tools/tts/README.md:77. (Also worth adding bf_lily, present in the pack but never mentioned anywhere, while touching this.)

Reduce-complexity option: Yes — the two-place (arguably three, counting the tools/tts/__init__.py:68 comment) hand-maintained curated subset is exactly the kind of duplicated data that drifts from the real pack. Recommend deriving KOKORO_VOICES at runtime from Kokoro.get_voices() (or generating the constant from it) instead of hand-listing a subset in code, docs, and a comment independently — that would have made this class of bug structurally impossible.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions