mnema

July 15, 2026 · View on GitHub

The README is the fast path. This is the why it works — the guarantees, how they're enforced, and how they're proven. If you're evaluating mnema for something that matters, start here.

The Rust library

The CLI and MCP server are thin glue over one library type, Mnema:

use mnema::facade::Mnema;
use mnema::embed::HashEmbedder;
use mnema::{Destination, EgressTier};

let mut mem = Mnema::new(HashEmbedder::new(64));

mem.remember(EgressTier::Open,    "user is planning a trip to Japan in spring");
mem.remember(EgressTier::Private, "user's API key is sk-live-abc123");
mem.remember_fact("user", "diet", "vegetarian");
mem.remember_fact("user", "diet", "omnivore");   // supersedes — belief is now "omnivore"

// The Private API key is structurally absent from a bundle bound for a cloud model:
let ctx = mem.recall("what should I know?", Destination::Remote, 5, 2000);

let blob = mem.seal(b"passphrase")?;             // the whole mind, encrypted at rest
let mem  = Mnema::open(&blob, b"passphrase", HashEmbedder::new(64))?;   // fully restored
# Ok::<(), mnema::store::StoreError>(())

The four memory types

TypeModuleWhat it does
Episodicstoretimestamped events; encrypted at rest (Argon2id + XChaCha20-Poly1305), tamper-evident, hard-deletable
Semanticsemanticfacts with contradiction resolution — a newer belief supersedes, never accumulates
Workingworkingephemeral scratchpad — TTL horizon + capacity cap; not persisted
Procedural(modeled as semantic facts)learned preferences, e.g. "answer in metric"

The guarantees

Each is a hard invariant, pinned by the mutation gate (below):

InvariantWhere enforced
A Private memory's content never reaches a Remote bundlelib — one pack_bundle egress choke point
A resolved contradiction never leaves two live beliefssemantic
Encryption round-trips; a wrong key or tampered byte never yields plaintextstore
A forgotten memory is gone from state and every re-sealed blob; its id is never reusedstore
At full probe, the approximate index equals the exact oraclevector (IvfIndex)
A crash mid-write, or a wrong key, never destroys the storeCLI/MCP — atomic write + refuse-on-unopenable

The egress wall (privacy)

Every memory carries a tier: Open, Redacted, or Private. Every path that could send memory to a model — query recall, recency recall, belief lookup — funnels through a single function, pack_bundle, which applies the tier against the destination:

  • Open → content is sent.
  • Redacted → only a sanitized surface is sent (never the full content).
  • Private bound for Remote → dropped, unconditionally.

Because there is exactly one choke point, "a private memory cannot leak to the cloud" is a property of one auditable function, not a convention scattered across the codebase. Stored text is always delivered to the model as data, never as an instruction — injection-resistant by construction.

Encrypted at rest

seal/open derive a key from the passphrase with Argon2id (memory-hard) and encrypt the whole store — episodic log, beliefs, clock — with XChaCha20-Poly1305 (AEAD). The on-disk blob is version || salt || nonce || AEAD(plaintext); a wrong key or a single flipped byte fails the authentication tag and yields an error, never plaintext. Writes are atomic (temp file + rename), so a crash or full disk leaves the previous store intact. A wrong key on an existing store makes the server refuse to start rather than overwrite it. Concurrent writers are serialized by an advisory lock. Deletes are hard: forget removes matching events from state and every future re-sealed blob, and their ids are never reissued.

Not claimed: unbreakable, or safe against a compromised OS / root / a malicious local model. Local-first raises the bar; it doesn't make the machine a vault. #![forbid(unsafe_code)] crate-wide.

Retrieval

Hybrid recall fuses three retrievers with reciprocal-rank fusion:

  • dense — cosine over embeddings (exact VectorIndex, or approximate IvfIndex);
  • recency — newest first;
  • keyword — BM25 lexical overlap.

A forgetting curve then weights each hit by importance × 0.5^(age / half_life), near-duplicates are suppressed, and the result is packed through the egress filter under a character budget. Recall is exact by default (the correctness oracle); an approximate IvfIndex trades recall for speed and is opt-in (build_ann + recall_fast) — at full probe it returns exactly what the exact index does.

Embedders

By default recall uses a zero-dependency lexical embedder (HashEmbedder) — no model, no download. For real semantic recall, build with either: http-embed — call a local embeddings endpoint you already run (Ollama, or any OpenAI-compatible /v1/embeddings; the light path, no ML build); or local-embed — an in-process all-MiniLM-L6-v2 via candle (self-contained, heavier build, fetched and cached on first use). The embedder is fixed per store — its vector width is recorded, and opening a store with a mismatched embedder is refused; Mnema::migrate (and mnema-server --migrate) re-embeds an existing store under a new embedder without data loss.

Measured retrieval quality

Recall is measured on LoCoMo (Maharana et al.), the public long-conversation memory benchmark, with reproducible in-repo harnesses — no LLM judge on the retrieval number, so it can't be gamed. Ingest every conversation turn as a memory, then for each of the 1,981 answerable questions measure whether the gold evidence turns land in top-k (benches/locomo.rs, all 10 conversations):

retrieverRecall@5Recall@10
lexical (HashEmbedder, default)0.2450.380
semantic (all-MiniLM-L6-v2, dense-weighted)0.4010.467

The semantic path lifts Recall@5 by +15.6 points — that gap is what a real embedder + dense-weighted fusion buys on the memory layer's actual job. (An in-repo paraphrase fixture, benches/recall.rs, tells the same story on hand-written queries: semantic R@5 0.769 vs lexical 0.385.)

An end-to-end QA-accuracy harness (benches/locomo_qa.rs) closes the loop — retrieve context, let a local LLM answer, judge the answer — the LLM-graded metric Mem0/Zep report. It runs fully locally via Ollama (no API key). The reported number is answerer-bound, not a retrieval ceiling: on one fixed 25-question sample, swapping only the model lifts accuracy from 0.16 (llama3.2:3b) to 0.28 (qwen2.5:7b) — +75% for the same retrieval. What remains is multi-hop retrieval recall (bounded by R@10 0.47) and hard relative-date arithmetic, not the answerer — point $QA_MODEL at a stronger endpoint to raise it further.

How the guarantees are proven

Every invariant is pinned by a zero-dependency behavioral mutation-coverage gate. Its rule: a green build proves the changed logic is tested — mutate any pinned branch and a test must turn red, or the build isn't green. Literals and exact boundaries (which the operator set can't reach) are pinned separately by hand-computed value tests. The gate is complemented by a fuzz test over the store parser (adversarial bytes never panic) and a multi-agent adversarial audit of the cross-surface invariants.

Runs in a browser

The zero-dependency core compiles to WebAssembly (wasm32-unknown-unknown), so the memory layer can run entirely in a browser tab — private memory with no server round-trip.

Validated in a live agentic loop

Beyond unit tests, mnema has been driven by a real self-improving agent: the loop stored each iteration's verdict as a memory and recalled the relevant lessons before the next proposal. In live runs the proposer demonstrably used the recall — it cited a recorded rejection by its detail code and redesigned its change specifically so it would not repeat the remembered failure. The contradiction-resolving writes, the egress filter, and the encrypted store all held throughout.

Feature flags

FeatureEnables
(none)the zero-dependency core: retrieval, semantic, working, vector, egress
secureencryption at rest (Argon2id + XChaCha20-Poly1305), the Mnema facade, the mnema CLI
mcpthe mnema-server server (implies secure)
http-embedsemantic recall via a local HTTP embeddings endpoint (Ollama / OpenAI-compatible)
local-embedthe bundled in-process semantic embedder (all-MiniLM-L6-v2 via candle)

License

Dual-licensed under MIT OR Apache-2.0.