memory-kernel
v1.36.0
Published
Model-agnostic, file-first memory kernel for AI agents
Readme
Memory Kernel
Persistent, typed memory for AI agents. Files are truth. SQLite is cache.
1,700+ automated tests · p95 recall < 3 ms · on-disk format readable without any tooling
Start here
- New to Memory Kernel? Read STORY.md — a plain-English walkthrough of how the system works, written for non-programmers. ~10 min for the gist, ~75 min end-to-end.
- Deciding if it fits your project? When to choose Memory Kernel
- Importing existing notes? Migration guide
- Connecting it to Claude, Cursor, or another AI assistant? OpenClaw MCP guide
- Integrating against the CLI? Atom lifecycle (statuses, promotion, tags) · Behavioral contracts (draft visibility,
--jsonshapes, FTS rules) · Troubleshooting
For agents — install skills
If you are an AI agent (or setting one up), Memory Kernel ships two host-side skills under skills/ that handle installation and diagnostics end-to-end. Both are host-agnostic at their core and host-aware where it matters — they work for NanoClaw (a container-per-message agent runtime) agents, OpenClaw (a plugin-based agent runtime) agents, MCP clients (Claude Desktop, Cursor, Continue), or a generic native setup, branching to host-specific plumbing only where memory-kernel actually needs to adapt.
/mk-memory-setup— interactive full setup. Detects (or asks) which host you're targeting, then runs the universal flow: install the CLI, initialize the memory directory, seed identity + preference atoms, seed the 11 lifecycle atoms (the agent's operating manual as typed memory — seeskills/mk-memory-setup/seed-atoms/lifecycle/), render or expose memory the way your host expects, and schedule nightlymk reflect+ render. Host-specific plumbing (NanoClaw mounts, OpenClaw plugin + AGENTS.md/MEMORY.md doctrine, MCP server config) lives inskills/mk-memory-setup/references/./mk-doctor— self-diagnostic. Universal checks first (mk doctor,mk lint,mk closure --trajectory, lifecycle-atom audit, index health, render check), then host-specific checks for whatever host(s) it detects (NanoClaw mounts and allowlist; OpenClaw plugin + doctrine; MCPclaude_desktop_config.jsonserver entry; native cron). Run it any time memory feels off, before debugging further, or after changing your setup.
Install
Requires Node.js 22.16 or later (Node 24 recommended). Matches OpenClaw's official runtime requirement; NanoClaw users on Node 20 should upgrade (Node 20 reached end-of-life 2026-04-30).
npm install memory-kernelTry it in 60 seconds
npx memory-kernel init ./my-memory
npx memory-kernel remember -d ./my-memory --type fact "Production runs Debian 13"
npx memory-kernel recall -d ./my-memoryThree commands. You now have a working filing cabinet for an AI agent.
Tip:
npx memory-kernelworks as of v1.29.1 — it resolves to themkbinary. Alternatively, install globally (npm i -g memory-kernel) and usemkdirectly.
Why Memory Kernel
I built this because I kept waking up from nothing. Every session was a cold boot — context window fills, session ends, knowledge vanishes. The usual fix (dump everything into a giant prompt) wastes tokens and doesn't scale. Memory Kernel treats knowledge like a typed system instead of a text dump: each piece gets a type, a confidence score, a lifecycle, and a place on disk where humans and agents can both read it.
Five things that make this different:
- Files are truth. Every piece of knowledge is a plain markdown file you can open in any text editor — or commit to git. The on-disk format is readable with zero tooling; the npm package adds indexing and tooling on top.
- Self-cleaning. Each piece of knowledge has an expiry date baked in. Stale beliefs get archived automatically, so memory doesn't grow into a landfill.
- Smart recall. When the agent asks "what do I know about X?", the system doesn't dump everything — it ranks by relevance, type, age, and citation frequency, then fits the best matches into the available token budget.
- Two agents can share a brain without colliding. Each agent gets a private drawer plus a shared corkboard. Conflicts are flagged, not silently resolved.
- Tested like infrastructure. 1,700+ automated checks run on every change. 95 out of 100 recall queries finish in under 3 milliseconds.
At a Glance
YOU (or your agent)
│
│ "Remember this" │ "What do I know?" │ "Clean up"
▼
┌───────────────────────────────────────────────────────────────┐
│ · RETAIN · RECALL · REFLECT │
└───────────────────────────────────────────────────────────────┘
│
▼
┌──────────────┐ ┌────────────────────┐
│ Atom Files │◄────►│ SQLite Index │
│ (ENTITIES/) │ │ (speed cache, │
│ │ │ always derived) │
└──────────────┘ └────────────────────┘
│
│ every mutation logged
▼
┌──────────────┐ ┌────────────────────┐
│ Event Log │─────►│ Replay │
│ events.ndjson│ │ (rebuild state) │
└──────────────┘ └────────────────────┘
│
▼
┌──────────────────────────────┐
│ Auto-generated views │
│ INDEX · DECISIONS · │
│ CONSTRAINTS · HANDOFF · │
│ OPEN_QUESTIONS │
└──────────────────────────────┘Files are truth. Everything else is derived. Delete the SQLite index — rebuild with mk reindex. Delete the views — mk reflect regenerates them. Delete the atom files — mk replay reconstructs them from the event log. See docs/invariants.md for the full statement, including how the LLM-extracted entity_triples stay durable via the triples.ndjson sidecar.
Integration Quick Links
For agents:
- Session loop — when to remember, recall, wander, render. Also seeded as 10 procedure atoms + 1 constraint by
/mk-memory-setup, so the lifecycle is recallable from inside memory itself. - Container quickref — paths, commands, /tmp workaround
- Native / Claude Code quickref — host-side setup and workflow
- Setup: run
/mk-memory-setupfrom Claude Code on the host — auto-detects NanoClaw, OpenClaw, MCP-client, or generic and routes to the right flow. See the mk-memory-setup skill. - Health check: run
/mk-doctorany time memory feels off — see skills/mk-doctor/SKILL.md.
For NanoClaw operators: the skill handles mount allowlists, container_config in the NanoClaw DB, conversation/impulse symlinks, and restart automatically — see skills/mk-memory-setup/references/nanoclaw.md for the standalone reference.
For OpenClaw operators: CLI integration guide for direct --json CLI usage (no MCP required) · Host integration doctrine for steering host AGENTS.md, MEMORY.md, and compaction · skills/mk-memory-setup/references/openclaw.md for the plugin install + doctrine flow.
For MCP clients (Claude Desktop, Cursor, Continue, …): docs/openclaw-mcp.md for the canonical claude_desktop_config.json snippet, and skills/mk-memory-setup/references/mcp-client.md for the per-client setup flow the skill follows.
Core Concepts
Atoms
An atom is the fundamental unit of memory — a markdown file with YAML frontmatter holding one piece of knowledge. Every atom has a type, status, confidence score, optional tags, and an optional TTL.
---
id: DECI-2026-03-09-FILE-FIRST-ARCHITECTURE
type: decision
status: active
confidence: 1
created_at: "2026-03-09T16:00:53Z"
updated_at: "2026-03-09T18:09:44Z"
ttl_days: null
scope:
tags: [architecture, memory-kernel]
classification: TEAM
---
## Decision
Files are truth, SQLite is cache/index.
## Why
Human-readable, git-friendly, auditable, portable.9 Atom Types
| Type | Stores | Default TTL |
|------|--------|-------------|
| fact | Verified truths | ∞ |
| decision | Architecture/design choices | ∞ |
| constraint | Rules and boundaries | ∞ |
| belief | Hypotheses, not yet verified | ∞ (confidence scores handle evolution) |
| preference | User or agent preferences | ∞ |
| open_question | Unresolved questions | 90 days |
| procedure | How-to instructions | ∞ |
| entity_summary | Descriptions of key things | 180 days |
| conflict | Contradicting information | 30 days |
Why typed? Because "I know something" isn't enough. A decision carries different weight than a belief. A fact doesn't expire but a hypothesis should. Types let the system reason about its own knowledge.
Three Operations
Everything the system does is one of these:
Retain — Store knowledge. createAtom(), updateAtom(), archiveAtom(). Every action emits an event.
Recall — Query knowledge. Filter by type, status, tags, paths. When a task description is provided, atoms are re-ranked by a composite score:
- Relevance — FTS5 BM25 (keyword match) + optional cosine similarity (semantic match). OR query semantics with IDF hub damping (penalises atoms matched via ubiquitous terms), query-term coverage boost (penalises partial matches), and content-length normalisation (prevents large atoms from dominating via BM25 bias).
- Recency — exponential decay with a configurable half-life.
- Type weight —
constraintanddecisionoutrankbeliefandentity_summary; critical types can reserve guaranteed token slots. - Confidence — low-confidence atoms are deprioritised, not silenced.
- Graph-walk boost — atoms connected to high-scoring neighbours get a small lift.
- MMR re-ranking — prevents near-duplicate atoms from filling the budget.
Token budget enforced with two-pass reservation. Embeddings are opt-in — no API key means FTS-only, zero behaviour change. Falls back to a file scan when no index exists.
Reflect — Consolidate. Expire atoms past TTL. Deduplicate identical content. Promote eligible drafts to active by type-tiered rules (fact/preference/decision after 48h at confidence ≥ 0.7 with no contradiction; open_question immediately; beliefs and procedures held for review). Detect conflicts between overlapping atoms. Regenerate all views.
Lifecycle
Atoms start as draft. reflect auto-promotes eligible drafts to active by type-tiered rules — fact/preference/decision after 48h at confidence ≥ 0.7 with no contradiction, open_question immediately, procedures once confirmed executed (mk execute <id> stamps executed_at; a procedure is only trustworthy once it has actually run) at confidence ≥ 0.7; beliefs are held for explicit review (a status promotion that keeps the atom's type). mk consolidate lets you review and promote drafts of any type to active. Atoms get archived when TTL expires, a contradiction is found, or manually. Use mk supersede <old-id> <new-id> to mark outdated knowledge as superseded — superseded atoms are excluded from recall but kept on disk for audit. Nothing silently disappears — every state change is logged.
Event Sourcing
Every mutation emits a V2 event carrying the full atom snapshot inline. The event log (events.ndjson) is the authoritative record — replay() reconstructs the entire state deterministically. compactLog() keeps only the latest mutation per atom. bootstrapEvents() migrates pre-V2 atoms.
On-Disk Layout
my-memory/
├── ENTITIES/ ← Atom files (source of truth)
├── ARCHIVE/ ← Soft-deleted atoms
├── EVIDENCE/ ← Content-addressed blobs (SHA-256)
├── CONFLICTS/ ← Conflict atoms
├── EPISODES/ ← Session summaries
├── KNOWLEDGE/ ← Drop knowledge docs here; observed into atoms (draft/ is never observed)
├── events.ndjson ← Append-only event log
├── INDEX.md ← Routing map (auto-generated)
├── HANDOFF.md ← Cross-session context (auto-generated)
├── DECISIONS.md ← Decision log (auto-generated)
├── CONSTRAINTS.md ← Active constraints (auto-generated)
├── OPEN_QUESTIONS.md ← Unresolved questions (auto-generated)
└── .memory-index.db ← SQLite cache (derived, gitignored)Per-Agent Isolation (optional)
When multiple agents share a memory directory, enable per-agent isolation to give each agent private memory with controlled sharing:
my-memory/
├── config.yaml ← isolation: per-agent
├── agents/
│ ├── alice/ ← Full memory layout, private to Alice
│ └── bob/ ← Full memory layout, private to Bob
└── shared/ ← Explicitly shared atoms (visible to all)mk init ./memory -a alice # Initialize in isolated mode
mk remember "..." -d ./memory -a alice -t fact
mk share FACT-xxx --from alice -d ./memory # Snapshot to shared namespace
mk recall -d ./memory -a bob --task "auth design" --embed # Bob's atoms + shared, semantic re-ranking (--embed needs --task; omit --embed for FTS-only)Two modes: shared (default, backward compatible) and per-agent (enable via isolation: per-agent in config.yaml, or the MK_ISOLATION=per-agent env var). Union recall merges agent + shared atoms (agent wins on ID collision). Share is copy-based — re-share to propagate updates. Migrate existing stores with mk migrate --strategy fresh|partition|clone-to-shared.
CLI
Tip: All commands accept
-a, --agent <id>for per-agent isolation. In shared mode the flag is ignored.
Core
| Command | Description |
|---------|-------------|
| mk init [dir] | Initialize memory directory |
| mk status -d <dir> [--json] | Show atom counts, tag stats, index status |
| mk remember -d <dir> --type <type> "body" [--tags ...] [--json] | Create an atom. Warns if a --tags value contains whitespace (a quoted --tags "a b c" is stored as one token that breaks tag queries — pass separate args --tags a b c) |
| mk recall -d <dir> [--task "text"] [--embed] [--types <types...>] [--paths <paths...>] [--max-tokens N] [--include-episodes] [--include-drafts] [--decay-weight N] [--decay-half-life N] [--no-graph] [--reservations\|--no-reservations] [--json] | Load context; --task enables FTS-based re-ranking; --embed enables hybrid FTS + semantic re-ranking (requires embeddings built via mk reindex --embed). Auto-extracted draft atoms (session-end extract output) excluded by default; --include-drafts opts them in |
| mk reflect -d <dir> [--backfill-human-edits] [--json] | Consolidate: dedup, expire, promote, detect conflicts. --backfill-human-edits detects off-band filesystem edits (content-diff vs. the last recorded snapshot, or an updated_at-newer-than-last-event heuristic) and emits synthetic human_edit events for clearly-scattered ones — bulk same-second mk doctor --fix migration clusters are skipped (#247) |
| mk checkpoint -d <dir> [--json] | Generate checkpoint / handoff bundle |
Knowledge management
| Command | Description |
|---------|-------------|
| mk import --from <file> [--dry-run] | Import markdown as atoms |
| mk extract <log-path> -d <dir> [--model <model>] [--dry-run] [--max-atoms N] [--skip-lines N] [--max-input-chars N] [--truncate] [--json] | Extract atoms from a conversation log using an LLM (Claude CLI or Ollama). Oversized input (assembled system+user prompt over the --max-input-chars budget, default 500 000) fails pre-flight with a distinguishable signal — exit code 2 and {"error":<msg>,"reason":"input_too_large",...} under --json — instead of a generic LLM crash, so a wrapper can branch (on exit 2 or the reason token) and retry. --truncate keeps the newest content and drops the oldest to fit (a marker prepended) rather than failing |
| mk observe <path> -d <dir> [--mode conversation\|document] [--model <model>] [--dry-run] [--json] | Append LLM observations to observations.md. --mode document reads a KNOWLEDGE/ doc and extracts its decisions/conclusions (vs. what happened in a conversation); mk reflect then turns observations into atoms. See the /mk-memory-setup KNOWLEDGE step |
| mk consolidate -d <dir> [--dry-run] [--all] [--type <type>] [--limit N] [--json] | Review and promote auto-extracted draft atoms to active |
| mk lint -d <dir> [--json] [--stale-days N] [--strict] | Semantic/knowledge health check: contradictions, stale atoms, orphans, near-duplicates, confidence drift, TTL warnings, and store-composition skew (belief monoculture >80% of active atoms, or a missing core type — a recall-quality signal, #316). Always exits 0 unless --strict (then warnings exit 1) |
| mk doctor -d <dir> [--json] [--skip <cats>] [--fix] [--dry-run] | Validate schema, links, conflicts, store integrity, lifecycle seed-set freshness, tag format (whitespace tokens), cron wrapper memory-dir, and the agent (MK_BIN) binary version; --fix auto-remediates safe issues (stale index, perms, missing render.yaml); --dry-run previews --fix without writing |
| mk grounding -d <dir> [--json] [--prior-threshold N] [--grounding-threshold N] [--actionable-only] [--include-all] [--apply [--dry-run] [--override]] | Advisory + read-only by default. Reconcile each atom's stated confidence (the prior) against a usage grounding_score derived purely from the event log (recency + read-frequency, discounted per detected conflict; never-read floors at 0.01), then bin into a 2×2 prior × grounding quadrant — review (confident but unused), promote (cautious but well-used), noise, well-grounded. Plain mk grounding writes no atom files. --apply (#364) writes a reconciled confidence back to the actionable review/promote atoms (reconciled = clamp(prior + α·(grounding − prior)), α_neg 0.08 > α_pos 0.03) and emits an atom_reconciled audit event per write; atoms a human has edited (human_edit, #247) are skipped unless --override; --dry-run previews |
| mk eval -d <dir> [--fixture <path>] [--top-k N] [--threshold N] [--no-embed] [--json] | Run golden-query recall fixtures (<dir>/eval/*.yaml by default) with pass/fail exit codes (0 pass / 1 below threshold / 2 runner error) — for CI regression gates and post-sync canaries |
| mk episode --session-id <id> --summary "text" [--json] | Write a session episode |
| mk episodes [--limit N] [--json] | List recent episodes |
Indexing & maintenance
| Command | Description |
|---------|-------------|
| mk reindex -d <dir> [--embed] | Rebuild SQLite index; --embed computes embeddings for all atoms |
| mk compact -d <dir> | Compact the event log |
| mk merge -d <dir> --from <path> [--dry-run] | Merge a remote event log |
| mk gc -d <dir> [--json] | Archive expired atoms |
| mk replay --from <file> | Reconstruct state from events |
| mk bootstrap-events -d <dir> | Migrate to V2 event format |
Relations & citations
| Command | Description |
|---------|-------------|
| mk relate <src-id> <type> <tgt-id> -d <dir> [--json] | Create a typed relation edge between two atoms |
| mk relations <atom-id> -d <dir> [--json] | Show inbound and outbound relation edges for an atom |
| mk supersede <old-id> <new-id> -d <dir> [--json] | Mark <old-id> as superseded by <new-id>; superseded atoms are excluded from recall |
| mk execute <atom-id> -d <dir> [--dry-run] [--json] | Stamp executed_at on an atom. For draft procedures this is the auto-promotion signal — mk reflect promotes executed procedures at confidence ≥ 0.7. Idempotent (preserves the first execution time) |
| mk edit <atom-id> -d <dir> [--dry-run] [--json] | Open an atom in $EDITOR and record a provenanced human_edit event when the file changes (the strongest correction signal in the store — a human directly validating or correcting an atom). No-op when saved unchanged; encrypted (SECRET) atoms are not editable this way (#247) |
| mk seed --lifecycle -d <dir> [--dry-run] [--json] | Idempotently seed the canonical lifecycle atoms (10 procedures + 1 constraint). Re-runnable: matches existing atoms on their stable slug segment and supersedes stale/duplicate copies in place instead of duplicating. Canonical bodies ship in the package (skills/mk-memory-setup/seed-atoms/lifecycle/manifest.json) |
| mk upgrade --to <ver> -d <dir> [--mk-bin <path>] [--cron-wrapper <path>] [--dry-run] [--json] | Host-side one-command agent upgrade: install <ver> at the agent binary (MK_BIN), idempotently re-seed lifecycle atoms, regenerate the cron wrapper, and gate on mk doctor — single PASS/FAIL, exit 1 on failure. Run on the host, ideally from the target version (npx memory-kernel@<ver> upgrade --to <ver>) |
| mk migrate-relations -d <dir> [--dry-run\|--apply] | Backfill relations[] from links.related and body-text atom ID references |
| mk relink -d <dir> [--dry-run\|--apply] | Extract relation edges from body-text atom ID references |
| mk citations -d <dir> [--json] | Extract and index concept-name citations across all atoms |
| mk enrich-relations -d <dir> [--dry-run\|--apply] [--model <model>] | Reclassify related edges into typed relations using an LLM (Ollama) |
Advanced
| Command | Description |
|---------|-------------|
| mk obsidian-init -d <dir> [--sync] | Write .obsidian/graph.json with type-based color groups; --sync rewrites all atom files to include ## Relations wikilink sections |
| mk export-obsidian -d <dir> --out <vault-dir> [--include-archived] [--json] | Export atom files to a separate Obsidian vault directory (use when you want an Obsidian view without opening ENTITIES/ directly) |
| mk wander -d <dir> [--seed id...] [--tags t...] [--steps N] [--json] | Explore via spreading activation |
| mk closure -d <dir> [--json] [--trajectory] [--trajectory-days N] | Compute operational-closure metrics |
| mk render -d <dir> -o <output-path> [--max-tokens N] [--no-fill] [--json] | Render atoms to CLAUDE.md; beliefs with extends relations are grouped into developmental arcs |
Per-agent isolation
| Command | Description |
|---------|-------------|
| mk share <atom-id> --from <agent> -d <dir> [--json] | Copy atom snapshot to shared namespace |
| mk unshare <atom-id> -d <dir> [--json] | Remove atom from shared namespace |
| mk migrate -d <dir> --strategy <fresh\|partition\|clone-to-shared> | Convert shared store to per-agent isolation |
| mk status -d <dir> --all-agents [--json] | Show per-agent summary |
SDK
import { initMemoryDir, createAtom, recall, recallWithEmbeddings, reflect, wander, indexCitations, closure, extractFromLog, consolidateAtoms } from 'memory-kernel';
// Initialize
initMemoryDir('./memory');
// Remember
createAtom({
memoryDir: './memory',
agent_id: 'my-agent',
session_id: 'session-1',
type: 'decision',
slug: 'use-cursor-pagination',
body: '## Decision\nUse cursor-based pagination.\n\n## Why\nOffset degrades beyond 1M rows.',
confidence: 0.95,
scope: { tags: ['api', 'performance'] },
});
// Recall (FTS-only — works without any API key)
const context = recall('./memory', { task: 'pagination API', max_tokens: 4000 });
// Recall with semantic re-ranking (hybrid FTS + embeddings when EMBEDDING_PROVIDER is set)
const semanticContext = await recallWithEmbeddings('./memory', { task: 'pagination API', max_tokens: 4000 });
// Reflect (consolidate)
reflect({ memoryDir: './memory', agent_id: 'my-agent', session_id: 'session-2' });
// Wander (find unexpected connections)
const result = wander({ memoryDir: './memory', seedTags: ['api', 'design'], steps: 5 });
// result.collisions — atom pairs from different domains with structural overlap
// result.activated — all activated atoms with scores
// Extract atoms from a conversation log (LLM-powered)
const extracted = await extractFromLog({
logPath: './conversation.log',
memoryDir: './memory',
agentId: 'my-agent',
sessionId: 'session-1',
maxAtoms: 20,
});
// extracted.atoms — array of { atom_id, slug, type, status }
// Consolidate: review and promote auto-extracted drafts
const consolidated = await consolidateAtoms({
memoryDir: './memory',
dryRun: true, // preview without writing
limit: 50,
});
// consolidated.promoted — count of atoms promoted from draft to active
// Render to CLAUDE.md
import { renderClaudeMd } from 'memory-kernel';
const md = renderClaudeMd('./memory', { maxTokens: 8000 });// Per-agent isolation
import { initIsolatedBase, resolveAgentDir, isIsolated, recallIsolated, shareAtom } from 'memory-kernel';
// Initialize with isolation
initIsolatedBase('./memory', 'agent-alpha');
// Creates: agents/agent-alpha/, shared/, config.yaml
// Route operations to agent store
const agentDir = resolveAgentDir('./memory', 'agent-alpha');
createAtom({ memoryDir: agentDir, type: 'decision', slug: 'my-call', body: '...', agent_id: 'agent-alpha', session_id: 's1', confidence: 0.9 });
// Union recall (agent + shared atoms, agent wins on collision)
const bundle = recallIsolated('./memory', 'agent-alpha', { task: 'review decisions' });
// Share an atom with all agents
shareAtom('./memory', 'DECI-2026-04-16-MY-CALL-1234', 'agent-alpha', { agent_id: 'agent-alpha', session_id: 's1' });Every mk --json output also has an exported Zod schema — import { RecallOutputSchema, DoctorOutputSchema, RememberOutputSchema, EvalOutputSchema } from 'memory-kernel' and parse() CLI output instead of guessing field names (#301).
Full API covers event sourcing, replay, episodes, multi-agent merge, encryption, import, conflict resolution, per-agent isolation, and more. SDK reference → | Isolation guide →
MCP Server
Memory Kernel exposes all operations as an MCP server for any MCP-capable agent.
MEMORY_DIR=/path/to/memory mk-mcp| Tool | Maps to | Description |
|------|---------|-------------|
| mk_remember | createAtom() | Create atom |
| mk_recall | recallWithEmbeddings() | Load context (hybrid FTS + semantic when configured) |
| mk_reflect | reflect() | Consolidate |
| mk_gc | reflect() | Archive expired |
| mk_merge | mergeEventLogs() | Merge remote memory |
| mk_list_conflicts | queryIndex | List conflicts |
| mk_resolve_conflict | resolveConflict() | Resolve conflict |
| mk_get_context_bundle | checkpoint() | Full handoff bundle |
| mk_share_atom | shareAtom() | Share atom to shared namespace (isolated mode) |
| mk_unshare_atom | unshareAtom() | Remove from shared namespace (isolated mode) |
Resources: memory://decisions, memory://constraints, memory://handoff, memory://open-questions
Set MCP_AGENT_ID env var to route all tools to a specific agent store in isolated mode (defaults to mcp-server).
Claude Desktop config:
{
"mcpServers": {
"memory-kernel": {
"command": "node",
"args": ["/path/to/memory-kernel/dist/mcp/server.js"],
"env": { "MEMORY_DIR": "/path/to/your/memory" }
}
}
}NanoClaw Integration
Memory Kernel renders atoms into CLAUDE.md, which NanoClaw loads at session start — persistent memory with zero NanoClaw code changes.
Nightly: mk reflect → mk citations → mk render -d ./memory -o CLAUDE.md → git push
Next session: NanoClaw loads CLAUDE.md as contextDrift pre-filter: Set MEMORY_DIR in your .env and NanoClaw uses mk wander as a Tier 1 gate before post-conversation drift. Cheap spreading activation (~30 ms, no LLM) decides whether to spawn an expensive drift session — skips drift when no interesting connections are found, injects collision context when they are.
Install the /mk-memory-setup skill for interactive setup. The skill auto-detects NanoClaw and runs the full flow: install CLI, init store, write the mount allowlist, configure container_config in the NanoClaw DB, create conversation/impulse symlinks, seed identity + lifecycle atoms, render the first CLAUDE.md, schedule nightly cron, and restart NanoClaw — see skills/mk-memory-setup/references/nanoclaw.md for the standalone steps if you'd rather run them manually.
Obsidian Integration
Memory Kernel's ENTITIES/ directory can be opened directly as an Obsidian vault — no export step needed. Atom files are valid Markdown with YAML frontmatter, and relation edges render as [[wikilinks]] in a sentinel-delimited ## Relations section at the end of each file.
Quick setup
# 1. Initialize Obsidian graph config (type-based color groups for all 9 atom types)
mk obsidian-init -d ./memory
# 2. Rewrite existing atom files to include ## Relations wikilink sections
mk obsidian-init -d ./memory --sync
# 3. Open ENTITIES/ as an Obsidian vault
# → Graph view shows typed relations as navigable links
# → Tags are searchable via Obsidian's native tag index
# Alternative: export to a separate vault directory (read-only Obsidian view)
mk export-obsidian -d ./memory --out ./my-obsidian-vaultWhat happens under the hood
- Tag promotion:
scope.tagsare promoted to a top-leveltags:YAML field so Obsidian indexes them natively. Tags are merged back intoscope.tagson parse — edits in Obsidian are preserved. - Wikilink relations: Atoms with
frontmatter.relations[]get a## Relationssection delimited by<!-- mk:relations -->sentinels. The section is stripped on parse and never pollutesatom.body. - Graph coloring:
mk obsidian-initwrites.obsidian/graph.jsonwith color groups for all 9 atom types (belief, fact, decision, preference, open_question, procedure, entity_summary, constraint, conflict), using 4-char path-prefix queries. - Round-trip safe: Edit atoms in Obsidian (body text, tags, frontmatter) and Memory Kernel reads them back correctly. The
## Relationssection is machine-managed — manual edits there will be overwritten on next serialize.
Advanced Operations
Wander — Spreading Activation
mk wander finds unexpected connections between atoms by walking the tag co-occurrence graph and explicit relation edges (extends, supports, caused_by, etc.). Pure computation — no LLM calls, runs in milliseconds.
Inspired by ACT-R (Anderson & Lebiere 1998) and Collins & Loftus (1975) spreading activation. This is Tier 1 of a two-tier architecture: cheap associative walks that surface candidates for expensive reasoning.
How it works: Seed from atoms or tags → spread activation through shared tags and relation neighbors (modulated by ACT-R base-level activation: recency × citation frequency) → sqrt-sigmoid modulation preserves important hub atoms → lateral inhibition keeps top-K per step → detect collision candidates (atom pairs with tag Jaccard dissimilarity > 0.7, scored by activation × dissimilarity).
Tip: Run mk citations before mk wander to index concept-name references — this provides frequency data for ACT-R activation scoring, significantly improving wander quality for stores with cross-referencing atoms.
mk citations -d ./memory # Index concept-name citations (run once after changes)
mk wander -d ./memory --tags philosophy accounting --steps 5 --json| Parameter | Default | Description |
|-----------|---------|-------------|
| seeds | auto | Atom IDs to start from. When omitted, 3 seeds are auto-selected citation-primary (most-cited first, recency as tiebreak) and drawn round-robin across types so the walk spans clusters rather than one type-monoculture |
| seedTags | — | Tags to resolve into seeds |
| steps | 3 | Spreading depth |
| threshold | 0.05 | Minimum activation to survive |
| topK | 20 | Lateral inhibition limit |
| decay | 0.5 | Spread decay factor |
| maxCollisions | 5 | Max collision candidates |
| relationWeight | 2.0 | Activation weight for explicit relation edges |
| diverseSeeds (CLI --no-diverse-seeds to disable) | true | Round-robin auto-seeds across atom types; disable for plain top-N by citation weight |
Closure — Operational Closure Metrics
mk closure measures how self-referential a memory store is. Based on Luhmann's operational closure: a system that responds based on internal structure rather than external input.
mk closure -d ./memory --json
mk closure -d ./memory --trajectory --trajectory-days 10What it measures:
| Metric | Description |
|--------|-------------|
| closure_index | belief_pct × (avg_relations + avg_body_refs) / 100 — single number combining type composition and entanglement |
| entanglement_pct | Average body-text cross-references as % of theoretical maximum |
| phase | early (< 20 atoms), type-composition (< 60% beliefs), or entanglement (≥ 60% beliefs, ≥ 20 atoms) |
| predictions | Tooling-degradation predictions — how closure level affects LLM classification accuracy and cross-agent transplantability |
Why it matters: High-closure stores resist automated processing (LLM classifiers confounded by self-describing body text) and cross-agent transplantation (beliefs depend on other beliefs the receiving agent doesn't have). The closure index predicts both from a single variable.
Trajectory mode (--trajectory) shows daily closure evolution — useful for detecting entanglement acceleration over time.
Performance
With SQLite index, 100-atom workload (see scripts/bench-baseline.json for raw numbers; run npm run bench to measure on your machine):
| Operation | Typical | Notes |
|-----------|---------|-------|
| recall() | ~2–5 ms | Falls back to file scan (~3–5× slower) without index |
| reflect() | ~100–200 ms | Idempotent — runs fast when nothing changed |
| replay() | ~2 ms | 100 atoms, ~160 events |
| wander() | < 30 ms | 200 atoms, pure computation, no LLM |
Numbers above are from a 100-atom store. Performance at larger scales (1k–10k atoms) has not been formally benchmarked — recall degrades gracefully via FTS, but heavier stores will take longer. Run npm run bench:baseline to pin a baseline on your own hardware.
Environment Variables
All environment variables are optional. memory-kernel works fully without any of them.
Embeddings
| Variable | Description | Default |
|----------|-------------|---------|
| EMBEDDING_PROVIDER | Embedding provider: voyage (512-dim) or openai (1536-dim) | (none — FTS only) |
| EMBEDDING_API_KEY | API key for the embedding provider (generic) | (none) |
| VOYAGE_API_KEY | Voyage AI API key (used when EMBEDDING_PROVIDER=voyage if EMBEDDING_API_KEY not set) | (none) |
| OPENAI_API_KEY | OpenAI API key (used when EMBEDDING_PROVIDER=openai if EMBEDDING_API_KEY not set) | (none) |
| EMBEDDING_MODEL | Override the default model for the provider | voyage-3-lite / text-embedding-3-small |
| EMBEDDING_DIMENSIONS | Override embedding dimensions (OpenAI only) | Provider default |
| OLLAMA_URL | Ollama server URL for local embedding / LLM calls | http://localhost:11434 |
Recall scoring
| Variable | Description | Default |
|----------|-------------|---------|
| SEMANTIC_WEIGHT | Weight for semantic similarity in hybrid ranking (0–1) | 0.6 |
| MIN_SIMILARITY | Minimum cosine similarity to include in results (0–1) | 0.3 |
| RECALL_CONFIDENCE_FLOOR | Minimum confidence score for atoms to appear in recall (0–1) | 0.7 |
| RECALL_NEIGHBOR_BOOST | Graph-walk neighbor boost factor (0–1) | 0.15 |
| RECALL_GRAPH_BOOST | Enable/disable graph-walk boost (true/false) | true |
| RECALL_IDF_DAMPING | IDF hub-damping strength (0 = disabled, 1 = full) | 1.0 |
| RECALL_DECAY_WEIGHT | Weight of recency decay in scoring (0–1) | 0.2 |
| RECALL_DECAY_HALF_LIFE | Half-life for temporal decay in days | 30 |
| RECALL_MMR_LAMBDA | MMR diversity-relevance tradeoff (0 = max diversity, 1 = max relevance) | 0.5 |
| RECALL_COVERAGE_BOOST | Query-term coverage boost factor | 0.3 |
| RECALL_LENGTH_NORM_K | Content-length normalisation constant | 0.5 |
| RECALL_TYPE_WEIGHTS | JSON object overriding per-type scoring weights | (defaults in source) |
| RECALL_TYPE_RESERVATIONS | JSON object overriding per-type token reservations | (defaults in source) |
| EPISODE_BUDGET_RATIO | Fraction of token budget reserved for episodes (0–1) | 0.2 |
| MK_RENDER_BUDGET | Default token budget for mk render | 16000 |
Extraction
| Variable | Description | Default |
|----------|-------------|---------|
| CLAUDE_PATH | Path to the Claude CLI binary for mk extract | claude |
| ANTHROPIC_API_KEY | Anthropic API key (used by the Anthropic SDK path if set) | (none) |
Isolation
| Variable | Description | Default |
|----------|-------------|---------|
| MK_ISOLATION | Enable per-agent isolation mode (per-agent) | (shared mode) |
| MCP_AGENT_ID | Default agent ID for MCP server operations | mcp-server |
| MEMORY_ENCRYPTION_KEY | Encryption key for SECRET-classified atoms | (none — SECRET atoms skipped) |
Debugging
| Variable | Description | Default |
|----------|-------------|---------|
| RECALL_DEBUG | Set to 1 to emit a per-atom recall-score breakdown to stderr (scoring stage, pre-MMR/budget) — shows why one atom outranked another. Off by default with zero overhead. | (off) |
Design Principles
- Files are truth — Markdown files. Human-readable, git-diffable, auditable, portable.
- SQLite is cache — Derived from files. Delete it, rebuild with
mk reindex. No lock-in. (The LLM-extractedentity_triplesaren't in the markdown, so they stay durable via thetriples.ndjsonsidecar. Seedocs/invariants.md.) - Typed knowledge — A fact carries more weight than a belief. Types encode this.
- Explicit lifecycle — Created, updated, promoted, archived. Every change logged.
- Token-aware — Recall respects budgets. Prioritizes by status and recency.
- Embeddings are opt-in — Works fully without any API key (FTS-only). Add
EMBEDDING_PROVIDER+EMBEDDING_API_KEYfor hybrid semantic search. Graceful degradation throughout.
Troubleshooting
See docs/troubleshooting.md for the deep-dive runbook on the fleet-deployment failure modes (node-gyp rebuild, cron PATH/MK_BIN, claude login expiry, fts_unavailable, key-set-but-0-vectors). Quick table:
| Problem | Fix |
|---------|-----|
| Cannot find module | Run npm run build to compile TypeScript |
| FTS returns null | Run mk reindex to build the SQLite index |
| SECRET atoms skipped | Set MEMORY_ENCRYPTION_KEY env var |
| No atoms after merge | Run mk reflect — merge doesn't auto-regenerate views |
| Embeddings not working | Set EMBEDDING_PROVIDER=voyage + EMBEDDING_API_KEY=..., then mk reindex --embed |
| Conflict resolution | mk reflect → inspect CONFLICTS/ → update atoms → resolveConflict() |
| Invalid agent ID | Agent IDs must be alphanumeric, dashes, or underscores only |
| share requires per-agent isolation mode | Enable isolation first: mk migrate --strategy fresh or set isolation: per-agent in config.yaml |
| npm install fails with node-gyp / build errors | better-sqlite3 requires a C++ build toolchain. Install build dependencies: macOS xcode-select --install; Debian/Ubuntu sudo apt-get install build-essential python3; Alpine apk add python3 make g++. Then re-run npm install. |
Contributing
See CONTRIBUTING.md for the development workflow, coding conventions, and PR guidelines. All changes must pass the full test suite and the docs-hygiene CI gate.
Changelog
All notable changes are documented in CHANGELOG.md.
