Muninn
A personal AI agent that lives in your house, on your hardware, on your terms.
Muninn is the brain of a self-hosted assistant: persistent memory, multi-step tool reasoning, identity-awareness, and proactivity — running as a single Rust binary against your own local LLM. It talks over Matrix (end-to-end encrypted), a terminal UI, a browser, or voice. No cloud, no data leaving your network, no account with anyone.
Named for the GPU server's raven — the one that flies out, gathers what's happening, and brings the knowledge home.
The idea: brain, body, ears & mouth
Most "smart home + AI" projects try to cram an assistant into the home-automation platform. Muninn takes the opposite stance: keep each layer doing the one thing it's great at, and put the thinking somewhere it can actually grow.
┌──────────────────────────────┐
│ MUNINN · the brain │
│ cognition · memory · │
│ identity · tool orchestration│
│ · proactivity │
└───────┬───────────────┬───────┘
controls │ │ speaks / listens
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────────┐
│ HOME ASSISTANT · body │ │ VOICE STACK · ears & mouth │
│ device state · control │ │ wake · STT · TTS · satellites│
│ the deterministic floor│ │ (Wyoming / LVA / your own) │
└─────────────────────────┘ └──────────────────────────────┘
Three rules keep the boundary honest:
- The body works without the brain. A light still turns off through plain Home Assistant even if Muninn (or the LLM) is down. The brain is augmentation, never the only path.
- The brain owns no I/O it can borrow. If a capability would make sense in someone else's house with a different brain, it belongs in Home Assistant or the voice layer — not here. Muninn only holds what's Muninn thinking.
- One boundary per limb, swappable. Control, voice, and spatial context each cross a clean interface, so any layer can be replaced without touching the others.
The result is an assistant that remembers, recognizes who's talking, and notices — without being a single point of failure for your house, and without shipping your life to anyone's cloud.
What it does
Full inventory in FEATURES. Highlights:
Many tools are optional integrations — they only register when their config block is present and their backing service (Home Assistant, Bazarr, Jellyseerr, a Mastodon instance, a self-hosted OSM maps stack, Prometheus/Alertmanager, etc.) is reachable. The core (memory, sub-agent loop, web search, shell, file/code tools) needs none of them.
- Four surfaces, one memory — Matrix (E2EE bot), TUI (terminal), Web (axum + OIDC), and a Voice HTTP receiver, all sharing one store.
- Persistent memory — SQLite + FTS5 + sqlite-vec (schema v31): hybrid retrieval (RRF + 1-hop graph), Ebbinghaus decay, episodic summaries, fact consolidation, correction archival, rolling conversation summaries, per-user dialectic profiles (Honcho-lite), an optional reflective "dream" pass, per-user / per-topic scoping.
- Sub-agent architecture — the main LLM sees only one tool (
agent, ~150 tokens); the real tools run in an ephemeral sub-context. ~70% fewer tool-definition tokens per turn, and tools scale without bloating the conversation. - Identity-aware — a registry bridges Matrix IDs, voice speaker-IDs, and devices into one canonical user, with role-based tool permissions and an onboarding/audit path for unknown speakers.
- Tools — weather, home query/action, music, photos (Immich slideshow), presence (who/what is where), calculate, web search, shell (2-gate safety), camera, image generation, speak, transcribe, file read/write/list, code run, project, memory lookup, scratchpad (per-task working memory), progress, schedule, email, calendar, contacts, youtube, research, subtitles (Bazarr), mastodon, geocode / route (self-hosted maps), media_request / music_request, code_task (delegate coding to Huginn), and self-modification tools (identity / workspace_edit / self_restart).
- Music — full Music Assistant control: play-by-name, favorites, discover, radio, genre/mood/era, volume, shuffle, like/dislike, room targeting, synced multi-room. See MUSIC.
- DJ mode — opt-in radio host: a background worker speaks a short, language-matched intro over each new song (ducked, not muted), grounded in a quick web search for real artist facts; personas + rotating "vibes" live in an editable markdown file. Needs Music Assistant + TTS.
- Email — IMAP read/search/flags + SMTP send over one or more mailboxes (
imap/mail-parser/lettre, default gateway a Mailcow server): list, read, search, send, threaded reply (reply-all), archive, delete, plusextract_events— scan mail for appointments and propose calendar events (confirm-first, never writes the calendar itself; Message-ID dedup ledger skips already-processed mail). Admin-only. - Calendar — appointments with timed reminders Muninn delivers to the conversation (default 1 day + 1 hour before, overridable). One shared calendar with per-event owner + public/private visibility; recurring events (iCal RRULE, DST-safe). Local SQLite is the source of truth, best-effort mirrored to SOGo CalDAV so events show in real calendar clients. Admin-only.
- Contacts — CardDAV name→email/phone lookup + add over the SOGo address book (so "email a contact" resolves the address): search/list/add, hand-rolled
addressbook-queryREPORT + vCard parse. Admin-only. - YouTube — summarize a video from its transcript:
yt-dlppulls auto-captions, a VTT cleaner produces a transcript, an LLM writes a TL;DR + key points. URL host-validated to YouTube. admin/adult. - Research — deep research in one call → a cited markdown brief: plan → SearXNG search → fetch (SSRF-guarded) → synthesize, bounded and backgroundable via
!bg. admin/adult. - Presence & family map — a co-location engine fuses everyone's HA
device_trackers (with GPS-freshness so stale fixes don't ghost), answers "is anyone home? / where is X? / are X and Y together?", and feeds a live people layer + custom drop-pin places to the self-hosted map. - Open-house mode — opt-in (
[tools] open_house): any voice — recognized, guest, or unknown — can control the house (lights, music, photos, presence), while the family-private surfaces (camera, memory) stay gated. - Subtitles —
subtitlesdrives Bazarr to fetch subs for a movie or TV show on demand (re-checks what actually landed), then triggers a Jellyfin scan; a whole-season ask runs in the background and posts results back to the room. - Maps —
geocode(worldwide, Photon) +route(car/bike/foot, North & Central America + Europe) over the self-hosted OSM stack;routeattaches a traced map snapshot of the journey. - Mastodon — native tool that generates an image and posts the daily "On This Day in History" status (3×/day via the scheduler).
- Delegate coding to Huginn —
code_taskhands runnable code jobs to the Huginn builder over NATS (new workspace or existing repo) with live progress + mid-flight steering, opening an interactive child tab in the Hermes console. - Resilient brain — DeepSeek primary with a fallback brain on a separate host and round-robin peer pools, so a single LLM box going down doesn't make Muninn brain-dead.
- Learns from failure — every sub-agent run is recorded to an outcome ledger; an opt-in nightly review distils recurring failures into "constraint" memories that surface back through retrieval (
!failures/!review-failures). - Infra alerts + self-heal — an Alertmanager → Matrix relay with generic, config-driven auto-remediation (first use: re-kicking a wedged Onkyo AVR off WiFi).
- MCP client — stdio + HTTP / streamable-http transports; admin-gated.
- Observability —
⏱timing logs, credential scrubbing, output garbage-guard, loop detector, per-tool progress lines,!stop/steer mid-task, trajectory export + replay (muninn replayre-runs a captured turn so you can A/B a prompt change against the real LLM).
Sub-agent architecture
The main LLM sees one tool definition (agent). When it needs to act, it delegates to a sub-agent that runs in a throwaway context with the full toolset. This saves ~70% on tool tokens per request, scales as tools are added, and isolates tool execution from the conversation.
Try it in 5 minutes (lean core)
You don't need Matrix, Home Assistant, or a voice stack to kick the tires — just an OpenAI-compatible LLM endpoint (Ollama, llama.cpp, vLLM, anything). The TUI runs the same brain locally with bundled SQLite memory:
# Prereqs: Rust toolchain + libsqlite3-dev libssl-dev pkg-config (+ python3 for the calculate tool)
cp muninn-tui.toml.example muninn-tui.toml
$EDITOR muninn-tui.toml # set [llm] api_url + model
cargo run --release -- --tui
That's the whole lean core: an LLM endpoint and a SQLite file. Everything below — Matrix, Home Assistant, voice, image generation, web search — is an opt-in config section. Leave a section out and that limb stays dark.
Embeddings (Ollama) are optional — without them, memory retrieval degrades gracefully to FTS5 full-text search.
Full setup
Muninn ships three surfaces in one daemon — Matrix (always-on bot), TUI (terminal chat), Web (browser, behind OIDC) — plus an optional voice receiver. Templates live in the repo; copy each, drop the .example suffix, fill in secrets. None of the real configs are tracked (all gitignored to keep credentials and your topology out of git).
| Template | Copy to | Used by |
|---|---|---|
muninn.toml.example |
muninn.toml.local |
Daemon (Matrix + Web). Read via MUNINN_CONFIG. |
muninn-tui.toml.example |
muninn-tui.toml |
TUI launcher (separate DB, read-only peer of the daemon's memory). |
coabai-muninn.service.example |
~/.config/systemd/user/coabai-muninn.service |
systemd user unit (%h-portable). |
deploy.env.example |
deploy.env |
Build-and-ship target for ./deploy.sh. |
Daemon (Matrix + Web)
cp muninn.toml.example muninn.toml.local
$EDITOR muninn.toml.local # Matrix creds, LLM URL, optional [tools]/[web] blocks
cargo build --release
MUNINN_CONFIG=muninn.toml.local ./target/release/coabai-muninn # foreground test
# or as a user service:
cp coabai-muninn.service.example ~/.config/systemd/user/coabai-muninn.service
systemctl --user daemon-reload && systemctl --user enable --now coabai-muninn
journalctl --user -u coabai-muninn -f
# subsequent rebuild + restart (set MUNINN_HOST in deploy.env first):
./deploy.sh
Web (optional)
The [web] block adds a browser surface inside the daemon — bearer auth against any OIDC issuer with a userinfo endpoint, bound to 127.0.0.1:7792 by default. Flip the bind to 0.0.0.0 or a VPN IP once you trust your auth setup.
Configuration
See muninn.toml.example for the annotated reference (and the docs server at /config).
| Section | Key fields |
|---|---|
[matrix] |
homeserver, user_id, password, allowed_users |
[llm] |
api_url, model, system_prompt, workspace_path, max_tokens, temperature, top_p, top_k, reasoning_budget, enable_thinking, replay_capture, fallback_url / fallback_model / fallback_api_key, api_peers / fallback_peers (round-robin) |
[memory] |
db_path, extraction_enabled, consolidation_enabled, ollama_url, decay_half_life_days, profile_*, dream_*, failure_review_enabled |
[tools] |
searxng_url, shell_enabled, shell_sandbox_hosts, shell_ssh_remote_allowlist, shell_protected_hosts, ha_url, ha_token, comfy_url, music_*, voice_server_url, stt_url, stt_model (direct STT), maps_url, presence_*, open_house, [[tools.mcp_servers]] |
[tools.bazarr], [tools.jellyfin] |
enabled + base_url + api_key — Bazarr subtitle fetch + optional Jellyfin auto-scan; admin/adult/child |
[tools.mastodon] |
enabled + instance_url + access_token; admin-only |
[alertmanager] |
webhook → Matrix relay; [[alertmanager.remediation]] blocks (alertname, command, cooldown_secs, description) for config-driven auto-remediation |
[tools.email], [tools.calendar] |
enabled master toggle + [[…accounts]] array tables (IMAP+SMTP mailboxes / SOGo CalDAV calendars); admin-only. [tools.calendar] default_reminders sets the lead times |
[tools.contacts] |
enabled + carddav_url + user/password (SOGo address book); admin-only |
[tools.youtube] |
enabled + binary (yt-dlp path) + default_lang; admin/adult |
[tools.research] |
enabled + max_subquestions / results_per_search / max_page_chars / fetch_pages; reuses [tools] searxng_url; admin/adult |
[voice], [heartbeat] |
optional surfaces/workers; [[users]], [[devices]], [[persona_voices]] array tables |
[docs] |
port |
LLM sampling (per-request)
| Model | temp | top_p | top_k | reasoning | notes |
|---|---|---|---|---|---|
| Qwen-class MoE | 0.6 | 0.95 | 20 | off | some llama.cpp builds break tool calls with thinking on |
| Gemma-class | 1.0 | 0.95 | 64 | on (budgeted) | thinking helps tool accuracy |
Both need llama.cpp --jinja for tool-template parsing.
Commands
| Command | Description |
|---|---|
!status |
Uptime, memory stats, link count, embedding coverage, token counts |
!memories / !forget <q> / !links <id> |
Inspect / delete / graph memories |
!consolidate / !decay / !graveyard / !embed |
Memory maintenance |
!topics · !skills · !profile |
Topic, skill, and per-user profile management |
!failures / !review-failures |
Failure-learning review · distil failures into constraint memories |
!schedules / !schedule … |
Scheduled tasks |
!bg-list / !bg-cancel |
Backgrounded tasks (!bg <text> to force-background) |
!stop |
Stop the current task (or steer it mid-flight with plain language) |
!ping · !clear · !help |
Health check · reset context (memories kept) · list commands |
An embedded docs server (axum) starts automatically at http://localhost:7790.
Status
This is a personal project being prepared for a wider audience. It has a lot of moving parts by design — that depth is the point — but the lean core (TUI + an LLM + SQLite) runs on its own, and every integration is opt-in. Expect rough edges in setup ergonomics; issues and notes welcome.
License
Not yet chosen for public release — see OPEN_SOURCE_PREP. Currently private.