Encrypted memory and cache for AI agents, in one file.
MNemo gives an agent durable memory and a cache for expensive calls, inside a single encrypted .mnemo file. There's no server or vector database to run. Copy the file and the memory comes with it.
$ pip install mnemo-engine
What you get
Memory and a cache, without the infrastructure
Most agent memory means running a separate vector service: another thing to deploy, and another place for secrets to leak. MNemo treats memory like SQLite treats data. It's a file.
One portable file
Memories, cache, index, write-ahead log and snapshot history all live in one .mnemo file. A plain copy is a complete, consistent backup.
Encrypted at rest
Every 8 KiB page is sealed with AES-256-GCM under a key derived with Argon2id. Changing the passphrase re-wraps one key; the data is never rewritten.
Text in, text out New
Hand MNemo plain text. It calls your embedding model, checks it's the one the file was built with, and ranks recall on meaning, recency, importance and use.
A cache for expensive calls
Cache LLM responses, tool outputs and API calls by exact key, or match near-identical prompts by meaning. Namespaces, expiry and size limits included.
MCP server built in
mnemo serve --mcp exposes memory and cache as tools to Claude Desktop, Cursor or any MCP client.
Crash-safe writes
Each commit goes through a write-ahead log with a single fsync. Kill the process mid-write and the file recovers cleanly next time it opens.
Point-in-time restore
Every commit is a snapshot. Roll back to any past transaction or moment, and roll forward again if you change your mind.
Safe to share
One writer, many readers, enforced with OS file locks. A second writer is turned away instead of corrupting the file.
For agents
Plug it into Claude, Cursor or any MCP client
Point your MCP client at a .mnemo file and the agent gets memory and caching as ordinary tools. The passphrase comes from an environment variable, never a tool argument.
Add --embed-cmd and the tools accept plain text. MNemo starts your embedding program once, keeps it running, and restarts it cleanly if it misbehaves. A slow or broken embedder produces an error, never a mismatched memory.
{
"mcpServers": {
"mnemo": {
"command": "mnemo",
"args": [
"serve", "--mcp", "/absolute/path/to/agent.mnemo",
"--embed-cmd", "python3 /absolute/path/to/embedder_server.py",
"--embed-name", "sentence-transformers/all-MiniLM-L6-v2"
],
"env": { "MNEMO_PASSPHRASE": "your-passphrase-here" }
}
}
}
Quick start
Memory and caching in a few lines
The same engine behind every surface. These examples use the current release, v0.7.0.
import mnemo_engine from sentence_transformers import SentenceTransformer MODEL = "sentence-transformers/all-MiniLM-L6-v2" model = SentenceTransformer(MODEL) def embed(texts): return model.encode(texts, normalize_embeddings=True).tolist() with mnemo_engine.open("agent.mnemo", "a long passphrase", dimensions=384) as db: db.set_embedder(embed, name=MODEL, dimensions=384) # checked against the file # Durable memory, plain text in and out db.remember_text("the user prefers dark mode", "semantic", importance=0.8) for hit in db.recall_text("which theme does the user like?", top_k=5): print(round(hit["score"], 3), hit["content"]) # Cache an expensive call prompt = "Summarize the release notes" hit = db.cache_get("llm", prompt) if hit is None: answer = call_llm(prompt) # your LLM call db.cache_put("llm", prompt, answer, ttl_secs=3600) else: answer = hit["value"].decode()
use mnemo_engine::{CachePutOpts, EmbedCmd, EmbedderIdentity, MemoryType, Mnemo, MnemoConfig, RecallRequest}; fn main() -> mnemo_engine::Result<()> { let cfg = MnemoConfig { dimensions: 384, ..Default::default() }; let mut db = Mnemo::create("agent.mnemo", "a long passphrase", cfg)?; // Any program that turns text into vectors (see examples/embedder_server.py) let id = EmbedderIdentity { name: "sentence-transformers/all-MiniLM-L6-v2".into(), dimensions: 384, normalize: true, }; db.set_embedder(EmbedCmd::new("python3 embedder_server.py", id)?)?; db.remember_text("the user prefers dark mode", MemoryType::Semantic)?; db.cache_put("llm", "prompt-42", br#"{"answer":"..."}"#, CachePutOpts { content_type: "json".into(), ttl_secs: Some(3600), cost_hint_ms: None })?; db.flush()?; // one atomic, crash-safe commit let req = RecallRequest::from_text(db.embedder().unwrap(), "which theme does the user like?")?.top_k(5); for hit in db.recall(&req)? { println!("{:.3} {}", hit.score, hit.memory.content); } Ok(()) }
$ export MNEMO_PASSPHRASE='a long passphrase' # never put it on the command line $ mnemo init agent.mnemo --dimensions 384 # new file, self-describing from the start $ mnemo about agent.mnemo # what is this file and how do I use it? $ EMB=(--embed-cmd "python3 embedder_server.py" --embed-name sentence-transformers/all-MiniLM-L6-v2) $ mnemo remember agent.mnemo --text "the user prefers dark mode" --importance 0.8 "${EMB[@]}" $ mnemo recall agent.mnemo --text "which theme does the user like?" --top-k 5 "${EMB[@]}" $ mnemo cache put agent.mnemo --ns llm --key prompt-42 --value '{"answer":"..."}' --content-type json --ttl 3600 $ mnemo cache get agent.mnemo --ns llm --key prompt-42 $ mnemo snapshots agent.mnemo # every commit is restorable $ mnemo restore agent.mnemo --to-txn 12 $ mnemo serve --mcp agent.mnemo "${EMB[@]}" # expose it to MCP clients, text in
Multi-signal recall
Ranked on more than similarity
An agent needs the right memory, not just the closest vector. Every candidate is scored on four signals, with weights you can set per query. The three bonuses are scaled by similarity, so an unrelated memory can't win by being new, important or popular.
How close the meaning is: cosine, dot product or L2.
× (
Counted from when the memory was stored. Fades with a 7-day half-life by default.
A 0–1 weight set when the memory is stored, centred on 0.5.
A capped boost for memories the agent keeps coming back to, so popular memories can't snowball.
Episodic
What happened: conversation turns and events. Sessions promote their turns here when they close.
Semantic
What's known: facts about the user, the project and the world.
Procedural
How to behave: preferences, rules and working conventions.
Working
Right now: the current session's scratchpad, kept or discarded when it ends.
Agent-first
Every file introduces itself
Hand an agent a .mnemo file and its passphrase, and one command tells it what the file is: which embedding model it expects, the conventions used, and any notes the author left. No separate README required.
- Self-describing. A manifest stored inside the file, printed by
mnemo aboutordb.about(). - Ready from creation.
mnemo initadds a starter manifest so new files are never blank. - Same everywhere. The Rust library, Python package, CLI and MCP server share one engine and one behaviour.
$ mnemo about agent.mnemo # agent.mnemo (66 memories · 384-dim · encrypted · 33 snapshots) ### MANIFEST This .mnemo file is the project memory for MNemo. Embedder: sentence-transformers/all-MiniLM-L6-v2 (384-dim, normalized). Default agent_id: cursor-agent. ### onboarding / mission Ongoing project context lives in this file, so new AI sessions stay aligned without relying on chat history. ## Quick start mnemo list agent.mnemo mnemo recall agent.mnemo --query VEC mnemo get agent.mnemo <id> --verbose
Security & reliability
Private by default, tamper-evident on disk
Without the passphrase the file is opaque, and changed bytes are detected rather than silently trusted.
- Pages are pinned to their position. A valid encrypted page moved to another slot fails authentication.
- The header is sealed. Pointing it at an older catalog is caught on open as tampering.
- No password oracle. A wrong passphrase and a damaged key look identical.
- Safe to copy and keep writing. Every writing session uses a fresh random nonce prefix, so two diverged copies of a file never reuse encryption nonces (fixed in v0.5.2; see the advisory).
- Tested hard. Fuzzed parsers, property tests with simulated crashes, and a dependency audit on every change.
- No
unsafecode. The core crate forbids it outright. - Verifiable releases. PyPI packages are published from CI with signed provenance (Trusted Publishing).
Out of scope, stated plainly: anyone with write access can still delete or truncate the file, swapping in an older valid copy of the whole file can't be detected from inside it, and anyone who has the passphrase is the owner. Read the full security model.
How the keys fit together
format version, key-derivation settings, wrapped data keyplaintext + sealed
pending commits, replayed after a crashencrypted
memories and cache entriesencrypted
what's where, plus the IVF+PQ search indexencrypted
one entry per commit, for restoreencrypted
Performance
Measured, not promised
We only publish numbers we've measured. These are from an Apple M1 Pro (release build, 384-dimension vectors). See the full results.
From v0.5.1, Apple Silicon and ARM Linux builds use the CPU's hardware AES, which makes scans 3–6× faster than v0.5.0, depending on the chip.
Roadmap
Where MNemo is headed
Built in the open, one reviewed release at a time. Full history in the changelog.
Shipped v0.4 – v0.7
- Better ranking: the right memory comes first more often, and frequently shown memories no longer crowd out the relevant one
- Accurate index search: the default finds 96–98% of the true best matches, up from 40%
- Stronger crash recovery: a damaged file header is rebuilt from the write-ahead log, and tampering is always reported
- Plain text in and out from Python, the CLI and MCP; MNemo calls your embedding model
- A check that each file is used with the embedding model it was built with
- Result cache: exact keys and similar prompts
- MCP server for Claude, Cursor and other clients
- File locking and read-only opens
- Fuzzing, property tests and dependency audits in CI
- Benchmarks at 100K–1M memories, plus a ranking-quality test
- Hardware AES on ARM: 3–6× faster scans
- Automated releases, with signed provenance on PyPI
After that planned
- Speedups and smaller files, wherever the benchmarks point
- An optional built-in local embedding model, no separate program needed
- Smarter memory: merge duplicates, summarise sessions, link related memories, forget stale ones
- A Node.js package and framework integrations
Give your agent a memory it can carry
Open source under Apache-2.0. Written in Rust, with Python, CLI and MCP on top.