diff --git a/20-07-2026-BACKLOG.md b/20-07-2026-BACKLOG.md deleted file mode 100644 index 9c82761..0000000 --- a/20-07-2026-BACKLOG.md +++ /dev/null @@ -1,396 +0,0 @@ -beyond the model and tts work, the useful additions are mostly around **reliability, context, and reach**, not more intelligence. - -## highest-value additions - -### 1. unified event intake - -maven should receive normalized events from: - -* praxis -* calendar -* telegram -* local notifications -* system/service health -* manual checklists -* eventually email bridges - -one internal envelope: - -```go -type Event struct { - Source string - Kind string - EntityIDs []string - Title string - Body string - Priority string - OccurredAt time.Time - Payload json.RawMessage -} -``` - -this gives digestion one stable input instead of source-specific logic. - ---- - -### 2. explicit morning routine engine — **core engine done (2026-07-20)** - -`internal/morning` — pure checklist engine, mirrors `internal/loop`/ -`internal/routine`'s no-I/O contract. `Evaluate(routine, facts, now)` answers -"what's still missing" any time (order-independent — checks facts, not -sequence); `Due(routines, facts, last, now)` fires the once-per-day nag only -at `NudgeAt` (defaults to window end) and only when something's unevidenced, -with a `last`-map dedupe identical in shape to `routine.Due`'s cold-start/ -last-fire tracking. Evidence is just a fact timestamped inside today's -window — manual (voice-tapped) and inferred (another daemon writing the same -key) are indistinguishable, satisfying the manual/inferred requirement for -free. Weekday/weekend variants are two `Routine`s with different `Weekdays` -sets under different names. Wired into `config.MorningRoutineConfig` + -`cmd/mavend/tick.go`'s `fireMorningRoutines` (reads only the fact keys the -configured items reference, dispatches through the normal severity/presence -routing table, body is literal joined item labels — not LLM-phrased, same -no-hallucination rationale as cron routines). 13 unit tests in -`internal/morning/morning_test.go`. - -Added since (2026-07-20, same day): a read-only `/morning` page in mavweb — -`ipc.CoreAPI.MorningStatus` (new wire method, mirrors `TickTrace`'s -daemon-cache-only shape: the store adapter errors, `daemonAPI` serves it from -a `tickLoop.morningStatus` closure) returns each routine's active/window/ -per-item done state, server-rendered same as `/trace` (no live-update loop — -checklist state moves on minutes, not seconds). - -Not yet done: no config wired in `deploy/mavend.json` (no morning routines -configured on homesrv yet — add items there when the medicine/water/pets -fact keys the phone/desktop write are settled), no voice query path for -"what did I miss this morning" (Evaluate supports it; nothing calls it yet), -no way to create/edit routines from the web UI — construction still means -hand-editing config, deliberately deferred: routines are operator-declared -config (like cron routines), and a CRUD editor would mean moving them to a -DB table + hot-reload, a bigger change than this pass. - -not ordinary reminders. - -support: - -* required morning items -* order-independent completion -* soft time windows -* skipped-step detection -* one nudge, not repeated spam -* manual and inferred completion evidence -* weekend/weekday variants - -example: - -```text -08:00–11:00 -- medicine -- water -- pets -- check praxis attention -``` - -maven should know what is still missing, not merely fire four timers. - ---- - -### 3. cross-device presence - -**status (2026-07-20):** the hysteresis engine and 3 of the listed signals are -already built and wired live: `internal/store/presence.go` (noisy-OR combiner -+ Schmitt-trigger bucket resolve), fed by `desk_active` (workstation, via -`scripts/desk-active.sh` posting to `/api/signal`), `page_heartbeat` (mavweb -tab, `app.js`), and `wg_handshake` (`mavpoll` polling `wg show`) — threaded -into the tick loop via `internal/loop/gather.go`. Not done: phone-reachable, -homesrv-available, audio-output, and active-maven-client signals from the -list below are still missing. - -a small presence daemon on each trusted device: - -* workstation active/idle -* phone reachable -* homesrv available -* last keyboard/mouse activity -* wireguard presence -* current audio output -* active maven client - -mavend receives only compact state, not raw activity logs. - -useful for: - -* choosing delivery channel -* suppressing voice while away -* surfacing reminders when you return -* knowing whether an agent result should be spoken or sent as text - ---- - -### 4. interruption policy — **done (2026-07-20), turned out to already be built** - -audited the existing code before writing anything new: `internal/loop.Gate` -already answers deliver_now vs. drop (quiet-hours/cooldown/snooze/presence/ -calendar-busy), and `cmd/mavend/tick.go`'s `digestQ` + `config.DigestConfig` -already implement queue/digest (low-severity nudges batch into one -notification, flushed on window elapsed or max-items reached). The four -outcomes below were already covered by these two mechanisms; nothing new to -build for the core policy. - -Gap that *was* real: `deploy/mavend.json` had no `digest` block, so batching -was disabled in prod despite being fully implemented. Fixed — see the config -change alongside this note. - -before delivering anything, evaluate: - -```text -urgency -current activity -quiet hours -recent nudges -available channels -whether already surfaced -``` - -result: - -```text -deliver_now -queue -digest -drop -``` - -this prevents maven from becoming annoying once praxis and other sources start producing more data. - ---- - -### 5. entity-aware memory — **done (2026-07-20)** - -`03fa52d`/`9876187` (Vikunja #279): facts gain `Subject`/`EntityID`/ -`ResolutionState`; an async enrichment worker resolves free-text subjects to -canonical Nexus entity_ids (mirrors Praxis's enrichment pattern). Ambiguous -or unreachable Nexus never guesses — the fact stays `pending` or terminal -`ambiguous`. Voice-tapped facts (`IntentFact`) now flow into the enrichment -queue automatically via an optional `Subject` field on `WriteFactReq` (old -callers unaffected). - -Landed alongside this in the same session (not originally on this list, but -closes the plumbing gaps the last brief flagged for Nexus/Praxis maturity): -a typed Praxis lifecycle client (`398997f` — surface/acknowledge/resolve/ -ignore/pin; fixes the surfaced≠acknowledged gap where reading an item aloud -left no trace), correlation-ID/version headers on the Nexus/Praxis clients -(`b743860`), entity-scoped Praxis attention queries (`0579ef9`), a durable -delivery outbox with begin-before-send/complete-after semantics -(`29f23e3`+`9ff726e` — closes a duplicate-send-on-crash bug), fail-closed -handling on ambiguous IPC mutation outcomes and Nexus/Hexis dependency -errors (`838fde1`+`d9fa4d6`), and a reusable fake-ecosystem test harness -with fault injection (`c932cd8`). - -connect maven memory to nexus ids. - -instead of: - -```text -key = "кошачий фонтан" -``` - -store: - -```text -entity_id = ent_pet_water_fountain -predicate = refilled_at -value = 2026-07-19T... -``` - -benefits: - -* stable russian/english aliases -* fewer duplicate facts -* better “when did i last…” queries -* easier routine detection -* cleaner praxis correlation - ---- - -### 6. bounded follow-up state - -for short continuations: - -* “yes” -* “tomorrow” -* “the second one” -* “not that project” -* “do it later” - -store explicit pending state instead of relying on chat history: - -```go -type PendingInteraction struct { - Kind string - Candidates []string - Args json.RawMessage - ExpiresAt time.Time -} -``` - -this matters a lot for a 1.7b model. - ---- - -### 7. evaluation lab — **skipped for now (2026-07-20)** - -runs on a different machine (GPU box), and CPT is currently in progress -there — deprioritized until the training pipeline has a checkpoint to gate. -Not abandoned, just off the immediate list. - -before every new checkpoint or lora deploy: - -* routing accuracy -* slot accuracy -* malformed json rate -* russian/english mixed input -* ambiguous entity handling -* reminder vs note vs fact -* direct answer vs tool call -* confirmation safety -* phrasing quality -* latency and ram - -also replay real anonymized traces against old and new checkpoints. - -this should be a hard deployment gate. - ---- - -### 8. replayable full-system simulator - -fake: - -* clock -* presence -* caldav -* telegram -* praxis -* nexus -* hexis -* stt -* tts -* llama-server - -scenario: - -```text -08:30 user appears -08:35 medicine not completed -08:40 correx agent waits -08:45 calendar sync stale -08:50 user says “what did i miss?” -``` - -assert: - -* what tools were called -* what was surfaced -* what stayed unresolved -* what maven said -* what was not executed - -this will save more time than another feature daemon. - ---- - -## useful second-wave additions - -### voice session quality - -* barge-in -* interrupt tts on wake word -* partial stt display -* confidence-aware clarification -* retry only failed stt segment -* per-room microphone profiles -* noise-floor calibration -* short response mode when speaking - -### notification bridge framework - -small adapters for: - -* ntfy -* telegram -* matrix -* web push -* android notification forwarding -* local dbus notifications - -normalize into maven/praxis events instead of treating each as a separate feature. - -### local knowledge ingestion - -* markdown/docs ingestion -* git repo summaries -* project decision records -* conversation exports -* provenance and source links -* incremental reindexing - -keep this read-only and separate from personal fact memory. - -### service self-diagnostics - -`maven doctor`: - -* socket reachability -* model health -* stt/tts readiness -* embedder availability -* caldav freshness -* telegram poll state -* praxis/nexus/hexis reachability -* db integrity -* disk usage -* recent failures - -### config and secret management - -* schema-validated config -* config migration -* secret references instead of inline values -* dry-run validation -* redacted config dump -* per-daemon health config -* startup dependency report - ---- - -## things i would not build yet - -* autonomous multi-step planning -* large external reasoner -* generic workflow engine -* self-editing memory -* automatic hexis actions from praxis -* emotion simulation beyond phrasing -* full home-assistant replacement -* more model layers before routing is stable - -## recommended order - -**status as of 2026-07-20:** - -1. ~~evaluation lab~~ — **skipped, GPU-box work, deprioritized while CPT is in progress** -2. ~~entity-aware memory~~ — **done** (`03fa52d`/`9876187`, plus adjacent - Nexus/Praxis plumbing hardening — see item 5 above) -3. ~~morning routine engine~~ — **core engine done** (`internal/morning` + - `cmd/mavend` wiring — see item 2 above; not yet configured on homesrv, - no voice query, no web UI) -4. interruption/delivery policy -5. presence agents -6. unified event intake -7. full-system simulator -8. notification bridges -9. knowledge ingestion -10. voice-session polish - -the main goal should be: **maven reliably knows what is happening, knows what you meant, and chooses the least annoying correct response**. everything else can wait. - diff --git a/PROGRESS.md b/PROGRESS.md deleted file mode 100644 index b3a11af..0000000 --- a/PROGRESS.md +++ /dev/null @@ -1,468 +0,0 @@ -## Maven — current state (updated 2026-07-20) - -### Session 2026-07-20 — ecosystem hardening + entity-aware facts - -Ten commits, focused on closing the Nexus/Praxis integration gaps flagged -as "wired but immature" in the prior review, plus the entity-aware-memory -backlog item (`20-07-2026-BACKLOG.md` item 5). - -- **Entity-aware fact resolution (Vikunja #279)** — facts gain - `Subject`/`EntityID`/`ResolutionState`; an async worker resolves - free-text subjects to canonical Nexus entity_ids (mirrors Praxis's own - enrichment pattern). Ambiguous/unreachable Nexus never guesses — stays - `pending` or terminal `ambiguous`. Voice-tapped facts (`IntentFact`) flow - into the queue automatically via an optional `Subject` field on - `WriteFactReq` (old callers unaffected, no signature break). -- **Typed Praxis lifecycle client (Vikunja #271)** — `GetItem`/`Search`/ - `Surface`/`Acknowledge`/`Resolve`/`Ignore`/`Pin`, routed through new RU/EN - dialogue verbs. Fixes a real lifecycle-invariant bug: reading an - attention item aloud now calls `Surface` — previously the digest path - read items without recording that they'd been surfaced, so "Maven - mentioned it" was indistinguishable from "never came up." -- **Durable delivery outbox (Vikunja #270)** — `BeginDeliveryAttempt` - before `Send`, `CompleteDeliveryAttempt` after; a stale `pending` row - found at startup reconciles to `unknown` (never silently resent or - dropped — same rule as Hexis's execution-timeout handling). Closes a - crash-window duplicate-send bug. Wired into `DispatchNudge`, - `DispatchReminder`, `RepeatUnacked`; reconciliation runs once at boot - before the tick loop resumes. -- **Fail-closed IPC/dependency handling (Vikunja #269, #272/#273)** — - ambiguous mutation outcomes (frame sent, reply lost) no longer blindly - retry; Nexus/Hexis dependency errors fail closed instead of guessing. -- **Correlation IDs + version headers (Vikunja #273)** — the hand-rolled - Nexus/Praxis HTTP clients now send `X-Nexus-Version`/`X-Praxis-Version` - and thread the same correlation ID already generated in - `executeCapability` through the whole call chain, matching the Hexis - client's existing behavior. -- **Entity-scoped Praxis attention queries** — callers holding a resolved - entity_id can ask "what needs attention for this entity" directly - instead of filtering the unscoped list client-side. -- **Fake-ecosystem test harness with fault injection** — a reusable - `fakeServer` (Nexus/Praxis/Hexis fixtures, runtime-toggleable - `SetFault`, fake clock) replacing ad-hoc per-test `httptest` servers; - covers a gap that had zero test coverage (`handlePraxisAct`) and adds a - fault-then-recovery regression test for the fail-closed fixes above. -- **Ops fix** — `deploy/mavend.json`'s phraser was pointed at a 4B model - with `n_gpu_layers=99`, which OOM'd under memory pressure and left a - zombie `llama-server` child; swapped to the 2B Qwen model matching the - intended resident-model size. - -Net effect: the Nexus/Praxis wiring described as "plumbing exists, thin -compared to Maven's test depth" in the prior review is now materially -hardened — typed clients, fail-closed error handling, durable delivery, -and a proper fault-injection test harness are all in place. Evaluation lab -(`20-07-2026-BACKLOG.md` item 7) is explicitly skipped for now — it runs -on the GPU box, which is occupied by CPT. Morning routine engine (backlog -item 3) is next up, not started. - ---- - -> **Resolved 2026-07-30 (task #318).** The resident checkpoint is -> **Qwen3.5-0.8B** (`Q4_K_M`), set in `deploy/mavend.json`; the **target** is -> the locally CPT'd **Qwen3-1.7B**, still training (#122). Older model claims -> below — the LFM references, the pipeline line, and the "swapped to the 2B -> Qwen model" ops entry above — are historical. Read them as a log of what was -> true at the time, not as current fact. Note also that `/mnt/hdd1/llms` is -> bind-mounted over `models/llm/`, so the LFM2.5 gguf in the repo tree is -> never loaded. - -Architecture decision (as written on 2026-07-20): the target resident -router/phraser is the locally trained Qwen3-1.7B model — still the target as -of 2026-07-30. Older LFM references below describe the then-deployed -historical stack, not the target checkpoint. RU CPT has a successful -full-weight checkpoint at step 1000/8077; evaluation and Qwen3 SFT tooling are -tracked in `docs/plans/2026-07-18-qwen3-resident-training-eval.md`. - -Consolidated status. The reactive↔proactive core is closed and testable through -the web PWA. The former SPEC's open items 1–7 (now `docs/design.md` § execution ledger) are 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. -The two big infra gaps from the jul5 revision are closed on `overnight-jul5`: -**at-rest encryption** (AES-256-GCM, tmpfs working copy — not sqlcipher, see -`internal/store/crypt.go`) and **Docker deployment** (one image, six daemon -containers). The `overnight-jul6` session (now on `master`) closed the biggest -*query-surface* gaps — **calendar querying, general-knowledge answers, and -weather** — plus a populated homelab act allowlist and two pure scaffolds -(dialogue state, long-term-memory vector store). ~15.2k LOC + ~8.5k test, 303 -tests, `-race` in `make test`. - -### 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 → - resident phraser (llama-server subprocess; Qwen3.5-0.8B as of 2026-07-30 — - this line historically named "LFM 2.5-1.2B") → 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 (sev1–2 care), service_down (sev4, `poll:uptimekuma`), - netdata_critical (sev3, `poll:netdata`). -- **Routines (`internal/routine`):** operator-declared clockwork — the third - proactive class beside reminders (user-stated) and care rules (world-state). - Config `routines[]` (cron + literal RU body + severity) fire through the normal - dispatcher on schedule (an 08:00 briefing, a 22:00 wind-down). Bodies are - literal (not LLM-phrased ⇒ can't hallucinate); rule name `routine:` so - they don't pollute the care autotuner; cold-start guard seeds on first sight so - a restart never replays a missed schedule. Pure `routine.Due`, unit-tested; the - tick driver holds the last-fired map. -- **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 `docs/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. -- **`mavwaked`** (always-on listening) — needs a systemd user unit on a client - box (desk PC, pi, etc.) where the mic is attached. Connects to mavend over wg - or local net via `-addr`. Deferred until a client box is wired with a mic. - -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. -- **Cold-start unlock tests are missing** — the key wrap/unwrap code - (`internal/webauthn/keywrap.go`) and locked-mode IPC gating (`cmd/mavend/main.go`) - are correct but have **zero test coverage**. The roadmap (item 2.1) required - three new test cases (wrap/unwrap round-trip, wrong-cred unwrap fails, - locked-mode IPC rejects non-unlock methods); none were written. `make test` - is green by omission. Write these before relying on the cold-start path with - real keys. - -### Done since last revision (overnight-jul6, 2026-07-06) - -Seven tasks (session board `SESSION-06-07-2026.md`, deleted 2026-07-30 — see git history), one commit each, merged to `master`. -This session was run through **opencode**, not Claude Code (co-author trailer). - -Since then (**2026-07-06, second session**): - -- **Always-on listening (gap 1, MVP)** — `cmd/mavwaked/`: 825 lines, 10 `-race` - tests. Energy-based VAD over 30ms windows (same RMS threshold as mavsttd's - `gateReason`), adaptive noise floor, speech→silence state machine. Captures - PCM from arecord(1) subprocess, sends `PushToTalk` with `Surface=SurfaceVoice` - (L0 — no destructive acts). Reply plays through aplay(1). No wake word yet - (pure VAD trigger); the 30ms frame shape matches silero-vad ONNX input 1:1, - so swapping energy-threshold for ONNX inference is a local change in vad.go. - `Makefile` `build-waked` target. Runs on client boxes (not docker/homesrv) - via systemd user unit; connects to mavend over wg or local net. - -Since then (**2026-07-06, third session** — roadmap execution agent): - -- **Cold-start unlock (ROADMAP 2.1)** — the at-rest AES key is now wrapped - (HKDF-SHA256 + AES-256-GCM, stdlib-only — no `x/crypto` dep) with the passkey - credential's public key and persisted to disk. At boot, if a wrapped key file - exists AND no env key is set, mavend starts **locked**: the IPC server runs - but `srv.Check` rejects everything except `MethodAssertStepUp` + - `MethodUnlock`. A passkey assertion at `/auth/passkey` calls `MethodUnlock` - with the credential's public key → unwraps the blob → opens the store → wires - voice/loop/delivery → `srv.SetAPI` swaps the locked stub for the real - CoreAPI. mavweb's `RegisterFinish` wraps the env key on enrollment; - `AssertFinish` calls `Unlock` on assertion. Env-key fallback preserved - (dev/CI path unchanged). **Test gap:** the roadmap required three new test - cases (wrap/unwrap round-trip, wrong-cred unwrap fails, locked-mode IPC - rejects non-unlock methods) — none were written. The code is correct but - untested; `make test` is green by omission, not coverage. -- **Conversation depth (ROADMAP 3.2)** — cross-intent anaphora + fact-by-key - lookup. `AnaphoraResolver` in `router/slots.go` detects RU pronouns - (это/он/она/оно/тот/мой + inflected forms). `followUpMerge` now handles - three cases: same-intent slot inheritance (existing), cross-intent anaphora - (Query/Fact/Reminder after a Fact with a pronoun inherits the prior key + - time), and query-after-fact (a query following a fact inherits the key for - fact-by-key lookup). `Session.History []Turn` added as the multi-turn - scaffold (capped at 4). 7 new test cases including the exact done-when - scenarios (anaphora query-after-fact, three-turn break, explicit-key-wins). -- **Routing quality + persona (ROADMAP 4.1/4.4)** — `QueryMinScore` is now a - config knob (`voice.query_min_score`, default 0.55) instead of a hardcoded - const. `make download-embedder` fetches Xenova/paraphrase-multilingual- - MiniLM-L12-v2 (~90MB ONNX) + tokenizer; AGENTS.md documents the embedder + - libonnxruntime setup. `Persona` field in `VoiceConfig` prepends to every - LLM system prompt (nudge phrasing, note queries, general knowledge); empty - = current hardcoded feminine-gendered Russian persona. Also fixed two - pre-existing data races found by `-race`: `voice/server.go` wg.Add vs - wg.Wait (accept mutex), `mavweb/server.go` s.api field (atomic.Value). - -- **Calendar querying (task 3)** — "что у меня завтра?" now answers from the - CalDAV facts the poller already writes. Added `store.CalendarEvents(from,to)`, - a RU date-scope parser («сегодня»/«завтра») in `router/slots.go`, and an - IPC `CalendarEvents` RPC (api/client/server/wire) feeding the `IntentQuery` - handler. Empty day → «на сегодня ничего нет». Previously calendar only *gated* - nudges; it's now queryable. -- **General-knowledge routing (task 4)** — when notes-RAG misses `queryMinScore`, - the query now falls through to the phraser with an anti-hallucination system - prompt (`router.KnowledgePrompt`, single tested source) instead of giving up. - Empty/errored/Stub phraser → «не знаю.», never a fabrication. -- **Weather (task 5)** — new `internal/weather/`: `Provider` interface, a stub - («погода не настроена»), and a real **keyless Open-Meteo** provider (geocode + - current_weather, injectable `*http.Client`, mocked in tests — no live network). - Wired into `IntentQuery` (keywords погода/градус/температура) with a ~5s - context timeout; selected by `voice.weather.provider` ("open-meteo" | "" → stub). -- **Homelab act allowlist (task 2)** — `voice.tools` seeded with read-only acts - (`systemctl status`, `docker ps`, `uptime`, `df`, `free`, `journalctl` reads) - as `destructive:false` and mutating ones (restart/stop/start/reboot, - docker-restart/stop) as `destructive:true`. Guardrail verified: no dangerous - verb is `destructive:false`. RU phrasings seeded in `act.txt`. -- **Embedder config validation (task 1)** — a partially-filled `voice.embedder` - block (some of model/tokenizer/lib paths missing) is now a load error instead - of a silent fall-through to the Hash floor; the floor fallback logs explicitly. -- **Dialogue state scaffold (task 6)** — `internal/dialogue/`: `Session` + - TTL `SessionStore` + pure `InheritSlots`. **Now wired** (post-merge follow-up): - the voice handler carries slots across same-intent turns within a 2-min window - (`followUpMerge`, unit-tested) — bounded gap-filling, not full multi-turn yet. -- **Long-term memory interface (task 7)** — `internal/memory/`: `Store` interface - + `InMemoryStore` (cosine). Wired into `IntentNote` (best-effort insert) and, - post-merge, into `IntentFact` (facts indexed) + `IntentQuery` (read-back after - notes-RAG misses). In-memory only — no persistent backend yet (gap #8). - -Follow-ups (Claude Code, post-merge): gofmt'd `handlers_test.go` (the jul6 -verification commit left it misaligned, so `gofmt -l` still flagged it despite the -"all gates green" claim); deduped the task-4 knowledge prompt to the single tested -`router.KnowledgePrompt()`. Tree is now genuinely green (gofmt/vet/303 tests). - -### Done since the jul5 revision (overnight-jul5, 2026-07-05) - -The overnight session (`SESSION-05-07-2026.md`, deleted 2026-07-30 — see git history; 25 tasks) closed the previous -"not built yet" items 1–3 and added feature depth: - -- **At-rest encryption** — the on-disk db is AES-256-GCM ciphertext; the daemon - works on a tmpfs (RAM) plaintext copy, sealed back atomically on close. Wrong - key / tamper ⇒ fail closed, never a plaintext fallback. Legacy plaintext dbs - upgrade on first clean shutdown. Key via config/env (`db_key_env`); no KDF — - raw 32-byte key, base64. The passkey cold-start unlock plugs into the same - `store.OpenEncrypted` seam later. -- **Docker deployment** — single image, one container per daemon - (`docker-compose.yml`); only mavend mounts the key + db volume; IPC over a - shared socket volume. `ipc.DialWait` (boot-order tolerance) + redial-on-drop - (core restarts don't kill modules). `deploy/README.md` has the runbook. -- **Tests** — mavcaldav, mavttsd, voicesink, mavweb main/handlers covered; - `make test` runs `-race -coverprofile`. -- **Recurring reminders** — `cron` + `next_fire_ts` on reminders; recurring ones - reschedule (instead of mark-fired) after successful delivery. -- **Notification digest/batching** — low-severity nudges queue and flush as one - digest per window/max-items (`digest` config block); stale-reminder bursts on - boot collapse into a single digest reminder, completed only after delivery. -- **Rule trace engine** — `ExplainTick`/`ExplainGate` record per-rule - predicate/gate/selection results each tick; served over IPC (`tick_trace`) - and rendered at mavweb `/trace` ("why didn't she nudge me"). -- **Web UI** — new `/history` (facts + revert buttons), `/notifications` (nudge - history), `/trace` pages; nav links on `/dash`; RU/EN cheatsheet toggle in the - PWA; manifest icons (`icon.svg`). POST `/tools` now requires an in-process - passkey step-up when WebAuthn is configured. -- **Revert/undo** — `RevertFact` voids the latest fact for a key (append-only - void-marker, audit trail intact); exposed at `/api/revert` from `/history`. -- **Tool scopes** — `scope` column on tools, threaded through propose/enable/UI. - `DisableTool` raised to AuthStepUp alongside Enable. -- **Passkey persistence** — mavweb credentials in a JSON file (`-passkey-file`), - surviving restarts; rollback-on-persist-failure keeps memory and disk in sync. -- **STT silence gate** — min-duration + RMS floor drop non-speech before whisper - hallucinates on it (`-min-ms`, `-silence-rms` flags on mavsttd). -- **Housekeeping** — `db_key.env` gitignored (+`.env.example`), `build-caldav` - target, zero-timestamp "never" fix on /dash. - -### Not built yet (ranked by ROI) - -1. **Multi-user (SPEC item 8)** — deliberately deferred, see the tail. - -Closed (jul6 follow-ups): `/api/revert` now sits behind the same passkey -step-up as POST `/tools`; `go.mod` direct deps (`onnxruntime_go`, -`coder/websocket`, `robfig/cron`) are labeled correctly — `go mod tidy` can't -run here because it walks the vendored `deps/go` toolchain tree. -Purge+rotate leaked db key (#12) — investigated and closed: the key was -**never committed** to git history (gitignored at introduction, no commit -ever tracked `deploy/db_key.env`), so nothing to scrub. File stays on disk -and in deploy env by design — at-rest encryption needs it at boot. - -Done earlier (2026-07-03): **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). - -### Gaps — why "voice assistant" is still aspirational (2026-07-06) - -What separates Maven today from the thing the spec describes. Dealbreakers -first — these define the category: - -1. **Always-on listening is code-complete (MVP).** `cmd/mavwaked` captures - PCM from arecord → energy-based VAD → PushToTalk with `Surface=SurfaceVoice` - (L0). Gap narrowed: no wake word yet (pure voice-activity trigger; every - utterance fires). The 30ms frame shape and 16kHz PCM match silero-vad's - ONNX input exactly, so a wake-word model swap is a local change in vad.go. - Hardware: the mic lives on a client box (desk PC, pi, etc.) — never the - homesrv. Deploy action: systemd user unit on whichever box has the mic, - connects to mavend over wg or local net. -2. **Conversation is deeper now, still not full dialogue.** The router - classifies one utterance → one reply, but `internal/dialogue` carries - context across turns: a 2-min session inherits slots for same-intent - follow-ups («напомни завтра» → «…позвонить маме»), and cross-intent - anaphora («запиши что я пил воду» → «когда я это сделал?») now resolves - RU pronouns (это/он/она/оно/тот/мой + inflections) to the prior turn's - key for fact-by-key lookup. `Session.History []Turn` is the scaffold for - real multi-turn. Still missing: LLM-driven dialogue manager (decide - ask-vs-act), anaphora beyond RU pronouns, single-slot session (single-user - box). The sub-1B phraser only words replies. -3. **Latency/shape of a turn.** Clip-based STT (record → upload → whisper → - route → phrase → piper → play). No streaming either direction, no barge-in; - every exchange is a full round trip. - -Capability-class gaps — built but thin: - -4. **Act surface is a small argv allowlist.** propose→enable works and the - allowlist now ships a homelab starter set (jul6 task 2 — status/ps/uptime/ - df/free/logs read-only, restart/stop/reboot gated). Still bounded to what's - seeded; broadening it is config, not code. -5. **Query answers now cover notes + calendar + weather + general knowledge** - (jul6 tasks 3/4/5). Calendar querying, keyless Open-Meteo weather, and a - phraser knowledge-fallback all landed; caveat — general-knowledge quality is - only as good as the sub-1B phraser, and weather needs `voice.weather.provider` - set. The cheatsheet and router are now roughly aligned. -6. **Routing quality depends on the ONNX embedder being configured** — the - HashEmbedder floor makes RU recall lexical/weak; many commands fall to - "clarify". `make download-embedder` now fetches the multilingual MiniLM - model + AGENTS.md documents libonnxruntime setup; `voice.query_min_score` - is a config knob (default 0.55) so the floor can be tuned without recompile. -7. **Presence is effectively one signal** (page_heartbeat); desk_active is - still an undeployed script — "voice when near" routing runs on a guess. -8. **Long-term memory is now persistent (store-backed), not the spec's chroma.** - `internal/memory` has a `Store` interface; the daemon now wires - `store.MemoryStore` (`internal/store/memory.go`) — a **persistent** backend - in the **same encrypted sqlite db** (survives restarts; recall text inherits - at-rest encryption, so no plaintext sidecar). Vectors are float32 blobs, - search is brute-force cosine (fine at single-user scale; ANN is the later - swap behind the same interface). Notes **and facts** are indexed on capture; - `IntentQuery` reads it back (after notes-RAG misses, before general-knowledge) - — fact recall («когда я пил воду?») is its distinct payoff. The in-memory - impl remains the test/no-store floor. Remaining: an ANN/external index is - optional-scale, not a gap. Custom TTS voice (kami-picked, replaces the irina - floor — [[custom-voice-training]]) is still a future item. - -Ops footnote: voice-over-web verified 2026-07-06 — mavend binds 0.0.0.0:9100 -and mavweb reaches it cross-container at mavend:9100 (nc -z confirmed). -mavpoll uses network_mode=host to reach localhost services (netdata, kuma). - -### Future / logged, not now - -Custom TTS voice training (kami-picked voice, replaces irina floor); listening -modes 2–3 (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 `docs/design.md` § Users) 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. diff --git a/cmd/mavend/simulator_test.go b/cmd/mavend/simulator_test.go index f790d43..2d1b295 100644 --- a/cmd/mavend/simulator_test.go +++ b/cmd/mavend/simulator_test.go @@ -1,5 +1,5 @@ // mavend/simulator_test.go — the replayable full-system simulator -// (Vikunja #284, 20-07-2026-BACKLOG.md item 7). +// (Vikunja #284). // // # What it is // diff --git a/maven-feature-ranking.md b/docs/archive/2026-07-03-feature-ranking.md similarity index 96% rename from maven-feature-ranking.md rename to docs/archive/2026-07-03-feature-ranking.md index ec83127..001cf7e 100644 --- a/maven-feature-ranking.md +++ b/docs/archive/2026-07-03-feature-ranking.md @@ -1,5 +1,10 @@ # maven — feature ranking +> **Archived 2026-08-02 (V-447).** The mandatory and easy tiers are now Vikunja tasks +> 449-458. The doable and epic tiers stay here because they are reasoning. Some of them +> exist only to record why something is not worth doing yet. Read this for the why, +> not as a work queue. + > dated 2026-07-03. companion to `docs/design.md` (folded from the former `maven.md`). ranks everything discussed post-repo-state against the infra blockers, not a replacement for the build order. --- diff --git a/docs/design.md b/docs/design.md index d3510c0..a5db622 100644 --- a/docs/design.md +++ b/docs/design.md @@ -672,8 +672,8 @@ Broadening to home automation, media or comms is JSON, not code. ## Execution ledger -Condensed from `ROADMAP.md` (2026-07-06). The live queue is -`20-07-2026-BACKLOG.md`; current state is `PROGRESS.md`. +Condensed from `ROADMAP.md` (2026-07-06). The live queue is the Vikunja board +(project Maven, ID 2); this table is history, not a work list. | # | Item | Prio | Status | |---|------|------|--------| diff --git a/internal/delivery/voicesink/voicesink.go b/internal/delivery/voicesink/voicesink.go index 15b0fe2..4f49e6f 100644 --- a/internal/delivery/voicesink/voicesink.go +++ b/internal/delivery/voicesink/voicesink.go @@ -19,7 +19,7 @@ // 3. if no live session exists, Send returns voice.ErrNoSession // (wrapped). The daemon logs the partial dispatch; an OPEN deferred // question is whether the dispatcher should reroute to away-channels -// instead of returning partial — listed in PROGRESS.md. +// instead of returning partial. // // Import direction: voicesink imports internal/tts (synth seam) and // internal/voice (Sessions registry). Both are siblings of delivery; the diff --git a/internal/event/event.go b/internal/event/event.go index 64a8457..17a7382 100644 --- a/internal/event/event.go +++ b/internal/event/event.go @@ -1,5 +1,4 @@ -// Package event is the unified intake envelope (Vikunja #283, -// 20-07-2026-BACKLOG.md item 1). +// Package event is the unified intake envelope (Vikunja #283). // // # The problem it solves // diff --git a/internal/morning/morning.go b/internal/morning/morning.go index beabb0b..4034bef 100644 --- a/internal/morning/morning.go +++ b/internal/morning/morning.go @@ -1,5 +1,5 @@ // Package morning is maven's morning routine engine — item #3 off the -// 2026-07-20 backlog (see Maven/20-07-2026-BACKLOG.md). +// 2026-07-20 backlog (Vikunja #280). // // A Routine is NOT four independent reminder timers. It's a checklist for a // daily window: several Items, each evidenced by a fact key, completed in