Always-on speech-to-text daemon plus a separate voice-assistant layer. Python 3.11+ (dev env is
3.13, but requires-python = ">=3.11" and ruff targets py311 — don't use 3.12+ syntax),
uv, src/ layout.
uv sync # install into .venv
uv run pytest -v # full suite (hardware- and network-free)
uv run pytest tests/test_segmenter.py::test_name -v # single test
uv run ruff check . # only static check (no mypy/pyright)
uv build # sdist + wheel (hatchling)
There is no CI, pre-commit, Makefile, or task runner. Run ruff + pytest manually before finishing.
Three processes wired by UNIX sockets under $XDG_RUNTIME_DIR (fallback /tmp):
stt-daemon (cli.py) — capture → pre-roll/VAD → segment → transcribe → stdout + JSONL.
Publish-only on stt-daemon.sock: it never accepts inbound messages; lifecycle is signals only.stt-assistant (assistant/runner.py) — subscribes to that bus, owns all state (modes, command
buffer, chat history, LLM, tools, audit), serves stt-assistant.sock.stt-tui / stt-ctl — clients of the assistant control socket only.Stateful logic belongs in the assistant. Transcription runs on a single worker thread by design (faster-whisper isn't thread-safe) — do not parallelize it.
stt-daemon with no subcommand runs the daemon (legacy --check / --list-sources still work).
config.toml in the current working directory only (no env var, no XDG lookup);
--config overrides. A missing file means dataclass defaults.config.example.toml is the shipped template (values only); the README config section is the
annotated reference. Keep all three in sync when adding a key.load_config() in config.py.STT_DAEMON_STATE / XDG_STATE_HOME (provisioning state at
~/.local/state/stt-daemon/audio.json), XDG_RUNTIME_DIR (sockets), HF_HOME (Whisper cache).config.toml as a source of defaults; it is gitignored user state.tests/ has 220 tests, all hardware- and network-free. Preserve that: inject fakes instead of
touching real pactl, audio devices, or an LLM.Daemon(...) accepts capture_factory, vad_factory, transcriber_factory,
sink_factory; PulseAudio takes an injectable runner; AssistantService takes time_fn,
executor, audit.conftest.py — per-file fakes are the convention. Tests are the behavioral spec; read them
before changing behavior.config.toml, transcripts/, audio/, audit/, .omo/.assistant/tools.py. Model-supplied arguments are JSON-parsed and
schema-validated before running; set side_effect and requires_confirmation for anything
non-read-only..en variants (defaults auto-detect es/en)..omo/plans/ holds local design plans (gitignored), including the assistant-layer spec.