Files
Maven/CLAUDE.md
T
kami 76a6a007ef Pin the resident model to Qwen3.5-0.8B and name Qwen3-1.7B as the target
The most load-bearing decision in the project was stated four incompatible
ways: the docs said Qwen3-1.7B, deploy/mavend.json said Qwen3.5-2B, the repo's
models/llm/ held an LFM2.5-1.2B gguf, and five code comments still said LFM.
Answering "which model is deployed" meant re-deriving it from scratch every
time.

Two facts the review missed, found while resolving it:

- /mnt/hdd1/llms is bind-mounted over /opt/maven/models/llm, which shadows the
  repo's models/llm/. The LFM2.5 gguf sitting there was never loaded by
  anything, so it was not evidence of the deployed model at all.
- That library holds Qwen3.5-0.8B, -2B and -4B, and no Qwen3-1.7B. The config
  pointed at a file that does exist; the docs' Qwen3-1.7B was the stale claim,
  the reverse of the assumed direction. Qwen3-1.7B is the CPT target, and that
  training is still in flight (Vikunja #122), so no such gguf exists yet.

phraser.model_path moves to Qwen3.5-0.8B (Q4_K_M) — the smallest checkpoint on
disk, chosen for latency, and relevant to whether the LLM router is affordable
on this box. Docs and comments now say the same thing in one voice: 0.8B
resident now, CPT'd Qwen3-1.7B as the target, and the bind-mount shadowing
written down so the next reader does not mistake models/llm/ for ground truth.
Comments name the model, never a filename, so a swap stays a one-line config
change.

n_gpu_layers: 99 is correct and stays — compose passes /dev/dri and the render
gid for Vulkan offload to the Vega iGPU. CLAUDE.md's "CPU-only" was the stale
half of that contradiction and is corrected.

phraser.go also dropped a wrong "sub-1b, prompted not trained" size claim: the
target is trained end-to-end (RU CPT + joint persona/router SFT).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X5JApcrCRVGmqrxnhynSik
2026-07-30 23:40:33 +04:00

4.9 KiB

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 small model for routing + phrasing; whisper.cpp STT, piper TTS. Deploy target is a Ryzen laptop (homesrv) with Vulkan offload to the Vega iGPU (n_gpu_layers: 99, compose passes /dev/dri + the render gid) — the resident model stays ≤1.7B either way.

Resident model: currently Qwen3.5-0.8B (Q4_K_M), the smallest checkpoint in the gguf library, picked for CPU/iGPU latency. The target is the locally CPT'd Qwen3-1.7B; that training is still in flight (Vikunja #122), so no such gguf exists yet. Model files live in /mnt/hdd1/llms, bind-mounted to /opt/maven/models/llm — which shadows the repo's models/llm/, so the LFM2.5 gguf sitting there is not loaded by anything. Swapping the resident model is a one-line change to phraser.model_path in deploy/mavend.json.

See REARCH.md for the target architecture, DESIGN.md for the folded design spec, 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:

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):

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":<enum>, 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" "<active-page>"}}). No per-page <style> beyond true one-offs. Wrap every table in <div class=scroll> so wide data pans on a phone. Local preview + headless screenshot recipe is in AGENTS.md.

Vikunja

This repo is project Maven (ID 2) in Vikunja. MCP: http://localhost:9100/mcp (or http://192.168.1.104:9100/mcp from workpc). Feature/bug/deploy tasks go there.