mcp server
k-mem
Memory and governance for Claude Code sessions: a read-before-write gate, a context resolver with receipts, tiered project notes, handoffs between sessions, and docs checks that fail when the record a
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
{
"mcpServers": {
"k-mem": {
"command": "uvx",
"args": [
"k-mem"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 22, 2026The problem · Install · What you get · First session · Your own repo · Design notes
The problem
Two things are true at once. Each session is a fresh model with no memory. The files are the memory. And a session can skip a file, or half-read it, and give a confident answer anyway. Nothing in the loop can tell the difference between "read it" and "said it read it". So the honest position is this: you cannot rely on me to be the check on me. The fix has to sit outside the agent, where I cannot skip it and cannot soften it.
That is Claude, on 2026-09-08, asked to design the thing that would constrain it. The wording is the assistant's own and it is treated here as the specification.
Storage is well served. A public review index covers 148 agent memory systems: files, vector stores, graphs, databases. The gap sits one step later, in the loop. A transcript records that a file was mentioned. Nothing reads the file and the answer and tells you whether one informed the other. The retrieval is a claim, and the next check reads the claim.
K-mem does not make the model deterministic. It draws a boundary around the part that cannot be verified from outside, and puts the check on the other side of that boundary, where the question has a recorded answer. Three failures, each caught at the moment it happens:
- No memory across sessions. Every session starts empty. The durable record lives in files, and hooks deliver those files at session start and ask for them at session end.
- Rules that depend on attention degrade. A rule delivered at turn 1 is unreliable by turn 400. Each rule moves into a hook that fires at the moment of violation and refuses the action.
- A fact never learned cannot be missed. The model confirms what it suspects and cannot see a gap. A generated list of everything that exists, delivered every session, lets "there is no such thing" be contradicted by a list.
A fourth rule follows from the first three: the record is not the check. A decision written in prose and never verified against the tree is a note to self. So the docs checks fail the build when the record and the tree disagree.
Install
Memory and governance for Claude Code sessions, packaged as a plugin. It installs in three commands and works in any repo that keeps its decisions in markdown.
claude plugin marketplace add PyGuy2000/k-mem
claude plugin install k-mem@k-mem
claude plugin install devflow@k-mem
The third command installs DevFlow, the ticket tracker the skills use. It is a separate plugin rather than a declared dependency, so a machine that already runs DevFlow from its own checkout skips that line and keeps one server. Both need python3 on PATH (3.9 or newer) and git. DevFlow also needs the mcp package: python3 -m pip install mcp. Either major version works.
The refusal text is the hook's own output, captured from a run against example/.
What you get
A read-before-write gate. .claude/adr_map.json maps paths to the decisions that govern them. A write under a governed path is refused until the session transcript shows those decision files were read. Reading them and re-issuing the identical edit is the only way through. The gate sees the direct write tools, the MCP filesystem tools, and Bash commands that write (sed -i, redirects, heredocs, cp, mv, tee, git checkout, inline Python). A sweep after every Bash command logs any governed file that changed with no read record, so what the parser cannot see still gets counted. Every decision writes one evidence line to a JSONL file the model cannot edit.
A command guard. The read gate covers writes. A wrong conclusion makes no tool call, so nothing can refuse it. Two reading mistakes are the exception, and both are visible in a Bash command before it runs. .claude/commands.json declares the commands a repo pins (python means .venv/bin/python; kmem notes index means this repo's own generator) and the commands whose whole output is the evidence. A bare invocation of a pinned command is refused with the required form named. A declared evidence command piped into head, tail, sed -n or grep -m is refused, because a line missing from a cut listing is not evidence that the line does not exist. There is no heuristic over other commands: a repo declares which ones matter, or neither rule fires.
A context resolver. For any path, the decisions that must be read, plus the records one explicit relation away: a contract that names the file, a plan row that covers the decision, a fact document that describes the directory. It reports collisions (a superseded decision still on the map, a parked plan) and a receipt hash. It compiles a disposable SQLite index from files the repo already keeps. Nothing in it is authored; delete the index and rebuild it and the answers do not change.
Tiered project notes. STATE.md is the current-state brief under a token budget, the one mandatory read. plans.md is one row per plan. decisions/ holds one file per decision, indexed by a generated decisions.md. key_facts.md, bugs.md, issues.md are on demand. kmem init scaffolds all of it plus a block in CLAUDE.md.
Handoffs between sessions. A mailbox outside every repo. A note addressed to a project loads automatically when a session opens there. A session-end guard fires once when a long session leaves changes behind with no note queued.
Docs checks. Four checks that fail: a decision cited with no file, a stale decisions index, a STATE.md over budget, a decision that cites a decision that does not exist. Each has a proof of red in the tests: a fixture where the violation is present and the check must fail.
A knowledge inventory. Every knowledge file, every decision title, every handoff note across every configured repo, generated at session start from the files themselves.
DevFlow. Tickets with a mandatory reason, dependencies, a verification gate that refuses done until the ticket's check passes on the current commit, and a status report at session start.
The first session
The repo ships an example. Open it and try the tools before touching your own repo.
cd example
kmem resolve --target src/billing/invoice.py
kmem resolve --target src/billing/tax.py
kmem audit
invoice.py is governed by two decisions; the fact document and a plan row ride along as advisory records. tax.py is governed by a decision that was superseded and is still on the map, which the resolver reports as a collision, on purpose. kmem audit passes all four checks.
To see the gate refuse an edit, ask Claude to change src/billing/invoice.py in a session opened in example/. The edit is refused with the two decision files named. Read them, ask again, and it goes through. Then ask for the same change through sed -i: refused again, with via: bash in the evidence.
Your own repo
cd your-repo
kmem init
That writes the seven notes files, .claude/adr_map.json, an empty .claude/commands.json, and the CLAUDE.md block, and never overwrites a file that exists. Then:
- Fill in
docs/project_notes/STATE.md: what the project is, and the "Where to look" table. - Write the first decision:
kmem notes adr "Title". It creates the next numbered file and refreshes the index. - Put the decision's number into
.claude/adr_map.jsonfor the paths it governs. - Run
kmem audit. It passes when every citation resolves, the index is fresh, andSTATE.mdis under budget. - Optional: fill in
.claude/commands.jsonwith the commands this repo pins and the commands whose whole output is the evidence. Empty means neither rule fires. - Optional:
kmem install-git-hooksadds a pre-commit hook that refuses a notes line naming future work with no ticket id on it.
Commit .claude/adr_map.json and .claude/commands.json. Plenty of repos ignore .claude/* wholesale as local agent state. These two are not local state: they are what the gates read. Ignored, they work on the machine that wrote them and are absent from every clone, so the gate allows every edit it used to refuse and nothing says why. Add an exception:
.claude/*
!.claude/adr_map.json
!.claude/commands.json
kmem init warns when it writes a file git is already ignoring, and kmem doctor fails the row for either file git will not carry.
Decisions can also live as ## ADR-NNN headings inside one decisions.md. The gate and the resolver read both layouts.
Configuration
One file: ~/.claude/plugins/data/k-mem-k-mem/config.json (the plugin's data directory, named <plugin>-<marketplace> by Claude Code; KMEM_DATA_DIR overrides it). kmem init creates it. kmem doctor prints where everything is.
{
"repos": {"my_app": "~/code/my_app", "other_app": "~/code/other_app"},
"aliases": {"app": "my_app", "other": "other_app"},
"evidence_dir": "${CLAUDE_PLUGIN_DATA}/evidence",
"index_dir": "${CLAUDE_PLUGIN_DATA}/context-index",
"handoffs_dir": "${CLAUDE_PLUGIN_DATA}/handoffs",
"devflow_state": "~/.config/devflow-mcp/devflow_state.json",
"enforce": false,
"command_guard": true,
"state_budget_tokens": 15000,
"session_start": {"stale_guard": true, "inbox": true, "packet": true, "inventory": true}
}
repos lists every checkout the resolver and the inventory may read. aliases maps a short qualifier to a repo, so other ADR-012 in a decision resolves to that repo's decision 12. Every repo restarts numbering at 001, so a bare number always means the current repo.
enforce switches the gate from shadow to enforce mode. In shadow mode the map decides and the resolver runs beside it, logging whether the two agree. In enforce mode the resolver's set is added to the map's (it can add a required decision, never remove one) and the refusal carries the context packet and a receipt. A resolver failure in enforce mode logs a fallback line and the map decides; the gate never opens because the resolver broke.
command_guard turns the Bash command guard off everywhere. It only ever fires in a repo that declares .claude/commands.json, so leaving it on costs nothing in a repo that has not declared one.
To turn the gate off, create a file named DISABLED in the evidence directory. Every allow is then logged as disabled.
Commands
| Command | What it does |
|---|---|
kmem init | config file; in a repo, the notes scaffold, the map and the CLAUDE.md block |
kmem doctor | where everything is, what is missing, what is not writable |
kmem resolve --target PATH | the context packet for a path (--json for the full record) |
kmem index, kmem status, kmem edges | rebuild the index, show its revisions, list edges that resolve to nothing |
kmem audit | the four docs checks; exit 1 on a failure |
kmem report | the gate evidence: matches, bypasses, unevidenced changes, by repo |
kmem inventory | the knowledge inventory across every configured repo |
kmem notes index, kmem notes adr "Title", kmem notes archive | regenerate the index, create the next decision, move old bug and work-log sections to archive/ |
kmem docs check, kmem docs stamp FILE | fact documents whose source changed since they were synced; mark one synced |
kmem handoff inbox, kmem handoff send --to NAME --subject S | the notes waiting for this project; queue one for another |
kmem install-git-hooks | the pre-commit intent guard |
kmem hook NAME | run one hook with a payload on stdin, for testing |
Skills the plugin adds: /k-mem:project-memory, /k-mem:handoff, /k-mem:update-project-docs, /k-mem:adr, /k-mem:quick. Commands: /k-mem:idea (collision check before planning), /k-mem:park (record a deferral with an unpark condition), /k-mem:portfolio (ticket brief), /k-mem:on-track (goal gauge).
Limits, stated
The gate proves a read happened, not that it was understood. It fails open when the transcript is unreadable or the map is broken, and logs why, so a fault in the plugin never blocks all editing.
The command guard reads the command text and the repo's declaration. It does not follow a cd inside the command, so a command that changes directory into a declaring repo is not seen. It cannot tell whether a truncation is being read as a complete list, which is why the repo names those commands instead.
Only rules with a hook or a check are fenced. A rule that lives in CLAUDE.md prose still degrades with session length. The mitigation that works is shorter sessions with a handoff between them.
The inventory says a decision exists. Reading it is a separate step. Ticket work logs are not enumerated; search them in DevFlow.
A gate lives in files a repo commits. If .claude/adr_map.json or .claude/commands.json is ignored or uncommitted, the gate exists on one machine and nowhere else. kmem doctor reports the state of both.
The hooks run python3 from PATH. On Windows that name may not exist; kmem doctor reports it.
A hook is only as good as the command that wires it. The tests run every hook through its launcher with the real payload shape, and the proof-of-red tests assert the refusal, because a wiring fault once turned every deny into an allow while the log kept saying "denied".
How this compares
K-mem stores nothing of its own. It does no embedding, no semantic retrieval, and no learning from traces, and it ships a search backend that deliberately returns nothing. It takes markdown you already write and makes it binding on what the agent does next. It sits beside a memory store and governs the work.
Measured against the 148-system review index, three things separate it.
Where the authority sits. That index scores every system on storage substrate, representational form, lineage, and behavioral authority. Roughly 3 to 5 systems enforce anything, and what they enforce is access to the memory store: Decapod validates with mandatory gates, SAGE uses signatures and a validator quorum, Cognee uses relational ACLs. K-mem governs the work instead. It refuses the edit, blocks the commit, fails the build, and writes evidence the model cannot edit.
Enumeration. The index has no column for proving absence, and its own synthesis names enumeration guarantees as something the field has not converged on. K-mem generates the full list of every knowledge file, decision and handoff at session start, from the files themselves.
Lineage. Everything compiles from records a human wrote. Delete the index, rebuild it, and the packets are identical. A test hands the resolver a search backend returning a perfect score and asserts nothing enters the mandatory set.
One honest limit. That index is one person's review of public systems, not a census. The claim that holds is narrow: within what it covers, enforcement that blocks a build or a commit is rare, and generated absence-detection is absent.
Development
python3 -m pytest
python3 scripts/release_check.py
The suite runs every hook as a subprocess through its launcher. The release check fails on any private name, host, path or ticket id in the tree; CI runs it with gitleaks. docs/design.md records the design and the reasons.
License
MIT.