Talk to your machine: hands-free, push-to-talk, or wake-word mode. On-device speech recognition (Whisper) and speech synthesis (Kokoro) keep your voice off the cloud; an expressive face UI and a shared visual board round it out. One Rust workspace, two dependency-free web UIs, no Python anywhere.
- Voice line (
openbutler-voice): mic capture with VAD endpointing, Whisper STT in-process, an AI brain turn (via theopencodeCLI), Kokoro TTS speech with interruption (barge-in), spoken settings verbs, wake-word gating. - Face (
openbutler-face): local web UI (ui/face) with several faces, driven by a tiny file bus. Any program can drive it by writing files. - Board (
openbutler-board): shared visual board (ui/board) — cards, images, notes, and a 3D-props airlock, pluscmd/stateCLI verbs. - Setup (
openbutler-setup): first-use wizard and auditor (check/init/fix).
- OS: Fedora with
toolbox/podman(heavy crates link C++ and must build inside the container; the host typically lackslibstdc++for linking). Other Linuxes work too — install the same native toolchain (cmake,gcc,clang, ALSA dev headers,espeak-ng+ dev headers,ffmpeg) and build natively. - Rust: recent stable via rustup (shared into the
container through
$HOME/.cargo). opencodeCLI onPATH, logged in — the brain runs oneopencode runprocess per turn. No opencode, no answers.- Host audio helpers:
espeak-ng+ffmpeginstalled on the host itself (setup checkverifies both). - Mic + speaker (any PipeWire/ALSA device),
curlfor model fetches. - Disk + network: several GB free. First builds download prebuilt MKL/oneDNN math libraries; first runs download voice models (see below).
git clone <your-fork> && cd openbutler
# 1. System deps inside the build container (idempotent):
toolbox run -c openbutler ./toolbox-setup.sh
# (Use your container name if it differs from .env VOICE_CONTAINER.)
# 2. Build — heavy crates INSIDE the container, light ones anywhere:
toolbox run -c openbutler cargo build
# Host-only alternative for the light crates:
# cargo build -p openbutler-common -p openbutler-face -p openbutler-board -p openbutler-setup -p openbutler-cli
# 3. First-use setup: identity Q&A -> .env -> rendered configs -> models:
./target/debug/openbutler-setup init
./target/debug/openbutler-setup check # see "expected gaps" belowsetup check audits 12 items and exits 1 on gaps. Two gaps are normal on
a fresh machine, not failures:
toolbox: your container name doesn't exist yet —toolbox createit, or pointVOICE_CONTAINERat the one you have.input: no/dev/inputaccess — push-to-talk degrades (hands-free is unaffected). Fix with the push-to-talk step.
Weights live in ~/.cache (override with KOKORO_DIR, JARVIS_WAKE_DIR,
HF_HUB_CACHE):
| Model | How it arrives | Size |
|---|---|---|
Wake word (frontend + hey_jarvis classifier) |
setup init downloads from openWakeWord releases (pins + sha256 in models/wake.json, registry of 6: hey_jarvis, alexa, hey_mycroft, hey_rhasspy, timer, weather) |
~4MB |
Kokoro TTS (kokoro-v1.0.onnx + voices-v1.0.bin) |
setup init fetches from the thewh1teagle model-files-v1.0 release; manual fallback: drop any *.onnx + voices-v1.0.bin into ~/.cache/jarvis/kokoro (model, voices) |
~340MB |
Whisper STT (small.en default) |
auto-downloads from HuggingFace Hub on first listen | hundreds of MB |
The name is set during setup — init asks for it (default
"Assistant"), and you can use anything you like. It shows up in the
spoken greeting, the face and board titles, and — lowercased — in the
hang-up phrase: with name "Butler" you say "goodbye butler".
Change it later without losing tuning: edit AGENT_NAME in .env, then
openbutler-setup init --yes to re-render (managed keys only; your voice,
speed, effort and other tuning survive).
Wake word follows the name — when a stock classifier says it. init
matches your name against the built-in wake models (models/wake.json):
"Alexa" → alexa ("alexa"), "Mycroft" → hey_mycroft ("hey mycroft"),
anything Jarvis-like → hey_jarvis ("hey jarvis"), plus hey_rhasspy,
timer, weather. A name with no matching classifier keeps the default
(hey_jarvis) and init tells you so.
# From a real terminal, not an AI session:
./start.sh # face + voice
./start.sh --no-face # voice only- Face: http://127.0.0.1:8790/faces/neural/ (faces: board, neural,
radial, rain — switch with
config set face.face radial). - Talk: just speak — the default is hands-free (
open) mode. Say "goodbye <name>" to hang up. Ctrl-C stops everything. - Ports (loopback only):
8790face,8794board,8791voice lock. The:8791lock is machine-global — never run two voice lines at once. - Logs:
logs/voice.log(brains + latencies),logs/stts-serve.log.
./target/debug/openbutler-voice config set mic_mode wake # "wake word" mode
./target/debug/openbutler-voice config set mic_mode ptt # push-to-talk
./target/debug/openbutler-voice run --open-mic --barge-in # one-shot flagsSpoken equivalents: "go hands free", "wake word mode", "push to talk
mode". Wake sensitivity: wake.threshold / wake.patience /
wake.attention_s. Switch classifier anytime (takes effect on next
launch; setup init fetches it, setup check verifies its sha):
./target/debug/openbutler-voice config set wake.model alexaA fully custom name needs a custom-trained classifier: train one with
openWakeWord, drop the .onnx
into the wake cache dir, point wake.model at its id and wake.phrase
at what users say (JSON-only advanced keys).
The talk key reads /dev/input (evdev), which needs an access rule:
sudo cp udev/70-openbutler-input.rules /etc/udev/rules.d/
sudo udevadm control --reload && sudo udevadm trigger
# then re-login (or: sudo usermod -aG input $USER)Without it the line still runs — PTT just reports itself unavailable and hands-free carries on.
openbutler-setup init renders configs/{voice,face,board}.json from
.env; re-render safely anytime (init --yes keeps tuning — managed keys
only). For day-to-day changes use the one settings CLI (takes effect on
next launch; UPPER .env aliases like STT_MODEL work too):
./target/debug/openbutler config # every setting, one list
./target/debug/openbutler config get speed
./target/debug/openbutler config set speed 1.1
./target/debug/openbutler config set stt_model medium.en # JSON + .env together
./target/debug/openbutler config set name Butler # .env + re-rendered JSONsopenbutler config set writes the live JSON and keeps the .env seed in
sync, so later re-renders stay drift-free. Editing .env by hand only
seeds missing values — it never overwrites tuning you already set
(voice, stt_model, greeting, faces, ports). voice config get/set
still works as before for the tuning keys.
| Key | What |
|---|---|
name |
assistant name (greetings, quit phrase, face/board titles) |
voice / speed (0.5–2.0) |
TTS voice (54 built in) and rate |
effort |
low / medium / high / max reasoning effort |
mic_mode |
open hands-free (default), ptt, wake |
stt_model |
e.g. small.en, medium.en (larger = slower, sharper) |
greeting / memory_vault / voice_container |
spoken greeting, Obsidian vault dir, toolbox container for builds |
wake.threshold / patience / attention_s |
wake sensitivity, confirmations, follow-up window |
face.* / board.* |
face.name, face.face, face.port, board.name, board.port (ports validated 1–65535) |
Spoken equivalents exist for the common ones ("go hands free", "wake word mode", "push to talk mode", "switch voice to …", "usage report").
- New voice verbs: add a matcher in
crates/voice/src/console.rs(console_matchfor one-shots). - New settings: extend the registry in
crates/common/src/settings.rs(shared byopenbutler configandvoice config, so add once) plus a row in theopenbutlerCLI key table when the setting needs.envsync. - New faces: drop a folder with
index.html(+ optionalface.json) intoui/face/faces/— it is listed and served automatically. - New board verbs: extend
ALLOWEDincrates/board/src/main.rs. - New voices: Kokoro ships 54 across 9 languages; point
voiceat any of them (af_*,bf_*,bm_*,ef_*,ff_*,hf_*,if_*,jf_*,pf_*,zf_*). - Models: pins + sha256 in
models/wake.json; weights live in~/.cacheand are never committed.
See CONTRIBUTING.md for build discipline (heavy-binary rules that will
save you gigabytes and hours).
cargo buildfails withunable to find library -lasound/libstdc++: you're building heavy crates on the host — build them in the container (toolbox run -c <name> cargo build).- MKL install eats the disk: stale
target/debug/build/intel-*-prebuild-*trees withoutmkl/latestinside are failed-install junk, safe to delete. ANOTHER VOICE LINE IS ALREADY RUNNING: a line holds:8791— use that window, or kill theopenbutler-voiceprocess first.- "opencode not found on PATH": install + log in the opencode CLI.
- PTT unavailable: do the sudo step.
- No mic audio: check PipeWire/ALSA device + desktop mic-privacy
settings;
mic_deviceinvoice.jsoncan pin a device by name substring. - Changed
.envbut the runtime ignores it: expected —.envonly seeds missing values. Useopenbutler config set <key> <value>, which writes the live JSON and the seed together. - A live
configs/*.jsonis corrupted (not valid JSON):setup checkreports render drift andconfig setrefuses to touch it — nothing is silently rebuilt. Runopenbutler-setup fix: it quarantines the broken file to.unwired(with your confirm) and re-renders from.env.init --yeswill not overwrite it. - TypeError-ish silence after interrupt: fixed long ago (stale-stop
guard) — report with
logs/voice.loglines if you ever hit a mute.
AGPL-3.0-or-later — see LICENSE. Model weights carry their own licenses
(wake graphs: Apache-2.0; Kokoro-82M: Apache-2.0).