Contributor reference: how to run the tests, where the code lives, how to lint and build, and the limits the design accepts on purpose.
Run the test suite (no audio hardware required):
uv run pytest -v
Tests cover:
end_reason = "pause")setup/ensure/teardown, orphan-name cleanup, and clear idempotencychat_capture/triage_chat capture gatingsubstantive_update gate, the periodic periodic_s accumulating-speech trigger, single-outstanding brief-update coalescing, the code-owned append-only FactLedger (dedupe/eviction/render), the stateless brief engine, and the durable brief filetests/test_speaker_enroll.py)DebugSink JSONL lines, disabled-means-None, emit-throw safety, per-day byte cap, concurrent-emit integrity (tests/test_debug.py); assistant lock-free emits via debug_sink_factory (tests/test_assistant_service.py)src/stt_daemon/
├── __init__.py - Package metadata
├── __main__.py - Entry point for `python -m stt_daemon`
├── cli.py - Argument parsing and entry point (`stt-daemon`)
├── ctl.py - One-shot control client (`stt-ctl`, hotkeys)
├── config.py - Config loading (TOML)
├── audio.py - Audio capture (parec, sounddevice)
├── pulse.py - PulseAudio/pactl wrapper (devices, modules, sink checks)
├── provisioner.py - Virtual sink + loopback lifecycle (setup/ensure/teardown)
├── state.py - Provisioning state file (audio.json)
├── picker.py - Interactive device picker (questionary / numbered menu)
├── segmenter.py - VAD + pre-roll buffer + utterance segmentation (core)
├── transcriber.py - Faster-Whisper wrapper
├── speakers.py - Speaker prototype store, matcher, mint/adapt guards
├── embedder.py - sherpa-onnx embedding extractor (lazy import, build_embedder)
├── enroll.py - Offline pre-enrollment from WAV files (`speaker-enroll`)
├── enroll_assist.py - Assisted seed-based enrollment review (`speaker-enroll-seed`)
├── evaluate.py - Labeled-clip accuracy scorer (`speaker-eval`)
├── output.py - Sinks (stdout, JSONL, optional WAV)
├── daemon.py - Orchestrator (threads, queues, event emission)
├── events.py - NDJSON event envelope + codec
├── bus.py - Publish-only event bus (socket, ring replay)
├── debug.py - Structured health-signal log (`DebugSink`, `troubleshoot/debug/YYYY-MM-DD.jsonl`)
├── assistant/
│ ├── modes.py - Mode state machine (idle/command_armed/executing/chat)
│ ├── service.py - Modes, command buffer, readiness, timeouts
│ ├── control.py - Control socket (viewers, commands, broadcast)
│ ├── executor.py - NullExecutor / LlmExecutor (tool loop)
│ ├── tools.py - Tool registry + schema validation + get_datetime
│ ├── llm.py - llama.cpp client, chat session (block trimming), stateless brief engine
│ ├── inference.py - Single-lane LLM queue (chat/command/brief-update)
│ ├── triage.py - Triage analyzer (normalize, flag, ledger prompt/parse) + code-owned FactLedger
│ ├── laya_lane.py - In-process Laya lane (loader + single-flight worker)
│ ├── triage_dump.py - Opt-in per-triage I/O dump (`dump_io`)
│ ├── brief_file.py - Opt-in durable brief snapshot writer (`[brief]`)
│ ├── audit.py - Daily action audit log
│ ├── runner.py - `stt-assistant` process
│ └── __main__.py - Entry point for `python -m stt_daemon.assistant`
└── tui/
├── state.py - Pure view-state reducer
├── client.py - Control-socket client
└── app.py - Textual app + `stt-tui` entry point
tests/ - 1028 tests, no audio hardware or live LLM required
scripts/
├── audio-sink.sh - Thin wrapper over `stt-daemon setup|clear|list`
└── hotkeys/ - sxhkd / xbindkeys examples
uv run ruff check .
# Build sdist + wheel (hatchling backend, installed in an isolated env by uv)
uv build
stt-assistant process adds chat/commands/tools on top, and can be disabled with [llm].enabled = falseparec backend assumes UNIX-like systems with PulseAudio. macOS/Windows should use sounddevicesmall model on CPU