From 4f34a232d4ad3c24d80c5aa767065edce27b4ad3 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 2 Aug 2026 09:11:11 +0400 Subject: [PATCH] docs: retire the two root queues, archive the ranking (V-447) PROGRESS.md and 20-07-2026-BACKLOG.md were state snapshots that git log and the Vikunja board already carry. Everything PROGRESS.md claimed as shipped is a QA task. The backlog's only untracked item, bounded follow-up state, is now V-448. maven-feature-ranking.md moves to docs/archive/2026-07-03-feature-ranking.md instead of dying. Its mandatory and easy tiers became V-449 through V-458; the doable and epic tiers are reasoning about why things are not worth doing yet, which no task captures. Four code comments and the design.md ledger pointed at the deleted files. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Xwomr2cT93KMSz9u3digX5 --- 20-07-2026-BACKLOG.md | 396 --------------- PROGRESS.md | 468 ------------------ cmd/mavend/simulator_test.go | 2 +- .../archive/2026-07-03-feature-ranking.md | 5 + docs/design.md | 4 +- internal/delivery/voicesink/voicesink.go | 2 +- internal/event/event.go | 3 +- internal/morning/morning.go | 2 +- 8 files changed, 11 insertions(+), 871 deletions(-) delete mode 100644 20-07-2026-BACKLOG.md delete mode 100644 PROGRESS.md rename maven-feature-ranking.md => docs/archive/2026-07-03-feature-ranking.md (96%) 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