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

185 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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 `/dash`** — `RecentNotes` 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.