Back to Case Studies
Voice AI

Voice Commander: speech to keystroke with no cloud in the loop

A local-first Windows voice command daemon: press a hotkey, speak plain English, a keystroke fires in about 700 milliseconds, with nothing leaving the machine.

26 July 2026 · 3 min read

Pythonfaster-whisperCUDAFastAPIReact
700ms

hotkey to fired command, measured on our own machine

Outcome

Speech-end to fired keystroke lands at around 700 milliseconds on our own machine, with zero audio or transcripts leaving the device.

Cloud dictation is fast but sends a hot microphone to someone else's server. Programmable voice frameworks are powerful but demand you learn a command grammar before you say a single useful word. Voice Commander sits in the boring, useful middle: press a hotkey, say a plain-English command, a keystroke fires, and nothing ever leaves the machine.

The problem

The two existing options for voice control both have a real cost. Cloud tools such as Google, Siri or Dragon are quick to start using but put a live microphone feed through a third party's servers. Programmable frameworks such as Talon or Dragonfly give you full control but require learning an entire grammar before first use. Neither fits a push-to-talk, plain-English, on-device workflow, so we built one for our own daily use: a utility knife next to that Swiss army knife.

What we built

A pipeline of five stages connected by thread-safe queues: a hotkey listener wakes a streaming recorder, which resamples audio and runs it through local voice-activity detection, hands the result to a local Whisper model running on CUDA with no temporary file written to disk, routes the transcript through a deterministic verb router, and dispatches the matching multi-step plan to keystroke and window-control tools. Commands and workflows are authored visually, as directed graphs in a React Flow builder, saved as plain JSON and executed by a graph runtime with topological sort and branching. A separate sprite process renders a small on-screen companion that mirrors daemon state, connected to the daemon only by a one-way event stream, so a UI crash can never take voice recognition down with it.

Under the hood

The routing decision is the whole point. The default path is exact and synonym matching against a command registry, no model involved, and an early architecture decision to try an LLM router was later reversed outright: ADR 0082 removed LLM routing from the default path entirely once the deterministic router proved sufficient. That reversal is documented, not hidden, alongside 99 other architecture decision records covering every locked engineering call in the project. The codebase backs that discipline with scale most internal tools never reach: about 20,800 lines of source code against roughly 29,900 lines of test code across 172 test files, more test than implementation. Every utterance is captured as a span tree in a local runs database and streamed live over server-sent events to an observability panel, the same instinct we build into client systems, applied to our own tool first. The daemon ships 22 starter command graphs and exposes 36 FastAPI routes for the builder UI and runs panel to talk to. Test coverage is enforced, not just aspired to: an 80 percent floor is configured directly into the build and fails it if coverage drops below that line. The visual command builder itself is a separate 4,100-line React and TypeScript application on React Flow and Zustand, kept firmly on the other side of the process boundary from the recognition daemon it configures.

What changed

Voice Commander is what deterministic-first looks like as a tool we use every day rather than a slide in a pitch deck. The fast path never touches a model, an LLM only gets involved when explicitly asked for and even that fallback was later cut back, and a crash in the visual layer stays contained to the visual layer. On our own machine, the budget from end of speech to a fired keystroke lands at around 700 milliseconds, close to the roughly 1.5-second ceiling the project holds itself to, with the observability to prove it on every single run.

Questions we get about this build

No. Whisper runs on local GPU via CUDA, voice-activity detection runs locally, and the primary command path involves no network call at all.

No, not by default. The primary path is deterministic name and synonym matching against a command registry. An LLM is only invoked on explicit request, and a later architecture decision (ADR 0082) removed LLM routing from the default path entirely.

Nothing, to voice recognition. The sprite UI runs as a separate OS process connected only by a one-way event stream, so a rendering crash cannot take down the recognition daemon, and a supervisor respawns it independently.

It is our own daily-driver tool, not a client deliverable. We are publishing it as a working example of deterministic-first design, not a product for sale.

Have a process that should run itself?

Bring the messiest workflow you have. We will tell you what to automate, what to leave manual, and when an agent is the wrong answer.

Talk to MerlinGuided intake