A Tour of moonshine-go's 11 Runnable Voice Samples
For years, the classic voice cascade (Speech-to-Text → LLM → Text-to-Speech) was written off as too slow compared to monolithic speech-to-speech models. The cascade never lost on capability. It lost on milliseconds.
moonshine-go combines Moonshine's streaming STT models with pure-Go runtime bindings (ebitengine/purego) to eliminate cgo overhead and local IPC delays.
Once latency stops being the bottleneck, the cascade's original advantages return:
- Control: Every stage is open to gate, swap, or configure.
- Observability: Every utterance is an inspectable, loggable event.
- Privacy: Audio dies at the microphone; only text you choose leaves the box.
- Composability: The transcript acts as a bus any process can attach to in any language.
What You Can Build
Before diving into how the samples work under the hood, here are three real-world voice applications you can build on top of these patterns today:
1. Meeting or Field Copilot with Cited RAG
samples/go-cascade-faq demonstrates an offline voice agent answering questions over a local document index. Swapping StaticRetriever for a vector database or local search engine yields an ambient copilot for meetings, clinical dictation, or field service. Because retrieval runs as a named tool call with logged arguments, every spoken answer cites its exact source document in the event stream.
2. Voice-Controlled Terminal (voice-tmux) — Open Idea
This application does not exist in the repository yet: a standalone Go bridge that streams moonshine serve transcripts into tmux send-keys, enabling voice control over any terminal session. Dictation streams into the active pane by default, while fast-path verbs ("run it", "interrupt", "clear", "new window") execute control commands. We have this tracked as open epic moonshine-go-avc. Pick it up if you want to contribute a flagship sample.
3. Deterministic Hands-Free Control
For industrial inspection, sterile laboratory work, or accessibility tools, a voice command must be provable and repeatable. A probabilistic LLM might hallucinate a safety command or fail under noise. Fast-path intent matching (IntentMatcher or pkg/agentflow) intercepts fixed control verbs in under 1ms before any LLM evaluates the line.
The Latency Advantage
moonshine serve cuts latency at every stage of the cascade:
| Stage | Cloud Speech-to-Speech | Moonshine Local Cascade |
|---|---|---|
| Audio Transport / Ingress | 150ms – 350ms (cloud upload) | 0ms – 5ms (local IPC / loopback WS) |
| STT Time-to-First-Token | 300ms – 600ms | 25ms – 75ms (tiny-streaming) |
| STT Line Finalization | 400ms – 800ms | 30ms – 90ms (LastLatencyMs) |
| Fast-Path Intent Execution | Not available (requires full LLM turn) | < 1ms (deterministic rule / AgentFlow) |
| RAG / Tool Call Round-Trip | 400ms – 1000ms | 10ms – 50ms (StaticRetriever / local RPC) |
| TTS First Audio Chunk | 200ms – 500ms | 15ms – 40ms (local Piper / Web Audio) |
| Total End-to-End Latency | 1,050ms – 2,500ms | 40ms – 120ms (fast-path) / 180ms – 450ms (full LLM) |
Eliminating cloud network round-trips and running fast-path rules before invoking an LLM keeps control actions under 100ms, a speed cloud speech-to-speech cannot reach.
Every Sample Runs in CI
Every example in our samples/ directory is a standalone program tested in CI on every commit (./scripts/verify-samples.sh). A broken sample fails go build or python before anyone reads it.
The sample catalog spans three integration tiers plus in-process native embedding.
Tier 0: Subscribe to Live Transcripts in Any Language
Connecting to moonshine serve's transcript feed requires zero SDKs or custom codegen: just JSON over a WebSocket (ws://localhost:8765/ws) or gRPC (:9090).
samples/go-listen contains ~90 lines of Go with no dependencies on moonshine-go itself. It demonstrates the finalized-once idempotency contract: track processed line IDs from finalized_line_ids so interim frames dropped under backpressure do not cause duplicate output.
// samples/go-listen/main.go
seen := make(map[uint64]bool)
for {
var env envelope
if err := wsjson.Read(ctx, conn, &env); err != nil {
return
}
if env.Kind != "transcript" {
continue
}
var ev transcriptEvent
json.Unmarshal(env.Payload, &ev)
byID := make(map[uint64]line, len(ev.Lines))
for _, l := range ev.Lines {
byID[l.ID] = l
}
for _, id := range ev.FinalizedLineIDs {
if !seen[id] {
seen[id] = true
if l, ok := byID[id]; ok {
fmt.Printf("[FINAL] %s\n", l.Text)
}
}
}
}
samples/python-listen implements the same pattern in ~40 lines of Python with standard websockets.
Tier 2 Flagship: Multi-Turn Voice Agents with pkg/agentflow
For Go developers building structured, multi-turn voice applications, samples/go-cascade-faq is our flagship sample. It runs a local voice agent that answers questions about moonshine-go's mission using local RAG. No network call, no LLM API key.
It uses pkg/agentflow, a Go-native voice agent DSL supporting trigger-phrase matching (ListenFor), global control handlers (Always), multi-turn dialogs (Say, Ask, Confirm, Choose), and fallback guidance (Otherwise):
// samples/go-cascade-faq/main.go
func newAgentFlow(sink serveapi.ActionSink) serveapi.AgentHandler {
flow := agentflow.New()
flow.ActionSink(sink)
// Intercept control commands before FAQ logic runs
flow.Always("stop listening", func(d *agentflow.Dialog) error {
fmt.Println("[agent] heard 'stop listening' -- pausing session")
_, err := d.PauseListening()
return err
})
flow.Always("resume listening", func(d *agentflow.Dialog) error {
fmt.Println("[agent] heard 'resume listening' -- resuming session")
_, err := d.ResumeListening()
return err
})
// FAQ conversation flows using local StaticRetriever
flow.ListenFor("mission", func(d *agentflow.Dialog) error {
results, err := retriever.Retrieve(context.Background(), "mission")
if err != nil || len(results) == 0 {
return nil
}
return d.Say(results[0].Snippet)
})
flow.Otherwise(func(utterance string) {
fmt.Println("[agent] no match -- try: mission, privacy, or 'stop listening'")
})
return agentflow.NewHandlerAdapter(flow)
}
In-Process Mode: AgentFlow can also run inside the daemon via
moonshine serve --agent agentflow --allow-actionswithout a separate process.
Native Embedding: Speech-to-Text as an MCP Tool
When an application does not run a daemon, pkg/moonshine provides in-process STT inference with zero cgo build toolchains.
samples/mcp-transcribe embeds pkg/moonshine inside an official Model Context Protocol (MCP) server. Claude Desktop, Cursor, or any MCP-compatible agent host can call the transcribe tool to process local .wav files in Go memory space:
# Run MCP server over stdio for Claude Desktop / Cursor
go run . -model-dir ./models/tiny-en
Summary of the Sample Catalog
| Sample | Tier | Description |
|---|---|---|
| go-listen | Tier 0 | Real-time transcript feed in Go (~90 lines, zero moonshine-go deps) |
| python-listen | Tier 0 | Python twin of go-listen in ~40 lines |
| grpc-listen | Tier 0 | High-throughput gRPC transcript client via pkg/servepb protobufs |
| browser-listen | Tier 1 | Zero-install browser mic capture via --audio-source remote |
| browser-cascade-faq | Tier 1 | In-browser voice FAQ with Web Audio TTS playback in pure JS |
| go-cascade-faq | Tier 1/2 | Flagship: Offline Go RAG voice FAQ powered by pkg/agentflow |
| python-agent | Tier 1 | Python external agent sending speak / session.pause JSON actions |
| go-bulk-analysis | Tier 2 | Batch transcribes folders at ~50–70x real-time + LLM report pass |
| go-embedded | Native | Direct in-process batch & streaming STT without a daemon |
| mcp-transcribe | Native | Embedded MCP server exposing in-process transcribe tool |
| desktop-app | Native | Native Wails v2 desktop GUI app with in-process STT |
Next Steps
- Read
samples/TUTORIAL.md: Build an Offline Voice Agent in 60 Minutes (4-part guided walkthrough). - Review
samples/GUIDE.mdfor architecture deep-dives and pattern selection matrices. - Check
samples/CONTRIBUTING.mdto add a sample to the repository.