[Mnemolith: memory that stays true to your code]
Mnemolith
Shared memory for AI coding tools that stays true to your code.
[Mnemolith demo: a memory goes stale when docker-compose.yml changes, and the user records what is true now]
Mnemolith (NEE-mo-lith): "memory stone." What your AI learns, carved where every tool can read it. (Why the name)
Formerly Aether Cache. If you installed aether-cache, see Moving from Aether Cache: your memories come with you.
Website · PyPI
Claude Code, Cursor, and other AI tools each forget your project between sessions, and when they do remember things,
nothing checks whether those memories are still correct. Mnemolith gives every tool one shared, local memory, and keeps
it honest:
- Memories go stale when the code changes. Each memory is linked to the files it's about, with a snapshot of their
git content hash. When
docker-compose.yml changes, the memory about it is retired automatically instead of being
fed to your AI forever.
- Contradictions get replaced, not hoarded. "We migrated to MySQL" retires "we use Postgres" instead of sitting
next to it and confusing the model. Near-identical repeats are merged.
- Every memory can explain itself. Which tool and session saved it, which file versions it was based on, and every
change since: created, seen again, went stale, confirmed, replaced.
- It works with the files you already use. Current memories sync into a managed section of
AGENTS.md and
CLAUDE.md, so they're committed, reviewed in PRs, and visible to teammates who don't run Mnemolith.
It runs entirely on your machine, on top of a database engine I wrote from scratch.
Not a Redis clone
Mnemolith started as a from-scratch rebuild of Redis in Java, then kept going. Every layer here is written from
scratch, and each one does something the layer beneath it can't:
The bottom layer alone is a working Redis: redis-cli, redis-benchmark, and redis-py talk to it unchanged. The
memory layer, git awareness, and MCP server are things Redis doesn't do at all.
Does it help?
We tested it. Claude Code did 7 tasks in a sample codebase, 3 times each, in three setups. Every task needs
something only a teammate would know: a past decision, who owns which code, a promise to a partner, a business
rule. Two need a fact mentioned in an earlier session. An automatic check grades every run.
Without memory, Claude edited another team's code, changed a frozen partner API and skipped refund approval, every
time. A CLAUDE.md file held the same facts but missed a database address mentioned in an earlier session 2 times
out of 3; Mnemolith saved it on its own and used it next session. The cost is about 15% more per task than a notes
file, at API prices, because recalling and keeping memories up to date takes a few extra turns. 63 runs on Claude Opus 5.5, November 2026.
In an earlier round where every fact could also be found in the code, all three setups tied.
Run it yourself (uses your own Claude Code plan; see eval/ for what each task checks):
What it looks like
The dashboard (mnemolith ui): memories whose files changed wait for a decision, with the file versions side by side.
[The Mnemolith dashboard showing stale memories and a possible conflict]
And from your AI tool's side, a memory linked to a file, the file changes, and the next lookup catches it:
A newer fact replacing an older one:
Quick start
One line installs everything and runs setup. On macOS or Linux:
On Windows (PowerShell):
The installer (install.sh, install.ps1) gets uv if you
don't have it (uv brings Python), installs Mnemolith, and runs mnemolith setup. Prefer your own tools? Any of
these works, followed by mnemolith setup:
setup starts the engine in the background, downloads the embedding model once (about 90 MB), and connects every
supported AI tool it finds on your machine: Claude Code, Claude Desktop, Cursor, and Codex. That's it. Open your
AI tool in any git repo and try:
Remember that our tests need DOCKER_HOST set. It depends on docker-compose.yml.
Then start a new session and ask how to run the tests.
Only use Claude Code? You can install Mnemolith as a plugin instead, with no setup step (needs
uv):
The plugin (plugin/) runs the same server and adds a /mnemolith:dashboard command. Use the plugin or
setup, not both, or Claude sees the tools twice.
The engine runs on Java 11+. If you have it, it's used. If you don't, Mnemolith downloads a private copy (about
50 MB) into ~/.mnemolith/java, checks it against its published checksum, and uses it only for Mnemolith;
nothing is installed system-wide, and mnemolith uninstall removes it.
Already keep notes for your AI in CLAUDE.md, AGENTS.md, or Cursor rules? Run mnemolith import in the repo
(or use Import notes in the dashboard) to turn them into memories, linked to the files they mention.
Memories are kept per project (the git repo's folder name), so projects never mix. Everything stays on your machine:
the engine only accepts connections from it, and your memories live in ~/.mnemolith.
If mnemolith isn't found after installing, run uv tool update-shell and open a new terminal. Port 7380 taken?
Set MNEMOLITH_PORT and run setup again. To uninstall: mnemolith uninstall, then uv tool uninstall mnemolith.
Mender: drafted fixes for stale memories
Flagging a stale memory is half the job: someone still has to work out what's true now and type it in. Mender, an
optional part of Mnemolith, drafts that for you. mnemolith setup asks whether to turn it on; mnemolith mender on
(or Settings in the dashboard) changes it later. It needs no API key and installs nothing extra.
- Simple changes are drafted by plain rules, on your machine. Mender compares the version of each file the memory
was saved against (from git) with the file now.
image: postgres:16 became image: mysql:8, so "The database is
Postgres (postgres:16 image)" becomes "The database is MySQL (mysql:8 image)". It handles version bumps, moved files
(the path is updated, not treated as a deletion), and changes that don't touch the memory at all (only a comment, or
lines it doesn't mention), which it suggests confirming as still true.
- Everything else is drafted by the AI tool you already use. With Mender on, Claude Code, Cursor, and Codex get two
more tools:
list_stale shows each stale memory with what changed in its files, and propose_fix submits a
draft. Drafts are checked against the code: quotes in the reason and new numbers or versions must actually be in
the files, or the draft is marked Unverified.
- You approve every change in the dashboard, where each draft sits under the diff it came from, with what it's
based on. Approve it, edit it, or dismiss it. Approving is refused if the files changed again after the draft was
made (it would be based on old code), and if the memory was already settled some other way.
Changed your mind? Approving shows an Undo, and any fix can be reverted later from the memory's details in the
dashboard (Revert this fix) or with mnemolith mender revert m7: the old memory comes back for you to decide,
and Mender won't suggest the same fix again unless the files change. mnemolith status and your AI tool tell you
when fixes are waiting.
mnemolith mend drafts fixes for the repo you're in and lists them.
Moving from Aether Cache
Mnemolith used to be called Aether Cache. If you installed aether-cache, run the installer above. It installs
Mnemolith, removes aether-cache, moves your memories from ~/.aether-cache to ~/.mnemolith, and reconnects your AI
tools. Nothing is lost, and memories synced into CLAUDE.md / AGENTS.md are picked up where they are.
Installed some other way? Install mnemolith the same way, run mnemolith setup, then remove aether-cache
(uv tool uninstall aether-cache, pipx uninstall aether-cache, or pip uninstall aether-cache).
Tools the AI can call
Design decisions, and what the measurements said
Similarity can't detect contradictions, so the AI judges
The first version flagged a new memory as a possible contradiction when it was similar to an existing one. Calibrating
with real all-MiniLM-L6-v2 embeddings showed that can't work:
Duplicates scored 0.73 to 0.92 and contradictions 0.30 to 0.94, so no threshold separates them. A threshold low enough
to merge real duplicates would silently merge "timeout is 30s" with "timeout is 60s", the worst bug this product could
have. So the engine only auto-merges near-identical text (similarity ≥ 0.97). For everything else, embeddings find
the related memories and the AI, which already understands the sentences, decides whether one replaces another.
Staleness compares file contents, not commits
Each linked file is stored as path@<git blob hash>, computed with git hash-object. Comparing contents rather than
commit ids catches uncommitted edits, ignores commits that didn't touch the file, and needs no git hooks: recall
re-hashes the handful of linked files before every search.
Memories never mutate silently
Every change is a logged event with a timestamp and a reason. The engine logs each memory with the id and timestamp
it assigned, so replaying the log after a restart rebuilds the exact same memories and histories, down to the
millisecond, instead of minting new ones.
The engine underneath
A Redis-compatible server written from scratch in Java. It speaks RESP, so redis-cli, redis-benchmark, and
redis-py work with it unchanged. The core was built by completing every stage and extension of the
CodeCrafters "Build Your Own Redis" challenge; the vector index,
persistence hardening, and the memory layer are my own additions.
[progress-banner]
Throughput vs. official Redis
redis-benchmark -t set,get, 100K requests, 50 parallel clients, 3-byte values, same machine:
Typical latency is on par with Redis. The gap is in worst-case latency, from JVM garbage collection and JIT warm-up,
which Redis (written in C) doesn't have.
HNSW vector search vs. exact search
100,000 AG News headlines embedded with all-MiniLM-L6-v2 (384 dimensions, cosine similarity), 500 held-out queries,
k = 10. Recall is measured against exact brute-force search on the same data.
At ef=100, HNSW returns 99.4% of the true top-10 about 22× faster than exact search. Building the index for 100K
vectors took 136 seconds (M=16, M0=32, efConstruction=200). Latencies are measured from the Python client, so they
include network and client overhead. Full setup and raw numbers: results.json.
How it works: each vector links to its nearest neighbors in a layered graph. A search starts on a sparse top layer
of long-range links, hops greedily toward the query, drops down a layer at a time, and finishes with a best-first search
on the bottom layer that keeps the ef best candidates. Neighbor selection uses the diversity heuristic from the HNSW
paper (prefer neighbors in different directions over near-duplicates), which is what keeps recall high.
Filtered search: HNSW doesn't know about memory status, so searches over-fetch candidates, drop the ones that don't
match (stale or superseded), widen the search if too few survive, and fall back to an exact scan if needed. In a test
with 2,000 memories where only 2% were active, filtered searches returned exactly the correct top 10 on 30/30 queries.
The catch: at that selectivity, the exact fallback runs most of the time, which is the known weak spot of
post-filtering on graph indexes.
Crash safety
Every write goes to an append-only file (AOF) that is replayed on startup.
kill -9 crash test (crash_test.py): a client writes as fast as it can, the server is killed at a random moment,
restarted, and every write the server acknowledged is checked.
Recovery took 0.6 seconds per restart, replaying up to ~185K writes.
Torn writes: a machine crash can cut the last write off halfway. The first version refused to start on such a file;
now replay stops at the last complete command, trims the partial one, and starts normally with all earlier data intact.
Cost of each fsync policy (SET throughput, 50 clients):
everysec is 2.7× faster than always and within 4% of no, which is why it's the sensible default. On macOS, fsync
doesn't force the SSD's own cache to flush, so always looks cheaper here than it would on a Linux server.
Full command list
- Memory:
MEM.ADD, MEM.GET, MEM.SEARCH (with STATUS filter), MEM.STATUS, MEM.WHY, MEM.LIST
MEM.FILES, MEM.SEEN, MEM.CONFLICTS, MEM.RESOLVE
- Vectors:
VADD, VSEARCH (HNSW, tunable EF, EXACT mode)
- Strings:
GET, SET (EX / PX), INCR, STRLEN, TYPE, KEYS *
- Lists:
RPUSH, LPUSH, LRANGE, LLEN, LPOP, blocking BLPOP
- Streams:
XADD (explicit, partial, and auto IDs), XRANGE, XREAD (with BLOCK and $)
- Sorted sets:
ZADD, ZRANK, ZRANGE, ZCARD, ZSCORE, ZREM
- Bitmaps:
SETBIT, GETBIT, BITCOUNT, BITOP AND / OR (binary-safe strings)
- Geospatial:
GEOADD, GEOPOS, GEODIST, GEOSEARCH ... FROMLONLAT ... BYRADIUS
- Transactions:
MULTI, EXEC, DISCARD, WATCH, UNWATCH (optimistic locking via per-key write versions)
- Pub/Sub:
SUBSCRIBE, UNSUBSCRIBE, PUBLISH
- Replication:
- Master–replica handshake (
PING, REPLCONF, PSYNC) and command propagation
REPLCONF GETACK offset tracking and WAIT
- Persistence: RDB loading; AOF with a manifest,
appendfsync always / everysec / no, and torn-write recovery
- Auth:
AUTH, ACL WHOAMI / GETUSER / SETUSER (SHA-256 password hashes)
Running the engine on its own
Reproducing the benchmarks
Each benchmark runs against the engine you've started:
Related work
Mnemolith is an early, open-source project. These are the production systems in the same space:
Status and limitations
Mnemolith is v1 and early. It works end to end in automated tests and is now being tried in real Claude Code sessions.
Not done yet:
- A with-memory vs. without-memory evaluation on real coding tasks, to measure whether it actually reduces
re-explaining.
- AOF compaction: the log only grows, and every status change adds a line.
Engine limitations:
- Threading: one OS thread per connection, not an event loop. That's fine for a local tool, but it won't scale to
thousands of clients like Redis.
- Transactions:
EXEC isn't isolated, so other clients' commands can interleave with a transaction's commands.
- Vector index: re-adding a vector updates it without rewiring its graph links, and there's no delete. Retired
memories are filtered out, not removed.
- Replication: full resync only, and a new replica assumes the master's dataset is empty.
- Smaller gaps:
KEYS * lists only string keys.
GEOSEARCH scans every member.
- ACL supports only the
default user.
HELLO (RESP3) isn't supported, so clients must use RESP2.
Project layout
Releasing
Bump the version in pyproject.toml, mnemolith/__init__.py, and server.json, commit, then:
Once the release is on PyPI, point the Claude Code plugin at it: bump the version in
plugin/.claude-plugin/plugin.json and plugin/pyproject.toml, run uv lock in plugin/, then commit and push.
The plugin can't move first, because its lockfile needs the new release to exist.
The release workflow checks the versions match, builds and smoke-tests the package,
publishes it to PyPI and the MCP Registry, and creates the GitHub Release.
License
MIT. See LICENSE.