MuninnDB uses a two-layer auth model. The layers are separate because they serve different actors with different needs.
In every traditional database, reads are transparent. A SELECT in Postgres doesn't modify row weights. A GET in Redis doesn't affect TTLs. You can give someone read access and be certain that their queries won't alter the data landscape for anyone else.
In MuninnDB, reads are not transparent. When you activate a memory:
- Its access count increases, which raises its stability score
- The Hebbian weights between co-activated engrams strengthen
- Its temporal score refreshes — it becomes "recent" again
- RRF fusion scores shift for the next retrieval by anyone in that vault
This is the cognitive model working correctly. A brain that remembers something is not the same brain it was before. But it means that a careless read-only consumer can silently reshape the vault's learned relevance for every other user.
The auth model is designed around this reality.
Admin users access the system operator layer: the web UI, the shell (muninn shell), and vault management endpoints. They do not normally interact with vault data directly — that is what API keys are for.
First run: MuninnDB prints a generated root password on the first startup. Save it. Change it via the web UI afterward. The password is never printed again.
┌─────────────────────────────────────────┐
│ MuninnDB — First Run Auth │
│ │
│ Admin username: root │
│ Admin password: xK9mP2nQ4rT7wY1aZb3c │
│ │
│ Change this password after first login. │
└─────────────────────────────────────────┘
Admin credentials authenticate to:
- Web UI — session cookie, 24-hour TTL
- Shell — prompted at
muninn shell, no session stored
A vault is either open (no API key required) or locked (API key required). The built-in default vault ships open out of the box so that any MCP client can connect without configuration. Additional vaults you create start locked and must be explicitly opened.
Open vault requests currently run in full mode unless a caller presents a different API key. Use an observe key when you need read access without cognitive-state writes.
A vault can have multiple API keys — one per integration point. You might have:
vault: default
mk_abc... label: "ai-agent" mode: full
mk_def... label: "analytics-dash" mode: observe
mk_ghi... label: "backup-exporter" mode: observe
| Mode | Reads | Cognitive state writes | Use case |
|---|---|---|---|
full |
Yes | Yes — temporal scores refresh, Hebbian weights update, access counts increment | AI agents, primary integrations, anything that is part of the brain |
observe |
Yes | No — mutating REST routes and gRPC RPCs return 403 before the engine is reached, and on MCP every tool classified mutating is refused at dispatch; engine-layer cognitive mutations are also suppressed |
Dashboards, analytics, read-only partners, exports |
The observe mode exists because the vault's cognitive state is the thing of value. A dashboard reading engrams 1000 times a day should not inflate access counts and distort what the AI agent sees as relevant. observe keys see the brain; they don't affect it, and semantically mutating REST routes are rejected.
mk_xK9mP2nQ4rT7wY1aZb3cD5eF6gH7iJ8kL9m
│ └─────────────────────────────────────── 32 random bytes, base64url encoded
└── prefix identifies MuninnDB API keys
Keys are 46 characters. The raw bytes are generated with crypto/rand. The token itself is the credential — MuninnDB stores only a SHA-256 hash of the raw bytes, so a compromised database file does not expose valid tokens.
Tokens are shown once at creation and never again. Copy them immediately.
Include the key as a bearer token on every request:
curl http://127.0.0.1:8475/api/engrams?vault=default \
-H "Authorization: Bearer mk_xK9m..."The key implicitly identifies the vault. If the key belongs to default and the request specifies a different vault, the request is rejected.
For body-based REST routes, the vault must be supplied as ?vault=. Some routes allow vault to be in the JSON body for compatibility but this will be deprecated soon. If both are present, they must match.
Capabilities are a second vault credential type, designed for short-lived, scoped access in agentic workflows. Where an mk_ key is a long-lived integration credential you create and rotate by hand, a cap_ token is minted programmatically, carries a mandatory expiry, and cannot escalate its own privileges.
mk_ API key |
cap_ capability |
|
|---|---|---|
| Lifetime | Long-lived, until revoked | TTL-bound (default 168h / 7d) |
| Created by | Admin, via the admin API | muninn_create_workflow_vault tool, programmatically |
| Can mint vaults? | Yes (with full mode + opt-in) |
No — structural recursion guard |
| Auth identity | IsAPIKey |
IsCapability |
| Mode-enforced | Yes (full / observe / write) |
Yes — same guards |
cap_xK9mP2nQ4rT7wY1aZb3cD5eF6gH7iJ8kL9m
│ └─────────────────────────────────── 32 random bytes, base64url encoded
└── prefix identifies MuninnDB capabilities
Like mk_ keys, the raw bytes are generated with crypto/rand and only a SHA-256 hash is persisted — the token itself is the credential. The defining difference is the mandatory ExpiresAt: a capability with no TTL is refused at mint time.
A cap_ token resolves to IsCapability=true, distinct from IsAPIKey. This distinction is load-bearing:
- Mode-enforced identically. A
fullcap can mutate cognitive state; anobservecap cannot — the sameReadOnlyGuard/denyReadOnlyMutationpaths apply. - Rejected at the privileged tool boundary.
muninn_create_workflow_vaultrequiresIsAPIKey && full. Because capabilities authenticate asIsCapability, they are rejected at dispatch. Recursion is structurally impossible, not merely policy.
MCP-only authentication. cap_ tokens are resolved on MCP transports only (stdio, SSE, HTTP-streamable). The REST (port 8475) and gRPC transports call only ValidateAPIKey, so they accept mk_ keys but not cap_ tokens — a cap_ bearer on REST/gRPC is rejected as an invalid key. Use mk_ keys to drive those transports, or reach the vault over MCP with the cap_ token.
The MCP tool muninn_create_workflow_vault creates a workflow vault and mints a full-mode capability for it in one call. It is opt-in and off by default.
Opt-in (operator):
export MUNINN_AGENT_VAULT_CREATE=1When unset (or any value other than 1), the tool returns forbidden: agent vault creation disabled even if the caller holds a valid full-mode mk_ key. The caller must also be an mk_ key in full mode — a cap_ bearer is rejected by the recursion guard.
Call (MCP):
{
"name": "wf-acme-sprint",
"label": "acme-worker",
"ttl_hours": 168
}| Arg | Default | Notes |
|---|---|---|
name |
auto wf-<8hex> |
1-64 lowercase alphanumeric / hyphen / underscore |
label |
agent-minted |
Stamped on the capability for audit and listing |
ttl_hours |
168 (7 days) |
Matches the working preset's 7-day retention |
Response:
{
"vault": "wf-acme-sprint",
"capability_id": "A1B2C3D4",
"capability_secret": "cap_xK9m...",
"mode": "full",
"expires_at": "2026-07-23T12:19:21Z",
"auto_evap_days": 7,
"warning": "capability_secret is shown once; distribute it to worker agents. The vault auto-evaporates engrams after 7 days."
}The vault is created with the working preset (default cognition + 7-day auto-evaporation) and multi_user enabled, so the agent guide steers worker agents toward per-user-scoped recall. capability_secret is returned once — distribute it to worker agents out-of-band.
- Per-call: the
ttl_hoursarg (default168). - Operator-wide ceiling:
MUNINN_WORKFLOW_CAP_TTL_HOURSclamps the per-call value — when set, a caller'sttl_hoursis honored only up to this fleet-wide maximum, and any larger request is capped.
- Bearer token. A
cap_is a bearer credential — anyone holding it can access the vault at its mode until it expires or is revoked. The TTL bounds exposure if a token leaks via logs or transcripts; it does not prevent the leak. Distribute secrets out-of-band and never log them. - Shown once.
capability_secretappears in the MCP response and never again. The store holds only a SHA-256 hash. - Recursion-proof by construction. The privileged tool checks
IsAPIKey && fullat dispatch. Capabilities authenticate asIsCapabilityand cannot satisfy that check, so no capability can mint another capability — a leakedcap_cannot spiral into unauthorized vault creation.
curl -X POST http://127.0.0.1:8475/api/admin/keys \
-H "Content-Type: application/json" \
-d '{
"vault": "default",
"label": "my-agent",
"mode": "full"
}'Response:
{
"token": "mk_xK9m...",
"key": {
"id": "A1B2C3D4",
"vault": "default",
"label": "my-agent",
"mode": "full",
"created_at": "2026-02-20T..."
}
}curl "http://127.0.0.1:8475/api/admin/keys?vault=default"Token values are not returned. You see the key metadata (ID, label, mode, created date) only.
curl -X DELETE "http://127.0.0.1:8475/api/admin/keys/A1B2C3D4?vault=default"Revocation is immediate. The token stops working on the next request.
The default vault is created as public on first run — no API key required. Any additional vault you create starts locked (fail-closed) until you explicitly open it.
To open a vault (allow unauthenticated access):
curl -X PUT http://127.0.0.1:8475/api/admin/vaults/config \
-H "Content-Type: application/json" \
-d '{"name":"myvault","public":true}'To lock a vault (require an API key):
curl -X PUT http://127.0.0.1:8475/api/admin/vaults/config \
-H "Content-Type: application/json" \
-d '{"name":"default","public":false}'Any vault without an explicit config requires an API key. Only the default vault is pre-configured as public by Bootstrap.
Plasticity controls the cognitive pipeline for a vault — how it learns, forgets, and traverses connections between engrams. Each vault can have its own plasticity settings independent of others.
Five preset profiles are available: default (balanced Hebbian + temporal), reference (preserves with strong Hebbian bonds), scratchpad (rapid fading, minimal history), knowledge-graph (rich traversal, strong associative learning), and working (default cognition with 7-day auto-eviction for shared workflow scratch vaults; pair with multi_user per RFC #597).
Get the current plasticity configuration for a vault:
curl "http://127.0.0.1:8475/api/admin/vault/default/plasticity" \
-H "Authorization: Bearer <admin-session>"Response includes both the saved configuration and the fully resolved values (preset merged with overrides):
{
"config": {
"preset": "default",
"hebbian_enabled": true,
"temporal_halflife": 30
},
"resolved": {
"hebbian_enabled": true,
"temporal_enabled": true,
"hop_depth": 2,
"semantic_weight": 0.6,
"fts_weight": 0.3,
"relevance_floor": 0.05,
"temporal_halflife": 30,
"hebbian_weight": 0.5,
"temporal_weight": 0.4,
"recency_weight": 0.3,
"traversal_profile": ""
}
}Update plasticity for a vault:
curl -X PUT "http://127.0.0.1:8475/api/admin/vault/default/plasticity" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <admin-session>" \
-d '{
"preset": "knowledge-graph",
"temporal_halflife": 60,
"traversal_profile": "causal"
}'Configuration fields:
| Field | Type | Range | Purpose |
|---|---|---|---|
preset |
string | default | reference | scratchpad | knowledge-graph | working |
Base cognitive profile; overrides applied on top |
hebbian_enabled |
bool | — | Enable/disable Hebbian co-activation learning. Symmetric (COG-32): gates weight updates, association decay, and the read-side boost recall applies from association weights |
temporal_enabled |
bool | — | Enable/disable time-based temporal scoring |
multi_user |
bool | — | Vault shared by multiple users/agents: guide + recall hints steer clients to per-user scoped recall; where_left_off/session flagged vault-global |
hop_depth |
int | 0–8 | BFS hops for associative retrieval; higher = broader context |
semantic_weight |
float | 0–1 | Multiplier for semantic similarity in fusion scoring |
fts_weight |
float | 0–1 | Multiplier for full-text keyword match scoring |
relevance_floor |
float | 0–1 | Minimum relevance score; prevents memories from becoming invisible |
temporal_halflife |
float | >0 | Days before an engram reaches half-life |
traversal_profile |
string | default | causal | confirmatory | adversarial | structural |
Link traversal strategy; empty = auto-infer |
predictive_activation |
bool | — | Enable/disable Predictive Activation Signal (PAS); default: true |
pas_max_injections |
int | 1–20 | Max transition candidates injected per activation; default: 5 |
A vault is a single cognitive entity. All connections with full keys participate in that entity's learned state equally — there is no per-user relevance. The vault's access patterns, Hebbian weights, and temporal scores reflect the collective behavior of every full connection.
This is a deliberate design decision:
Why not per-user cognitive state?
If each user had their own relevance weights, the vault would have N brains instead of one. You'd lose the emergent collective intelligence — the thing that makes a shared knowledge base useful is that everyone's usage teaches the system what matters to the group. Per-user weights would also multiply storage requirements for every engram by the number of users.
If you need isolation, use separate vaults. A vault per service, per project, or per person gives you isolated cognitive states. Multiple keys into one vault gives you a shared brain with controlled access.
| Property | Detail |
|---|---|
| Token storage | SHA-256 hash only — plaintext never persisted |
| Admin passwords | bcrypt with default cost |
| Session tokens | HMAC-SHA256 signed, 24h TTL, HttpOnly cookie |
| Transport | HTTP by default; serve TLS natively (see tls.md) or behind a TLS-terminating proxy |
| Key revocation | Immediate, no grace period |
| Observe isolation | Enforced at both the transport layer (ReadOnlyGuard on REST, denyReadOnlyMutation on gRPC) and the engine activation layer — not just an honor system |
| Encryption at rest | Not built-in — use OS/volume encryption; see self-hosting guide |
| Capability tokens | TTL-bound bearer; SHA-256 hash only (plaintext never persisted); recursion-guarded at dispatch — IsAPIKey is required to mint, so no cap_ can mint another |
Existing vaults default to public: true. Nothing breaks. You add auth incrementally:
- Admin user is created on first run with the new binary
- All existing vaults continue to work without keys
- Lock specific vaults by setting
public: falsevia the admin API - Generate keys for your integrations
- Update your integrations to include
Authorization: Bearer mk_...
You can lock vaults one at a time while rolling out keys to your services.
See also: Self-Hosting · Quickstart