Add a read-only /ecosystem page that consumes the sibling services' JSON APIs (Nexus entities, Praxis attention, Hexis capabilities), fetched concurrently with honest per-panel error states. Siblings stay headless — mavweb is their human surface (arch §16). Wired via mavweb -nexus/-praxis/-hexis flags; mavweb joins the ecosystem compose network. Fix mobile horizontal overflow across all pages: .content is a flex child with default min-width:auto, so it refused to shrink below the tables' intrinsic width. min-width:0 lets wide tables pan inside .scroll instead of dragging the page sideways. Verified via CDP geometry check (scrollWidth === clientWidth at 430px). Also includes in-progress Ethos UI redesign, ecosystem deploy compose, and planning docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.2 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 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:
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:
llmrouteris wirednil(aroundvoice.go), so theclassifier.go+embedder.gonearest-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.