Owner's call. M1 is the turn path, M2 is memory he cannot correct, M3 is step-up. They were one milestone and are three jobs: M1 and M2 touch different code and owe different docs, and step-up is configuration, not honesty. Nine milestones now. Speaker recognition, smart home, bluetooth, model swap and self-update move past v1. Bluetooth was on the v1 list and comes off it: no bluez on the box. Their spec entries keep their DoD. --no-verify: committing on master by the owner's call this session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
26 KiB
Spec: every capability, and what done means for each
Last verified: 2026-08-15 @ 231248a
What Maven is for, capability by capability, with a definition of done that can
be observed on the running box. CLAUDE.md carries the rules. docs/roadmap.md
carries the order. This file carries the target.
The capability list is the union of two sources. The 39 rows measured in
docs/evals/2026-08-13-capability-audit.md, and the 18 items the owner named as
v1 scope. Twelve of the owner's items had no audit row, so this file has 51.
Five entries are marked deferred past v1. They keep their DoD, so it is written
when one comes back.
How to read an entry
State is a reference, never a word. It points at the living doc that holds the current or the desired implementation. Where no such doc exists, the entry says so, and that absence is a finding this file is surfacing on purpose.
DoD is a plain list of observable conditions. Each item is checkable on the running stack. An utterance and its answer, a log line, an outbox row, an HTTP response, or a store row.
Scenario names a file in cmd/mavend/testdata/scenarios/, which scopes the
flow from utterance to output. Five exist today: act_degraded,
assistant_workday, conversation_anaphora, evening_degraded,
morning_missed. A name marked (to write) does not exist yet, and writing it
is part of that capability's work.
Every clock time in this file is Europe/Samara, declared once in
docker-compose.yml with /etc/localtime pointed at it (V-545). Every path
reads time.Local.
What v1 means
v1 is a voice assistant, minimum viable. Voice is the spine and the capability list is the surface. Each DoD below is written at "voice-reachable and honest", not at "feature-complete". Honest means she names the gap when she cannot do the job, and never fills it with a guess.
Seven capabilities in v1 have no design yet. They keep a DoD here, written at what done would look like, and their state entry names the missing doc. The gap stays visible instead of implied-done.
The turn
Route an utterance
- State:
docs/routing.md. - DoD
- Seven intents score separately from the source, on every turn.
- A question about what Maven remembers routes to query, not remember. The audit's two misrouted probes of seven pass.
- Any resident-model error falls through to the classifier and the turn still answers.
- Every turn writes a decision trace naming the stage that decided.
- Scenario:
conversation_anaphora,assistant_workday.
Ask instead of guessing
- State:
docs/routing.md, the clarify head and the parked clarify ride. - DoD
- An incomplete reminder parks and asks for the missing slot, not for all of them.
- "отмена" during a parked clarify drops the parked turn and says so.
- A parked clarify survives an interleaved unrelated turn and resumes.
- Scenario:
conversation_anaphora.
Speak as herself
- State:
docs/language.md. - DoD
- No masculine self-reference reaches the wire. The audit caught "Хорошо,
сохранил" live while
CheckFemininepassed in the eval, so the check runs on the outbound path and not only in the eval. - She says "ты" throughout, and no pet name appears.
- A rejected reply is logged with what failed, not silently rephrased.
- No masculine self-reference reaches the wire. The audit caught "Хорошо,
сохранил" live while
- Scenario:
morning_missedpins the constant.persona_wire(to write) pins model output on the outbound path.
Answer from your own data
- State:
docs/routing.md,queryWalkincmd/mavend/actions_query.go. - DoD
- "что у меня сегодня по плану?" returns the real checklist and its open items.
- The owner's sources are asked before anything outside, every time.
- A source that looks rather than guesses is asked even when a destination is named.
- Scenario:
assistant_workday,morning_missed.
Answer from the world
- State:
docs/world.md. - DoD
- A general-knowledge question returns a Russian summary that does not invent physics. The audit's "почему небо голубое?" answer is the failing case.
- Only the utterance leaves the box. No persona block, no history, no matched notes.
- Deleting the
searchblock turns the capability off with a named gap, not an error.
- Scenario:
world_summary_quality(to write).
Read an encyclopedia
- State:
docs/world.md, the Kiwix section. - DoD
- A Russian question lands on the Russian book and an English one on the English book.
- Kiwix answers when the line is down.
- The retrieved article is on the question's topic, not merely a lexical match.
- Scenario:
kiwix_language_pick(to write).
Weather
- State:
internal/weather. No living doc covers it. Finding: the provider seam, the home city and the clarify path have no written reasoning anywhere. The audit found the capability broken on configuration alone. - DoD
- A
weatherblock indeploy/mavend.jsonnames a provider and a home city. - "какая сейчас погода?" answers for the home city without asking.
- Naming another city answers for that city. The audit's follow-up "Самара" died with "Я тебя не разобрала", so the follow-up parks as a clarify instead of emitting a question through the answer path.
- With no provider configured she names the gap and does not guess a forecast.
- A
- Scenario:
weather_followup(to write).
See an image
- State:
internal/vision, the seam that stores the image and says so. No living doc. Finding: V-667 has the gemma-4 mmproj on the box and no written contract for what a vision call returns. - DoD
- An image sent through Telegram gets a Russian description.
- With no vision model configured she says she cannot look, and the image is stored.
- The vision call goes to the workstation and falls back silently when it is down.
- Scenario:
vision_degraded(to write).
Memory
The four memory rows below have no living doc. Finding: docs/design.md
sketches the store and docs/routing.md covers recall's routing. Nothing owns
the fact and note contracts, the supersede rule, or the digestion worker's
consolidation pass. This is the largest documentation gap in the list.
Facts
- State:
internal/store/facts.go,cmd/mavend/factenrichment.go. No living doc. - DoD
- A stated fact is written and confirmed in his own words, in the feminine.
- A confirmation that says she wrote something is never emitted without the row existing. The audit's "я записала информацию о тебе" wrote nothing.
- A superseding fact retires the old value and both are readable.
actionFact.Subjectresolves through Nexus, never through a local key.
- Scenario:
morning_missed.
Notes
- State:
internal/memory. No living doc. - DoD
- A note captured from any reach is recallable by question.
- A note can be deleted by voice and from the web UI.
/api/revertvoids facts by key and nothing voids a note today (V-494). - A question is not stored as a statement. The audit's "я рассказывал тебе про байкал?" became a junk note.
- Scenario:
note_delete(to write).
Recall
- State:
docs/routing.mdfor the query walk and the personal boundary. - DoD
- The embedder loads at 384 dimensions with the marker check passing, on every start.
EmbedQueryandEmbedPassagecarry their prefixes. A plainEmbedon a note fails the build or the test.- The personal boundary scores a question about him as personal.
TestONNXPersonalBoundaryis green. - A recall miss says she does not remember, and does not answer from the world instead.
- Scenario:
assistant_workday.
Memory evaluation
- State:
internal/memeval. No living doc. Finding: the evaluator ships, writes notes and cannot speak, and nothing records what its conclusions mean (V-248). - DoD
- One evaluation run is observed on the box and its notes are read back.
- What it concluded is checkable against the notes it read.
- Scenario:
memeval_run(to write).
Proactive
Reminders
- State:
internal/store,internal/delivery. No living doc covers the reminder lifecycle. Finding: parking, firing, delivery, retry and cancellation are spread across three packages with no written contract. - DoD
- A one-shot reminder set by voice fires at its time and is delivered.
- Recurring works from speech: meetings, pills, the dog, the vet and the
kibble.
store.RemindercarriesCronandipc.CreateRemindertakes a cron argument, and no caller incmd/mavendpasses one. Recurring is unbuilt with its storage and delivery already finished under it. - A recurring reminder states its schedule back when it is set, and again when asked.
- Cancellation works by voice and from
/reminders, and a refusal is honoured. - A failed delivery retries into another reach rather than looping. The audit watched the ntfy failure run once a minute until 03:05.
- Scenario:
recurring_reminders(to write). Cancellation is covered by the V-719 eval.
Interruption policy
- State:
docs/handler-wiring.mdfor the dispatch decision. Finding: the four presence-and-severity outcomes have never been written down as intended behaviour, only as code (V-281). - DoD
- The four outcomes are named in a doc before any of them changes.
- A severity-1 item with him present at the desk reaches him through some reach.
The audit logged
dropped morning:утро (sev1, presence=present). - Nothing unprompted arrives during a quiet tick.
- Scenario:
morning_missed,evening_degraded.
Digest of held nudges
- State:
internal/worker, the digestion worker. No living doc. - DoD
- A suppressed nudge candidate is observed surfacing in a later digest, on the box.
- The semantic fingerprint is checked before the phraser is paid.
- Digestion never calls Hexis.
- Scenario:
evening_degraded.
Morning routine
- State:
internal/morning,internal/routine. No living doc. - DoD
- The morning plan reaches him inside its 08:00-11:00 window,
Europe/Samara. - When the voice reach has no session the plan falls back to the non-voice reaches rather than being dropped (owner's call; related V-281).
- A missed window is stated as missed, not silently swallowed.
- The morning plan reaches him inside its 08:00-11:00 window,
- Scenario:
morning_missed.
Routine proposals
- State:
internal/routine. No living doc. Finding: the proposer reads a hand-written Russian verb list, which the language rules forbid as a route or fact source (V-606). - DoD
- One proposal is observed on the box from real repeated behaviour.
- The verb list is replaced by
internal/lexiconor the embedder. - A proposal is a nudge he can decline, and declining it is stored.
- Scenario:
routine_proposal(to write).
Tasks
- State:
internal/tasks. No living doc. - DoD
- Open tasks are read back ordered by deadline and urgency.
- A task captured by voice appears in the list and on the web UI.
- Mail-derived candidates are never spoken as tasks until he accepts one (V-130).
- Scenario:
assistant_workday,morning_missed.
RSS and news
- State:
internal/rss. No living doc. - DoD
- Feed items are read when asked and never announced unprompted. That is the intended shape, not a defect.
- A question about a topic finds the matching item across the configured feeds.
- A dead feed names itself as dead, and the others still answer.
- Scenario:
morning_missed.
Reach
Telegram
- State:
docs/deployment.md,internal/delivery/telegramsink. - DoD
- An outbound message is delivered and the outbox row records the delivery.
- His chat is read continuously from restart.
- The socks relay being down names the gap and holds the message.
- Scenario:
evening_degraded.
ntfy
- State:
internal/delivery/ntfysink, disabled in the committed config. - DoD
- A write-scoped
NTFY_TOKENexists before re-enabling. It was switched off after a 403 storm. - A 403 stops retrying instead of looping once a minute.
- A write-scoped
- Scenario:
ntfy_403(to write).
Voice
- State:
docs/protocol.mdfor the wire,internal/delivery/voicesinkfor the sink. Finding: the wire is documented and the listener is not. Nothing describes what holds a live voice session open. - DoD
- A proactive message reaches him by speech without him speaking first. Every proactive message during the audit fell through with "no live voice session".
- The port stays on homesrv loopback and reaches workpc over ssh.
SurfaceVoicecaps acts at L0, and reading is not capped.
- Scenario:
voice_push(to write).
Web UI
- State:
docs/deployment.md. - DoD
- All pages answer 200.
- Every capability with a surface has a page: reminders, notes, tasks, facts.
- A destructive action on a page is gated by step-up.
- Scenario: covered by
cmd/mavwebtests, not by a scenario.
Desk notifications
- State:
cmd/mavweb/ambient.go,internal/event. No living doc. Finding: the inbound direction exists as theambient:notifsource and the outbound direction does not exist at all. Which one the owner means is an open product decision. - DoD
- Inbound: a desktop notification becomes a fact at the ambient path's own confidence, filed low, and never spoken back unprompted.
- Outbound: a nudge can appear on the workpc desktop as a fourth reach, or the outbound half is explicitly dropped from v1.
- Scenario:
morning_missedcovers inbound. Outbound has none.
Speech and senses
Speech to text
- State:
docs/offload.md,docs/deployment.md. - DoD
- Russian speech transcribes accurately enough to route.
mavsttdis the local floor and the workstation transcriber is the better path. - The workstation being down falls back to
mavsttdsilently. - The floor arm is exercised on its own, not only behind the workstation.
- Russian speech transcribes accurately enough to route.
- Scenario: covered by
docs/evals/2026-08-09-crisperwhisper2-russian-wer.md.
Text to speech
- State:
docs/offload.md,docs/deployment.md. - DoD
- A reply is spoken in Russian with correct number and abbreviation expansion.
internal/ttsnormexpands times and dates intoEurope/Samaraphrasing.
- Scenario:
tts_normalisation(to write).
Wake word
- State:
docs/deployment.md,mavwakedunder systemd on workpc. - DoD
- "Мэйвен" wakes her and a near-miss does not.
- Waking her opens a voice session the proactive path can push into. Waking is not the same as having a session (V-515).
- Scenario:
voice_push(to write).
Hearing
- State:
internal/capture,internal/audio. No capture client ships (V-514). - DoD
- A capture client runs on workpc and streams to
mavsttdor the workstation. - The path is reachable end to end from microphone to reply.
- A capture client runs on workpc and streams to
- Scenario:
voice_push(to write).
Speaker recognition
Deferred past v1, owner's call 2026-08-15. See docs/roadmap.md.
- State:
internal/speaker. No living doc (V-255). - DoD
- The owner's voice is distinguished from another voice.
- A voice that is not his cannot reach the act path.
- Scenario:
speaker_gate(to write).
The ecosystem
Nexus
- State:
docs/ecosystem.md. - DoD
- Entities exist. Nexus answers "no entities yet" today, so every act naming a target has nothing to resolve against. Seeding is a Nexus-side job.
- Free text resolves to a canonical entity id before any mutating call.
- Ambiguous resolution asks him and does not pick.
- Nexus down produces a named gap, not a broken turn.
- Scenario:
act_degraded.
Praxis
- State:
docs/ecosystem.md. - DoD
- An item needing attention is read back on request.
- Reading an item aloud calls
Surface, neverAcknowledge. - Attention arrives over HTTP, never from its SQLite file.
- Digestion may summarise Praxis and may not call Hexis.
- Scenario:
morning_missed,evening_degraded.
Hexis
- State:
docs/ecosystem.md. - DoD
- An act runs against a real target once Nexus has entities.
- Confirmation binds capability id, target entity, arguments, requester and expiry.
- LLM output alone never authorizes.
- Every call carries a correlation id, a contract version and
X-Requested-By: maven.
- Scenario:
act_degraded.
Smart home
Deferred past v1, owner's call 2026-08-15. See docs/roadmap.md.
- State:
internal/smarthome, disabled in config (V-256). - DoD
- A device is controlled through Hexis, resolved through Nexus, never by free text.
- Disabled means a named gap.
- Scenario:
act_degraded.
Network scans
- State:
internal/netscan,internal/netaddr. No living doc. - DoD
- A scan of the configured subnets returns hosts and open ports on request.
- The result is read back as prose, not as a table dump.
- The rate limit in the config is honoured.
- Scenario:
netscan_query(to write).
Bluetooth control
Deferred past v1, owner's call 2026-08-15. See docs/roadmap.md.
- State: no package. Finding: nothing exists, and the box has no bluez (V-257). This is the only v1 item blocked on the host rather than on code.
- DoD
- bluez is present on the box that owns the radio.
- A paired device is connected and disconnected by voice, through Hexis.
- Scenario:
bluetooth_control(to write).
MCPs
- State:
internal/mcp. No living doc. Finding: the allowlist, the stdio and http transports and the webfetch door all exist. Nothing records which servers may run, or why. - DoD
- A configured MCP server's tools are callable through the act path.
- The allowlist is the only path to a tool, and a tool outside it is refused.
- A server that dies is a named gap, and the rest of the registry still answers.
- Scenario:
mcp_tool_call(to write).
Operations
The deployed stack
- State:
docs/deployment.md. - DoD
- All five compose services run the current build. The audit found three on a four-day-old image.
mavwakedon workpc is on the current build too, or its drift is stated.- A restart loses nothing.
- Scenario: none. This is checked by
docker compose psand the startup log.
Encrypted database
- State:
docs/deployment.md,docs/caveats/storage.md. - DoD
- The database is encrypted at rest with the working copy in tmpfs.
- The key comes from the environment and is held only by
mavend. - Passwords are read from files, never taken as flag values.
- Scenario: none. Checked by
mavsealand the startup log.
Passkey and step-up
- State:
docs/caveats/security.md,internal/webauthn,internal/auth. - DoD
- WebAuthn is configured, so no step-up gate is fail-open. Today every one is,
including
POST /api/chat, which reaches the act path (V-683). - Enrollment requires an existing credential once the first one exists.
- Step-up is per-request, not process-global.
- WebAuthn is configured, so no step-up gate is fail-open. Today every one is,
including
- Scenario:
stepup_gate(to write).
Model swap
Deferred past v1, owner's call 2026-08-15. See docs/roadmap.md.
- State:
docs/deployment.md(V-250). - DoD
phraser.swap_modelslists the allowed gguf paths, or the page is removed.- A swap survives a restart, or the page states that it will not.
- Scenario: none.
Self-update
Deferred past v1, owner's call 2026-08-15. See docs/roadmap.md.
- State:
cmd/mavupdate,internal/update. Blocked at step 3: it cannot reach the containerized socket (V-477). - DoD
- An update runs to completion from inside the deployment.
- A failed update rolls back and says so.
- Scenario: none.
Tests and analyzers
- State:
docs/qa.md,docs/workflow.md. - DoD
make testis green with-raceandMAVEN_ONNX_LIBset, so the fourTestONNX*measurements run instead of self-skipping.make analyzepasses against its baselines, and a fix deletes its entry.- The one latency test that fails only under coverage is fixed or filed (V-718).
- Scenario: none. This gate is the suite itself.
Undesigned in v1
These seven have no design and no living doc. Each keeps a DoD written at what done would look like, so the gap is visible instead of implied-done. A design pass comes before any of them is built.
Email triage
- State:
internal/email,cmd/mavmaild. Built and not indocker-compose.yml. Finding: the product decision comes first. What she does with his mail is undecided, and deploying the daemon before deciding writes the decision by accident. - DoD
- The owner has decided what triage means: read-only summary, task extraction, or reply drafting.
- Mail-derived task candidates stay candidates until he accepts one (V-130).
- No mail content leaves the box.
mavmaildis in compose, or its absence is deliberate and recorded.
- Scenario:
email_triage(to write).
Calendar management
- State:
internal/calendar,cmd/mavcaldav. Built and not indocker-compose.yml. Same product decision as email. - DoD
- Today's and tomorrow's events are read back, times in
Europe/Samara. - An event is created by voice, with the slot asked for rather than guessed.
- A conflicting event is stated as a conflict.
mavcaldavis in compose, or its absence is deliberate and recorded.
- Today's and tomorrow's events are read back, times in
- Scenario:
calendar_create(to write).
Web crawling
- State:
internal/crawlwithrobots.goandwatch.go,internal/webfetch. No living doc. Finding: politeness and robots are implemented. The scheduling policy is not written anywhere. - DoD
- A page is fetched on request and summarised in Russian.
- A watched page reports what changed, on a schedule he set by voice.
- robots and the politeness delay are honoured, observable in the log.
- Only the URL and the utterance leave the box.
- Scenario:
crawl_watch(to write).
Summaries
- State: no package. Finding: summarisation exists inside the world chain and inside digestion, and nothing owns it as a capability he can ask for.
- DoD
- "перескажи" over a note, a feed item, a page or a mail returns a Russian summary.
- The summary names its source.
- A summary that would invent content is refused, the way
Response.Empty()gates a world answer.
- Scenario:
summarise(to write).
Webhooks
- State: only
internal/delivery/telegramsink/intake.go, which is Telegram's own inbound webhook. Finding: there is no general webhook capability in either direction, and no doc says which direction is wanted. - DoD
- Inbound: an authenticated external event becomes a fact or a nudge candidate.
- Outbound: a nudge can post to a configured URL as a reach.
- Neither direction is reachable without authentication.
- Scenario:
webhook_inbound(to write).
Cron jobs
- State:
internal/routine,cmd/mavend/tick_routines.go. Finding: routines carry aCronand are a separate mechanism from reminders. Whether "cron jobs" means user-defined scheduled acts or the existing routines is undecided. - DoD
- A scheduled job is created by voice, with its schedule stated back.
- It runs on schedule in
Europe/Samaraand its run is recorded. - A job that runs an act is bound by the same confirmation rules as any act.
- Its relationship to routines and to recurring reminders is written down, so three schedulers do not exist.
- Scenario:
cron_job(to write).
Learning the style
- State: no package. Finding: nothing exists beyond
internal/phraser/eval/checks.go, which scores style and does not learn it. Learning means behavioral, not weights: stored outcomes, no adapter, no training set. - DoD
- A corrected phrasing is stored as an outcome and changes a later reply.
- No model weights change and no training set is built.
- What was learned is readable on a page and can be deleted.
- Scenario:
style_correction(to write).
Learning from mistakes
- State: no package. Same behavioral rule as above.
- DoD
- A dismissed nudge, a corrected phrasing and a repaired route are each stored as an outcome.
- A repeated dismissal suppresses that nudge shape.
- A repaired route changes the next routing of the same utterance.
- Every stored outcome is readable and deletable.
- Scenario:
learn_from_dismissal(to write).
Command chaining
- State: no package. The
chainininternal/routeris the world chain and the source chain, not command chaining. Finding: nothing exists. - DoD
- "напомни мне и запиши это" performs both, or asks which one.
- A chain containing an act confirms each act separately.
- A failed step stops the chain and names the step that failed.
- Scenario:
command_chain(to write).
What this file rules out
A definition of done that a test suite can score. The suite was green during the audit and 22 of 39 capabilities were not live. Every criterion above is observable on the running box.
A roadmap ordered by code work. None of the four broken capabilities is a code defect. Weather has no configuration block. Nexus has no data. The voice reach has no listener. Step-up has no WebAuthn credential.
The documentation gaps this file surfaced
Applying the "state is a reference" rule found seventeen capabilities with no
living doc. Memory is the largest cluster. Facts, notes and the digestion worker
have no owning document at all. Proactive is the second. The reminder lifecycle
spans three packages with no written contract. docs/roadmap.md orders the work,
and these gaps are part of it.