Files
Maven/docs/architecture/build_inventory.py
T
claude bae81b66c8 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>
2026-08-26 12:45:20 +04:00

1235 lines
99 KiB
Python

"""Builds docs/architecture/maven-architecture.json from a hand-verified inventory.
Every entry here was read out of the repository at commit 5cae33a. Nothing is
inferred from a directory name.
"""
import json, os
C = []
def c(id, type, group, responsibility, files, symbols, reads=None, writes=None,
calls=None, called_by=None, confidence="high", status="implemented", notes=""):
C.append(dict(id=id, type=type, group=group, responsibility=responsibility,
files=files, symbols=symbols, reads=reads or [], writes=writes or [],
calls=calls or [], called_by=called_by or [], confidence=confidence,
status=status, notes=notes))
E = []
def e(src, dst, kind, label, confidence="high", status="implemented", evidence=""):
E.append(dict(**{"from": src, "to": dst, "kind": kind, "label": label,
"confidence": confidence, "status": status, "evidence": evidence}))
CFG = "deploy/mavend.json"
# ---------------------------------------------------------------- processes
c("proc.mavend", "process", "homesrv",
"The core daemon and the only holder of the database key. Owns the store, the IPC socket, the voice TCP listener, the tick loop, every in-core background worker and the child llama-server.",
["cmd/mavend/main.go", "cmd/mavend/boot.go", "docker-compose.yml"],
["main", "run", "daemonLock", "startBackground", "backgroundWorkers", "newDaemonAPI", "depsNow"],
reads=["cfg.mavend"], writes=["state.db_file"],
calls=["core.ipc_server", "core.voice_server", "core.tick_loop", "svc.llama_server"],
notes="compose service `mavend`. Two nets: `default` (module DNS) and `ecosystem`. Publishes 127.0.0.1:9110 -> container 9100.")
c("proc.mavsttd", "process", "homesrv",
"Speech-to-text module process. Loads whisper.cpp (ggml-small.bin) through cgo and serves transcription on a unix socket.",
["cmd/mavsttd/main.go", "cmd/mavsttd/whisper_handler.go", "docker-compose.yml"],
["main", "whisperHandler"], called_by=["core.stt_seam"],
notes="Key-free. /run/maven/stt.sock on the shared `sockets` volume. Needs /dev/dri and the render gid for the Vulkan build.")
c("proc.mavttsd", "process", "homesrv",
"Text-to-speech module process. Runs piper with ru_RU-irina-medium and returns wav bytes on a unix socket.",
["cmd/mavttsd/main.go", "cmd/mavttsd/piper_handler.go", "docker-compose.yml"],
["main", "piperHandler"], called_by=["core.tts_seam"], notes="/run/maven/tts.sock.")
c("proc.mavweb", "process", "homesrv",
"The HTTP surface: PWA, dashboards, chat page, tool and routine consoles, WebAuthn enrollment and step-up, presence and ambient ingest.",
["cmd/mavweb/main.go", "cmd/mavweb/pages.go", "cmd/mavweb/chat.go", "cmd/mavweb/ambient.go",
"cmd/mavweb/credentials.go", "cmd/mavweb/models.go", "cmd/mavweb/facts.go", "cmd/mavweb/ecosystem.go",
"cmd/mavweb/notifications.go"],
["main", "corePage", "gatedPage", "handlePTT", "handleChatPage", "handleModels",
"handleSignal", "handleTools", "handleRoutines", "logUnguardedSurfaces"],
calls=["core.ipc_server", "core.voice_server", "ext.nexus", "ext.praxis", "ext.hexis"],
writes=["state.passkey_file"],
notes="Opens THREE ipc.Client connections to mavend (main, /models, /api/chat) because ipc.Client serialises on one mutex. Published on 127.0.0.1:9201 only.")
c("proc.mavpoll", "process", "homesrv",
"Environment poller. Reads netdata alarms, uptime-kuma metrics, wireguard handshakes and optionally zenmoney, writing facts(kind=env, source=poll:*) over IPC. Writes only on value change.",
["cmd/mavpoll/main.go", "internal/zenmoney/"], ["run"],
writes=["state.facts"], calls=["core.ipc_server", "ext.netdata", "ext.uptimekuma", "ext.zenmoney"],
notes="network_mode: host. Holds the third-party credentials so core never sees them; the zenmoney arm is dark because compose mounts no token file.")
c("proc.mavwaked", "process", "workpc",
"Always-on listening client. Runs arecord, silero VAD and the keyword head, ships one utterance per activation over the voice wire, plays the reply through aplay, and receives proactive pushes on the same conn.",
["cmd/mavwaked/main.go", "cmd/mavwaked/vad.go", "cmd/mavwaked/silero.go", "cmd/mavwaked/wakeword.go",
"cmd/mavwaked/wakefeatures.go", "cmd/mavwaked/nudge.go", "cmd/mavwaked/playback.go", "cmd/mavwaked/session.go",
"deploy/mavwaked.service"],
["main", "run"], calls=["core.voice_server"],
notes="systemd USER unit on workpc, never in docker-compose. Requires maven-voice-tunnel.service: it reaches mavend's loopback 9100 over ssh, not the LAN.")
c("proc.mavgpud", "process", "workpc",
"GPU supervisor on the workstation. Keeps llama-server loaded while the card is free, unloads on idle or contention, supervises the CrisperWhisper2 transcriber, and proxies both behind a bearer token.",
["cmd/mavgpud/main.go", "cmd/mavgpud/gpu.go", "cmd/mavgpud/runner.go", "cmd/mavgpud/auth.go",
"deploy/mavgpud.service"],
["main", "config", "requireToken"], called_by=["core.model_seam", "core.stt_seam"],
notes="Deployed separately from every Maven daemon. Maven never asks it to start anything; llm.Pair only reads /health.")
c("proc.mavcaldav", "process", "homesrv",
"CalDAV reader and renderer. Polls a collection into facts(kind=env, source=poll:caldav) and can publish Maven's own reminders back as iCal.",
["cmd/mavcaldav/main.go", "cmd/mavcaldav/render.go", "internal/calendar/"],
["run", "checkRenderTarget"], writes=["state.facts"], calls=["core.ipc_server"],
status="built-not-deployed",
notes="Commented out in docker-compose.yml, and the comment there names the cost: the `calendar` query source and loop.State.CalendarBusy read facts nobody writes.")
c("proc.mavmaild", "process", "homesrv",
"IMAP reader. Fetches unseen messages and hands each to core on ipc.MethodIngestMail; core extracts task candidates with the resident model.",
["cmd/mavmaild/main.go", "internal/email/"], ["run"],
calls=["core.mail_intake"], writes=["state.maildata"], status="built-not-deployed",
notes="Commented out in docker-compose.yml. Reads its password from a file so core never sees it.")
c("proc.mavenclient", "process", "workpc",
"Reference voice client. Ships one wav per invocation over the voice wire and writes the reply wav. No VAD, no keyword.",
["cmd/mavenclient/main.go"], ["main"], calls=["core.voice_server"],
notes="A reference and test binary, not a deployed service.")
c("proc.mavseal", "process", "homesrv",
"Recovery CLI. Re-encrypts a live tmpfs working copy back over the ciphertext file when mavend was killed rather than shut down.",
["cmd/mavseal/main.go"], ["main"], reads=["state.db_tmpfs"], writes=["state.db_file"],
notes="Not part of the daemon. Uses VACUUM INTO, so it is safe against a live database.")
c("proc.mavupdate", "process", "homesrv",
"Deployment CLI with automatic rollback. The only trigger for the update path.",
["cmd/mavupdate/main.go", "internal/update/"], ["main", "update.Updater"],
calls=["core.ipc_server"],
notes="Deliberately has no IPC method and no web button: there is no MethodApplyUpdate in internal/ipc, and mavend never constructs an update.Updater.")
c("proc.e2eprobe", "process", "dev",
"Typed IPC driver written for the 2026-08-15 acceptance session.",
["cmd/e2eprobe/main.go"], ["main"], calls=["core.ipc_server"], status="temporary",
notes="Its own doc comment says it is removed after the session. It is still in the tree.")
c("proc.labelgen", "process", "dev",
"Offline labeller. Runs the real stage 0 grammars over an utterance file and prints JSONL training data for the routing heads.",
["cmd/labelgen/main.go"], ["main"], reads=["router.stage0"],
notes="Omits the wakeword-act grammar, whose allowlist is a deployment's enabled tool names.")
# ---------------------------------------------------------------- external
c("svc.llama_server", "model", "homesrv",
"The resident model. A llama-server child process mavend starts and owns, serving both routing and phrasing.",
["internal/phraser/server.go", "internal/phraser/llmphraser.go", "internal/llm/client.go", CFG],
["phraser.NewLLMPhraser", "phraser.Config", "llm.Client", "llm.Gate"],
called_by=["core.phraser", "core.llm_router", "core.replier"],
notes="deploy/mavend.json currently points model_path at maven-instruct-b2-Q4_K_XL.gguf; the committed value was Qwen3-1.7B-UD-Q4_K_XL. n_ctx 4096, n_gpu_layers 99, cache_ram_mib 512.")
c("ext.searxng", "external", "homesrv",
"Self-hosted metasearch. The first world source, asked after every source reading his own data.",
["cmd/mavend/searchwire.go", "internal/websearch/", CFG],
["wireSearch", "websearch.Client.Search"], called_by=["core.q.search"],
notes="http://searxng:9563. Needs `json` in search.formats. Only the query string leaves the box.")
c("ext.kiwix", "external", "homesrv",
"Offline ZIM encyclopedia server. The fallback behind SearXNG.",
["cmd/mavend/kiwixwire.go", "internal/kiwix/", CFG],
["wireKiwix", "kiwix.Client"], called_by=["core.q.kiwix"],
notes="http://kiwix-server:8080. Books wikipedia_en_all_maxi_2026-02 and wikipedia_ru_all_maxi_2026-02.")
c("ext.nexus", "external", "ecosystem",
"Identity service. Resolves free text to a canonical entity id; ambiguity asks rather than picks.",
["cmd/mavend/ecosystem.go", CFG], ["nexusClient", "nexusClient.Resolve", "wireEcosystem"],
called_by=["core.ecosystem", "core.fact_enrichment", "core.daemon_api"], notes="http://nexus:9740.")
c("ext.praxis", "external", "ecosystem",
"Attention and operational-state service, read over its HTTP tools API and never from its SQLite file.",
["cmd/mavend/ecosystem.go", "cmd/mavend/attentionq.go", CFG],
["praxisClient", "praxisClient.ListAttention", "praxisClient.postItemAction"],
called_by=["core.q.attention", "core.ecosystem_acts"],
notes="http://praxis:8989. Surfaced is not acknowledged; acknowledged is not resolved.")
c("ext.hexis", "external", "ecosystem",
"Capability execution service. The path a mutating act takes when it is not a local allowlisted tool.",
["cmd/mavend/ecosystem.go", "cmd/mavend/ecosystem_acts.go", CFG],
["hexisclient.New", "handleHexisAct", "pendingHexisExec"],
called_by=["core.action_act"],
notes="http://hexis:9741. Free text never reaches a mutating call: an entity id is resolved first.")
c("ext.telegram", "external", "internet",
"Telegram Bot API through a SOCKS relay. Both an away reach and, since V-637, an inbound channel.",
["internal/delivery/telegramsink/telegramsink.go", "internal/delivery/telegramsink/intake.go", CFG],
["telegramsink.New", "telegramsink.NewPoller"],
called_by=["core.sink_telegram", "core.telegram_intake"],
notes="proxy socks5://192.168.240.1:10808, intake: true in the deployed config. Long-poll getUpdates, not a webhook.")
c("ext.ntfy", "external", "internet",
"Push reach. Present in the config and disabled there.",
["internal/delivery/ntfysink/", "cmd/mavend/main.go", CFG], ["ntfysink.New", "wireNtfySink"],
called_by=["core.sink_ntfy"], status="configured-off",
notes='deploy/mavend.json sets ntfy.disabled = true, so wireNtfySink returns a nil Sink and the dispatcher slot is nil.')
c("ext.netdata", "external", "homesrv", "Resource alarms, read by mavpoll.",
["cmd/mavpoll/main.go"], ["run"], called_by=["proc.mavpoll"])
c("ext.uptimekuma", "external", "homesrv",
"Service up/down, read by mavpoll over /metrics with an API key. The source of truth for service_down.",
["cmd/mavpoll/main.go", "internal/loop/rules.go"], ["run", "loop.ServiceDownRule"], called_by=["proc.mavpoll"])
c("ext.zenmoney", "external", "internet", "Spending totals. Dark: compose mounts no token file.",
["internal/zenmoney/", "cmd/mavpoll/main.go"], ["zenmoney.Client"],
called_by=["proc.mavpoll"], status="configured-off")
c("ext.homeassistant", "external", "lan",
"The house. Discovery proposes one always-destructive tool row per controllable device.",
["internal/smarthome/", "cmd/mavend/smarthome.go", CFG], ["wireSmartHome", "smarthome.Client", "homeWiring"],
called_by=["core.home_worker", "core.q.home"], status="configured-off",
notes='deploy/mavend.json smarthome.enabled = false.')
c("ext.openmeteo", "external", "internet", "Weather provider.",
["internal/weather/", "cmd/mavend/voicewire.go"], ["weather.NewOpenMeteoProvider", "weather.NewStubProvider"],
called_by=["core.q.weather"], status="configured-off",
notes="wireVoice picks it only when cfg.Voice.Weather.Provider == 'open-meteo'. The deployed voice block has no `weather` key, so the Stub provider is wired and the weather query source answers from a stub.")
c("ext.vikunja_mcp", "external", "lan",
"MCP server whose tools are PROPOSED into the same act allowlist as everything else.",
["internal/mcp/", "cmd/mavend/mcp.go", CFG], ["wireMCP", "mcp.Manager", "mcpWiring"],
called_by=["core.mcp_worker"], status="configured-off", notes="mcp.servers[0].enabled = false.")
c("ext.cw2_stt", "external", "workpc",
"CrisperWhisper2 turbo on the workstation, supervised by mavgpud on port 8081. A second service, not a second endpoint.",
["cmd/mavgpud/main.go", "internal/stt/", "cmd/mavend/voicewire.go", "deploy/cw2/", CFG], ["stt.Pair", "sttSeam"],
called_by=["core.stt_seam"], notes="Silent fallback to mavsttd when the workstation is down; a worse transcript is still a turn.")
c("ext.piper", "external", "homesrv", "The TTS binary mavttsd runs.",
["cmd/mavttsd/piper_handler.go"], ["piperHandler"], called_by=["proc.mavttsd"])
c("ext.whispercpp", "external", "homesrv", "whisper.cpp, linked into mavsttd through cgo.",
["cmd/mavsttd/whisper_handler.go"], ["whisperHandler"], called_by=["proc.mavsttd"])
c("ext.alsa", "external", "workpc", "arecord and aplay, spawned by mavwaked; the mic is a named ALSA plug device.",
["cmd/mavwaked/main.go", "deploy/asoundrc", "deploy/mavwaked.service"], ["run"], called_by=["proc.mavwaked"])
# ---------------------------------------------------------------- boundaries
c("bnd.ipc", "boundary", "homesrv",
"The core-to-module boundary. Length-prefixed JSON over a unix domain socket; 0700 dir and 0600 socket are the auth floor. Core mediates and never hands back a db handle.",
["internal/ipc/frame.go", "internal/ipc/wire.go", "internal/ipc/server.go", "internal/ipc/client.go"],
["writeFrame", "readFrame", "maxFrame", "Method", "ipc.Server", "ipc.Client", "ipc.DialWait"],
called_by=["proc.mavweb", "proc.mavpoll", "proc.mavcaldav", "proc.mavmaild", "proc.mavupdate", "proc.e2eprobe"],
notes="Hand-rolled framing, kept deliberately (Vikunja #410). 64 method constants in internal/ipc/wire.go, 63 plus Ping.")
c("bnd.voice_tcp", "boundary", "homesrv",
"The client-to-core network surface. Plaintext TCP with no auth of its own; every conn registers a Session and carries both requests and server-initiated pushes.",
["internal/voice/server.go", "internal/voice/wire.go", "internal/voice/session.go", "docker-compose.yml"],
["voice.Server", "voice.Sessions", "PushToTalkReq", "PushToTalkResp", "Sessions.PushToMostRecent"],
called_by=["proc.mavwaked", "proc.mavenclient", "proc.mavweb"],
notes="Bound 0.0.0.0:9100 inside the container so mavweb can reach it by name, published only on 127.0.0.1:9110. workpc reaches it over ssh. TWO representations of reach, both ignored: server.go:198 only DEFAULTS an empty p.Surface, so a client-asserted SurfaceVoice survives and HandlePushToTalk never reads it; server.go:148 hardcodes Session.Surface to SurfacePCClient for every conn. req.Surface is request payload on an unauthenticated wire, so any client can claim pc_client. It must not become an authorization input as it stands.")
c("bnd.worker", "boundary", "homesrv",
"The core-to-stt/tts boundary. One Client, one conn, one mutex; the Transcriber and Synthesizer interfaces are the Stub/Remote swap seam.",
["internal/worker/client.go", "internal/worker/server.go", "internal/worker/wire.go",
"internal/stt/stt.go", "internal/tts/tts.go"],
["worker.Client", "worker.Dial", "stt.NewRemote", "tts.NewRemote"],
called_by=["core.stt_seam", "core.tts_seam"])
c("bnd.http_web", "boundary", "homesrv",
"The mavweb HTTP surface. Loopback-only by construction; step-up exists only when -webauthn-origin and -webauthn-rpid are set.",
["cmd/mavweb/main.go"], ["mux", "logUnguardedSurfaces", "mavwebHTTPServer", "requireStepUp"],
notes="Six surfaces are named as unguarded without WebAuthn: POST /tools, /routines, /models, /api/revert, /api/chat, /api/ptt.")
c("bnd.http_ecosystem", "boundary", "ecosystem",
"The Maven-to-ecosystem HTTP boundary. Every request carries a contract version header, X-Requested-By: maven and a correlation id minted once per action.",
["cmd/mavend/ecosystem.go"],
["ecosystemHTTP", "setHeaders", "withCorrelationID", "ecosystemAPIVersion", "mavenRequester", "ecosystemError"])
# ---------------------------------------------------------------- core: entry
c("core.ipc_server", "service", "mavend",
"The IPC listener. Accepts module connections, runs the one Check authorization hook, then dispatches to CoreAPI or to one of the bypass function fields.",
["internal/ipc/server.go", "cmd/mavend/main.go"],
["ipc.Listen", "Server.Serve", "Server.Check", "Server.SetAPI", "Server.StepUp", "Server.UnlockFn",
"Server.WrapKeyFn", "Server.IngestMailFn", "Server.SwapModelFn", "Server.ModelStatusFn",
"Server.DescribeImageFn", "Server.CaptureStartFn"],
calls=["core.auth_gate", "core.daemon_api"], called_by=["bnd.ipc"],
notes="Eight function fields bypass CoreAPI entirely. Each is nil unless its config block exists, and nil means ErrUnknownMethod on the wire.")
c("core.auth_gate", "service", "mavend",
"The single authorization guard. Locked, it is a default-deny allowlist of three methods; unlocked, it is auth.Gate over Enrollment and PasskeySession.",
["internal/auth/gate.go", "internal/auth/policy.go", "internal/auth/tier.go", "internal/auth/enrollment.go",
"cmd/mavend/main.go"],
["auth.Gate.Check", "auth.Requirement", "auth.Can", "auth.MaxLayer", "auth.Surface", "auth.Layer",
"auth.NewFloorEnrollment", "webauthn.NewPasskeySession", "errLocked"],
called_by=["core.ipc_server"],
notes="Two tier systems exist and this is the one the turn path does NOT read. FloorEnrollment maps every same-uid caller to SurfaceCoreProcess/L3, and no file in cmd/mavend reads req.Surface or Session.Surface. The live act gate is core.risk_policy in internal/tool.")
c("core.daemon_lock", "service", "mavend",
"Cold-start unlock. When a wrapped key blob exists and no env key is set the daemon boots LOCKED, serves three methods, and wires everything else inside UnlockFn after a passkey assertion.",
["cmd/mavend/main.go", "cmd/mavend/keyfile.go", "internal/webauthn/"],
["daemonLock", "daemonLock.unlock", "daemonLock.closeStore", "srv.UnlockFn", "srv.WrapKeyFn",
"webauthn.UnwrapKey", "webauthn.WrapKey", "wrapKeyToFile", "BlobV1"],
reads=["state.wrapped_key"], writes=["state.wrapped_key", "state.db_file"],
notes="The whole daemon is wired twice, in two places, minutes or days apart. boot.go exists because those two lists had already drifted (V-639).")
c("core.daemon_api", "adapter", "mavend",
"The daemon's CoreAPI: the store adapter plus eight closures over the tick loop, the event bus, the decision ring, the voice handler and the Nexus client.",
["cmd/mavend/tick_api.go", "cmd/mavend/boot.go"],
["daemonAPI", "newDaemonAPI", "bootDeps", "daemonAPI.Chat", "daemonAPI.TickTrace",
"daemonAPI.DayPlan", "daemonAPI.ResolveEntity", "daemonAPI.RecentEvents", "daemonAPI.TurnDecisions"],
calls=["core.store_api", "core.tick_loop", "core.reactive_handler", "core.event_bus", "ext.nexus"],
called_by=["core.ipc_server"],
notes="daemonAPI.chatFn is the voice handler; the handler's own api field is back-patched to daemonAPI by upgradeAPI. A deliberate two-way back-patch, documented on both sides.")
c("core.store_api", "adapter", "mavend",
"The plain CoreAPI over the store. Every module method that is a state operation lands here.",
["internal/ipc/storeapi.go", "internal/ipc/coreapi.go", "internal/ipc/api.go", "internal/ipc/unimplemented.go"],
["ipc.NewStoreAPI", "ipc.CoreAPI", "ipc.UnimplementedCoreAPI"],
calls=["state.db"], called_by=["core.daemon_api", "core.intake_api"])
c("core.intake_api", "adapter", "mavend",
"A decorator over CoreAPI that publishes one envelope per successful intake write (WriteFact, WriteNote, CaptureTask) into the in-memory journal.",
["cmd/mavend/intake.go"], ["newIntakeAPI", "intakeAPI", "newEventBus", "intakeEventsFn"],
calls=["core.store_api", "core.event_bus"], called_by=["core.daemon_api"],
notes="cmd/mavend/mail.go reaches past the decorator to st.CaptureTask and publishes by hand. That exception is stated in intake.go.")
c("core.event_bus", "shared-state", "mavend",
"Bounded in-memory intake journal. Read only by /events and recent_events; nothing Maven says depends on it, and it dispatches nothing.",
["internal/event/bus.go", "internal/event/event.go"],
["event.Bus", "Bus.Publish", "Bus.Recent", "Bus.Subscribe", "Event.Normalize"],
called_by=["core.intake_api", "core.daemon_api"])
c("core.voice_server", "service", "mavend",
"The voice TCP listener and session registry. One goroutine per conn; the same conn carries request/response and server-initiated pushes, serialised by the per-session mutex.",
["internal/voice/server.go", "internal/voice/session.go", "cmd/mavend/voicewire.go"],
["voice.NewServer", "voice.Server.Serve", "voice.Sessions", "voice.Handler"],
calls=["core.reactive_handler"], called_by=["bnd.voice_tcp"])
c("core.wiring", "service", "mavend",
"wireVoice: builds the stt and tts seams, the embedder, the routing heads, the tool executor, MCP, the house, the LAN scanner, the weather provider, the model seam, the router cascade, the sessions registry, the voice sink, memory, dialogue, the replier, the handler and the TCP listener.",
["cmd/mavend/voicewire.go"],
["wireVoice", "voiceWiring", "buildRouter", "modelSeam", "sttSeam", "pickLLMRouter",
"seedClassifier", "seedTools", "repairFactVectors", "checkStoredEmbedder", "runReembed"],
called_by=["proc.mavend"],
notes="One 270-line function that constructs seventeen subsystems and returns a struct the rest of the daemon reads fields off.")
# ---------------------------------------------------------------- core: turn
c("core.reactive_handler", "service", "mavend",
"The turn pipeline shared by every reach. Holds stt, tts, the router, the CoreAPI, the tool executor and matcher, the phraser, the replier, recall, the crawler, search, Kiwix, the house, the LAN scanner, the weather provider, the raw store, the time parser, dialogue and clarify state, the decision ring, the trace sink, the ecosystem clients and five pieces of per-turn mutable state.",
["cmd/mavend/voice.go", "cmd/mavend/voicewire.go"],
["reactiveHandler", "runTurn", "HandlePushToTalk", "handleText", "applyAction", "upgradeAPI",
"reply", "chatHistory", "turnSource", "sourceVoice", "sourceText"],
calls=["core.turn_route", "core.preroute", "core.action_table", "core.replier", "core.stt_seam", "core.tts_seam"],
called_by=["core.voice_server", "core.daemon_api", "core.telegram_intake"],
notes="34 fields. It is the single junction of routing, memory, tools, ecosystem, search, the house, the LAN and dialogue state.")
c("core.turn_route", "arbitration", "mavend",
"This turn's routing, memoised with sync.Once and carried on the context so the clarify resolver and the action pipeline act on one decision. Also decides an elliptical follow-up from the previous turn instead of routing it.",
["cmd/mavend/turnroute.go", "cmd/mavend/continuation.go", "cmd/mavend/turnrole.go"],
["turnRoute", "turnRoute.resolve", "withTurnRoute", "turnRouteFrom", "routeForRole",
"needsRoute", "continuationDecision"],
calls=["router.cascade"], called_by=["core.reactive_handler", "core.preroute"],
notes="Exists because arbitration was inverted in V-560: routing twice cost a second on the resident model and could disagree with itself.")
c("core.preroute", "arbitration", "mavend",
"The ordered ladder of stateful pre-emptors that may claim a turn before routing: expired-clarify notice, confirm answer, targeted repair, untargeted repair, command prohibition, clarify answer, quiet toggle, snooze, ack, reminder cancellation, ordinal selection.",
["cmd/mavend/voice.go", "cmd/mavend/confirm.go", "cmd/mavend/repair.go", "cmd/mavend/clarify.go",
"cmd/mavend/command_prohibition.go", "cmd/mavend/quiet_toggle.go", "cmd/mavend/snooze.go",
"cmd/mavend/ack.go", "cmd/mavend/reminder_cancel.go", "cmd/mavend/ordinal.go", "cmd/mavend/decisiontrace.go"],
["resolveConfirm", "resolveRepair", "resolveUntargetedRepair", "resolveCommandProhibition",
"resolveClarifyAnswer", "resolveQuietToggle", "resolveSnooze", "resolveAck",
"resolveReminderCancellation", "resolveCandidate", "preRouteLadder", "notePreRoute",
"clarifyExpiredNotice", "withNotice", "withResumed"],
calls=["state.clarify_store", "state.pending_act", "state.dialogue_sessions"],
called_by=["core.reactive_handler"],
notes="Eleven rungs, order argued step by step in runTurn. A new rung must also appear in preRouteLadder or it is missing from the decision record.")
c("core.action_table", "arbitration", "mavend",
"Per-intent dispatch. Seven entries, one per router intent; a handler returning the empty string defers to the replier.",
["cmd/mavend/actions.go"], ["actionHandlers", "applyAction"],
calls=["core.action_fact", "core.action_reminder", "core.action_act", "core.action_note",
"core.action_chat", "core.action_system", "core.query_chain"],
called_by=["core.reactive_handler"])
c("core.action_fact", "handler", "mavend",
"IntentFact: refuse question-shaped and complaint utterances, write the fact, prune and re-insert its recall vector, feed pattern detection.",
["cmd/mavend/actions_fact.go", "cmd/mavend/factgate.go", "cmd/mavend/patterns.go"],
["actionFact", "factConfidence", "pruneFactVectors", "store.FactRecallText", "router.IsQuestionShaped",
"router.IsTransientComplaint"],
writes=["state.facts", "state.memory_vectors"],
calls=["core.recall", "core.query_chain", "core.action_chat"], called_by=["core.action_table"],
notes="Re-routes a question-shaped fact into actionQuery and a complaint into actionChat, so a fact handler can return a query answer or a chat answer.")
c("core.action_reminder", "handler", "mavend",
"IntentReminder: fall back to the time parser for stage-0 matches, create the row, and confirm from the stored fire time rather than from the utterance.",
["cmd/mavend/actions_reminder.go", "cmd/mavend/reminderwhen.go", "cmd/mavend/reminderbody.go"],
["actionReminder", "reminderConfirm", "reminderBody", "router.ResolvedTheHour", "refusesCommand"],
writes=["state.reminders"], called_by=["core.action_table"],
notes="Never sets Cron. store.Reminder and ipc.CreateReminder both carry a Cron field and no spoken path writes it.")
c("core.action_act", "handler", "mavend",
"IntentAct: the enabled-tool allowlist, the destructive confirm gate, the Hexis capability path, and entity resolution in front of any mutation.",
["cmd/mavend/actions_act.go", "cmd/mavend/confirm.go", "cmd/mavend/ecosystem_acts.go",
"cmd/mavend/entityname.go", "internal/tool/"],
["actionAct", "pendingAct", "resolveConfirm", "tool.Executor", "tool.Matcher",
"handleHexisAct", "pendingHexisExec", "hexisBeforeClarify"],
reads=["state.tools"], calls=["ext.hexis", "ext.homeassistant", "ext.vikunja_mcp"],
called_by=["core.action_table"],
notes="Three gates, none of them the caller's surface: the enabled allowlist, the risk tier (core.risk_policy) and the confirm turn. A Hexis confirmation binds capability id, target entity, arguments, requester and expiry. Filling in a clarified argument never grants authority.")
c("core.ecosystem_hexis_gate", "handler", "mavend",
"The Hexis capability act path. Resolves the entity through Nexus, reads the tier Hexis declares rather than deriving a second opinion, and parks a mutating capability for a confirm bound to capability, target and expiry.",
["cmd/mavend/ecosystem_acts.go"],
["handleHexisAct", "execHexis", "pendingHexisExec", "hexisBeforeClarify"],
calls=["ext.hexis", "ext.nexus"], called_by=["core.action_act"],
notes="The second act path. It reuses internal/tool's policy deliberately (ecosystem_acts.go:768), so local rows and Hexis capabilities share one tier vocabulary. Praxis does not.")
c("core.praxis_acts", "handler", "mavend",
"The Praxis item-lifecycle mutations: acknowledge, resolve, ignore, pin. Reached from actionAct before the local executor, resolved against the last read-out list for an ordinal, and executed directly.",
["cmd/mavend/ecosystem_acts.go"],
["handlePraxisAct", "praxisItemAction", "praxisItemAction.handle", "praxisCapabilities",
"resolveSurfacedPosition", "rememberSurfaced"],
calls=["ext.praxis"], reads=["state.surfaced_items"], called_by=["core.action_act"],
notes="The third act path, and the one with NO risk tier and NO confirm turn: handle() calls a.call(ctx, px, id) directly (ecosystem_acts.go:158). A remote mutation that runs on first hearing. Reversible on the Praxis side, which is a reason nothing in the code states.")
c("core.risk_policy", "service", "mavend",
"The tier system that actually gates an act. RiskOf sorts a tool row into safe, destructive or irreversible; PolicyFor maps the tier to a confirm requirement and a VoiceMayRun flag. Unknown shapes default upward to destructive.",
["internal/tool/risk.go", "internal/tool/tool.go"],
["Risk", "TierSafe", "TierDestructive", "TierIrreversible", "RiskOf", "RiskOfCapability",
"PolicyFor", "Policy.Confirm", "Policy.VoiceMayRun", "irreversibleVerbs", "Executor.Exec"],
called_by=["core.action_act"],
notes="VoiceMayRun is NOT conditioned on the reach: Executor.Exec takes (ctx, name, args, confirmed) and no surface. It also has no proof the boolean was bound correctly: that invariant lives in pendingAct and resolveConfirm, and Exec trusts it because only two callers exist. Covers two of the three act paths (local rows, and Hexis via RiskOfCapability at ecosystem_acts.go:768); the Praxis lifecycle path bypasses it entirely. `systemctl reboot` is TierDestructive, not TierIrreversible, so it runs after one spoken confirm. internal/tool/risk.go:84 states it is not a sandbox.")
c("core.action_note", "handler", "mavend", "IntentNote: write the note and index it for recall.",
["cmd/mavend/actions_note.go", "cmd/mavend/notefragment.go"], ["actionNote"],
writes=["state.notes", "state.memory_vectors"], called_by=["core.action_table"])
c("core.action_chat", "handler", "mavend",
"IntentChat: build history from the dialogue session and let the phraser answer from general knowledge plus the context block.",
["cmd/mavend/actions.go"], ["actionChat", "chatHistory", "phraser.PhraseChat", "phraser.ChatFallback"],
calls=["core.phraser"], called_by=["core.action_table", "core.action_fact"])
c("core.action_system", "handler", "mavend",
"IntentSystem: clock, date and system-observable answers, read off the handler's own clock and keyword tests.",
["cmd/mavend/voice.go", "cmd/mavend/actions.go"], ["actionSystem", "replySystem"], called_by=["core.action_table"])
# ---------------------------------------------------------------- router
c("router.cascade", "arbitration", "mavend",
"The routing cascade. Stage 0 grammars win outright at confidence 1.0; then the routing heads; then the LLM router; then the classifier with slot extraction and a confidence gate. Every stage may decline and the next one answers.",
["internal/router/router.go", "internal/router/intent.go", "internal/router/source.go",
"internal/router/slots.go"],
["router.Router", "router.New", "Router.Route", "router.Config", "Decision", "Intent", "Source",
"Sources", "ValidSource", "gateLLMDecision", "fillSlots", "CorrectMisroute", "SourceAnchored"],
calls=["router.stage0", "router.heads", "router.llm", "router.classifier", "router.extractor"],
called_by=["core.turn_route"],
notes="A route produces TWO decisions: Intent (one of seven) and Source (one of twelve, read on IntentQuery alone). SourceAnchored is set only by a stage 0 grammar.")
c("router.stage0", "arbitration", "mavend",
"The deterministic grammar set, in the daemon's order. First match wins. The order is the contract and each rule's comment argues its position.",
["internal/router/stagezero.go", "internal/router/stage0.go", "internal/router/agendaq.go",
"internal/router/worldquery.go", "internal/router/praxis.go", "internal/router/task.go",
"internal/router/commandframe.go", "internal/router/barecapture.go", "internal/router/possession.go",
"internal/router/list.go", "internal/router/feeds.go", "internal/router/help.go",
"internal/router/implicitquery.go", "internal/router/fragment.go", "internal/router/reminderreport.go",
"internal/router/taskstatus.go", "internal/router/knowledge.go"],
["StageZeroGrammars", "Grammar", "Grammar.Evaluate", "DefaultGrammars", "CommandProhibitionGrammar",
"AmbiguousFragmentGrammar", "SystemTimeDateGrammars", "AgendaQueryGrammars", "ImplicitElapsedQueryGrammar",
"WorldQueryGrammars", "MavenHelpGrammar", "FeedQueryGrammar", "TaskListGrammar", "ListGrammars",
"ReminderGrammar", "ReminderCancellationReportGrammar", "PraxisGrammars", "TaskStatusGrammar",
"BareCaptureGrammar", "TaskCaptureGrammar", "NarrativeQueryGrammars", "PossessionStatementGrammar",
"StripWakeToken"],
called_by=["router.cascade", "proc.labelgen", "eval.router"],
notes="One list, called by both buildRouter and the eval fixture, because the two copies had already drifted (V-693). PraxisGrammars is the only path to Praxis.")
c("router.heads", "model", "mavend",
"Routing heads: a softmax over the label set, run on a fine-tuned copy of the e5-small encoder. Runs after stage 0 and before the model. Declines below its own threshold.",
["internal/router/heads.go", "internal/router/onnxruntime.go", CFG],
["RouterHeads", "NewRouterHeads", "RouterHeads.Route"],
called_by=["router.cascade"],
notes="Configured at voice.embedder.heads_path. A missing or broken weights file logs and leaves the field nil, which is the pre-V-664 cascade exactly. heads_path must never equal model_path; that is refused at config load.")
c("router.llm", "model", "mavend",
"The LLM router. A grammar-constrained call to the resident model that names an intent and a destination. Any error or parse failure falls through to the classifier.",
["internal/router/llmrouter.go", "internal/router/currentversion.go", "cmd/mavend/voicewire.go"],
["LLMRouter", "LLMRouter.Route", "Completer", "pickLLMRouter"],
calls=["svc.llama_server"], called_by=["router.cascade"],
notes="voice.llm_router = true in the deployed config. gateLLMDecision thins a structurally incomplete route rather than trusting it.")
c("router.classifier", "model", "mavend",
"The nearest-centroid classifier over the embedder. The floor: it answers when the resident model is off, absent or erroring, and it names no destination.",
["internal/router/classifier.go", "internal/router/embedder.go", "cmd/mavend/voicewire.go",
"cmd/mavend/seed.go", "models/seeds/"],
["Classifier", "Classifier.Classify", "seedClassifier", "loadSeedFile", "ErrNoIntents", "seedDir"],
called_by=["router.cascade"],
notes="Seeded from models/seeds at boot. It sets no Source, so a box whose model is down walks the whole query chain in table order.")
c("router.embedder", "model", "mavend",
"The ONNX multilingual-e5-small encoder, or the HashEmbedder floor when no embedder block is configured. Backs the classifier, recall, topics and the trace encoder id.",
["internal/router/onnxembedder.go", "internal/router/embedder.go", CFG],
["NewONNXEmbedder", "NewHashEmbedder", "EmbedQuery", "EmbedPassage", "EmbedderID", "tokenizerRev"],
called_by=["router.classifier", "core.recall", "core.feed_worker", "core.crawl_worker", "core.topics"],
notes="EmbedQuery and EmbedPassage apply the query:/passage: prefixes the model was trained with. Calling plain Embed on a note is a bug.")
c("router.extractor", "service", "mavend",
"Stage 2 slot extraction: time, act target, fact key and value, ordinals, number words, URLs.",
["internal/router/slots.go", "internal/router/dateparser.go", "internal/router/acttarget.go",
"internal/router/timementions.go", "internal/router/numwords.go", "internal/router/halfpast.go",
"internal/router/url.go", "internal/router/remindersubject.go"],
["Extractor", "Extractor.Extract", "DateTimeParser", "NewPythonDateParser", "DefaultFactParser",
"ResolvedTheHour"],
called_by=["router.cascade", "core.preroute"])
c("router.claim", "planned", "mavend",
"A comparable unit of evidence for the many claimants that compete for one utterance: consumed span, unexplained remainder, band and veto reason.",
["internal/claim/claim.go", "internal/router/claim.go", "docs/plans/19-dialogue-arbitration.md"],
["claim.Claim", "claim.Split", "claim.Band", "router.ClaimOf", "bandOf", "vetoOf", "filledSlots"],
status="planned-unwired", confidence="high",
notes="Nothing calls ClaimOf. Its own doc comment says so. DEFECT: claimSpans includes Slots.Text unconditionally (internal/router/claim.go:38) while Router.fillSlots backfills the raw utterance into Text for note, query and chat (internal/router/router.go:334), so Claim.Coverage returns 1.0 for a claim that extracted nothing, and MoreSpecificThan reads coverage first. filledSlots in the same file guards against exactly this and claimSpans does not. All five cases in claim_test.go set Text == Utterance and assert Band only.")
c("router.modes", "planned", "mavend",
"An inventory of roughly thirty downstream behaviours mapped back to the seven public intents.",
["internal/modes/modes.go", "internal/modes/modes_v1.json"], ["modes.Inventory", "modes.Mode"],
status="planned-unwired", confidence="high",
notes="No file outside internal/modes imports it. Only its own test loads the JSON.")
c("core.query_chain", "arbitration", "mavend",
"The ordered chain of 22 query sources actionQuery walks. First source to claim answers the turn; queryWalk narrows the chain against the destination the cascade named.",
["cmd/mavend/actions_query.go", "cmd/mavend/querysource.go"],
["querySources", "querySource", "queryWalk", "actionQuery", "queryTurn", "noteQuerySource",
"querySourceNames", "withQuerySourceSink"],
called_by=["core.action_table", "core.action_fact"],
notes="Table order IS the arbitration. queryWalk takes sources OUT and never moves one. Only sources marked guesses:true are droppable, and the personal boundary only when SourceAnchored was set by a literal stage 0 pattern (V-666).")
# ---------------------------------------------------------------- query sources
qs = [
("core.q.factbykey", "fact-by-key", "Reads the current value for a key straight out of the fact store.",
"cmd/mavend/actions_query.go", "queryFactByKey", "SourceRecall", False, False),
("core.q.dayplan", "day-plan", "Assembles today's plan from the tick loop, not from a table.",
"cmd/mavend/actions_query.go", "queryDayPlan", "SourceCalendar", False, False),
("core.q.habits", "habits", "Answers a habit question from the behaviour profile over recorded events.",
"cmd/mavend/actions_query.go", "queryHabits", "SourceCalendar", False, False),
("core.q.tasks", "tasks", "Answers from the task list.",
"cmd/mavend/actions_task.go", "queryTasks", "SourceTasks", False, False),
("core.q.attention", "attention", "Answers from what Praxis says needs looking at.",
"cmd/mavend/attentionq.go", "queryAttention", "SourceAttention", True, False),
("core.q.list", "list", "Answers from the shopping and other named lists.",
"cmd/mavend/actions_list.go", "queryList", "SourceList", True, False),
("core.q.money", "money", "Answers from the spending facts the poller wrote.",
"cmd/mavend/actions_money.go", "queryMoney", "SourceMoney", False, False),
("core.q.history", "history", "Answers 'what did I tell you' from the facts he tapped in.",
"cmd/mavend/historyq.go", "queryHistory", "SourceRecall", False, False),
("core.q.feeds", "feeds", "Answers from the RSS notes the feed worker wrote.",
"cmd/mavend/actions_query.go", "queryFeeds", "SourceFeeds", True, False),
("core.q.home", "home", "Answers from Home Assistant state.",
"cmd/mavend/actions_query.go", "queryHome", "SourceHome", True, False),
("core.q.network", "network", "Answers from a bounded LAN scan.",
"cmd/mavend/actions_query.go", "queryNetwork", "SourceNetwork", True, False),
("core.q.calendar", "calendar", "Answers from calendar events. The only date-aware source, so the only one a continuation may reach.",
"cmd/mavend/actions_query.go", "queryCalendar", "SourceCalendar", False, False),
("core.q.weather", "weather", "Answers the forecast for a place.",
"cmd/mavend/actions_query.go", "queryWeather", "SourceWeather", True, False),
("core.q.self", "self", "Answers a question about Maven herself, above the sources that search his data.",
"cmd/mavend/self.go", "querySelf", "SourceSelf", True, False),
("core.q.embed", "embed", "Embeds the query once and fills the turn scratch for the passes below it.",
"cmd/mavend/actions_query.go", "queryEmbed", "SourceRecall", False, False),
("core.q.memory", "memory", "Vector search over notes and facts together.",
"cmd/mavend/actions_query.go", "queryMemory", "SourceRecall", False, False),
("core.q.notes", "notes", "The notes-only recall pass.",
"cmd/mavend/actions_query.go", "queryNotes", "SourceRecall", False, False),
("core.q.personal", "personal", "THE BOUNDARY. A question about him that got this far has no answer in his data, so the walk stops rather than letting the world guess.",
"cmd/mavend/personalboundary.go", "queryPersonal", "SourceRecall", True, True),
("core.q.search", "search", "SearXNG, the live web. Only the query string leaves the box.",
"cmd/mavend/actions_query.go", "querySearch", "SourceWorld", False, False),
("core.q.kiwix", "kiwix", "The offline ZIMs, the fallback behind the live search.",
"cmd/mavend/actions_query.go", "queryKiwix", "SourceWorld", False, False),
("core.q.web", "web", "Reads a page he named by URL. Claims only when a URL was spoken.",
"cmd/mavend/actions_query.go", "queryWeb", "SourceWorld", False, False),
("core.q.general", "general-knowledge", "The resident model answering from its own weights. Last.",
"cmd/mavend/actions_query.go", "queryGeneral", "SourceWorld", False, False),
]
for qid, name, resp, f, sym, dest, guesses, boundary in qs:
tags = []
if guesses:
tags.append("guesses:true — droppable when the cascade names another destination")
if boundary:
tags.append("boundary:true — droppable only by an anchored stage 0 destination (V-666)")
c(qid, "query_source", "query-chain",
resp, [f, "cmd/mavend/actions_query.go"], [sym, "querySources"],
called_by=["core.query_chain"], confidence="high",
notes=("chain name %r, dest %s. " % (name, dest)) + " ".join(tags))
# ---------------------------------------------------------------- core services
c("core.recall", "service", "mavend",
"The note-and-fact recall subsystem: the embedder, the vector store, and the two numbers that gate an answer.",
["cmd/mavend/recall.go", "internal/memory/store.go", "internal/memory/gate.go", CFG],
["recallWiring", "memory.Store", "memory.Search", "memory.Confident", "memory.ConfidentScores",
"query_min_score", "query_min_margin"],
reads=["state.memory_vectors"], writes=["state.memory_vectors"],
called_by=["core.q.memory", "core.q.notes", "core.q.embed", "core.action_fact", "core.action_note"],
notes="query_min_score 0.80, query_min_margin 0.008 in the deployed config.")
c("core.phraser", "service", "mavend",
"The phrasing seam. One parser for every model reply; a deterministic Stub when no phraser block is configured.",
["internal/phraser/phraser.go", "internal/phraser/llmphraser.go", "internal/phraser/parse.go",
"internal/phraser/prompts.go", "internal/phraser/nudge_llm.go", "internal/phraser/query.go",
"internal/phraser/world.go", "internal/phraser/replier.go", "internal/phraser/swap.go"],
["phraser.Phraser", "phraser.NewStub", "phraser.NewLLMPhraser", "parseResponseMood",
"PhraseNudge", "PhraseReminder", "PhraseChat", "LLMPhraser.UseRemote"],
calls=["svc.llama_server", "core.model_seam"],
called_by=["core.action_chat", "core.tick_loop", "core.query_chain", "core.mail_intake", "core.memory_eval"],
notes="parseResponseMood is the one parser for model text; every phrasing path reaches it.")
c("core.replier", "service", "mavend",
"Phrases the reply across a router decision when the action handler returned nothing. LLM-backed when a completion seam exists, a deterministic stub otherwise.",
["cmd/mavend/replier_llm.go", "internal/voice/replier.go"],
["voice.Replier", "voice.NewStubReplier", "newLLMReplier"],
calls=["core.model_seam"], called_by=["core.reactive_handler"])
c("core.model_seam", "service", "mavend",
"The completion seam the hot paths use. Either an llm.Pair preferring the workstation model with a silent fallback to the resident model, or the resident client alone, or nothing.",
["cmd/mavend/voicewire.go", "cmd/mavend/main.go", "internal/llm/remote.go", "internal/llm/client.go",
"internal/llm/gate.go", CFG],
["modelSeam", "llm.Pair", "llm.NewPair", "Pair.Available", "Pair.Complete", "llm.Gate",
"llmClientFor", "cacheRAMMiB"],
calls=["svc.llama_server", "proc.mavgpud"],
called_by=["router.llm", "core.replier", "core.phraser"], status="configured-off",
notes='deploy/mavend.json sets workstation.model_disabled = true, so the model arm is dark and only the STT arm of the workstation block is live. Deleting the block restores pre-workstation behaviour.')
c("core.stt_seam", "service", "mavend",
"Transcription. Remote to mavsttd when a socket is configured, an in-process Stub otherwise, with an optional stt.Pair preferring the workstation CW2 service.",
["cmd/mavend/voicewire.go", "internal/stt/"], ["sttSeam", "stt.Transcriber", "stt.NewRemote", "stt.NewStub", "stt.Pair"],
calls=["proc.mavsttd", "ext.cw2_stt"], called_by=["core.reactive_handler"])
c("core.tts_seam", "service", "mavend",
"Synthesis. Remote to mavttsd when a socket is configured, an in-process Stub otherwise.",
["cmd/mavend/voicewire.go", "internal/tts/", "internal/ttsnorm/"],
["tts.Synthesizer", "tts.NewRemote", "tts.NewStub", "ttsnorm"],
calls=["proc.mavttsd"], called_by=["core.reactive_handler", "core.sink_voice"])
c("core.ecosystem", "adapter", "mavend",
"The three ecosystem clients and the shared JSON transport. All HTTP; no component reads another's database.",
["cmd/mavend/ecosystem.go", "cmd/mavend/ecosystem_acts.go"],
["wireEcosystem", "ecosystemWiring", "nexusClient", "praxisClient", "hexisclient",
"resolveEntityReference", "newCorrelationID"],
calls=["ext.nexus", "ext.praxis", "ext.hexis"], writes=["state.ecosystem_traces"],
called_by=["core.reactive_handler", "core.fact_enrichment", "core.daemon_api"],
notes="All three are nil unless configured and each degrades alone. An outage means a named gap, never a guess.")
c("core.decision_trace", "service", "mavend",
"The per-turn arbitration record: who claimed the turn, who lost it, who was never asked. Written to a bounded in-memory ring and, since V-629, persisted for fitting the routing heads.",
["internal/decision/", "cmd/mavend/decisiontrace.go", "cmd/mavend/routingtrace.go"],
["decision.Record", "decision.Ring", "decision.With", "decision.Note", "decision.Expect",
"decision.Claim", "preRouteLadder", "traceSink", "pruneTracesOnStart", "persistDecision"],
writes=["state.routing_traces", "state.decision_ring"], called_by=["core.reactive_handler", "router.cascade"],
notes="Retained 14 days, enforced on write and again on start.")
c("core.topics", "service", "mavend",
"Open-set Russian topic matching with the embedder, one of the three sanctioned word-matching mechanisms.",
["cmd/mavend/topics.go", "cmd/mavend/ruwords.go", "internal/lexicon/", "internal/morph/"],
["topics", "lexicon", "morph"], called_by=["core.query_chain", "core.preroute"],
notes="Russian words are matched by internal/lexicon (closed classes), internal/morph (grammar) and topics.go with the embedder (open sets), and by no fourth mechanism.")
# ---------------------------------------------------------------- proactive
c("core.tick_loop", "worker", "mavend",
"The proactive driver. One pass per tick: gather state, pick at most one nudge candidate, queue or phrase and dispatch it, flush the digest, fire operator routines, accepted routines and morning routines, detect patterns, deliver due reminders, and repeat un-acked sev4 telegram alarms.",
["cmd/mavend/tick.go", "cmd/mavend/tick_digest.go", "cmd/mavend/tick_routines.go",
"cmd/mavend/tick_morning.go", "cmd/mavend/tick_api.go"],
["tickLoop", "newTickLoop", "tickLoop.run", "tickLoop.tick", "deliverReminder", "savePresence",
"maybeFlush", "flushDigest", "enqueueSuppressedDigest", "fireRoutines", "fireAcceptedRoutines",
"fireMorningRoutines", "detectPatterns", "stopFinishedAlarms", "tune", "cachePhrase"],
reads=["state.facts", "state.reminders", "state.nudges", "state.digest_entries", "state.proposed_routines",
"state.events", "state.presence_state"],
writes=["state.nudges", "state.reminders", "state.presence_state", "state.digest_entries",
"state.facts", "state.proposed_routines", "state.delivery_attempts"],
calls=["core.gatherer", "core.rules", "core.phraser", "core.dispatcher", "core.pattern"],
called_by=["proc.mavend"],
notes="Thirteen distinct jobs in one tick function on one 60s ticker, plus a second slower autotune ticker.")
c("core.gatherer", "service", "mavend",
"Builds the tick's consistent state snapshot out of the store: facts, presence, quiet hours, calendar busy, due reminders, collapsed reminder groups.",
["internal/loop/gather.go", "internal/loop/state.go"],
["loop.Gatherer", "NewGatherer", "GatherState", "SetQuietHours", "loop.State", "State.Since",
"State.FactsUnder", "inQuietWindow", "collapseReminders"],
reads=["state.facts", "state.reminders", "state.nudges", "state.presence_state"],
called_by=["core.tick_loop"])
c("core.rules", "service", "mavend",
"The pure nudge rule set and the restraint gate. Rules are data; Gate and ExplainGate are pure functions of state.",
["internal/loop/rules.go", "internal/loop/loop.go", "internal/loop/explain.go", "internal/loop/feedback.go",
"internal/loop/digest_identity.go"],
["loop.Rule", "DefaultRules", "RulesExcept", "WaterRule", "MealRule", "BreakRule", "ServiceDownRule",
"NetdataCriticalRule", "loop.Gate", "ExplainGate", "ExplainTick", "Tick", "Severity",
"RemindDecisions", "TuneCooldown", "DigestEligible"],
called_by=["core.tick_loop", "core.gatherer"],
notes="disabled_rules is empty in the deployed config, so all five rules are live.")
c("core.pattern", "service", "mavend",
"Detects a stable recurring action+object pair in the events table and proposes a routine for it.",
["internal/pattern/detector.go", "cmd/mavend/patterns.go", "cmd/mavend/tick_routines.go", CFG],
["pattern.Detect", "pattern.ProposedRoutine", "detectPatterns", "announceProposal"],
reads=["state.events"], writes=["state.proposed_routines"], called_by=["core.tick_loop"],
notes="pattern_proposals.notify = false in the deployed config: detect silently, never announce.")
c("core.morning", "service", "mavend",
"Daily checklists. An item is done when its fact_key gets a non-voided fact inside the window; the nudge fires once at the end of the window and only for what is still open.",
["internal/morning/", "cmd/mavend/tick_morning.go", CFG],
["morning.Routine", "morning.Candidate", "fireMorningRoutines", "gatherMorningFacts",
"morningStatus", "dayPlan"],
reads=["state.facts"], called_by=["core.tick_loop"],
notes="One routine configured: medicine, water, pets, 08:00-11:00, nudge at 10:30.")
c("core.routines", "service", "mavend",
"Operator-declared and user-accepted scheduled behaviours, fired through the normal delivery routing. Bodies are literal operator text, never model-phrased.",
["internal/routine/", "cmd/mavend/tick_routines.go"],
["routine.Routine", "routine.Due", "routinesFromConfig", "fireRoutines", "fireAcceptedRoutines"],
reads=["state.proposed_routines"], called_by=["core.tick_loop"],
notes="The deployed config declares no `routines` block, so only accepted proposals can fire.")
c("core.dispatcher", "service", "mavend",
"Delivery fan-out. A pure routing table over (severity, presence) picks channels; the dispatcher holds the sinks, the durable outbox and the nudge recorder.",
["internal/delivery/dispatcher.go", "internal/delivery/channel.go", "internal/delivery/sink.go",
"internal/delivery/ack.go", "internal/delivery/sendable.go", "cmd/mavend/main.go"],
["delivery.NewDispatcher", "delivery.Config", "ChannelsFor", "Channel", "DispatchNudge",
"RepeatUnacked", "beginOutbox", "completeOutbox", "wireDispatcher", "NudgeRecorder", "Outbox",
"ErrVoiceNoSession", "ErrPermanent"],
writes=["state.nudges", "state.delivery_attempts", "state.ack_sends"],
calls=["core.sink_voice", "core.sink_ntfy", "core.sink_telegram"], called_by=["core.tick_loop"],
notes="Double authority is intentional: the gate decides whether a rule EMITS, delivery decides where it LANDS. Reminders bypass the restraint gate.")
c("core.sink_voice", "adapter", "mavend",
"Pushes a spoken nudge to the most recently active voice session through the same conn that serves requests.",
["internal/delivery/voicesink/", "cmd/mavend/voicewire.go"],
["voicesink.New", "voicesink.Sink", "Sessions.PushToMostRecent"],
calls=["core.tts_seam", "core.voice_server"], called_by=["core.dispatcher"])
c("core.sink_ntfy", "adapter", "mavend", "The ntfy push reach.",
["internal/delivery/ntfysink/"], ["ntfysink.New"], calls=["ext.ntfy"],
called_by=["core.dispatcher"], status="configured-off",
notes="nil in the deployed dispatcher because the config block says disabled.")
c("core.sink_telegram", "adapter", "mavend", "The telegram away reach, and the sev4 repeat-til-ack channel.",
["internal/delivery/telegramsink/telegramsink.go", "internal/delivery/telegramsink/botapi.go"],
["telegramsink.New", "telegramsink.Sink"], calls=["ext.telegram"], called_by=["core.dispatcher"])
c("core.telegram_intake", "worker", "mavend",
"Long-polls telegram getUpdates from exactly one chat and runs each message as a text turn, offering the same correction gesture the web does.",
["cmd/mavend/telegramintake.go", "internal/delivery/telegramsink/intake.go",
"internal/delivery/telegramsink/correction.go"],
["wireTelegramIntake", "chatTurnFn", "telegramsink.NewPoller", "Poller.Run"],
calls=["core.daemon_api", "core.reactive_handler"], called_by=["proc.mavend"],
notes="Started with wg.Add plus a bare goroutine rather than through goWorker or backgroundWorkers, so it is absent from the assertable worker set.")
# ---------------------------------------------------------------- background workers
c("core.fact_enrichment", "worker", "mavend",
"Resolves a fact's free-text Subject to a canonical Nexus entity id, with per-fact backoff.",
["cmd/mavend/factenrichment.go"], ["factEnrichmentWorker", "newFactEnrichmentWorker", "tick",
"resolveOne", "enrichmentBackoff", "forgetDeparted"],
reads=["state.facts"], writes=["state.facts"], calls=["ext.nexus"], called_by=["proc.mavend"])
c("core.memory_eval", "worker", "mavend",
"Scores recall quality against a stored fixture using the phraser.",
["cmd/mavend/memoryeval.go", "internal/memeval/", "internal/memory/recalleval/"],
["memoryEvalWorker", "newMemoryEvalWorker"], calls=["core.phraser"], called_by=["proc.mavend"],
status="configured-off", notes="nil unless configured; the deployed config has no block for it.")
c("core.feed_worker", "worker", "mavend",
"Polls RSS sources and writes each item as a note with source rss:<name>, embedding it for recall.",
["cmd/mavend/feeds.go", "internal/rss/poller.go", CFG],
["feedWorker", "newFeedWorker", "rss.Poller"],
writes=["state.notes", "state.memory_vectors", "state.facts"],
calls=["router.embedder", "core.intake_api"], called_by=["proc.mavend"],
notes="Two sources configured: lwn and archlinux, 30m interval.")
c("core.crawl_worker", "worker", "mavend",
"Watches configured pages, and holds the fetcher the on-demand queryWeb source uses.",
["cmd/mavend/crawls.go", "internal/crawl/", "internal/webfetch/", CFG],
["crawlWorker", "newCrawlWorker", "crawl.Crawler", "crawl.Watcher", "onDemandCrawler",
"factHashes", "crawlFetcher"],
writes=["state.notes", "state.facts"], called_by=["proc.mavend"],
notes="crawl.on_demand = true and no watches are configured, so the worker runs with nothing to watch while queryWeb is live.")
c("core.mcp_worker", "worker", "mavend",
"Connects to configured MCP servers, proposes their tools into the act allowlist, and withdraws proposals for tools that vanish.",
["cmd/mavend/mcp.go", "internal/mcp/", CFG],
["mcpWiring", "wireMCP", "connect", "propose", "withdrawGone", "status", "caller"],
writes=["state.tools"], calls=["ext.vikunja_mcp"], called_by=["proc.mavend"], status="configured-off")
c("core.home_worker", "worker", "mavend",
"Refreshes Home Assistant state and proposes one always-destructive tool row per controllable device.",
["cmd/mavend/smarthome.go", "internal/smarthome/", CFG],
["homeWiring", "wireSmartHome", "propose", "run", "caller"],
writes=["state.tools"], calls=["ext.homeassistant"], called_by=["proc.mavend"], status="configured-off")
c("core.netscan", "service", "mavend",
"Bounded LAN scan. A read, so it has no allowlist row; what keeps it safe is that its range comes from config alone.",
["cmd/mavend/netscan.go", "internal/netscan/", CFG],
["netWiring", "wireNetScan", "scan", "scanSummary", "writeScanRecord", "isNetworkQuery"],
writes=["state.notes"], called_by=["core.q.network"],
notes="netscan.enabled = true in the deployed config, subnet 192.168.1.0/24.")
c("core.vision", "service", "mavend",
"Describes one image and stores the blob. Dark without a media block; MethodDescribeImage then answers ErrUnknownMethod.",
["cmd/mavend/vision.go", "internal/vision/", "internal/media/"],
["wireVision", "Server.DescribeImageFn", "media.Store"],
writes=["state.notes", "state.media_blobs"], called_by=["core.ipc_server"], status="configured-off",
notes="No `media` block in the deployed config.")
c("core.capture", "service", "mavend",
"The meeting recorder. Off unless a media block exists AND capture.enabled is true; all four capture methods then answer ErrUnknownMethod.",
["cmd/mavend/capture.go", "internal/capture/"], ["wireCapture", "Server.CaptureStartFn"],
writes=["state.notes", "state.media_blobs"], called_by=["core.ipc_server"], status="configured-off",
notes="The load-bearing default: on an unconfigured box no wire path begins a recording.")
c("core.speaker", "service", "mavend",
"Voice identification. Enrolment plumbing only until a speaker-embedding model exists on disk; off entirely without a speaker block.",
["cmd/mavend/speaker.go", "internal/speaker/"], ["wireSpeaker"],
called_by=["core.ipc_server"], status="configured-off")
c("core.mail_intake", "service", "mavend",
"Extracts task candidates from one fetched message using the resident model and writes them as candidates he reviews on /tasks.",
["cmd/mavend/mail.go", "internal/email/"], ["wireMailIntake", "mailIntake.ingest", "Server.IngestMailFn"],
writes=["state.tasks"], calls=["core.phraser"], called_by=["core.ipc_server"], status="configured-off",
notes="Nil unless an email block is configured AND there is a llama-server. Reaches past the intake decorator to st.CaptureTask directly.")
c("core.modelswap", "service", "mavend",
"Swaps the resident model on the fly and reports model status. Nil unless phraser.swap_models lists at least one model.",
["cmd/mavend/modelswap.go", "internal/phraser/swap.go"],
["wireModelSwap", "Server.SwapModelFn", "Server.ModelStatusFn"],
called_by=["core.ipc_server"], status="configured-off",
notes="No swap_models key in the deployed phraser block.")
# ---------------------------------------------------------------- persistence
c("state.db", "storage", "persistence",
"The one SQLite database. Opened at SetMaxOpenConns(1), so every write is serialised at the database and the IPC server adds no locking of its own.",
["internal/store/store.go", "internal/store/schema.sql", "internal/store/migrations.go"],
["store.Store", "store.Open", "store.OpenEncrypted", "Store.Close", "migrations", "schemaSQL"],
called_by=["core.store_api", "core.tick_loop", "core.wiring", "core.reactive_handler"],
notes="Append-only discipline: a wrong fact is superseded, never overwritten. 20 migrations on top of schema.sql.")
c("state.db_file", "storage", "persistence",
"The at-rest ciphertext. A fixed magic header, a random GCM nonce and AES-256-GCM over the whole sqlite file. Decrypted into tmpfs on Open and re-encrypted atomically on Close.",
["internal/store/crypt.go", "docker-compose.yml", CFG],
["OpenEncrypted", "cryptMagic", "encState.closeAndSeal", "atomicWrite"],
notes="/var/lib/maven/maven.db.enc on the dbdata volume. Wrong key or a tampered file fails closed; there is never a plaintext fallback.")
c("state.db_tmpfs", "storage", "persistence",
"The decrypted working copy, in RAM. Lives and dies with the container.",
["internal/store/crypt.go", CFG], ["db_tmpfs"],
notes="/dev/shm/maven-plain.db. A daemon killed rather than stopped loses everything since the last clean shutdown; that is what mavseal recovers.")
c("state.wrapped_key", "storage", "persistence",
"The database key wrapped under a passkey PRF output (HKDF-SHA256 + AES-GCM). Its presence with no env key is what puts the daemon in locked mode.",
["cmd/mavend/keyfile.go", "internal/webauthn/", "cmd/mavend/main.go"],
["wrapKeyToFile", "webauthn.WrapKey", "webauthn.UnwrapKey", "BlobV1", "BlobV2"],
notes="A v1 blob is derived from the credential PUBLIC key, which mavweb also writes to its passkey file; unwrapping one logs a SECURITY line.")
c("state.facts", "table", "persistence",
"Every observation: self, env and config. ts is valid-time. A correction points voids_id at the row it cancels.",
["internal/store/facts.go", "internal/store/entityfacts.go", "internal/store/schema.sql"],
["Store.WriteFact", "Store.LatestFact", "Store.RecentFacts", "Store.VoidLatestFact", "Store.CorrectValue",
"FactKind", "FactResolutionState"],
notes="Written by six unrelated components: the voice fact handler, the quiet toggle, mavpoll, mavcaldav, mavweb (/api/signal and /api/ambient), the feed and crawl watermark writers, and the fact enrichment worker.")
c("state.reminders", "table", "persistence",
"User intent, with a fire time, a durable delivery group, a cached phrase, an attempt count and a bounded backoff.",
["internal/store/reminders.go", "internal/store/migrations.go"],
["Store.CreateReminder", "ListPendingReminders", "CompleteReminderDelivery",
"CompleteSuccessfulReminderAttempt", "BlockReminderDelivery", "store.Reminder"],
notes="Carries cron and next_fire_ts columns since migration #2. No spoken path writes them.")
c("state.nudges", "table", "persistence",
"Every proactive send and its outcome. This table IS the restraint memory and the only input to the feedback tuner.",
["internal/store/nudges.go", "internal/store/schema.sql"],
["Store.RecordNudge", "Store.ResolveNudge", "Store.RecentOutcomes", "Store.UnackedTelegramRules",
"Store.SnoozedUntil"],
notes="Recorded AFTER a successful send, so a failed send does not pollute the feedback signal.")
c("state.notes", "table", "persistence",
"Free text he captured, plus everything the feed, crawl, capture, vision and netscan paths write. Embedding is a little-endian float32 blob, scanned brute force.",
["internal/store/notes.go", "internal/store/schema.sql"], ["Store.WriteNote", "Store.QueryNotes", "Store.RecentNotes"],
notes="Six writers: the note handler, the RSS poller, the crawl watcher, meeting capture, image description and the LAN scan record.")
c("state.memory_vectors", "table", "persistence",
"The vector index behind recall, over notes and facts together. Backed by store.VectorMemory or the in-memory floor.",
["internal/store/memory.go", "internal/store/factvectors.go", "internal/memory/store.go",
"cmd/mavend/actions_fact.go", "cmd/mavend/voicewire.go"],
["Store.VectorMemory", "MemoryStore.Insert", "MemoryStore.Search", "MemoryStore.DeletePrefix",
"pruneFactVectors", "repairFactVectors", "checkStoredEmbedder", "runReembed"],
notes="Marked with the embedder id. Swapping the embedder or bumping tokenizerRev requires ReembedAll.")
c("state.tools", "table", "persistence",
"The act allowlist. A `proposed` row drives nothing; the executor only runs `enabled` rows, and enabling is a human act on an authed surface, never the voice path.",
["internal/store/tools.go", "internal/store/schema.sql", "cmd/mavend/voicewire.go", CFG],
["Store.ProposeTool", "Store.EnableTool", "Store.LookupTool", "seedTools"],
notes="Three unrelated proposers write here: config seeding, MCP discovery and Home Assistant discovery. Twelve tools are seeded from the deployed config, five of them destructive.")
c("state.presence_state", "table", "persistence",
"The one stateful bit of presence: the hysteresis bucket, rewritten each tick.",
["internal/store/presence.go", "internal/store/presence_state.go", "internal/store/schema.sql"],
["Store.SavePresenceState", "Store.LoadPresenceState", "Store.PresenceProbes", "Bucket"],
notes="A singleton row. Presence is otherwise a pure function.")
c("state.events", "table", "persistence", "Recorded action+object events; the only input to pattern detection.",
["internal/store/events.go", "internal/ipc/storeapi.go"], ["Store.CreateEvent", "Store.DistinctEventPairs",
"Store.EventsFor", "storeAPI.SeedEvent"],
notes="SeedEvent is reachable only when mavend runs with -allow-seed, and the flag makes seedStore nil rather than merely refusing.")
c("state.proposed_routines", "table", "persistence", "Detected patterns awaiting acceptance, and accepted ones with their fire clock.",
["internal/store/proposed_routines.go"], ["ListProposedRoutines", "AcceptProposedRoutine", "DismissProposedRoutine"])
c("state.digest_entries", "table", "persistence", "Candidates the restraint gate BLOCKED, held durably for later resurfacing.",
["internal/store/digest.go", "cmd/mavend/tick_digest.go"], ["enqueueSuppressedDigest", "maybeDrainDigest", "expireStaleDigest"],
notes="A separate mechanism from the in-memory digestQ, which batches candidates the gate ALLOWED.")
c("state.delivery_attempts", "table", "persistence",
"The durable delivery ledger. Intent is recorded BEFORE the external send, so a crash leaves a pending row rather than silence.",
["internal/store/delivery.go", "internal/delivery/dispatcher.go"],
["BeginDeliveryAttempt", "CompleteDeliveryAttempt", "ReconcileStaleDeliveryAttempts"],
notes="Reconciled to `unknown` at boot before the tick loop resumes, so nothing auto-resends into the ambiguity.")
c("state.ack_sends", "table", "persistence", "sev4 telegram repeat-til-ack tracking.",
["internal/store/ack.go"], ["Store.WasAcked", "Store.MarkSent", "Store.MarkAcked", "Store.LastSent"])
c("state.dialogue_sessions", "table", "persistence", "The follow-up slot carry-over, persisted so a restart mid-conversation keeps the thread.",
["internal/store/dialogue.go", "internal/dialogue/"],
["dialogue.NewPersistentSessionStore", "SessionStore.Get", "SessionStore.Load"],
notes="TTL 2 minutes, pruned on load. The clarify store deliberately does NOT persist: a restart expires the open question.")
c("state.tasks", "table", "persistence", "Task candidates and tasks, from the voice path, the web form and the mail reader.",
["internal/store/tasks.go", "internal/tasks/"], ["Store.CaptureTask", "ListTasks", "SetTaskStatus", "EditTask"])
c("state.list_items", "table", "persistence", "The shopping and other named lists.",
["internal/store/listitems.go"], ["Store.AddListItem", "ListItems"])
c("state.routing_traces", "table", "persistence", "Persisted turn decision records, for fitting the routing heads.",
["internal/store/routingtraces.go", "cmd/mavend/routingtrace.go"], ["WriteRoutingTrace", "PruneRoutingTraces"],
notes="Retained 14 days, enforced on write and again on start.")
c("state.routing_labels", "table", "persistence", "Owner corrections of a turn's route, the label side of the same data.",
["internal/store/routinglabels.go"], ["Store.CorrectTurn", "Store.RoutingLabels"])
c("state.ecosystem_traces", "table", "persistence", "One row per ecosystem call, keyed by correlation id.",
["internal/store/ecotraces.go"], ["Store.WriteEcosystemTrace", "Store.RecentEcosystemTraces",
"Store.PruneEcosystemTraces"])
c("state.meta", "table", "persistence", "Schema version and the embedder marker.",
["internal/store/meta.go"], ["Store.Meta", "Store.SetMeta"])
c("state.passkey_file", "storage", "persistence",
"mavweb's WebAuthn credential store, a JSON file outside the database.",
["cmd/mavweb/credentials.go", "internal/webauthn/"], ["passkeys.json", "-passkey-file"],
notes="Held by mavweb, not mavend. A v1 wrapped key blob plus this file together recover the database key with no authenticator.")
c("state.maildata", "storage", "persistence", "mavmaild's seen-UID file, on its own volume so neither side can be restored from the other's backup.",
["cmd/mavmaild/main.go", "docker-compose.yml"], ["-state"], status="built-not-deployed")
c("state.media_blobs", "storage", "persistence", "The blob store shared by vision and the meeting recorder, with a retention loop.",
["internal/media/", "cmd/mavend/vision.go", "cmd/mavend/capture.go"], ["media.Store", "media.Blob"], status="configured-off")
# ---------------------------------------------------------------- shared mutable state
c("state.decision_ring", "shared-state", "in-memory",
"The last few turns' arbitration records. In memory and bounded, because none of his words belong in a table that outlives the diagnosis.",
["internal/decision/ring.go"], ["decision.Ring", "Ring.Push", "Ring.Recent"],
called_by=["core.reactive_handler", "core.daemon_api"])
c("state.clarify_store", "shared-state", "in-memory",
"The parked request behind an open question, as a per-reach stack. Deliberately not persisted: a restart expires the question.",
["internal/dialogue/clarify.go"], ["dialogue.ClarifyStore", "PendingQuestion", "Put", "Push", "Pop",
"CompleteTop", "TakeExpired", "Depth"],
called_by=["core.preroute"])
c("state.pending_act", "shared-state", "in-memory",
"Three single-slot confirmation registers on the handler, under one mutex: a destructive local act, a routine proposal and a mutating Hexis capability. Last-asked wins.",
["cmd/mavend/voice.go", "cmd/mavend/confirm.go", "cmd/mavend/ecosystem_acts.go"],
["reactiveHandler.pending", "pendingRoutine", "pendingHexis", "lastRouted", "confirmTTL"],
called_by=["core.preroute", "core.action_act"],
notes="Single-user box. A second act while one waits overwrites it; each expires after confirmTTL.")
c("state.surfaced_items", "shared-state", "in-memory",
"The Praxis item ids she last read out, in order, so an ordinal has something to mean. Replaced by the next attention digest, with no TTL.",
["cmd/mavend/voice.go", "cmd/mavend/ordinal.go", "cmd/mavend/attentionq.go"],
["reactiveHandler.surfacedItems", "resolveCandidate"], called_by=["core.preroute", "core.q.attention"])
c("state.tick_memo", "shared-state", "in-memory",
"The tick loop's own mutable memory: the last phrase per rule, the last trace, the digest queue, per-routine last-fire maps and the proposal cooldown clock.",
["cmd/mavend/tick.go"],
["tickLoop.lastPhrase", "tickLoop.lastTrace", "tickLoop.digestQ", "routineLast", "morningLast",
"lastProposalAt"],
called_by=["core.tick_loop", "core.daemon_api"],
notes="lastProposalAt is in memory on purpose: a restart is allowed to permit one more announcement.")
# ---------------------------------------------------------------- config
c("cfg.mavend", "config", "configuration",
"The single daemon config. Sets sockets, the database paths, the phraser, the embedder and heads, every reach, every world source, the ecosystem URLs, the tool allowlist and every capability's on/off state, with ${VAR} expansion from a gitignored env file.",
[CFG, "internal/config/", "deploy/telegram.env"],
["config.Load", "config.Config", "DBEncryptionKey", "DefaultWrappedKeyPath", "DefaultRouterThreshold",
"MorningRoutinesFromConfig"],
called_by=["proc.mavend"],
notes="Presence of a block is the on-switch for most capabilities. Absence is silent and, for the calendar, load-bearing.")
c("cfg.compose", "config", "configuration",
"The deployment shape. Five services run; mavcaldav and mavmaild are present as commented-out blocks with their reasoning.",
["docker-compose.yml"], ["x-image", "services", "volumes", "networks"],
notes="Count services against compose, not against `make build`. mavwaked and mavgpud are systemd units on workpc.")
c("cfg.systemd", "config", "configuration",
"The workstation units: the listening client, the GPU supervisor and the ssh tunnel that is the only path to mavend's voice port.",
["deploy/mavwaked.service", "deploy/mavgpud.service", "deploy/maven-voice-tunnel.service", "deploy/asoundrc"],
["ExecStart", "Requires=maven-voice-tunnel.service"])
# ---------------------------------------------------------------- eval
c("eval.router", "test", "evaluation",
"The routing fixture. Scores the real stage 0 set, the classifier and the resident model on a frozen Russian corpus.",
["internal/router/eval/eval.go", "internal/router/eval/ru_routing_v1.json",
"internal/router/eval/ru_ecosystem_v1.json", "internal/router/eval/reach.go", "Makefile"],
["eval", "make eval-router", "make eval-reach"], reads=["router.stage0"],
notes="Calls router.StageZeroGrammars directly, so a grammar change moves the measurement with it.")
c("eval.phrasing", "test", "evaluation",
"The persona checks: address form, feminine self-reference and the cringe list.",
["internal/phraser/eval/checks.go", "Makefile"], ["CheckAddress", "CheckFeminine", "CheckCringe", "make eval-phrasing"])
c("eval.gates", "test", "evaluation",
"The static gates. staticcheck, deadcode and govulncheck pass against a baseline rather than against zero, and fail on a baseline entry whose finding is gone.",
["scripts/analyzers/deadcode.baseline", "scripts/analyzers/staticcheck.baseline", "Makefile"],
["make analyze", "make lint", "make deadcode", "make vuln"],
notes="The deadcode baseline currently accepts 13 unreachable symbols.")
# =================================================================== edges
def process_edges():
e("proc.mavwaked", "bnd.voice_tcp", "tcp", "PushToTalk over ssh tunnel", evidence="deploy/mavwaked.service -addr 127.0.0.1:9100; Requires=maven-voice-tunnel.service")
e("proc.mavenclient", "bnd.voice_tcp", "tcp", "one wav per invocation", evidence="cmd/mavenclient/main.go")
e("proc.mavweb", "bnd.voice_tcp", "tcp", "POST /api/ptt", evidence="cmd/mavweb/main.go handlePTT(w, r, *voiceAddr, ...)")
e("bnd.voice_tcp", "core.voice_server", "in-process", "accept, register Session", evidence="internal/voice/server.go")
e("proc.mavweb", "bnd.ipc", "unix", "three ipc.Client connections", evidence="cmd/mavweb/main.go core/swapConn/turnConn")
e("proc.mavpoll", "bnd.ipc", "unix", "WriteFact", evidence="cmd/mavpoll/main.go -socket /run/maven/mavend.sock")
e("proc.mavcaldav", "bnd.ipc", "unix", "WriteFact", status="built-not-deployed", evidence="docker-compose.yml: commented out")
e("proc.mavmaild", "bnd.ipc", "unix", "IngestMail", status="built-not-deployed", evidence="docker-compose.yml: commented out")
e("proc.mavupdate", "bnd.ipc", "unix", "Ping only", evidence="cmd/mavupdate/main.go; there is no MethodApplyUpdate")
e("proc.e2eprobe", "bnd.ipc", "unix", "typed probe", status="temporary", evidence="cmd/e2eprobe/main.go")
e("bnd.ipc", "core.ipc_server", "in-process", "dispatch", evidence="internal/ipc/server.go serveConn")
e("core.stt_seam", "bnd.worker", "unix", "Transcribe", evidence="cmd/mavend/voicewire.go worker.Dial(cfg.Voice.Stt.Socket)")
e("core.tts_seam", "bnd.worker", "unix", "Synthesize", evidence="cmd/mavend/voicewire.go worker.Dial(cfg.Voice.Tts.Socket)")
e("bnd.worker", "proc.mavsttd", "unix", "/run/maven/stt.sock", evidence="docker-compose.yml mavsttd command")
e("bnd.worker", "proc.mavttsd", "unix", "/run/maven/tts.sock", evidence="docker-compose.yml mavttsd command")
e("core.stt_seam", "ext.cw2_stt", "http", "preferred transcriber, silent fallback", evidence="cmd/mavend/voicewire.go sttSeam; mavend.json workstation.stt")
e("core.model_seam", "proc.mavgpud", "http", "/health probe then completion", status="configured-off", evidence="mavend.json workstation.model_disabled = true")
e("proc.mavsttd", "ext.whispercpp", "in-process", "cgo", evidence="cmd/mavsttd/whisper_handler.go")
e("proc.mavttsd", "ext.piper", "subprocess", "piper argv", evidence="docker-compose.yml mavttsd -piper")
e("proc.mavwaked", "ext.alsa", "subprocess", "arecord / aplay", evidence="cmd/mavwaked/main.go")
e("proc.mavend", "svc.llama_server", "subprocess", "child llama-server", evidence="internal/phraser/server.go; mavend.json phraser.bin_path")
e("proc.mavgpud", "svc.llama_server", "subprocess", "supervised child on the workstation card", evidence="cmd/mavgpud/runner.go")
e("proc.mavpoll", "ext.netdata", "http", "alarms", evidence="docker-compose.yml -netdata http://127.0.0.1:19999")
e("proc.mavpoll", "ext.uptimekuma", "http", "/metrics", evidence="docker-compose.yml -kuma")
e("proc.mavpoll", "ext.zenmoney", "http", "spending", status="configured-off", evidence="docker-compose.yml: the token mount is commented out")
e("proc.mavweb", "ext.nexus", "http", "read-only /ecosystem panel", evidence="docker-compose.yml -nexus http://nexus:9740")
e("proc.mavweb", "ext.praxis", "http", "read-only /ecosystem panel", evidence="docker-compose.yml -praxis")
e("proc.mavweb", "ext.hexis", "http", "read-only /ecosystem panel", evidence="docker-compose.yml -hexis")
def core_edges():
e("core.ipc_server", "core.auth_gate", "calls", "Check before every dispatch", evidence="internal/ipc/server.go Server.Check")
e("core.ipc_server", "core.daemon_api", "calls", "CoreAPI dispatch", evidence="cmd/mavend/main.go ipc.Listen(cfg.SocketPath, coreAPI)")
e("core.ipc_server", "core.mail_intake", "calls", "IngestMailFn bypass", status="configured-off", evidence="cmd/mavend/main.go wireMailIntake")
e("core.ipc_server", "core.modelswap", "calls", "SwapModelFn / ModelStatusFn bypass", status="configured-off", evidence="cmd/mavend/main.go wireModelSwap")
e("core.ipc_server", "core.vision", "calls", "DescribeImageFn bypass", status="configured-off", evidence="cmd/mavend/main.go wireVision")
e("core.ipc_server", "core.capture", "calls", "four Capture* bypasses", status="configured-off", evidence="cmd/mavend/main.go wireCapture")
e("core.ipc_server", "core.speaker", "calls", "speaker enrolment bypass", status="configured-off", evidence="cmd/mavend/main.go wireSpeaker")
e("core.ipc_server", "core.daemon_lock", "calls", "UnlockFn / WrapKeyFn / StepUp", evidence="cmd/mavend/main.go srv.UnlockFn")
e("core.daemon_api", "core.store_api", "embeds", "embedded ipc.CoreAPI", evidence="cmd/mavend/tick_api.go type daemonAPI struct { ipc.CoreAPI ... }")
e("core.daemon_api", "core.intake_api", "wraps", "coreFor() = newIntakeAPI(ipc.NewStoreAPI(st), evBus, time.Now)", evidence="cmd/mavend/main.go coreFor")
e("core.intake_api", "core.event_bus", "publishes", "one envelope per intake write", evidence="cmd/mavend/intake.go")
e("core.daemon_api", "core.reactive_handler", "calls", "chatFn = handler.handleText", evidence="cmd/mavend/boot.go newDaemonAPI")
e("core.reactive_handler", "core.daemon_api", "calls", "handler.api back-patched by upgradeAPI", evidence="cmd/mavend/voice.go upgradeAPI; boot.go d.voiceW.handler.upgradeAPI(api)")
e("core.daemon_api", "core.tick_loop", "reads", "trace, morningStatus, dayPlan", evidence="cmd/mavend/boot.go getTrace / getDayPlan")
e("core.daemon_api", "ext.nexus", "http", "ResolveEntity", evidence="cmd/mavend/tick_api.go daemonAPI.ResolveEntity")
e("core.voice_server", "core.reactive_handler", "calls", "HandlePushToTalk", evidence="internal/voice/server.go Handler")
e("core.telegram_intake", "core.daemon_api", "calls", "api.Chat per inbound message", evidence="cmd/mavend/telegramintake.go chatTurnFn")
e("core.reactive_handler", "core.stt_seam", "calls", "step 1 transcribe", evidence="cmd/mavend/voice.go HandlePushToTalk")
e("core.reactive_handler", "core.preroute", "calls", "steps 1-5e, eleven rungs", evidence="cmd/mavend/voice.go runTurn")
e("core.reactive_handler", "core.turn_route", "calls", "step 6 route", evidence="cmd/mavend/voice.go rt.resolve(ctx)")
e("core.reactive_handler", "core.action_table", "calls", "step 9 applyAction", evidence="cmd/mavend/actions.go actionHandlers")
e("core.reactive_handler", "core.replier", "calls", "step 10, only when the handler returned nothing", evidence="cmd/mavend/voice.go h.replier.Reply(ctx, dec)")
e("core.reactive_handler", "core.tts_seam", "calls", "step 6 synthesize", evidence="cmd/mavend/voice.go h.reply")
e("core.reactive_handler", "core.decision_trace", "writes", "one record per turn", evidence="cmd/mavend/voice.go decision.With(ctx, text)")
e("core.preroute", "core.turn_route", "reads", "the clarify resolver reads the routed decision before claiming", evidence="cmd/mavend/turnroute.go routeForRole")
e("core.preroute", "state.clarify_store", "reads", "parked question", evidence="cmd/mavend/clarify.go")
e("core.preroute", "state.pending_act", "reads", "confirm register", evidence="cmd/mavend/confirm.go")
e("core.preroute", "state.surfaced_items", "reads", "ordinal resolution", evidence="cmd/mavend/ordinal.go")
e("core.turn_route", "router.cascade", "calls", "Route", evidence="cmd/mavend/turnroute.go r.h.router.Route")
e("core.turn_route", "state.dialogue_sessions", "reads", "previous turn for a continuation", evidence="cmd/mavend/turnroute.go dialogueSessions.Get")
e("router.cascade", "router.stage0", "calls", "stage 0, first match wins at 1.0", evidence="internal/router/router.go for i, g := range r.grammars")
e("router.cascade", "router.heads", "calls", "stage 0b, declines below threshold", evidence="internal/router/router.go if r.heads != nil")
e("router.cascade", "router.llm", "calls", "stage 1a, any error falls through", evidence="internal/router/router.go if r.llm != nil")
e("router.cascade", "router.classifier", "calls", "stage 1, the floor", evidence="internal/router/router.go r.classifier.Classify")
e("router.cascade", "router.extractor", "calls", "stage 2 slots", evidence="internal/router/router.go r.extractor.Extract / fillSlots")
e("router.llm", "svc.llama_server", "http", "grammar-constrained completion", evidence="internal/router/llmrouter.go")
e("router.classifier", "router.embedder", "calls", "nearest centroid", evidence="internal/router/classifier.go")
e("router.heads", "router.embedder", "reads", "a fine-tuned COPY of the same encoder", evidence="mavend.json voice.embedder.heads_path; heads_path must never equal model_path")
e("core.action_table", "core.query_chain", "calls", "IntentQuery", evidence="cmd/mavend/actions.go actionHandlers[IntentQuery]")
e("core.action_table", "core.action_fact", "calls", "IntentFact", evidence="cmd/mavend/actions.go")
e("core.action_table", "core.action_reminder", "calls", "IntentReminder", evidence="cmd/mavend/actions.go")
e("core.action_table", "core.action_act", "calls", "IntentAct", evidence="cmd/mavend/actions.go")
e("core.action_table", "core.action_note", "calls", "IntentNote", evidence="cmd/mavend/actions.go")
e("core.action_table", "core.action_chat", "calls", "IntentChat", evidence="cmd/mavend/actions.go")
e("core.action_table", "core.action_system", "calls", "IntentSystem", evidence="cmd/mavend/actions.go")
e("core.action_fact", "core.query_chain", "calls", "a question-shaped fact is answered as a query", evidence="cmd/mavend/actions_fact.go return h.actionQuery(ctx, q)")
e("core.action_fact", "core.action_chat", "calls", "a complaint is answered as chat", evidence="cmd/mavend/actions_fact.go return h.actionChat(ctx, c)")
e("core.action_fact", "state.facts", "writes", "WriteFact source tap:voice or tap:text", evidence="cmd/mavend/actions_fact.go")
e("core.action_fact", "state.memory_vectors", "writes", "prune then insert one vector per key", evidence="cmd/mavend/actions_fact.go pruneFactVectors")
e("core.action_reminder", "state.reminders", "writes", "CreateReminder", evidence="cmd/mavend/actions_reminder.go h.api.CreateReminder")
e("core.action_act", "state.tools", "reads", "enabled allowlist only", evidence="internal/tool/tool.go:164 t.Status != enabled")
e("core.action_act", "core.risk_policy", "calls", "PolicyFor(RiskOf(t)) before every local exec", evidence="internal/tool/tool.go:181")
e("core.action_act", "core.praxis_acts", "calls", "intercepted BEFORE the local executor", evidence="cmd/mavend/actions_act.go:44 handlePraxisAct")
e("core.praxis_acts", "ext.praxis", "http", "acknowledge / resolve / ignore / pin, no tier and no confirm", evidence="cmd/mavend/ecosystem_acts.go:158 a.call(ctx, px, id)")
e("core.ecosystem_hexis_gate", "core.risk_policy", "calls", "RiskOfCapability then PolicyFor, the same policy reused", evidence="cmd/mavend/ecosystem_acts.go:768")
e("core.risk_policy", "core.action_act", "gates", "ErrNeedsAuthedSurface / ErrNeedsConfirm", evidence="internal/tool/tool.go:182-187")
e("core.action_act", "ext.hexis", "http", "confirmed mutating capability", evidence="cmd/mavend/ecosystem_acts.go handleHexisAct")
e("core.action_note", "state.notes", "writes", "WriteNote", evidence="cmd/mavend/actions_note.go")
e("core.action_chat", "core.phraser", "calls", "PhraseChat with dialogue history", evidence="cmd/mavend/actions.go")
for qid, name, resp, f, sym, dest, guesses, boundary in qs:
e("core.query_chain", qid, "calls",
"chain position: %s" % name,
status="implemented", evidence="cmd/mavend/actions_query.go querySources")
e("core.q.search", "ext.searxng", "http", "query string only", evidence="cmd/mavend/actions_query.go querySearch")
e("core.q.kiwix", "ext.kiwix", "http", "ZIM search", evidence="cmd/mavend/actions_query.go queryKiwix")
e("core.q.web", "core.crawl_worker", "calls", "on-demand page fetch", evidence="cmd/mavend/crawls.go onDemandCrawler")
e("core.q.general", "core.phraser", "calls", "the model answers from its own weights, last", evidence="cmd/mavend/actions_query.go queryGeneral")
e("core.q.attention", "ext.praxis", "http", "ListAttention", evidence="cmd/mavend/attentionq.go")
e("core.q.attention", "state.surfaced_items", "writes", "the read-out order, for the next ordinal", evidence="cmd/mavend/attentionq.go")
e("core.q.home", "ext.homeassistant", "http", "device state", status="configured-off", evidence="mavend.json smarthome.enabled = false")
e("core.q.network", "core.netscan", "calls", "bounded LAN scan", evidence="cmd/mavend/netscan.go")
e("core.q.weather", "ext.openmeteo", "http", "forecast", status="configured-off", evidence="wireVoice picks NewStubProvider without a voice.weather block; the deployed config has none")
e("core.q.calendar", "state.facts", "reads", "facts(kind=env, source=caldav:*)", status="partially-wired", confidence="high", evidence="docker-compose.yml: mavcaldav is commented out, so nobody writes them")
e("core.q.memory", "state.memory_vectors", "reads", "vector search over notes and facts", evidence="cmd/mavend/actions_query.go queryMemory")
e("core.q.notes", "state.notes", "reads", "notes-only recall pass", evidence="cmd/mavend/actions_query.go queryNotes")
e("core.q.factbykey", "state.facts", "reads", "current value for a key", evidence="cmd/mavend/actions_query.go queryFactByKey")
e("core.q.money", "state.facts", "reads", "facts(kind=env, source=poll:zenmoney)", status="configured-off", evidence="compose does not mount the zenmoney token")
e("core.q.tasks", "state.tasks", "reads", "task list", evidence="cmd/mavend/actions_task.go")
e("core.q.list", "state.list_items", "reads", "named lists", evidence="cmd/mavend/actions_list.go")
e("core.q.feeds", "state.notes", "reads", "notes with source rss:*", evidence="cmd/mavend/actions_query.go queryFeeds")
e("core.q.dayplan", "core.tick_loop", "reads", "the plan is assembled by the tick loop, not read from a table", evidence="cmd/mavend/tick_morning.go dayPlan; voice.go upgradeAPI comment")
e("core.q.habits", "state.events", "reads", "behaviour profile over recorded events", evidence="internal/memory/behavior.go BuildProfile")
e("core.q.history", "state.facts", "reads", "what he tapped in", evidence="cmd/mavend/historyq.go")
def tick_edges():
e("core.tick_loop", "core.gatherer", "calls", "GatherState, aborts the tick on error", evidence="cmd/mavend/tick.go")
e("core.gatherer", "state.facts", "reads", "the tick snapshot", evidence="internal/loop/gather.go readFact")
e("core.gatherer", "state.reminders", "reads", "due and collapsed", evidence="internal/loop/gather.go collapseReminders")
e("core.gatherer", "state.nudges", "reads", "restraint memory", evidence="internal/loop/gather.go")
e("core.tick_loop", "core.rules", "calls", "ExplainTick, at most one candidate", evidence="cmd/mavend/tick.go loop.ExplainTick")
e("core.tick_loop", "core.phraser", "calls", "PhraseNudge / PhraseReminder", evidence="cmd/mavend/tick.go")
e("core.tick_loop", "core.dispatcher", "calls", "DispatchNudge, RepeatUnacked", evidence="cmd/mavend/tick.go")
e("core.tick_loop", "core.pattern", "calls", "detectPatterns every tick", evidence="cmd/mavend/tick_routines.go")
e("core.tick_loop", "core.morning", "calls", "fireMorningRoutines", evidence="cmd/mavend/tick_morning.go")
e("core.tick_loop", "core.routines", "calls", "fireRoutines and fireAcceptedRoutines", evidence="cmd/mavend/tick_routines.go")
e("core.tick_loop", "state.presence_state", "writes", "savePresence each tick", evidence="cmd/mavend/tick.go savePresence")
e("core.tick_loop", "state.digest_entries", "writes", "gate-suppressed candidates", evidence="cmd/mavend/tick_digest.go")
e("core.tick_loop", "state.tick_memo", "writes", "lastPhrase, lastTrace, digestQ", evidence="cmd/mavend/tick.go")
e("core.tick_loop", "state.facts", "writes", "the cooldown feedback fact from tune()", evidence="cmd/mavend/tick.go tune; internal/loop/feedback.go FeedbackKey")
e("core.dispatcher", "core.sink_voice", "calls", "present", evidence="internal/delivery/channel.go ChannelsFor")
e("core.dispatcher", "core.sink_ntfy", "calls", "away, sev3+", status="configured-off", evidence="wireNtfySink returns nil when disabled")
e("core.dispatcher", "core.sink_telegram", "calls", "away sev4, repeat til ack", evidence="internal/delivery/channel.go")
e("core.dispatcher", "state.delivery_attempts", "writes", "begin before send, complete after", evidence="internal/delivery/dispatcher.go beginOutbox")
e("core.dispatcher", "state.nudges", "writes", "recorded AFTER a successful send", evidence="internal/delivery/dispatcher.go NudgeRecorder")
e("core.sink_voice", "core.voice_server", "calls", "PushToMostRecent on the request conn", evidence="internal/delivery/voicesink/")
e("core.sink_telegram", "ext.telegram", "http", "sendMessage through the SOCKS relay", evidence="mavend.json telegram.proxy")
e("ext.telegram", "core.telegram_intake", "http", "getUpdates long poll", evidence="internal/delivery/telegramsink/intake.go")
def worker_edges():
e("proc.mavend", "core.tick_loop", "spawns", "named worker `tick`", evidence="cmd/mavend/boot.go backgroundWorkers")
e("proc.mavend", "core.fact_enrichment", "spawns", "named worker `fact-enrichment`", evidence="cmd/mavend/boot.go")
e("proc.mavend", "core.memory_eval", "spawns", "named worker `memory-eval`", status="configured-off", evidence="cmd/mavend/boot.go, nil unless configured")
e("proc.mavend", "core.feed_worker", "spawns", "named worker `feed`", evidence="cmd/mavend/boot.go")
e("proc.mavend", "core.crawl_worker", "spawns", "named worker `crawl`", evidence="cmd/mavend/boot.go")
e("proc.mavend", "core.mcp_worker", "spawns", "named worker `mcp`", status="configured-off", evidence="cmd/mavend/boot.go")
e("proc.mavend", "core.home_worker", "spawns", "named worker `home`", status="configured-off", evidence="cmd/mavend/boot.go")
e("proc.mavend", "core.voice_server", "spawns", "named worker `voice`", evidence="cmd/mavend/boot.go")
e("proc.mavend", "core.telegram_intake", "spawns", "bare goroutine, NOT in backgroundWorkers", confidence="high",
evidence="cmd/mavend/telegramintake.go wg.Add(1); go func(){...}()")
e("core.fact_enrichment", "state.facts", "writes", "entity_id and resolution_state", evidence="cmd/mavend/factenrichment.go resolveOne")
e("core.fact_enrichment", "ext.nexus", "http", "Resolve with backoff", evidence="cmd/mavend/factenrichment.go")
e("core.feed_worker", "state.notes", "writes", "one note per item, source rss:<name>", evidence="internal/rss/poller.go")
e("core.feed_worker", "state.facts", "writes", "the feed watermark", evidence="cmd/mavend/feeds.go")
e("core.crawl_worker", "state.facts", "writes", "crawl:hash:<name> watermark", evidence="cmd/mavend/crawls.go hashKey")
e("core.crawl_worker", "state.notes", "writes", "changed page text", evidence="internal/crawl/watch.go")
e("core.mcp_worker", "state.tools", "writes", "proposed rows", status="configured-off", evidence="cmd/mavend/mcp.go propose")
e("core.home_worker", "state.tools", "writes", "proposed destructive rows", status="configured-off", evidence="cmd/mavend/smarthome.go propose")
e("core.netscan", "state.notes", "writes", "one scan record", evidence="cmd/mavend/netscan.go writeScanRecord")
def web_edges():
e("proc.mavweb", "state.facts", "writes", "POST /api/signal presence, POST /api/ambient meeting time", evidence="cmd/mavweb/ambient.go, cmd/mavweb/facts.go")
e("proc.mavweb", "state.tools", "writes", "POST /tools enable/disable", evidence="cmd/mavweb/pages.go handleTools")
e("proc.mavweb", "state.proposed_routines", "writes", "POST /routines accept/dismiss", evidence="cmd/mavweb/pages.go handleRoutines")
e("proc.mavweb", "state.routing_labels", "writes", "POST /api/correct", evidence="cmd/mavweb/main.go handleCorrectAPI")
e("proc.mavweb", "state.facts", "writes", "POST /api/revert voids the latest fact for a key", evidence="cmd/mavweb/main.go handleRevert")
e("proc.mavweb", "state.passkey_file", "writes", "WebAuthn credential store", evidence="cmd/mavweb/credentials.go -passkey-file")
e("proc.mavweb", "state.wrapped_key", "writes", "StoreEncryptionKey after an assertion", evidence="cmd/mavend/main.go srv.WrapKeyFn, called by mavweb")
def storage_edges():
e("state.db", "state.db_file", "persists", "sealed on Close", evidence="internal/store/crypt.go")
e("state.db", "state.db_tmpfs", "persists", "decrypted working copy in RAM", evidence="internal/store/crypt.go")
e("proc.mavseal", "state.db_file", "writes", "recovery re-seal", evidence="cmd/mavseal/main.go")
for t in ["state.facts", "state.reminders", "state.nudges", "state.notes", "state.memory_vectors",
"state.tools", "state.presence_state", "state.events", "state.proposed_routines",
"state.digest_entries", "state.delivery_attempts", "state.ack_sends",
"state.dialogue_sessions", "state.tasks", "state.list_items", "state.routing_traces",
"state.routing_labels", "state.ecosystem_traces", "state.meta"]:
e("state.db", t, "contains", "table", evidence="internal/store/schema.sql and internal/store/migrations.go")
def uncertain_edges():
e("core.action_act", "ext.vikunja_mcp", "http", "an enabled MCP tool executes through tool.Executor.WithMCP",
confidence="medium", status="configured-off",
evidence="cmd/mavend/voicewire.go exec = exec.WithMCP(w.mcp.caller()); no MCP server is enabled, so no such row can exist today")
e("core.action_act", "ext.homeassistant", "http", "an enabled house tool executes through tool.Executor.WithHome",
confidence="medium", status="configured-off",
evidence="cmd/mavend/voicewire.go exec = exec.WithHome(w.home.caller()); smarthome.enabled = false")
e("core.ecosystem", "state.ecosystem_traces", "writes", "one row per ecosystem call",
confidence="medium", evidence="internal/store/ecotraces.go:34 Store.WriteEcosystemTrace; the call site was not read in full")
e("core.memory_eval", "state.notes", "reads", "the recall fixture",
confidence="low", status="configured-off",
evidence="internal/memeval/eval.go references WriteNote; the worker is nil on this deployment so the path was not traced")
e("proc.mavcaldav", "state.reminders", "reads", "the render side publishes pending reminders back as iCal",
confidence="medium", status="built-not-deployed",
evidence="cmd/mavcaldav/render.go; -render-url is off and checkRenderTarget refuses reading its own writes")
e("router.claim", "router.cascade", "calls", "the arbiter that would read claims",
confidence="high", status="planned-unwired",
evidence="internal/router/claim.go: 'Nothing in Route calls this yet.'")
process_edges()
core_edges()
tick_edges()
worker_edges()
web_edges()
storage_edges()
uncertain_edges()
doc = {
"schema": 1,
"generated": "2026-08-25",
"repo": "/mnt/server/home/kami/apps/Maven",
"commit": "5cae33a517ebeca55e2875512766c007b4e7457b",
"working_tree": "dirty: deploy/mavend.json modified (phraser.model_path swapped to maven-instruct-b2), docs/evals/CLAUDE.md modified, two untracked files",
"method": "Read from source, config, compose, systemd units, schema and migrations. No component is inferred from a directory name. Every entry cites the files and symbols it was read from.",
"legend": {
"type": ["process", "service", "worker", "handler", "arbitration", "query_source", "adapter",
"boundary", "storage", "table", "shared-state", "model", "external", "config",
"test", "planned"],
"status": {
"implemented": "on the running path of the deployed configuration",
"configured-off": "wired in code, dark because its config block is absent or disabled",
"built-not-deployed": "a complete binary or service that docker-compose.yml does not run",
"partially-wired": "the code path exists and one end of it has no producer or consumer",
"planned-unwired": "written and tested, called by nothing",
"temporary": "explicitly marked in its own source as disposable",
"dead": "unreachable, accepted in scripts/analyzers/deadcode.baseline"
},
"confidence": {
"high": "read directly from code, config or schema",
"medium": "the wiring is in the source but the full call path was not traced end to end",
"low": "inferred from one reference; treat as uncertain"
}
},
"components": C,
"edges": E,
}
out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "maven-architecture.json")
with open(out, "w") as f:
json.dump(doc, f, indent=2, ensure_ascii=False)
print("components:", len(C), "edges:", len(E))
ids = {x["id"] for x in C}
bad = [(x["from"], x["to"]) for x in E if x["from"] not in ids or x["to"] not in ids]
print("dangling edges:", bad)