# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. Maven is a self-hosted, privacy-first voice assistant (Russian + English). Go daemons talking over unix sockets; one resident Qwen3-1.7B for routing + phrasing; whisper.cpp STT, piper TTS. Deploy target is a CPU-only Ryzen laptop (homesrv) — the resident model stays at 1.7B. See `REARCH.md` for the target architecture and `AGENTS.md` for local-preview + model-download recipes. ## Build & test CGO daemons (`mavend`, `mavsttd`, `mavttsd`, `mavenclient`) need the vendored toolchain and libs wired through the Makefile — **do not** call `go build` on them bare, use `make`: ```sh make build # all 8 binaries make build-web # single daemon (pure-Go ones: web/waked/poll/caldav build without CGO) make test # go test -race across ./internal/... ./cmd/... with CGO env set ``` Run a single test (must carry the CGO env for packages that touch STT/TTS/voice): ```sh CGO_CFLAGS="-I$(pwd)/deps/include -I$(pwd)/deps/whisper.cpp/ggml/include" \ CGO_LDFLAGS="-L$(pwd)/deps/lib -Wl,-rpath,$(pwd)/deps/lib" \ LD_LIBRARY_PATH="$(pwd)/deps/lib" \ deps/go/go/bin/go test -run TestName ./internal/router/ ``` Pure-Go packages (`router`, `memory`, `mavweb`, …) run under a plain `go test ./pkg/`. ## The daemons (`cmd/`) | Binary | Role | |---|---| | `mavend` | **Core.** Router, phraser, memory, reminders, digestion tick. Owns the DB + IPC socket. | | `mavweb` | HTTP UI + PWA (`/dash`, `/history`, `/trace`, `/notifications`, `/tools`); WebAuthn auth. Connects to mavend's socket. | | `mavsttd` | Speech-to-text (whisper.cpp, CGO). | | `mavttsd` | Text-to-speech (piper subprocess). | | `mavwaked` | Wake-word / VAD gate. | | `mavenclient` | Voice loop client (mic → stt → core → tts). | | `mavpoll` | Telegram long-poll reach. | | `mavcaldav` | CalDAV calendar sync. | Daemons are wired socket-to-socket, not linked. `internal/ipc` is the client/server wire protocol; the config in `deploy/mavend.json` (with `${VAR}` env expansion from gitignored `deploy/telegram.env`) sets socket paths, model paths, and the phraser/embedder blocks. ## Routing — read this before touching the router `internal/router/` has TWO layered engines and the committed default is an **interim stopgap, not the intended design** (see memory `routing-architecture-target`): - **Target (REARCH.md):** LLM-as-router. One resident Qwen3-1.7B (`llmrouter.go`) emits GBNF-constrained structured JSON, and the SAME model phrases replies. Embedder is demoted from a routing gate to a RAG hint. - **Current stopgap:** `llmrouter` is wired `nil` (around `voice.go`), so the `classifier.go` + `embedder.go` nearest-neighbour cascade actually runs. It routes by similarity to frozen seed phrases — the known cause of weak RU query handling. Cascade order: `stage0.go` exact-match fast-path → LLM router (when non-nil) → classifier fallback. Any LLM error falls through to the classifier so a turn never breaks on the model. ## LLM output contract All phrasing paths emit `{"response":"...","mood":"..."}` (parsed in `replier_llm.go` and `internal/phraser/llmphraser.go`), with fallback to plain text and the legacy `{"body","summary"}`. Mood is a fixed enum. Router prompt is a separate contract: `[{"intent":, key?, value?, text?, verb?}, ...]`, 7 intents (`fact, reminder, note, query, act, chat, system`). `llm/check_prompt_parity.py` in the training workspace enforces that the Go and relabelling prompts remain identical. ## Non-goals (hard constraints) Never phones home. Not a nag, not autonomous. Maven's persona is **feminine** — Russian self-reference must use feminine forms (the user is male; see memory `maven-persona-gender`). ## Web UI conventions Server-rendered pages share `cmd/mavweb/static/ui.css` (served at `/ui.css`) and the `nav` partial (`navHTML` in `cmd/mavweb/main.go`, `{{template "nav" ""}}`). No per-page `