Persistent Kokoro TTS daemon for Claude Code
Project description
kokoro-speakd
Persistent Kokoro TTS daemon for Claude Code and other clients. Loads the model once and serves synthesis requests over a Unix domain socket, so a machine running 10+ concurrent Claude Code sessions doesn't pay the cold-start cost on every response.
- Single process, single model in RAM, regardless of how many clients.
- Preemption over queueing — new speech always cancels older in-flight playback, which is what you actually want when juggling many sessions.
- Lazy per-language pipelines — default English loads at startup, other languages spin up on first use.
- Nix-first packaging —
flake.nixexports both the daemon and a stdlib-only client, withdlinfo/en_core_web_smworkarounds baked in sopython3Packages.kokorobuilds cleanly on darwin. - Line-delimited JSON protocol over Unix sockets, trivial to drive from any shell or language.
Capability
Pattern. Persistent Kokoro TTS daemon over a Unix socket — one kokoro-speakd process holds the Kokoro-82M weights and en_core_web_sm phonemizer in RAM; a stdlib-only kokoro-speak client speaks line-delimited JSON to it.
Trade-off. A 5–8 s cold model load on first boot, in exchange for sub-500 ms warm-call latency on every subsequent kokoro-speak invocation. Per-call model reload is avoided entirely, regardless of how many concurrent clients are talking to the daemon.
Use when. A Claude Code hook, shell pipeline, or agent loop needs synchronous TTS without paying PyTorch warm-up cost per response — markdown stripping is built in, preemption replaces queueing so the latest thought always wins, and the socket path is stable enough to drop into Stop / SessionEnd / UserPromptSubmit hooks.
pip install kokoro-speakd # PyPI (when first release cut)
kokoro-speakd & # daemon, single instance per user
echo "hello world" | kokoro-speak # stdin → speech, ~412 ms warm-call
kokoro-speak < release-notes.md # markdown → strip → speech
kokoro-speak interrupt # cancel in-flight playback
Demo
A non-interactive 18-second asciinema cast covering kokoro-speakd --help, daemon start with warm-load timing, kokoro-speak ping, an inline echo … | kokoro-speak, a markdown-stripping kokoro-speak < file.md, and kokoro-speak interrupt is checked into the repo at docs/assets/kokoro-demo.cast. Replay locally:
asciinema play docs/assets/kokoro-demo.cast
A hosted player embed will land in a follow-up PR after the cast is uploaded to asciinema.org.
How kokoro-speakd compares
Closest peers in the open-source TTS-client ecosystem:
| Capability | kokoro-speakd (this repo) |
say (macOS native) |
espeak-ng |
piper-tts |
|---|---|---|---|---|
| Persistent daemon (warm-call <500 ms) | yes | n/a (system service) | no (per-call process) | manual (no built-in daemon) |
| Quality voice (Kokoro 82M ONNX) | yes | yes (macOS voices only) | no (formant-synth, robotic) | yes |
| Cross-platform (Linux + macOS) | yes | no (macOS only) | yes | yes |
| Markdown → speech preprocessing | yes (markdown.py strip) |
no | no | manual |
| Preemption over queueing (latest thought wins) | yes | no (queues) | no (no queue) | no |
| Lazy per-language pipelines | yes | n/a | n/a (single back-end) | manual |
| PEP 740 PyPI attestations | pending Trusted Publishing wire | n/a | n/a | no |
| Claude Code hook integration documented | yes (Stop / SessionEnd) |
no | no | no |
For multi-tenant TTS gateways and self-hosted REST services see coqui-ai/TTS or openedai-speech — different shape of problem, listed in What this is NOT.
What this is NOT
- Not a multi-tenant TTS SaaS. Each
kokoro-speakdinstall is scoped to one user, one model in RAM, one socket. - Not a REST gateway. Use
coqui-ai/TTSoropenedai-speechif that's what you want. - Not a real-time streaming TTS. Synthesis runs per-request; preemption swaps the playback target, it does not splice mid-utterance.
- Not a voice-cloning tool. Voices are picked from the 54 ONNX models Kokoro ships; bring-your-own-voice is upstream's problem.
Quick start (with Nix)
# Start the daemon in the foreground:
nix run github:yolo-labz/kokoro-speakd
# From another terminal:
echo "hello from kokoro" | nix run github:yolo-labz/kokoro-speakd#kokoro-speak
nix run github:yolo-labz/kokoro-speakd#kokoro-speak -- ping
nix run github:yolo-labz/kokoro-speakd#kokoro-speak -- interrupt
The first request after a cold daemon start takes ~5–8 seconds while the PyTorch weights warm up. Every request after that is <500 ms regardless of how many clients are talking to the daemon.
Claude Code hook integration (nix-darwin / home-manager)
-
Add the flake as an input:
kokoro-speakd = { url = "github:yolo-labz/kokoro-speakd"; inputs.nixpkgs.follows = "nixpkgs"; };
-
Install the daemon and client on your user, and register a launchd agent so the daemon auto-starts at login:
{ inputs, pkgs, system, ... }: let kokoro = inputs.kokoro-speakd.packages.${system}; in { home.packages = [ kokoro.kokoro-speak kokoro.kokoro-speakd ]; launchd.agents.kokoro-speakd = { enable = true; config = { ProgramArguments = [ "${kokoro.kokoro-speakd}/bin/kokoro-speakd" ]; KeepAlive = true; RunAtLoad = true; StandardOutPath = "${config.home.homeDirectory}/.cache/claude-code-tts/launchd.out.log"; StandardErrorPath = "${config.home.homeDirectory}/.cache/claude-code-tts/launchd.err.log"; }; }; }
-
Point Claude Code's
Stop,UserPromptSubmit, andSessionEndhooks atkokoro-speak— the daemon handles the rest. Seephsb5321/NixOSfor a full reference integration, including the transcript walk that pulls Claude's final narrative text out of the JSONL log.
Protocol
One JSON request per connection, newline-terminated. Response is a single newline-terminated JSON object.
// speak (replaces any in-flight speech)
{"action": "speak", "text": "hello there", "voice": "af_sky", "lang": "a"}
// -> {"status": "queued", "id": 42}
// -> {"status": "loading"} // model still warming up
// -> {"status": "empty"} // text was whitespace after stripping
// cancel current playback
{"action": "interrupt"}
// -> {"status": "ok"}
// health check
{"action": "ping"}
// -> {"status": "pong", "ready": true}
Defaults: voice="af_sky", lang="a" (American English). Override per
request or globally via KOKORO_DEFAULT_VOICE / KOKORO_DEFAULT_LANG.
Environment
Daemon:
KOKORO_SPEAKD_SOCKET— socket path (default~/.cache/claude-code-tts/kokoro-speakd.sock)KOKORO_SPEAKD_LOG— log file pathKOKORO_DEFAULT_VOICE,KOKORO_DEFAULT_LANG— fallback values
Client (kokoro-speak):
KOKORO_VOICE,KOKORO_LANG— per-request overridesKOKORO_MAX— hard char cap on the text sent (default 5000)KOKORO_SPEAKD_SOCKET— matching override to reach a non-default daemon
Voices
Kokoro ships 54 voices. American English defaults to af_sky; see
hexgrad's VOICES.md
for the full list including British English, Japanese, Mandarin, French,
Italian, and more. Set KOKORO_VOICE=af_bella (etc.) and reload the daemon.
License
MIT. See LICENSE.
Services
Compliance-grade AI architecture for regulated workloads — async-first, USD-denominated, LATAM-based / EN-fluent. See blog.home301server.com.br/services.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kokoro_speakd-0.3.0.tar.gz.
File metadata
- Download URL: kokoro_speakd-0.3.0.tar.gz
- Upload date:
- Size: 11.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50723298f982fb027d66ff12a3ff456f96fd468b7dcc3a2701e6ae277a799b2c
|
|
| MD5 |
6356e4e56f3034ecf62c061447ca6697
|
|
| BLAKE2b-256 |
63057cdf064cdf5c8ae934c7920b7064bc4a4a1909421f2c888ce93e6a1d3cd4
|
Provenance
The following attestation bundles were made for kokoro_speakd-0.3.0.tar.gz:
Publisher:
release.yml on yolo-labz/kokoro-speakd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kokoro_speakd-0.3.0.tar.gz -
Subject digest:
50723298f982fb027d66ff12a3ff456f96fd468b7dcc3a2701e6ae277a799b2c - Sigstore transparency entry: 1672331989
- Sigstore integration time:
-
Permalink:
yolo-labz/kokoro-speakd@d86645319976b337539df808c00943ec74d936f4 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/yolo-labz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d86645319976b337539df808c00943ec74d936f4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file kokoro_speakd-0.3.0-py3-none-any.whl.
File metadata
- Download URL: kokoro_speakd-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df8ee9dfb12ab50d107c613747f02d50907ec4ba9e816401af27f23130f3b5e2
|
|
| MD5 |
35005ed595a22cb1ec8156e05e449189
|
|
| BLAKE2b-256 |
5bbeb5ec84ab1e30a599888c8f469119fdb445c35d33dc227746f68f8d84438c
|
Provenance
The following attestation bundles were made for kokoro_speakd-0.3.0-py3-none-any.whl:
Publisher:
release.yml on yolo-labz/kokoro-speakd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kokoro_speakd-0.3.0-py3-none-any.whl -
Subject digest:
df8ee9dfb12ab50d107c613747f02d50907ec4ba9e816401af27f23130f3b5e2 - Sigstore transparency entry: 1672331992
- Sigstore integration time:
-
Permalink:
yolo-labz/kokoro-speakd@d86645319976b337539df808c00943ec74d936f4 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/yolo-labz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d86645319976b337539df808c00943ec74d936f4 -
Trigger Event:
push
-
Statement type: