Setting Up Her Voice¶
File: configs/tts.yaml
Command: kenzy-tts [config_path]
This service is Kenzy's voice — text in, speech out. It can speak from a cloud provider or entirely from your own hardware.
Cloud or local?¶
| Cloud (OpenAI) | Local (Kokoro) | |
|---|---|---|
| What it needs | An API key. No downloads. | The kokoro extra plus one system package. Happy on a CPU. |
| What leaves your network | Only what Kenzy says — never what you say. | Nothing. |
| What it costs | Per-character billing from the provider. | Some CPU while she's speaking. |
The default is cloud — zero downloads and it sounds good on day one.
Local speech unlocks spoken secrets
Lockbox secrets are only ever spoken aloud when speech stays on-box. With a cloud voice, asking for a secret gets a polite deflection to the dashboard instead. This is mechanical, not a setting — it fails closed.
Deciding this across all the services at once? Start at Running Fully Local.
Set it up¶
Switching is also a dropdown in the dashboard (Fleet → tts).
Requires: OPENAI_API_KEY in ~/.config/kenzy/.env, set from the
dashboard under Settings → API keys.
Voices, speed range and the automatic Kokoro failover are in OpenAI provider below.
sudo apt-get install espeak-ng # phonemization library
pip install 'kenzy[kokoro]' # the extra: kokoro + PyTorch
kenzy-setup # pre-download the model weights
Requires: no API key and no internet at runtime. In a source checkout
the extra is pip install -e ".[kokoro]".
Voice names, languages and device selection are in Kokoro provider below; the end-to-end walkthrough is Running Fully Local.
Pulled from the server
kenzy-tts pulls this config from the server at boot — it discovers the server via mDNS (or KENZY_SERVER_URL) and blocks until it answers, so start the server first. Edit it from the dashboard's Services tab (writes configs/services/tts.yaml on the server and restarts the service). Passing an explicit path loads locally instead (dev/offline). log_level (console) and log_capture_level (dashboard viewer depth, default debug) work like every service. See central config for backend services.
Advanced¶
Everything below is the full reference.
Service¶
| Key | Default | Description |
|---|---|---|
host |
"127.0.0.1" |
Bind address |
port |
8769 |
HTTP port |
Provider selection¶
| Key | Default | Description |
|---|---|---|
provider |
"openai" |
TTS backend: openai or kokoro |
OpenAI provider¶
Long responses are automatically split at sentence boundaries and concatenated, so there is no effective limit on response length.
| Key | Default | Description |
|---|---|---|
openai.model |
"gpt-4o-mini-tts-2025-03-20" (shipped config) |
OpenAI TTS model |
openai.voice |
"sage" |
Voice persona (see below) |
openai.speed |
1.0 |
Playback speed multiplier, 0.25–4.0 |
openai.fallback |
true |
On a cloud failure, silently retry with local Kokoro — only takes effect when the kokoro extra is installed (see below); otherwise the failure surfaces as the error cue. |
Available voices¶
alloy · ash · ballad · coral · echo · fable · nova · onyx · sage · shimmer
Kokoro provider¶
Kokoro runs entirely locally with no API key or internet connection required at runtime. It produces high-quality speech and outputs at 24 kHz mono — the same format as the OpenAI provider, so audio handling downstream is unchanged. One behavior does depend on the provider: lockbox secrets are only ever spoken when speech stays on-box — with a cloud provider, asking for a secret gets a polite deflection to the dashboard instead. Local speech is what unlocks spoken secret read-backs.
Note
The voice_prompt style instruction generated by the LLM (e.g. "speak warmly at a conversational pace") is an OpenAI-specific feature. It is silently ignored when using Kokoro.
| Key | Default | Description |
|---|---|---|
kokoro.voice |
"af_heart" |
Kokoro voice name (see below) |
kokoro.device |
"auto" |
Inference device (see below) |
kokoro.speed |
1.0 |
Playback speed multiplier, 0.5–2.0 |
kokoro.lang_code |
(from voice) | Language code. Derived automatically from the first character of the voice name if omitted. |
Device options¶
| Value | Description |
|---|---|
auto |
Detects the best available device at startup: CUDA → MPS → CPU (recommended) |
cpu |
Always use CPU |
cuda |
NVIDIA GPU. Also covers AMD GPUs when using a ROCm-enabled PyTorch build. |
mps |
Apple Silicon GPU (M1/M2/M3/M4) |
Voice names and languages¶
The voice name prefix determines the language:
| Prefix | Language | Example voices |
|---|---|---|
af_ |
American English (female) | af_heart, af_bella, af_sky |
am_ |
American English (male) | am_adam, am_michael |
bf_ |
British English (female) | bf_emma, bf_isabella |
bm_ |
British English (male) | bm_lewis, bm_george |
The lang_code is derived from the first character of the voice name (af_heart → 'a', bf_emma → 'b'). Set it explicitly only if you need to override this.
Wyoming listener (Home Assistant voice pipelines)¶
Expose this service as a native HA text-to-speech provider, so Assist replies on your phone are spoken in Kenzy's actual voice — see On Your Phone for the full setup.
| Key | Default | Description |
|---|---|---|
wyoming.enabled |
false |
Start the Wyoming protocol listener alongside the HTTP service. Uses the exact same synthesis path (provider, voice, fallback chain) as /speak. Requires the wyoming package (included in the tts extra). |
wyoming.port |
10200 |
Listener port (the Piper convention, so HA operators guess right). |
Wyoming is plain, unauthenticated TCP — the listener follows the service
bind, so it stays loopback-only unless you've deliberately opened the
service to the LAN (KENZY_BIND=0.0.0.0 / --listen-all).