Files
Maven/PROGRESS.md
T
kami bd1e2789eb progress: refresh for SPEC items 1-7; document item-8 deferral
The doc was from the initial commit and predated all open-item work. Update
the works-end-to-end list (protocol doc, away-fallthrough, mavcaldav,
quiet-hours schedule, tools enable/disable, note RAG, passkey step-up), rework
the not-built-yet ranking (at-rest crypto, mavweb/mavcaldav tests, systemd),
and add the rationale for deferring multi-user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 18:49:59 +04:00

12 KiB
Raw Blame History

Maven — current state (2026-07-03)

Consolidated status. The reactive↔proactive core is closed and testable through the web PWA. The SPEC's open items 17 are now landed (protocol doc, away-channel fallthrough, CalDAV poller, quiet-hours schedule, tools enable/disable, note RAG, passkey step-up); item 8 (multi-user) is deliberately deferred — see the tail. Big untouched infra remaining: at-rest security (sqlcipher) and systemd/ container deployment (still scripts + tmux). ~13.1k LOC + ~5.3k test, 202 tests.

Access model

  • Phone → needs the wg tunnel to reach homesrv (no homesrv DNS otherwise; raw IP or a DNS tweak can bypass, not the default).
  • PC → uses homesrv DNS, resolves the domains over local-net, no wg needed.
  • nginx + ufw both scope to 10.42.0.0/24 (wg) + 192.168.1.0/24 (LAN), deny all else.
  • Surface in use now: the web PWA (mavweb). Voice PTT + in-app nudges both ride it.

Works end-to-end (tested)

  • Reactive voice: PWA record → Whisper STT (mavsttd) → ONNX classifier → LFM 2.5-1.2B phraser (llama-server subprocess) → Piper TTS (mavttsd) → reply. HTTP POST path (mobile-Chrome drops WS for the audio).
  • Capture: fact (EN and RU — root-substring recognizers) + reminder persist through CoreAPI (source=tap:voice). This is the substrate the care rules read.
  • Notes / query (semantic recall, sqlite — no chroma): note → embed (the classifier's ONNX embedder) → notes table. query → embed → brute-force cosine top-k → confidence-gated (below queryMinScore 0.55 ⇒ "no note", not a guess). Note RAG (SPEC item 6): the gated top-k feed the phraser (PhraseQuery) to compose a natural answer ("вот что я нашла: …") instead of a verbatim dump; raw-notes fallback on any LLM error. Stub is deterministic.
  • Monitoring (/dash): mavweb server-renders presence + recent nudges (by outcome) + recent facts from the append-only store via CoreAPI. Read-only, meta-refresh, no JS.
  • Proactive loop: 60s dumb ticker, pure predicates over a State snapshot, universal gate (quiet-hours/presence/cooldown/snooze/calendar), one-nudge-per- tick max-severity, reminders (gate-bypassing), sev4 repeat-til-ack, feedback auto-tuner (outcome ratio → bounded cooldown, persisted as source=feedback).
  • Rules: water/meal/break (sev12 care), service_down (sev4, poll:uptimekuma), netdata_critical (sev3, poll:netdata).
  • Env facts (mavpoll): netdata alarms → netdata_alarm (fires immediately on a real CRITICAL); kuma monitor_status → service_down. Writes only on value-change (no append-only churn).
  • Presence: noisy-OR decay + Schmitt hysteresis. Live via page_heartbeat (PWA auto-pings /api/signal every 30s → present when a tab's open).
  • Delivery: ntfy / telegram / voice by f(severity, presence); minimal body on away channels. PWA subscribes to ntfy over WebSocket for in-app nudges.
  • Away-channel fallthrough (SPEC item 2): when the router picks voice but no live session exists at push time (presence guess was wrong), the dispatcher reroutes through the AWAY table — sev3→ntfy, sev4→telegram-repeat-til-ack, sev≤2→drop — instead of silently dropping. Covers nudges + reminders.
  • Calendar busy (SPEC item 3, mavcaldav): new poller queries a self-hosted Radicale CalDAV server on an interval, writes calendar_busy + event facts through CoreAPI (value-change only). The loop gate already consumes calendar_busy.
  • Quiet-hours schedule (SPEC item 4): the gate reads quiet_hours; a config time window (voice.quiet_hours, HH:MM, midnight-crossing handled) now sets it on each tick — in addition to the "тихий режим" voice toggle. Both activate quiet.
  • Client protocol (SPEC item 1): the voice wire format (length-prefixed JSON frames) is published in PROTOCOL.md, generated from internal/voice/wire.go so third-party clients don't need the Go source.
  • Passkey step-up (SPEC item 7): internal/webauthn does real WebAuthn — ES256/P-256 register + assert, ecdsa signature verification, rpIdHash + UP/UV flag binding (UV = the gesture), sign-count regression check. PasskeySession bumps the auth session L2→L3 for a TTL on assert. mavweb serves /auth/passkey (enroll + step-up) + the begin/finish endpoints. Crypto is round-trip tested (incl. tampered-sig / missing-UV / wrong-origin negatives).
  • Stability: llama-server orphan leak fixed (Pdeathsig kills the child on any mavend death); kill-maven.sh reaps strays (matches the model, not a bogus llama-server.*maven pattern); start-maven.sh wires -core + poller.

Wired but needs a deploy action (not code)

  • desk_active (strongest presence signal) — scripts/desk-active.sh runs on the desk PC (hypridle-gated systemd timer), posts over wg to mavweb.
  • Kuma service_down — needs an API key created in Kuma → Settings → API Keys, passed to mavpoll -kuma-key.

Caveats / gotchas:

  • desk_active is a workstation deploy, not code — 0 facts ever written; presence runs on page_heartbeat alone (dash reads "away"/"never at desk"). scripts/desk-active.sh
    • a hypridle-gated maven-desk timer must be installed on the desk PC (not homesrv).
  • Notes recall needs the ONNX embedder — under the HashEmbedder floor, cosine is lexical (token overlap), not semantic; scores are low, so most RU commands sit under the 0.35 route threshold and clarify. Configure voice.embedder for confident recall+routing. (The floor now at least tokenizes Cyrillic — see below — so it ranks correctly, just weakly.)
  • Switching the embedder model silently breaks old notes — different dim ⇒ cosine 0 ⇒ they stop matching; brute-force can't re-embed. Re-embed on a model change.
  • wg_handshake is OFF and should stay off — in this topology the phone only runs wg when outside, so a fresh handshake means AWAY, not here. The mavpoll -wg flag exists (defaults "") and could later back the spec's "away override" by flipping the sign; as a presence-here signal it's inverted. desk_active + page_heartbeat cover home presence.

Not built yet (ranked by ROI)

  1. At-rest encryption — sqlcipher + cold-start unlock. Today the store is plain sqlite: disk access = full read. Passkey step-up now gates actions, but not the data at rest.
  2. cmd/mavweb / cmd/mavcaldav tests — both are untested (0 test files). mavweb now carries the passkey + tools HTTP flow (the webauthn crypto is tested; the handlers and browser JS are not). Highest-value test gap.
  3. Deployment — still start-maven.sh + tmux. systemd user units (one per binary, socket-activated, After=mavend.socket, Restart=on-failure) are specced, not written. Docker/Podman is a later phase.
  4. Multi-user (SPEC item 8) — deliberately deferred, see the tail.

Housekeeping: go.mod isn't tidied — onnxruntime_go and coder/websocket are used but marked // indirect. Passkey credentials are mavweb in-memory (re-enroll after a mavweb restart — acceptable for single-user, by design).

Done since last revision: act tool executor, store-backed, full flow (internal/tool + internal/store/tools.go + tools CoreAPI methods).

  • Execution: IntentAct runs the matched fn against the store's ENABLED allowlist. argv, no shell → STT text can't inject. Live store read, so a newly-enabled tool runs without a daemon restart.
  • proposed→enabled→disabled (SPEC item 5): an act whose verb isn't enabled is scaffolded as a proposed tool (maven suggests). A human enables it (fills argv
    • destructive) on the authed mavweb /tools page — never voice — and can disable it back to proposed (kept in the store, won't run). EnableTool/ DisableTool sit at AuthStepUp; the gate is now live via PasskeySession, so /tools enable requires a passkey assertion at /auth/passkey first.
  • Confirm turn: a destructive enabled tool replies "выполнить X? да/нет" and parks; the next utterance (ru/en yes-no) confirms or cancels (90s TTL).
  • Config: voice.tools seeds enabled tools at boot (editing mavend.json = the human enable act); mavweb enables ad-hoc ones on top.
  • Russian: fixed grammar in reply strings + seed files; maven's self- reference is feminine ("she") — maven-persona-gender.

Also fixed:

  • HashEmbedder was blind to Cyrillic (tokenize iterated bytes, kept only a-z0-9) → every RU utterance embedded to the zero vector → cosine 0 across all intents → misrouted to act (alphabetical tie-break). Now rune-based (unicode.IsLetter). This was the real cause of "Найди заметку" (a query) landing in notes; added note-retrieval query seeds too.
  • Notes are now browsable on /dashRecentNotes plumbed through the store + CoreAPI; voice-captured notes were previously only reachable via semantic query. Earlier: notes/query recall, /dash monitoring, wg_handshake poller (NO-OP).

Future / logged, not now

Personality prompt; custom TTS voice training (kami-picked voice, replaces irina floor); listening modes 23 (meeting-record, ambient-derive).

Services & layout

  • mavend (core, IPC unix socket) — store + loop + phraser; the only key-holder.
  • mavsttd / mavttsd — STT/TTS worker modules (unix sockets).
  • mavweb — PWA bridge (HTTP), /api/ptt voice, /api/signal presence ingest, /api/ntfy WS-subscribe config, /dash read-only monitoring.
  • mavpoll — env poller (netdata/kuma → facts via CoreAPI).
  • mavcaldav — CalDAV poller (Radicale → calendar_busy + events via CoreAPI).
  • All behind wg + nginx deny-all; no phone-home. CGo only in mavsttd.
  • Start/stop: ./start-maven.sh [build], ./kill-maven.sh.
  • Config: ~/.config/maven/mavend.json (or mavend.json in repo root).

Key files

  • cmd/mavend/{main,tick,voice}.go — daemon wiring, loop driver, voice handler
  • internal/loop/{loop,rules,gather,feedback}.go — proactive engine
  • internal/store/ — append-only facts/reminders/nudges/presence/notes
  • cmd/mavweb/{main.go,dash.html} — PWA bridge + /dash monitoring
  • internal/router/{classifier,slots,stage0}.go — reactive routing + slot parse
  • internal/delivery/ — dispatcher + ntfy/telegram/voice sinks
  • internal/auth/ — scope/gate/policy; FloorEnrollment (same-uid = device trust) + webauthn.PasskeySession (real step-up for L3)
  • internal/webauthn/, cmd/mavweb/webauthn.go — passkey register/assert
  • cmd/mavcaldav/, cmd/mavpoll/, scripts/desk-active.sh — env producers

Why multi-user (SPEC item 8) is deferred

Not neglect — the one item where doing nothing now beats doing something:

  • No second user exists yet (the "gf phase"). Building per-user partitioning now means code exercised by zero users and validated by nobody — YAGNI.
  • The append-only schema makes it a migration, not a rewrite. No row is ever mutated, so adding facts/notes/reminders.user_id later is add-columns + backfill-to-"kami" — no reshaping, no dual-write window. Deferral is cheap.
  • The hard part is speaker attribution, and it needs the second voice. A voice-print discriminator (kami vs gf vs unknown) can't be trained or tuned with one voice in the house. Plumbing before the model is pipe with no water.
  • It's fenced deliberately (DO NOT TOUCH THIS PHASE in SPEC.md) so an autonomous agent doesn't add user_id columns while touching the store and commit us to a schema before the constraints that shape it exist.