From 0d70f115d1d57a3be373b61fa762f28d26b2b40f Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Oct 2026 19:21:22 -0700 Subject: [PATCH] docs(frank-tts): the harness sends a voice only in voice_id The section this PR adds claimed speech.voice was the forwarded voice key. It is not: the openai-speech transport builds {model, input, response_format, voice?} and sets voice only when the CALL passed voice_id, tts.localVoice is the local Kokoro backend key, and speech.voice is the TUI speak-aloud voice whose enum is Kokoro-only. A restarted session proved it the honest way - the tts device arrived at 7772 with no voice key and got "unknown voice None; available voices: narrator1, narrator2, narrator3" - so the fresh-process success earlier that day was the run that was told to pass voice_id, not evidence of a setting. The law now reads: voice comes from voice_id alone, so a voice-less call is the harness default shape (the endpoint grew DEFAULT_VOICE in its own PR to answer it), and speech.voice must stay a Kokoro name - a pack name there breaks the vocalizer and does nothing for synthesis. config.yml reverted to am_fenrir on the operator box, and the worklog entry that recorded narrator1 as wiring carries the correction. --- serverAppConfigs-docs/Dankclaw/Frank TTS.md | 27 +++++++++++++-------- serverAppConfigs-docs/log.md | 18 +++++++++++--- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/serverAppConfigs-docs/Dankclaw/Frank TTS.md b/serverAppConfigs-docs/Dankclaw/Frank TTS.md index 50db9fd..9bba640 100644 --- a/serverAppConfigs-docs/Dankclaw/Frank TTS.md +++ b/serverAppConfigs-docs/Dankclaw/Frank TTS.md @@ -154,17 +154,24 @@ The operator harness reaches this endpoint through `baseUrl: http://192.168.0.143:7772/v1`, `auth: none`, and the runner api `openai-speech` on the **model** row (naming a runner api at *provider* level fails the whole file's validation and disables every custom provider, which is -how the fleet's chat endpoints disappear) — plus `config.yml`: -`modelRoles.speech: frank-tts (dankclaw)/frank-tts` and `speech.voice: -narrator1`. Three laws, read out of the harness bundle and measured the day of -the flip: +how the fleet's chat endpoints disappear) — plus `config.yml` +`modelRoles.speech: frank-tts (dankclaw)/frank-tts`. Four laws, read out of the +harness bundle and measured the day of the flip: -- **The forwarded voice key is `speech.voice`, not `tts.localVoice`.** The - latter belongs to the local Kokoro backend and still carries `bm_george`; it - is what answered when the role pointed at `local/kokoro`. `speech.voice` - becomes the request's `voice`, and this endpoint treats an unknown name as an - error rather than a default — so a Kokoro-style revert has to move **both** - keys together. +- **The only voice the harness can send is the `voice_id` argument of the call.** + The transport builds `{model, input, response_format, voice?}` and adds `voice` + **only** when the caller passed `voice_id`; no setting feeds it. + `tts.localVoice` is the *local Kokoro backend's* voice (it is what answered + `voice=kokoro/bm_george, backend=local` in the control test), and + `speech.voice` is the TUI's **speak-aloud** voice — its enum is Kokoro's list, + and it never reaches an HTTP request. So a harness call arrives voice-less by + default, which is exactly why the endpoint grew `DEFAULT_VOICE`: the declared + pack answers a voice-less request and the journal marks that line `(default)`, + while an **unknown** name still 400s with the list that exists. +- **`speech.voice` must stay a Kokoro name.** Setting it to a pack name does not + select a pack; it hands the vocalizer a value outside its enum. (The first + draft of this section said the opposite and was reverted the same day, after a + fresh session's call came back `unknown voice None`.) - **There is no silent fallback.** An explicitly configured `modelRoles.speech` yields exactly one candidate, so a dead 7772 fails the speech device with *"No available speech model matches the speech role"* instead of quietly diff --git a/serverAppConfigs-docs/log.md b/serverAppConfigs-docs/log.md index 12cdf67..a7ff446 100644 --- a/serverAppConfigs-docs/log.md +++ b/serverAppConfigs-docs/log.md @@ -2706,9 +2706,10 @@ every restart since the cutover. actually selects a voice. Engine-side ledger for the harness request: `TTFA 67.5 ms`, `Total 2158.8 ms`, `audio 3.76 s, RTF 0.574`. - **The harness `speech` role moved off Kokoro**: `config.yml` - `modelRoles.speech: frank-tts (dankclaw)/frank-tts` and `speech.voice: - narrator1`. `tts.localVoice` stays `bm_george` — that key belongs to the local - backend, which is what answered in the control test. + `modelRoles.speech: frank-tts (dankclaw)/frank-tts`. No voice key moves with it. + The bundle is unambiguous: the speech device forwards the call's `voice_id` and + nothing else, `tts.localVoice` belongs to the local backend (it is what answered + in the control test), and `speech.voice` is the TUI's speak-aloud enum. - Two harness laws learned by breaking it first (both now on [[Frank TTS]]): an explicit speech role has **no fallback candidate**, so a dead endpoint errors instead of degrading; and `models.yml` loads at process start while @@ -2722,3 +2723,14 @@ every restart since the cutover. voice leg works; open-notebook itself is what is missing), the engine page's "Deploy order — nothing below has been run" is now a **Deploy record**, and the homepage card lost `wip: down` so the admin panel probes 7772 for real. +- Corrected the same evening, by breaking it in a restarted session: a harness + call that names no voice reaches 7772 with **no `voice` key at all** — `400 + unknown voice None; available voices: narrator1, narrator2, narrator3` — + because no setting feeds one. The earlier `Saved 180524 bytes + (voice=narrator1)` succeeded only because that run was *told* to pass + `voice_id`. The endpoint therefore grew `DEFAULT_VOICE` (its own PR): a + voice-less request speaks the declared pack and the journal line carries + `(default)`, while an unknown name still 400s with the list. Reverting the role + to Kokoro is one key, not two, and `speech.voice: narrator1` — briefly recorded + above as wiring — went back to `am_fenrir`: that key is the vocalizer's Kokoro + enum, so a pack name there breaks speak-aloud and does nothing for synthesis.