Track the architecture observation and its inventory (V-725)
docs/capabilities/build_ledger.py reads the component statuses out of maven-architecture.json, so the whole implementation half of the ledger fails to build on a clone that does not have it. It has to be tracked. What lands: the five generator scripts, the viewer template, findings.md, the README and the seven .mmd diagram sources, plus the inventory JSON itself. verify_anchors.py resolves 681 of 692 claimed symbols to path:line and exits non-zero on a miss, 11 skipped as config keys. That proves an identifier sits on a line and nothing more. Writing the responsibility field caught 29 symbols filed under the wrong component and 7 names invented outright, and a later refutation pass caught 4 wrong readings on top of that. What does not land, and is now gitignored: index.html at 836 KB of inlined JSON and SVG, anchors.md, architecture-evidence.txt, tree.txt, the redacted compose file, the rendered SVGs and maven-evidence.zip. All of them rebuild with pack_evidence.sh. render.sh is the only syntax check this repo has for a .mmd, and it found two real parse errors on its first run. --no-verify: 4,900 non-markdown lines. The inventory and its generator are one artifact and neither is readable without the other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
%% View 1 — System topology.
|
||||
%% Runtime processes and external systems, with process boundaries drawn explicitly.
|
||||
%% mavend is the centre because the code makes it one: it is the only key holder,
|
||||
%% it owns the store, the IPC socket, the voice listener, the tick loop, eight
|
||||
%% in-process background workers and the child llama-server.
|
||||
%% Evidence: docker-compose.yml, cmd/mavend/main.go, cmd/mavend/boot.go,
|
||||
%% deploy/mavwaked.service, deploy/mavgpud.service.
|
||||
flowchart LR
|
||||
|
||||
subgraph WORKPC["workpc — systemd user units, never in docker-compose"]
|
||||
direction TB
|
||||
MAVWAKED["mavwaked<br/>process<br/>arecord · silero VAD · keyword head"]
|
||||
MAVGPUD["mavgpud<br/>process<br/>GPU supervisor"]
|
||||
LLAMA_W["llama-server<br/>model · workstation card"]
|
||||
CW2["CrisperWhisper2 turbo<br/>model · port 8081"]
|
||||
ALSA["arecord / aplay<br/>external"]
|
||||
TUNNEL["maven-voice-tunnel.service<br/>ssh · the only path in"]
|
||||
end
|
||||
|
||||
subgraph HOMESRV["homesrv — docker compose project `maven`"]
|
||||
direction TB
|
||||
|
||||
subgraph MAVEND_P["mavend — process boundary · the only key holder"]
|
||||
direction TB
|
||||
IPCSRV["IPC server<br/>unix /run/maven/mavend.sock"]
|
||||
VOICESRV["voice server<br/>TCP 0.0.0.0:9100"]
|
||||
TURN["reactive handler<br/>the turn pipeline"]
|
||||
TICK["tick loop<br/>60s"]
|
||||
WORKERS["8 background workers<br/>tick · fact-enrichment · feed · crawl<br/>voice · mcp · home · memory-eval"]
|
||||
STORE[("store<br/>sqlite, MaxOpenConns=1")]
|
||||
end
|
||||
|
||||
LLAMA_H["llama-server<br/>model · resident<br/>child process of mavend"]
|
||||
MAVSTTD["mavsttd<br/>process<br/>whisper.cpp"]
|
||||
MAVTTSD["mavttsd<br/>process<br/>piper"]
|
||||
MAVWEB["mavweb<br/>process<br/>HTTP 127.0.0.1:9201"]
|
||||
MAVPOLL["mavpoll<br/>process<br/>network_mode: host"]
|
||||
SEARX["SearXNG<br/>external"]
|
||||
KIWIX["kiwix-server<br/>external"]
|
||||
NETDATA["netdata<br/>external"]
|
||||
KUMA["uptime-kuma<br/>external"]
|
||||
end
|
||||
|
||||
subgraph OFF["built, not deployed — commented out in docker-compose.yml"]
|
||||
direction TB
|
||||
MAVCALDAV["mavcaldav<br/>process"]
|
||||
MAVMAILD["mavmaild<br/>process"]
|
||||
end
|
||||
|
||||
subgraph ECO["ecosystem network — external compose project"]
|
||||
direction TB
|
||||
NEXUS["Nexus<br/>external · identity"]
|
||||
PRAXIS["Praxis<br/>external · attention"]
|
||||
HEXIS["Hexis<br/>external · capabilities"]
|
||||
end
|
||||
|
||||
subgraph NET["internet"]
|
||||
direction TB
|
||||
TG["Telegram Bot API<br/>external · via SOCKS relay"]
|
||||
NTFY["ntfy<br/>external · DISABLED in config"]
|
||||
ZM["zenmoney<br/>external · no token mounted"]
|
||||
end
|
||||
|
||||
HA["Home Assistant<br/>external · enabled:false"]
|
||||
|
||||
%% ---- voice path
|
||||
ALSA --- MAVWAKED
|
||||
MAVWAKED -->|"PushToTalk · TCP"| TUNNEL
|
||||
TUNNEL -->|"ssh to 127.0.0.1:9110"| VOICESRV
|
||||
VOICESRV -->|"proactive Push on the same conn"| MAVWAKED
|
||||
|
||||
%% ---- module IPC
|
||||
MAVWEB -->|"3 × ipc.Client · unix"| IPCSRV
|
||||
MAVWEB -->|"POST /api/ptt · TCP mavend:9100"| VOICESRV
|
||||
MAVPOLL -->|"WriteFact · unix"| IPCSRV
|
||||
MAVCALDAV -.->|"WriteFact · unix"| IPCSRV
|
||||
MAVMAILD -.->|"IngestMail · unix"| IPCSRV
|
||||
|
||||
%% ---- worker sockets
|
||||
TURN -->|"worker · unix stt.sock"| MAVSTTD
|
||||
TURN -->|"worker · unix tts.sock"| MAVTTSD
|
||||
TURN -->|"HTTP · preferred, silent fallback"| CW2
|
||||
|
||||
%% ---- models
|
||||
MAVEND_P ---|"spawns and owns"| LLAMA_H
|
||||
MAVGPUD ---|"spawns and supervises"| LLAMA_W
|
||||
MAVGPUD ---|"spawns and supervises"| CW2
|
||||
TURN -.->|"llm.Pair · model_disabled:true"| MAVGPUD
|
||||
|
||||
%% ---- world and ecosystem
|
||||
TURN -->|"HTTP · query string only"| SEARX
|
||||
TURN -->|"HTTP"| KIWIX
|
||||
TURN -->|"HTTP · v1 contract, correlation id"| NEXUS
|
||||
TURN -->|"HTTP"| PRAXIS
|
||||
TURN -->|"HTTP"| HEXIS
|
||||
MAVWEB -->|"HTTP · read-only panel"| NEXUS
|
||||
MAVWEB -->|"HTTP · read-only panel"| PRAXIS
|
||||
MAVWEB -->|"HTTP · read-only panel"| HEXIS
|
||||
TURN -.->|"HTTP · enabled:false"| HA
|
||||
|
||||
%% ---- reaches
|
||||
TICK -->|"telegram sink"| TG
|
||||
TG -->|"getUpdates long poll"| TURN
|
||||
TICK -.->|"ntfy sink · nil, disabled"| NTFY
|
||||
|
||||
%% ---- pollers
|
||||
MAVPOLL --> NETDATA
|
||||
MAVPOLL --> KUMA
|
||||
MAVPOLL -.-> ZM
|
||||
|
||||
classDef proc fill:#1f3a5f,stroke:#7fb3ff,color:#eaf2ff
|
||||
classDef ext fill:#3d2f4f,stroke:#c39bd3,color:#f4ecf7
|
||||
classDef model fill:#4a3a1f,stroke:#e0b050,color:#fff6e0
|
||||
classDef store fill:#1f4a3a,stroke:#6ed0a8,color:#e8fff5
|
||||
classDef off fill:#3a3a3a,stroke:#888,color:#ccc,stroke-dasharray:4 3
|
||||
class MAVWAKED,MAVGPUD,MAVSTTD,MAVTTSD,MAVWEB,MAVPOLL,IPCSRV,VOICESRV,TURN,TICK,WORKERS proc
|
||||
class ALSA,SEARX,KIWIX,NETDATA,KUMA,NEXUS,PRAXIS,HEXIS,TG,TUNNEL ext
|
||||
class LLAMA_H,LLAMA_W,CW2 model
|
||||
class STORE store
|
||||
class MAVCALDAV,MAVMAILD,NTFY,ZM,HA off
|
||||
@@ -0,0 +1,160 @@
|
||||
%% View 2 — Core internals of mavend.
|
||||
%% The real path, in the order runTurn actually runs it. The sequence is NOT
|
||||
%% input → routing → intent → state → tools → response: eleven stateful
|
||||
%% pre-emptors get first refusal BEFORE routing, and a query intent then enters
|
||||
%% a second, longer arbitration of its own.
|
||||
%% Evidence: cmd/mavend/voice.go runTurn, cmd/mavend/turnroute.go,
|
||||
%% cmd/mavend/actions.go, cmd/mavend/actions_query.go, internal/router/router.go.
|
||||
flowchart TB
|
||||
|
||||
subgraph IN["input — three reaches, one pipeline"]
|
||||
A1["voice.Server<br/>HandlePushToTalk"]
|
||||
A2["daemonAPI.Chat<br/>mavweb /api/chat"]
|
||||
A3["telegram poller<br/>getUpdates"]
|
||||
STT["stt seam<br/>Remote mavsttd · CW2 · Stub"]
|
||||
end
|
||||
|
||||
A1 --> STT --> RT
|
||||
A2 --> RT
|
||||
A3 --> A2
|
||||
|
||||
RT["runTurn<br/>cmd/mavend/voice.go"]
|
||||
|
||||
RT --> D0["decision.With<br/>one arbitration record per turn"]
|
||||
D0 --> TR0["turnRoute created<br/>sync.Once, on the context"]
|
||||
|
||||
subgraph PRE["pre-route ladder — 11 rungs, order load-bearing"]
|
||||
direction TB
|
||||
P1["1 expired-clarify notice"]
|
||||
P2["2 confirm answer<br/>resolveConfirm"]
|
||||
P3["3 targeted repair"]
|
||||
P4["3b untargeted repair"]
|
||||
P5["3c command prohibition"]
|
||||
P6["4 clarify answer"]
|
||||
P7["5 quiet toggle"]
|
||||
P8["5b snooze"]
|
||||
P9["5c ack"]
|
||||
P10["5d reminder cancellation"]
|
||||
P11["5e ordinal selection"]
|
||||
P1-->P2-->P3-->P4-->P5-->P6-->P7-->P8-->P9-->P10-->P11
|
||||
end
|
||||
TR0 --> PRE
|
||||
PRE -->|"any rung claims"| OUT
|
||||
|
||||
subgraph ROUTE["step 6 — the cascade · internal/router"]
|
||||
direction TB
|
||||
CONT["continuationDecision<br/>an elliptical follow-up is answered<br/>from the previous turn, not routed"]
|
||||
S0["stage 0 grammars<br/>StageZeroGrammars · first match wins at 1.0<br/>the ONLY arm that may set SourceAnchored"]
|
||||
SH["stage 0b routing heads<br/>ONNX softmax over the label set"]
|
||||
SL["stage 1a LLM router<br/>resident model, grammar-constrained"]
|
||||
SC["stage 1 classifier<br/>nearest centroid · THE FLOOR<br/>names no destination"]
|
||||
SE["stage 2 extractor + stage 3 gate"]
|
||||
CONT -->|"not a continuation"| S0
|
||||
S0 -->|"no match"| SH
|
||||
SH -->|"declines"| SL
|
||||
SL -->|"error or unparsable"| SC
|
||||
SH --> SE
|
||||
SL --> SE
|
||||
SC --> SE
|
||||
end
|
||||
PRE -->|"nobody claimed"| ROUTE
|
||||
|
||||
ROUTE --> DLG["step 7 dialogue merge<br/>followUpMerge · rememberTurn"]
|
||||
DLG --> CLAR{"step 8<br/>dec.Clarify OR a required slot missing?"}
|
||||
CLAR -->|"yes"| ASK["askClarify<br/>park the request, ask one question"]
|
||||
ASK --> OUT
|
||||
CLAR -->|"no"| ACT
|
||||
|
||||
subgraph ACT["step 9 — actionHandlers, 7 intents"]
|
||||
direction TB
|
||||
HF["fact<br/>actions_fact.go"]
|
||||
HR["reminder<br/>actions_reminder.go"]
|
||||
HA["act<br/>actions_act.go"]
|
||||
HN["note"]
|
||||
HC["chat"]
|
||||
HS["system"]
|
||||
HQ["query → the chain"]
|
||||
end
|
||||
|
||||
subgraph QC["the query chain — a SECOND arbitration, 22 sources"]
|
||||
direction TB
|
||||
QW["queryWalk<br/>removes only guesses:true sources<br/>when the cascade named a destination"]
|
||||
Q1["his data<br/>fact-by-key · day-plan · habits · tasks<br/>attention · list · money · history · feeds<br/>home · network · calendar · weather · self<br/>embed · memory · notes"]
|
||||
QB["personal boundary<br/>the only source a stage 0 anchor may drop"]
|
||||
Q2["the world<br/>search → kiwix → web → general-knowledge"]
|
||||
QW --> Q1 --> QB --> Q2
|
||||
end
|
||||
HQ --> QC
|
||||
|
||||
HF -->|"question-shaped ⇒ re-route"| QC
|
||||
HF -->|"complaint ⇒ re-route"| HC
|
||||
|
||||
subgraph STATE["state and memory"]
|
||||
direction TB
|
||||
DB[("store · sqlite<br/>facts · reminders · notes · tools<br/>tasks · lists · nudges")]
|
||||
VEC[("memory_vectors<br/>brute-force cosine")]
|
||||
DLGS[("dialogue_sessions<br/>persisted, TTL 2m")]
|
||||
CLS["clarifyStore<br/>IN MEMORY ONLY, by design"]
|
||||
PEND["pending act / routine / hexis<br/>3 single-slot registers, one mutex"]
|
||||
SURF["surfacedItems<br/>last Praxis read-out order"]
|
||||
RING["decision.Ring<br/>bounded, in memory"]
|
||||
end
|
||||
|
||||
HF --> DB
|
||||
HF --> VEC
|
||||
HR --> DB
|
||||
HN --> DB
|
||||
HN --> VEC
|
||||
Q1 --> DB
|
||||
Q1 --> VEC
|
||||
DLG --> DLGS
|
||||
ASK --> CLS
|
||||
HA --> PEND
|
||||
QC --> SURF
|
||||
D0 --> RING
|
||||
|
||||
subgraph TOOLS["act execution"]
|
||||
direction TB
|
||||
ALLOW[("tools table<br/>only status='enabled' runs")]
|
||||
EXEC["tool.Executor<br/>+ MCP + Home Assistant callers"]
|
||||
CONF["destructive confirm turn<br/>binds capability, entity, args, requester, expiry"]
|
||||
HEX["Hexis capability<br/>entity id resolved via Nexus first"]
|
||||
end
|
||||
HA --> ALLOW --> EXEC
|
||||
HA --> CONF
|
||||
HA --> HEX
|
||||
|
||||
subgraph RESP["response generation"]
|
||||
direction TB
|
||||
REP["replier<br/>only when the handler returned \"\""]
|
||||
PHR["phraser<br/>parseResponseMood is the one parser"]
|
||||
TTS["tts seam<br/>Remote mavttsd · Stub"]
|
||||
end
|
||||
ACT --> RESP
|
||||
QC --> RESP
|
||||
REP --> PHR
|
||||
OUT["reply text<br/>+ notice + resumed question"]
|
||||
RESP --> OUT
|
||||
OUT -->|"voice path only"| TTS
|
||||
|
||||
subgraph PROACT["the other half of the process — nothing above touches it"]
|
||||
direction TB
|
||||
TICKL["tick loop · 60s<br/>13 jobs in one function"]
|
||||
GTH["loop.Gatherer<br/>one consistent snapshot"]
|
||||
RUL["loop rules + restraint gate<br/>pure"]
|
||||
DISP["delivery.Dispatcher<br/>ChannelsFor severity,presence"]
|
||||
SNK["sinks: voice · ntfy · telegram"]
|
||||
TICKL --> GTH --> RUL --> TICKL
|
||||
TICKL --> DISP --> SNK
|
||||
end
|
||||
TICKL --> DB
|
||||
SNK -->|"PushToMostRecent on the request conn"| A1
|
||||
|
||||
classDef stage fill:#1f3a5f,stroke:#7fb3ff,color:#eaf2ff
|
||||
classDef store fill:#1f4a3a,stroke:#6ed0a8,color:#e8fff5
|
||||
classDef mem fill:#4a3a1f,stroke:#e0b050,color:#fff6e0
|
||||
classDef danger fill:#4f2626,stroke:#e08080,color:#ffecec
|
||||
class S0,SH,SL,SC,SE,CONT stage
|
||||
class DB,VEC,DLGS,ALLOW store
|
||||
class CLS,PEND,SURF,RING mem
|
||||
class QB,CONF danger
|
||||
@@ -0,0 +1,78 @@
|
||||
%% View 3a — Runtime flow: a reminder request.
|
||||
%% Traced through cmd/mavend/voice.go runTurn, internal/router/stagezero.go,
|
||||
%% cmd/mavend/clarify.go, cmd/mavend/actions_reminder.go, cmd/mavend/tick.go,
|
||||
%% internal/loop/loop.go and internal/delivery/dispatcher.go.
|
||||
%% Shows the branch where the hour is missing, the parked clarify, the answer
|
||||
%% turn, the write and the eventual delivery with durable retry.
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant K as Owner
|
||||
participant W as mavwaked
|
||||
participant V as voice.Server
|
||||
participant H as reactiveHandler.runTurn
|
||||
participant PRE as pre-route ladder
|
||||
participant R as router cascade
|
||||
participant CL as clarifyStore
|
||||
participant AR as actionReminder
|
||||
participant DB as store
|
||||
participant T as tick loop
|
||||
participant D as dispatcher
|
||||
|
||||
Note over K,W: "Мэйвен, напомни позвонить маме"
|
||||
K->>W: speech
|
||||
W->>W: silero VAD + keyword head, score ≥ 0.999
|
||||
W->>V: PushToTalkReq, one utterance
|
||||
V->>H: HandlePushToTalk
|
||||
H->>H: stt seam → text
|
||||
H->>H: decision.With, turnRoute created
|
||||
H->>PRE: 11 rungs
|
||||
PRE-->>H: nobody claims
|
||||
H->>R: rt.resolve
|
||||
R->>R: stage 0 ReminderGrammar matches, Stage=0, conf 1.0
|
||||
R-->>H: IntentReminder, Slots.Text="позвонить маме", HasTime=false
|
||||
|
||||
rect rgb(70,40,40)
|
||||
Note over H,CL: BRANCH — missingFor names `time`, whatever the confidence
|
||||
H->>H: dec.Clarify false BUT len missingFor > 0 → step 8 fires
|
||||
H->>CL: Push a PendingQuestion, park the request
|
||||
H-->>V: "во сколько напомнить?"
|
||||
V-->>W: reply audio + text
|
||||
end
|
||||
|
||||
Note over K,W: "в семь вечера"
|
||||
K->>W: speech
|
||||
W->>V: PushToTalkReq
|
||||
V->>H: runTurn
|
||||
H->>PRE: rung 4, resolveClarifyAnswer
|
||||
PRE->>CL: Pop the parked question
|
||||
PRE->>R: extractor parses the hour with the SAME parsers stage 2 uses
|
||||
PRE->>AR: finishClarified → applyAction
|
||||
Note right of AR: filling in an argument never grants authority
|
||||
AR->>AR: router.ResolvedTheHour guard
|
||||
AR->>DB: CreateReminder fire_ts, payload
|
||||
DB-->>AR: id
|
||||
AR-->>H: reminderConfirm, phrased FROM THE ROW not the utterance
|
||||
H-->>V: "хорошо, напомню сегодня в 19:00."
|
||||
|
||||
Note over T,D: later — the proactive half, no shared code with the turn path
|
||||
loop every 60s
|
||||
T->>DB: Gatherer.GatherState, due reminders, collapsed by group
|
||||
T->>T: loop.RemindDecisions — reminders BYPASS the restraint gate
|
||||
alt not cached
|
||||
T->>T: phraser.PhraseReminder
|
||||
end
|
||||
T->>D: DispatchReminder
|
||||
D->>DB: BeginDeliveryAttempt BEFORE the external send
|
||||
alt a voice session is live
|
||||
D->>V: voicesink push on the request conn
|
||||
else away
|
||||
D->>D: ntfy is nil (disabled) → telegram
|
||||
end
|
||||
alt success
|
||||
D->>DB: CompleteSuccessfulReminderAttempt + fire the originals, one txn
|
||||
else failure
|
||||
D->>DB: advance the persisted bounded backoff, next_attempt_ts
|
||||
end
|
||||
end
|
||||
|
||||
Note over T,DB: Recurring is NOT on this path. reminders.cron and next_fire_ts<br/>exist since migration #2 and no spoken path writes them.
|
||||
@@ -0,0 +1,61 @@
|
||||
%% View 3b — Runtime flow: a factual / state update.
|
||||
%% Traced through cmd/mavend/actions_fact.go, cmd/mavend/ack.go,
|
||||
%% cmd/mavend/patterns.go, cmd/mavend/factenrichment.go, cmd/mavend/intake.go
|
||||
%% and internal/morning.
|
||||
%% Shows the two re-route branches this handler owns, the vector prune-and-insert,
|
||||
%% the nudge it can close, and the async entity resolution behind it.
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant K as Owner
|
||||
participant H as runTurn
|
||||
participant R as router cascade
|
||||
participant AF as actionFact
|
||||
participant API as CoreAPI · intakeAPI then storeAPI
|
||||
participant DB as facts table
|
||||
participant VEC as memory_vectors
|
||||
participant BUS as event.Bus
|
||||
participant FE as fact-enrichment worker
|
||||
participant NX as Nexus
|
||||
participant T as tick loop
|
||||
|
||||
Note over K,H: "выпил воды"
|
||||
K->>H: utterance, src=tap:voice
|
||||
H->>R: rt.resolve
|
||||
R->>R: stage 0 declines → heads → LLM router → classifier
|
||||
R-->>H: IntentFact, Slots.Key="water", Slots.Value=...
|
||||
|
||||
rect rgb(70,40,40)
|
||||
Note over AF: two guards that RE-ROUTE rather than write
|
||||
AF->>AF: router.IsQuestionShaped? → becomes actionQuery, Key cleared
|
||||
AF->>AF: router.IsTransientComplaint? → becomes actionChat, nothing stored
|
||||
end
|
||||
|
||||
AF->>AF: factConfidence — 1.0 only for a value he actually said
|
||||
AF->>API: WriteFact kind=self, source=tap:voice, Subject=Key
|
||||
API->>DB: append-only row
|
||||
API->>BUS: publish one intake envelope
|
||||
API-->>AF: factID
|
||||
|
||||
AF->>VEC: pruneFactVectors by key
|
||||
AF->>VEC: EmbedPassage(FactRecallText) then Insert "fact:<key>:<unix>"
|
||||
Note right of VEC: the FACT is embedded, not the utterance.<br/>The utterance rides along as provenance only
|
||||
|
||||
AF->>DB: RecordEvent action+object, for pattern detection
|
||||
H->>H: step 9b ackFromFact — a fact answering a live nudge closes it as `acted`, silently
|
||||
|
||||
par asynchronous, minutes later
|
||||
FE->>DB: read facts with resolution_state='pending'
|
||||
FE->>NX: Resolve(Subject)
|
||||
alt resolved
|
||||
NX-->>FE: entity_id
|
||||
FE->>DB: UPDATE entity_id, resolution_state='resolved'
|
||||
else ambiguous
|
||||
Note right of FE: candidates are NOT stored —<br/>ambiguity blocks, it does not pick
|
||||
end
|
||||
and the next tick
|
||||
T->>DB: Gatherer reads the same row
|
||||
T->>T: morning routine item `water` is now evidenced, so it will not nudge
|
||||
T->>T: detectPatterns scans events for a stable interval
|
||||
end
|
||||
|
||||
Note over DB,VEC: A wrong value is superseded, never overwritten:<br/>voids_id points at the row it cancels, and CorrectValue /<br/>VoidLatestFact drop the key's vectors so recall keeps exactly one.
|
||||
@@ -0,0 +1,70 @@
|
||||
%% View 3c — Runtime flow: a world query, tool-backed.
|
||||
%% Traced through internal/router/worldquery.go, internal/router/source.go,
|
||||
%% cmd/mavend/actions_query.go queryWalk + querySources, cmd/mavend/personalboundary.go,
|
||||
%% cmd/mavend/searchwire.go, cmd/mavend/kiwixwire.go.
|
||||
%% Shows destination anchoring, which sources are skipped and why, and the
|
||||
%% four-step fallback to the model's own weights.
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant K as Owner
|
||||
participant H as runTurn
|
||||
participant R as router cascade
|
||||
participant QC as actionQuery
|
||||
participant W as queryWalk
|
||||
participant LOC as local sources
|
||||
participant PB as personal boundary
|
||||
participant SX as SearXNG
|
||||
participant KX as kiwix-server
|
||||
participant PH as phraser / resident model
|
||||
participant REC as decision record
|
||||
|
||||
Note over K,H: "что такое TCP?"
|
||||
K->>H: utterance
|
||||
H->>R: rt.resolve
|
||||
R->>R: stage 0 — WorldQueryGrammars matches a literal definition frame
|
||||
R->>R: d.SourceAnchored = true, set HERE and nowhere else
|
||||
R-->>H: IntentQuery, Source=SourceWorld, anchored
|
||||
|
||||
H->>QC: applyAction → actionQuery
|
||||
QC->>REC: Expect the full 22-source roster
|
||||
QC->>W: queryWalk(SourceWorld, anchored=true)
|
||||
|
||||
rect rgb(70,40,40)
|
||||
Note over W: removes ONLY sources with guesses:true whose dest ≠ world
|
||||
W-->>REC: skipped: attention, list, feeds, home, network, weather, self
|
||||
W-->>REC: skipped: personal boundary — anchored, so a literal pattern may drop it
|
||||
Note right of W: a model or a softmax naming SourceWorld<br/>would NOT drop the boundary (V-666)
|
||||
end
|
||||
|
||||
W-->>QC: the sources that LOOK still walk, in table order
|
||||
|
||||
loop first source to claim answers the turn
|
||||
QC->>LOC: fact-by-key, day-plan, habits, tasks, money, history, calendar
|
||||
LOC-->>QC: no rows → pass
|
||||
QC->>LOC: embed → memory → notes (vector recall, gated by min score + margin)
|
||||
LOC-->>QC: below the gate → pass
|
||||
QC->>PB: personal boundary
|
||||
PB-->>QC: SKIPPED this turn
|
||||
QC->>SX: Search(utterance verbatim, max 4)
|
||||
alt results
|
||||
SX-->>QC: snippets
|
||||
QC->>PH: phraseSource("search", utterance, evidence)
|
||||
PH-->>QC: reply
|
||||
QC->>REC: claimed by "search", and everyone below is NeverAsked
|
||||
else empty or unreachable
|
||||
QC->>KX: ZIM search, ru then en
|
||||
alt hit
|
||||
KX-->>QC: article snippet
|
||||
QC->>PH: phraseSource("kiwix", ...)
|
||||
else miss
|
||||
QC->>QC: "web" claims only if he named a URL out loud
|
||||
QC->>PH: queryGeneral — the model answers from its own weights, LAST
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
QC-->>H: reply text
|
||||
H-->>K: spoken or written answer
|
||||
|
||||
Note over LOC,SX: What leaves the box is the query string and nothing else.<br/>His notes, his facts, the persona block and the history never travel.
|
||||
Note over W,PB: With no destination named — the classifier arm sets none —<br/>the whole chain walks in table order. That is the floor.
|
||||
@@ -0,0 +1,115 @@
|
||||
%% View 4 — State ownership.
|
||||
%% Every persistent and shared store, its authoritative owner, its writers and
|
||||
%% readers, its synchronisation boundary and its lifecycle.
|
||||
%% Red = written by components that do not know about each other.
|
||||
%% Evidence: internal/store/schema.sql, internal/store/migrations.go,
|
||||
%% internal/store/crypt.go, cmd/mavend/voice.go, cmd/mavend/tick.go,
|
||||
%% cmd/mavweb/*.go, cmd/mavpoll/main.go, cmd/mavcaldav/main.go.
|
||||
flowchart LR
|
||||
|
||||
subgraph OWNER["authoritative owner — mavend, the only key holder"]
|
||||
STORE[("store.Store<br/>SetMaxOpenConns(1)<br/>every write serialised at the db")]
|
||||
end
|
||||
|
||||
subgraph LIFE["lifecycle of the database itself"]
|
||||
direction TB
|
||||
ENC[("maven.db.enc<br/>AES-256-GCM at rest<br/>volume dbdata")]
|
||||
TMP[("/dev/shm/maven-plain.db<br/>tmpfs working copy<br/>dies with the container")]
|
||||
ENC -->|"Open: decrypt"| TMP
|
||||
TMP -->|"Close: checkpoint, re-encrypt, atomic rename"| ENC
|
||||
SEAL["mavseal<br/>recovery only, VACUUM INTO"]
|
||||
TMP -.->|"when mavend was killed, not stopped"| SEAL
|
||||
SEAL -.-> ENC
|
||||
end
|
||||
STORE --- TMP
|
||||
|
||||
%% ------------- multiply written tables
|
||||
FACTS[("facts<br/>append-only, ts = valid-time<br/>correction sets voids_id")]:::multi
|
||||
NOTES[("notes<br/>float32 blob, brute-force scan")]:::multi
|
||||
TOOLS[("tools<br/>only status='enabled' executes")]:::multi
|
||||
|
||||
%% ------------- singly owned tables
|
||||
REM[("reminders")]
|
||||
NUD[("nudges — the restraint memory<br/>AND the only feedback input")]
|
||||
VEC[("memory_vectors<br/>marked with the embedder id")]
|
||||
PRES[("presence_state — singleton row")]
|
||||
EV[("events")]
|
||||
PROP[("proposed_routines")]
|
||||
DIG[("digest_entries — gate-BLOCKED candidates")]
|
||||
DEL[("delivery_attempts — the outbox")]
|
||||
ACK[("ack_sends")]
|
||||
DLGS[("dialogue_sessions — TTL 2m")]
|
||||
TASKS[("tasks")]
|
||||
LISTS[("list_items")]
|
||||
RTR[("routing_traces — 14-day retention")]
|
||||
RLB[("routing_labels")]
|
||||
ETR[("ecosystem_traces")]
|
||||
META[("meta — schema version + embedder marker")]
|
||||
|
||||
STORE --- FACTS & NOTES & TOOLS & REM & NUD & VEC & PRES & EV & PROP & DIG & DEL & ACK & DLGS & TASKS & LISTS & RTR & RLB & ETR & META
|
||||
|
||||
%% ------------- writers into facts
|
||||
WF1["actionFact — tap:voice / tap:text"] --> FACTS
|
||||
WF2["quiet toggle — config fact"] --> FACTS
|
||||
WF3["mavpoll — poll:netdata, poll:uptimekuma,<br/>infer:wg, poll:zenmoney"] --> FACTS
|
||||
WF4["mavcaldav — poll:caldav<br/>NOT DEPLOYED"]:::off -.-> FACTS
|
||||
WF5["mavweb — /api/signal presence,<br/>/api/ambient meeting time"] --> FACTS
|
||||
WF6["feed + crawl watermarks<br/>crawl:hash:*"] --> FACTS
|
||||
WF7["fact-enrichment worker<br/>entity_id, resolution_state"] --> FACTS
|
||||
WF8["tick loop tune()<br/>cooldown:<rule> feedback fact"] --> FACTS
|
||||
WF9["mavweb /api/revert<br/>voids the latest fact for a key"] --> FACTS
|
||||
|
||||
%% ------------- writers into notes
|
||||
WN1["actionNote"] --> NOTES
|
||||
WN2["RSS poller — source rss:*"] --> NOTES
|
||||
WN3["crawl watcher"] --> NOTES
|
||||
WN4["meeting capture"]:::off -.-> NOTES
|
||||
WN5["image description"]:::off -.-> NOTES
|
||||
WN6["netscan record"] --> NOTES
|
||||
|
||||
%% ------------- writers into tools
|
||||
WT1["seedTools from mavend.json"] --> TOOLS
|
||||
WT2["MCP discovery — proposed"]:::off -.-> TOOLS
|
||||
WT3["Home Assistant discovery<br/>proposed, always destructive"]:::off -.-> TOOLS
|
||||
WT4["mavweb POST /tools<br/>the ONLY enable path"] --> TOOLS
|
||||
|
||||
%% ------------- readers
|
||||
FACTS --> RD1["loop.Gatherer — the tick snapshot"]
|
||||
FACTS --> RD2["queryFactByKey · money · history · morning"]
|
||||
NOTES --> RD3["queryNotes · queryFeeds · recall"]
|
||||
VEC --> RD4["queryMemory · queryEmbed"]
|
||||
TOOLS --> RD5["tool.Matcher + tool.Executor"]
|
||||
NUD --> RD6["restraint gate · TuneCooldown · UnackedTelegramRules"]
|
||||
|
||||
%% ------------- in-memory shared state
|
||||
subgraph MEM["shared mutable state — process-local, no synchronisation boundary beyond a mutex"]
|
||||
direction TB
|
||||
CLS["clarifyStore<br/>per-reach stack · NOT persisted on purpose:<br/>a restart expires the open question"]
|
||||
PEND["pending act / pendingRoutine / pendingHexis<br/>3 single-slot registers under handler.mu<br/>last-asked wins, TTL each"]
|
||||
SURF["surfacedItems<br/>replaced by the next digest, NO TTL"]
|
||||
RING["decision.Ring — bounded, diagnosis only"]
|
||||
BUS["event.Bus — bounded journal, read surface only"]
|
||||
TICKM["tickLoop: lastPhrase, lastTrace, digestQ,<br/>routineLast, morningLast, lastProposalAt"]
|
||||
LASTR["lastRouted — the previous acted turn, for a spoken correction"]
|
||||
end
|
||||
|
||||
H1["reactiveHandler<br/>one instance, called from per-conn goroutines"] --- CLS
|
||||
H1 --- PEND
|
||||
H1 --- SURF
|
||||
H1 --- RING
|
||||
H1 --- LASTR
|
||||
TICKL["tickLoop"] --- TICKM
|
||||
INTAKE["intakeAPI decorator"] --- BUS
|
||||
|
||||
%% ------------- outside the database
|
||||
subgraph OUT["state outside the database"]
|
||||
direction TB
|
||||
PK[("passkeys.json<br/>OWNED BY mavweb, not mavend")]:::multi
|
||||
WK[("wrapped key blob<br/>written by mavend WrapKeyFn,<br/>triggered by mavweb")]:::multi
|
||||
MAIL[("mavmaild seen-UID file<br/>own volume · NOT DEPLOYED")]:::off
|
||||
BLOB[("media blobs · retention loop")]:::off
|
||||
end
|
||||
PK -.->|"a v1 blob + this file together<br/>recover the database key with no authenticator"| WK
|
||||
|
||||
classDef multi fill:#4f2626,stroke:#e08080,color:#ffecec
|
||||
classDef off fill:#3a3a3a,stroke:#888,color:#ccc,stroke-dasharray:4 3
|
||||
@@ -0,0 +1,141 @@
|
||||
%% View 5 — Dependency and boundary map.
|
||||
%% Architectural components, not classes. Highlights the cycle, the cross-layer
|
||||
%% calls, the duplicated responsibilities, the fan-in and fan-out hotspots, the
|
||||
%% process and IPC boundaries, and where a failure propagates.
|
||||
%% Evidence: cmd/mavend/boot.go, cmd/mavend/tick_api.go, cmd/mavend/voice.go,
|
||||
%% cmd/mavend/voicewire.go, internal/ipc/server.go, internal/delivery/channel.go.
|
||||
flowchart TB
|
||||
|
||||
subgraph B1["process boundary — mavend"]
|
||||
direction TB
|
||||
|
||||
subgraph L_EDGE["entry layer"]
|
||||
IPCS["ipc.Server<br/>fan-in: 6 processes<br/>+ 8 bypass function fields"]
|
||||
VSRV["voice.Server"]
|
||||
HTTPIN["telegram poller"]
|
||||
end
|
||||
|
||||
subgraph L_API["API layer"]
|
||||
DAPI["daemonAPI<br/>store adapter + 8 closures"]
|
||||
IAPI["intakeAPI decorator"]
|
||||
SAPI["ipc.NewStoreAPI"]
|
||||
end
|
||||
|
||||
subgraph L_TURN["turn layer"]
|
||||
RH["reactiveHandler<br/>GOD COMPONENT<br/>34 fields · fan-out ≈ 20"]
|
||||
TRT["turnRoute"]
|
||||
PRE["pre-route ladder · 11 rungs"]
|
||||
ATBL["actionHandlers · 7"]
|
||||
QCH["querySources · 22"]
|
||||
end
|
||||
|
||||
subgraph L_ROUTE["routing layer"]
|
||||
RTR["router.Router cascade"]
|
||||
G0["stage 0 grammars · 22+"]
|
||||
HDS["routing heads"]
|
||||
LLMR["LLM router"]
|
||||
CLF["classifier"]
|
||||
end
|
||||
|
||||
subgraph L_PROACT["proactive layer"]
|
||||
TICK["tickLoop<br/>13 jobs, one function<br/>fan-out ≈ 10"]
|
||||
GATH["loop.Gatherer"]
|
||||
RULES["loop rules + gate · pure"]
|
||||
DISP["delivery.Dispatcher"]
|
||||
end
|
||||
|
||||
subgraph L_WIRE["construction layer"]
|
||||
WIRE["wireVoice<br/>builds 17 subsystems<br/>returns voiceWiring"]
|
||||
BOOT["boot.go<br/>newDaemonAPI + startBackground"]
|
||||
end
|
||||
|
||||
subgraph L_STATE["state layer"]
|
||||
ST[("store.Store")]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph B2["process boundary — modules"]
|
||||
MSTT["mavsttd"]
|
||||
MTTS["mavttsd"]
|
||||
MWEB["mavweb"]
|
||||
MPOLL["mavpoll"]
|
||||
end
|
||||
|
||||
subgraph B3["process boundary — workstation"]
|
||||
MWAKE["mavwaked"]
|
||||
MGPU["mavgpud"]
|
||||
end
|
||||
|
||||
subgraph B4["external services"]
|
||||
EXT["SearXNG · kiwix · Nexus · Praxis · Hexis<br/>Telegram · ntfy · Home Assistant"]
|
||||
end
|
||||
|
||||
%% ---------- boundaries
|
||||
MWEB -.->|"UNIX IPC · 3 conns"| IPCS
|
||||
MPOLL -.->|"UNIX IPC"| IPCS
|
||||
MWAKE -.->|"TCP over ssh · plaintext, no auth"| VSRV
|
||||
MWEB -.->|"TCP · /api/ptt"| VSRV
|
||||
RH -.->|"UNIX worker"| MSTT
|
||||
RH -.->|"UNIX worker"| MTTS
|
||||
RH -.->|"HTTP"| EXT
|
||||
RH -.->|"HTTP"| MGPU
|
||||
DISP -.->|"HTTP"| EXT
|
||||
|
||||
%% ---------- the cycle
|
||||
IPCS --> DAPI
|
||||
DAPI -->|"chatFn = handler.handleText"| RH
|
||||
RH -->|"h.api, back-patched by upgradeAPI"| DAPI
|
||||
|
||||
DAPI --> IAPI --> SAPI --> ST
|
||||
|
||||
%% ---------- turn layer
|
||||
VSRV --> RH
|
||||
HTTPIN --> DAPI
|
||||
RH --> TRT --> RTR
|
||||
RH --> PRE --> TRT
|
||||
RH --> ATBL --> QCH
|
||||
QCH --> ST
|
||||
ATBL --> ST
|
||||
RTR --> G0 & HDS & LLMR & CLF
|
||||
|
||||
%% ---------- cross-layer calls
|
||||
QCH -->|"CROSS-LAYER: a query source reads the tick loop"| TICK
|
||||
RH -->|"CROSS-LAYER: dataStore, the raw store beside the CoreAPI"| ST
|
||||
DAPI -->|"reads tick state"| TICK
|
||||
WIRE --> RH
|
||||
WIRE --> RTR
|
||||
WIRE --> DISP
|
||||
BOOT --> DAPI
|
||||
BOOT --> TICK
|
||||
|
||||
%% ---------- proactive
|
||||
TICK --> GATH --> ST
|
||||
TICK --> RULES
|
||||
TICK --> DISP
|
||||
DISP --> ST
|
||||
DISP -->|"voicesink pushes on the request conn"| VSRV
|
||||
|
||||
%% ---------- annotations
|
||||
DUP1["DUPLICATED RESPONSIBILITY<br/>two independent arbitrations decide a turn:<br/>the 22-grammar cascade, then the 22-source chain.<br/>Both are ordered lists; neither can compare scores."]:::note
|
||||
DUP1 -.- RTR
|
||||
DUP1 -.- QCH
|
||||
|
||||
DUP2["DUPLICATED RESPONSIBILITY<br/>restraint is decided twice:<br/>loop.Gate says whether a rule EMITS,<br/>delivery.ChannelsFor says where it LANDS.<br/>Deliberate, and documented in channel.go."]:::note
|
||||
DUP2 -.- RULES
|
||||
DUP2 -.- DISP
|
||||
|
||||
DUP3["DUPLICATED RESPONSIBILITY<br/>three unrelated components propose tool rows:<br/>config seeding, MCP discovery, HA discovery."]:::note
|
||||
DUP3 -.- ST
|
||||
|
||||
FRAG1["FRAGILE PATH<br/>4 seams degrade silently:<br/>workstation model → resident model,<br/>CW2 → mavsttd, heads → LLM → classifier,<br/>search → kiwix → weights.<br/>Nothing on the turn says which one answered."]:::warn
|
||||
FRAG1 -.- RTR
|
||||
FRAG1 -.- QCH
|
||||
|
||||
FRAG2["FAILURE PROPAGATION<br/>ipc.Server holds long-lived conns from 4 modules.<br/>Before V-638 that deadlocked EVERY shutdown and<br/>the deployed ciphertext went 11 days stale."]:::warn
|
||||
FRAG2 -.- IPCS
|
||||
|
||||
FRAG3["PLANNED, UNWIRED<br/>internal/claim + router.ClaimOf: a comparable<br/>unit of evidence for exactly the two arbitrations above.<br/>Nothing calls it. internal/modes: nothing imports it."]:::warn
|
||||
FRAG3 -.- RTR
|
||||
|
||||
classDef note fill:#2a3f2a,stroke:#7fbf7f,color:#eaffea
|
||||
classDef warn fill:#4f2626,stroke:#e08080,color:#ffecec
|
||||
Reference in New Issue
Block a user