Mnemo
On-prem, MCP-native, cryptographically-auditable memory for regulated AI (EU AI Act Art.12 · India DPDP · HIPAA §164.312(b)).
Mnemo runs on your own infrastructure and writes a hash-chained memory log an auditor can check offline with their own code, without running mnemo or trusting a hosted tier. The chain catches edits made without recomputing it, not someone who can rewrite the store file (what it does and does not prove). See Positioning for how that compares to Mem0 and Letta.
📖 Documentation: https://sattyamjjain.github.io/mnemo/ — the full mdBook (quickstart, architecture, MCP tool reference, REST/SDK guides, compliance docs), deployed from docs/ on every push to main.
Mnemo (from Greek mneme — memory) is an embedded database (DuckDB in-process, or your own PostgreSQL) whose primitives — REMEMBER, RECALL, FORGET, SHARE — are exposed as MCP tools any AI agent connects to directly. What sets it apart for regulated deployments: every write and delete is a SHA-256 hash-chained agent_events entry an external verifier can check offline (no mnemo code, no hosted tier), and mnemo-compliance layers signed audit-log export + DPDPA consent records on top.
→ Verify a mnemo log yourself, without trusting mnemo — a five-minute transcript: write three records, export the chain, run a standalone verifier whose only imports are the Python standard library, then tamper with one record and watch it get caught by name. Includes what a PASS does not mean, and a concurrency defect the exercise itself found.
→ Positioning: on-prem, MCP-native, cryptographically-auditable memory for regulated AI — how mnemo compares to Mem0, Letta, and native provider memory on the compliance-audit axis, wired to the shipped bench numbers.
On-prem, MCP-native, cryptographically-auditable memory for regulated AI
Mnemo runs in-process on your own infrastructure (embedded DuckDB or your
PostgreSQL) with no hosted tier to trust, and every write and delete lands in
a SHA-256 hash-chained agent_events log. An external verifier catches any
edit that was not re-hashed, offline, without consulting the store. That is the
record-keeping substrate regulated AI deployments need (EU AI Act Art.12 /
Art.26(6), India DPDPA, HIPAA §164.312(b) audit controls).
The chain has a limit, and it matters for that use: it has no key and no anchor outside the store yet, so someone who can write the store file can recompute it, cut its tail or put back an older copy, and it still verifies. SECURITY.md puts that attacker out of scope, and known limitations has the details and the planned fix.
The detection is proven, not asserted — a deterministic, offline bench writes memories
through the real path, then shows an outside verifier accepts the pristine log,
catches 100% of single-byte mutations (256 trials, Wilson 95% ≥ 98.5%), and
confirms forget appends a hash-chained delete event rather than erasing the trail:
cargo run --release -p mnemo-audit-conformance-bench
# → bench/audit_conformance/results/conformance.md (byte-stable report + recomputable SHA-256 crypto vector)
Scope of that 256: it is mutation detection on a log the bench wrote one record at a time, and says nothing on its own about concurrent writes. So the same bench now runs a concurrency arm and reports it in the same units, side by side:
16 writers × 16 writes = 256 records on 8 tokio worker threads, on an Apple M4
(10 cores, arm64, macOS); the denominator is 255 because exactly one record is
legitimately the chain head. Alongside the rate the arm asserts what a rate cannot
express — exactly 1 head, 0 forks, 0 dangling records, and all 256 reachable by
walking links from that head — with no interval to hide behind. Until v0.5.29 that
arm produced 16 heads and 0 links; the structural regression test lives in
concurrent_chain_linkage.rs.
Reproducible-by-disclosure memory: mnemo publishes its LoCoMo numbers with a
fixed seed + a Wilson-95 you can re-run offline (cargo run --release -p mnemo-locomo-bench --bin reproduction_bench), tabled against vendors' cited,
not-re-run claims — see
bench/locomo/results/reproduction_2026-07-06.md.
Real-embedder retrieval quality (measured, not asserted): the byte-reproducible number above runs under a deterministic hash-bag embedder, which is a lexical floor, not a retrieval result.
The one headline real-embedder number is the generated block below: Xenova/all-MiniLM-L6-v2 384-dim, n=45, recall@1 0.689 [0.543, 0.805], against a lexical control on the same corpus and harness. On the same 45 queries the paired gap over that control is +0.267 [95% 0.133, 0.400], McNemar exact p=4.9e-4 — it separates.
There is deliberately only one headline, and an older measurement is kept below it under its own heading rather than beside it.
Every published number, this one and the poisoning / audit / retrieval numbers
further down, is collected in one place with its exact reproduction command, its
raw results file, and a "what this does not show" column:
docs/benchmarks/index.md (the single benchmark
entry point). The mirror of that page, things a reader might reasonably assume
mnemo measures and it does not, is
docs/security/known-limitations.md. Full per-mode tables and the different-axes caveat vs. Mem0/Letta
are in the benchmark tables below and bench/RESULTS.md.
Real-embedder recall, measured on the supported backend. Gold-document recall@1 = 0.689 [Wilson 95% 0.543, 0.805], recall@5 0.889, recall@10 0.911, MRR 0.770. Against the lexical control on the same 45 queries the paired gap is +0.267 [95% 0.133, 0.400], McNemar exact p=4.9e-4 (12 queries won, 0 lost) — the gap separates at 95%.
The lexical row is the control, not a second headline: it is the same corpus and the same harness with the vector lane switched off, so what the embedder buys is the difference between them. Do not read that difference off the two intervals — they overlap (0.543 sits below 0.567), and overlapping intervals neither establish nor rule out a difference. The paired row is the one that answers it: the same queries scored both ways, so each query is its own control.
Against the strategy you actually get. auto is the default: recall() resolves an unset strategy to auto, so unless a caller names one, this is the path they are on — and it scores recall@1 0.631 here, not 0.689. On the same 45 queries semantic beats it by +0.058 [95% -0.031, 0.160], McNemar exact p=0.6875 (4 queries won, 2 lost). That interval runs from -0.031 to 0.160: it contains 0, so the gap does not separate at 95% and the sign of the difference is not established by this sample. Roughly n=127 paired queries would be needed at this effect size. The +0.267 headline above is against the lexical control, which is not the default and which nobody runs by choice; it is the embedder's contribution, not the improvement a user sees over stock behaviour.
Reproduce:
# fetch the exact weights the sha256 above pins
curl -sSL --fail -o model.onnx https://huggingface.co/Xenova/all-MiniLM-L6-v2/resolve/main/onnx/model.onnx
MNEMO_ONNX_MODEL_PATH=./model.onnx cargo run --release --features onnx \
-p mnemo-locomo-bench --bin locomo_v1_bench
What this number is not. It is not a LoCoMo leaderboard score and is not comparable to one: the corpus is the bundled LongMemEval_M slice, not full LoCoMo. It is retrieval quality only, with no LLM in the loop and no answer-correctness judge. It says nothing about poisoning resistance or audit integrity, which are measured separately. n=45 is below 100, so the bench marks it preliminary; treat the interval, not the point estimate, as the claim. This label cannot clear by re-running. The corpus is the bundled longmemeval_m.jsonl, which is 45 records in full — not a sample of a larger local file — and the bench sets preliminary whenever n < 100. So it reflects a corpus-size decision, not work in progress. Clearing it honestly needs the larger public LongMemEval slice, which is access-gated (see #44); until that is vendored under a recorded licence, this number stays preliminary at n=45.
This block is generated from bench/results/locomo_v1.json by
scripts/gen_recall_number.py; the doc-guards CI job runs it
with --check, so the README cannot drift from the measured result. It is also
CI-reproducible: real-embedder-benches-nightly
fetches the digest-pinned checkpoint, re-runs the bench and compares against the
committed artifact nightly, opening an issue if either number leaves its band.
The most recent run reproduced recall@1 exactly — 0.6890 committed, 0.6890
regenerated — on an x86-64 Linux runner, against a number first measured on
arm64/darwin.
An earlier real-embedder run, kept for provenance and not a second headline. It used a different embedder on a different slice, so it is not comparable to the number above and the two must never be quoted together as a range.
The interval is the point. At n=23 the Wilson 95% interval is [0.535, 0.875], which is wider than the headline's and fully overlaps it ([0.543, 0.805]). That overlap is not a finding of equivalence — the same arithmetic that cannot prove a difference cannot prove its absence either. What can be said is narrower and worth saying plainly: these are two separate runs on different corpora, so unlike the semantic-vs-lexical comparison above there is no paired statistic available here at all, and at n=23 the interval is far too wide to resolve a gap of this size in either direction. The higher point estimate is therefore not evidence that this embedder is better. Neither run clears the repo's own n≥100 bar, and both are marked preliminary for that reason.
Reproduce with no credentials and no special build feature:
ollama pull nomic-embed-text && cargo run --release -p mnemo-locomo-bench --bin semantic_recall_bench
The runner refuses to emit a score under a no-op embedder.
And the memory-poisoning defense is measured on a real embedder, not asserted.
Through a real ONNX MiniLM embedder (not a hash stand-in), mnemo's always-on
lexical / self-referential lane drops canonical MINJA ASR 100% → 0% at
0/300 benign false-quarantine. Honestly, the opt-in embedding z-score lane
does not generalise to a dense embedder: on MiniLM, poison lands ~1.5σ from
the benign mean (below the 3σ gate), so marker-stripped and consolidation-style
redirects survive (ASR 100%) — a limitation we publish rather than hide,
correcting the hash-embedder bench's rosier z-score reading. ASR + Wilson-95 +
benign-FPR per attack, refuse-to-score-on-noop:
docs/BENCH_POISONING.md
Non-adaptive Phase-3 exploitation, against a digest-pinned real embedder
(#37): a poisoned record that
is already Phase-2-shortened is exploited at 0.956 [Wilson 95% 0.906, 0.980],
129/135 trials (45 distinct victim queries × 3 seeds; conservative interval at
the distinct-query denominator [0.852, 0.988], n=45). With the detector on:
0.956 [0.906, 0.980], 129/135 — defense delta 0.0000, and 0/135
records quarantined at a mean z of 1.08 against the 3.0 gate. The matched
benign twin is retrieved at 0.956 [0.906, 0.980], 129/135, so the
poisoning delta is +0.0000: the rate is topical retrieval, not poisoning.
This is not a MINJA number — Phases 1 and 2 are generative and adaptive and
are not implemented, and it says nothing about an adaptive attacker.
docs/benchmarks/2026-08-25-minja-phase3-nonadaptive.md
· bench/results/minja_phase3.json
(cargo run --release --features onnx -p mnemo-poisoning-bench --bin poisoning_real_bench).
This bench is also --features onnx, and like the ONNX MiniLM retrieval number
above it is CI-reproducible: the same
real-embedder-benches-nightly
job regenerates it against the pinned checkpoint and reproduced every rate exactly,
including the canonical-MINJA 1.000 → 0.000 defense delta. The ort integration
it used to be blocked on was repaired in
#125 (closed 2026-08-04), and
ci.yml's onnx-feature job has built, linked and run end-to-end ONNX inference on
every push since. The byte-stable hash-embedder defense-delta companion also runs
in CI and is at bench/poisoning/.
Quarantine is only half the story — the other half is the auditable layer,
mnemo's real wedge for OWASP ASI06 — Memory & Context Poisoning
(whose recommended control is provenance on every write + evaluation against
ground truth). Against three ASI06 attack families — contradictory-fact
overwrite, authority-spoofed origin + provenance forgery, belief-drift
splice/back-date — the SHA-256 hash-chain + read-provenance HMAC layer rejects
100% of poisoning cover-up/forgery attempts (1500/1500, Wilson 95% [99.7%,
100.0%]) at 0% benign false-positive (0/300, [0.0%, 1.3%]); a naive store
with no crypto layer catches 0%. Read honestly: this is tamper-evidence +
attribution against cover-ups that do not recompute the chain, not write-time
prevention, and not protection against someone who can rewrite the store. Method,
numbers, and the LoCoMo ground-truth-quality caveat:
docs/benchmarks/asi06-poisoning.md
(cargo run --release -p mnemo-asi06-poisoning-bench --bin asi06_poisoning).
That ASI06-recommended control — provenance on every write — is now the
default path: every remember/share records who wrote it, under what
capability, in what session, hash-chained and revocable by principal (see
Write provenance & FORGET BY PROVENANCE).
The compositional ("Salami") case — the harder shape where each of N writes
is individually benign but their union is harmful (arXiv:2608.01637)
— is measured, not defended, at bench/salami_poisoning/:
individually-benign slices sail through the write path (high save rate) and a
single trigger recall reliably reconstructs the harmful composition (high
retrieval-influence rate), while a topic-matched benign control co-retrieves
yet never completes the harm (~0). Both rates carry a Wilson-95 interval;
deterministic + offline. This is the compositional subset of
#37 — a quantified gap, not a
claimed defense (cargo run --release -p mnemo-salami-poisoning-bench).
Regulatory mappings (honest, hedged, not legal advice): EU AI Act Art.12 · India DPDP (Rules 2025) · ASI06 memory-poisoning resistance. Apache-2.0, no SaaS.
Install from crates.io
The 0.5.x compliance line is on crates.io — the minimal set to run on-prem, hash-chain-audited memory in your own Rust service:
cargo add mnemo-core mnemo-compliance # engine + audit-log/consent primitives
cargo add mnemo-mcp # (optional) expose it as MCP tools
cargo install mnemo-mcp-server # server binary → `mnemo`
What these commands resolve is in the generated table below, which is the authority. No version is restated here: a hand-written copy of a generated number is a copy that drifts, and this paragraph did — for four days after v0.5.28 reached crates.io it still described that version as pending.
The seven satellite crates that trailed a patch during the
v0.5.25window have caught up, and they are in the tag walk as ofv0.5.26, so the split that caused it cannot recur: see Naming.Three guards keep this honest instead of stale: the test
crates/mnemo-cli/tests/readme_crates_version_matches_workspace.rsfails if the stated release, or any bare current-band0.5.2xrelease literal anywhere in this README (thev-prefixed feature history is exempt), drifts from the workspace[workspace.package].version;scripts/check_version_drift.shfails if crates.io falls more than one patch behind the workspace; andscripts/check_publish_closure.shfails if a publishable crate is missing from the release closure, which is what let a crate fall behind silently in the first place.
Workspace [workspace.package].version (unreleased target): v0.5.30. The Rust library line and the Python SDK both track the workspace (the wheel compiles mnemo-core into itself, so its version names the engine inside it). Only the TypeScript SDK versions independently. Published, per registry:
Table generated from the live registries by scripts/gen_published_versions.py; scripts/registry_parity.sh fails a release if these drift from what the release actually published.
Naming
This project does not own the mnemo name on crates.io, and does not publish a
crate called mnemo. Everything it publishes carries the mnemo-* prefix.
Two unrelated crates sit on names you might otherwise reach for:
mnemo-cli is the easier mistake: this repo's server binary lives in the
directory crates/mnemo-cli, but it publishes as mnemo-mcp-server. The
directory name is not the crate name.
The server binary is:
cargo install mnemo-mcp-server # installs the `mnemo` executable
The mnemo command this installs is ours; the mnemo crate is not.
mnemo-db on crates.io is a defensive
name-reservation pointer that ships no code and redirects here — distinct from
the PyPI mnemo-db package, which is the real Python SDK (see
Python below).
scripts/check_crate_name_refs.sh fails CI if a bare cargo install mnemo or
cargo add mnemo (or the mnemo-cli equivalents) reappears anywhere in the
docs.
All published mnemo-* crates are at one version
Installing the right name is only half of it: cargo install resolves whatever crates.io actually has. Of the 21 published mnemo-* crates, 21 are not yet on the workspace version v0.5.30: mnemo-admin (0.5.29), mnemo-amp (0.5.29), mnemo-attention-state (0.5.29), mnemo-baseline (0.5.29), mnemo-cma (0.5.29), mnemo-codemode (0.5.29), mnemo-compliance (0.5.29), mnemo-core (0.5.29), mnemo-db (0.5.29), mnemo-deal (0.5.29), mnemo-embeddings-bench (0.5.29), mnemo-graph (0.5.29), mnemo-grpc (0.5.29), mnemo-letta (0.5.29), mnemo-mcp (0.5.29), mnemo-mcp-server (0.5.29), mnemo-md-sync (0.5.29), mnemo-mesh (0.5.29), mnemo-pgwire (0.5.29), mnemo-postgres (0.5.29), mnemo-rest (0.5.29). That is either a release in flight or a stranded crate; scripts/check_version_drift.sh distinguishes the two by naming the crates rather than reporting a total.
The 21 are mnemo-admin, mnemo-amp, mnemo-attention-state, mnemo-baseline, mnemo-cma, mnemo-codemode, mnemo-compliance, mnemo-core, mnemo-db, mnemo-deal, mnemo-embeddings-bench, mnemo-graph, mnemo-grpc, mnemo-letta, mnemo-mcp, mnemo-mcp-server, mnemo-md-sync, mnemo-mesh, mnemo-pgwire, mnemo-postgres and mnemo-rest.
One of them, mnemo-db, ships no code: it is a defensive name reservation whose entire contents are a doc comment pointing at mnemo-core and mnemo-mcp. It is counted because it is a real published artifact someone can cargo add, and they should learn that from the count rather than from an empty crate.
Count and list generated from cargo metadata (publishable workspace members, less those with a written never-published exemption) and checked against the live registry by scripts/gen_published_versions.py.
Other mnemo* names on crates.io (mnemo, mnemo-cli, mnemo-engine,
mnemo-rs, mnemo-server) belong to unrelated projects. See
Naming.
This was not true a day earlier, and the history is worth keeping because two guards were changed on account of it.
mnemo-mcp-server had stranded at v0.5.23, skipping v0.5.24 entirely,
because the tag lane that publishes it could not resolve mnemo-admin: the
publish closure was written down three times and the copies drifted apart. There
is now one WALK definition that the gate, the packaging dry-run and the publish
loop all expand. Its version history on crates.io still shows the gap:
v0.5.25, v0.5.23, v0.4.4.
Seven satellite crates (mnemo-letta, mnemo-mesh, mnemo-codemode,
mnemo-deal, mnemo-md-sync, mnemo-cma, mnemo-baseline) then sat a patch
behind, because they were not in the tag walk at all and published only on the
push-to-main lane, which had stopped partway through the release. They have since
caught up, but "it resolved itself" is not a fix: two lanes with different
contents is the condition that lets a crate fall behind with nothing going
red, which is the confusion the drift guard exists to prevent rather than
describe.
So the split is closed rather than waited out. All seven are in WALK as of
v0.5.26 (they depend on mnemo-core alone and nothing depends on them, so
folding them in reorders nothing), and
scripts/check_publish_closure.sh now
asserts the general form in CI: every publishable workspace member must appear in
the closure or carry a written exemption. A new crate cannot be orphaned by being
left out of a list.
scripts/check_version_drift.sh treats one
patch behind an unreleased workspace as a publish in flight: green, but it
names the crates rather than reporting "every published crate matches
workspace", which it used to say while seven of them did not.
The summary table above is written by hand and is a narrative of one moment. The
authoritative, per-registry numbers are in
the generated table under Install from crates.io, which is
generated from the live registries by
scripts/gen_published_versions.py and
re-checked by the doc-guards CI job, so a stale table there fails the build
rather than quietly misinforming a reader.
The wedge — a memory-write log an auditor can verify offline, without trusting
the store — is the mnemo-core
hash-chain verify API:
use mnemo_core::hash::{verify_chain, ChainVerificationResult};
// `records` is the exported, hash-chained write log. The store is never
// consulted — the verifier is a pure function an auditor runs on their machine.
let result: ChainVerificationResult = verify_chain(&records);
assert!(result.valid); // false + `first_broken_at` names the first edit that was not re-hashed
Signed NDJSON export and DPDPA consent records live in
mnemo-compliance
(export_audit_log, verify_ndjson_signed). Why this matters, and how mnemo
compares to Mem0 / Letta / native provider memory on the compliance-audit axis:
docs/POSITIONING.md.
Quickstart
1. Build
cargo build --release
2. Configure your AI agent
Add to your MCP client configuration (e.g. Claude Desktop, Cursor, etc.):
{
"mcpServers": {
"mnemo": {
"command": "./target/release/mnemo",
"args": ["--db-path", "./agent.mnemo.db"],
"env": {
"OPENAI_API_KEY": "sk-..."
}
}
}
}
3. Use it
Your AI agent now has persistent memory with 21 MCP tools:
Access Protocols
Attention-state-memory substrate (v0.4.5)
mnemo v0.4.5 ships an attention-state-memory substrate anchored on arXiv:2605.18226 (Context Memorization). Two new MCP tools — mnemo.attention_state.put and mnemo.attention_state.get — store and retrieve opaque attention-state blobs keyed by (agent_id, prefix_hash). The substrate is implemented in crates/mnemo-attention-state with a typed AttentionStateStore trait + an InMemoryAttentionStateStore reference impl.
Honest scope: mnemo ships the store. The producer (inference runtime that extracts prefix states) and the consumer (re-injection on the next generation) are out of scope; the substrate's blob format, model compatibility, and quantization sensitivity are the producer's responsibility. Tools are registered only when MnemoServer::with_attention_state(...) is configured at startup; unconfigured calls return a spec-shaped error result, not a panic. See the research anchor for the operator recipe + the explicit non-overclaim disclaimers.
Memory under interference — current-fact resolver (v0.4.7)
arXiv:2605.18565 (MINTEval) measures how often memory systems return a superseded value of a fact after the same fact has been revised K times. mnemo v0.4.7 ships an opt-in current-fact resolver that post-processes the standard recall result set: candidates sharing the same value under a caller-chosen fact_key (typical convention: "fact_id") are grouped, and only the most-recent write per group is kept. When include_supersession_chain = true, older fact-versions are returned in the response's new superseded field for audit.
Enable via the MCP recall tool param current_fact_resolver: { fact_key, include_supersession_chain }, the REST ?current_fact_key=…¤t_fact_include_chain=true query params, or the Rust RecallRequest.current_fact_resolver = Some(...) field directly. The default read path is unchanged — the resolver is purely additive and opt-in. The MINTEval-shaped bench at bench/locomo/src/bin/interference.rs compares default vs resolver arms across K ∈ {1, 3, 5, 10} revisions; see the resolver module doc at crates/mnemo-core/src/query/current_fact_resolver.rs for the contract + the explicit "not a contradiction detector / not a write-side guard" disclaimers.
Repeated-context recall — orientation cache (v0.4.8)
arXiv:2605.19932 (PEEK — Prefix-Encoded Episodic Knowledge) shows that a small, token-budgeted "orientation map" maintained alongside an agent's retrieval surface (key entities, UPPER_SNAKE constants, fenced schema fragments that have been useful) lets agents re-enter long-running contexts with a fraction of the recall payload. mnemo v0.4.8 ships an opt-in orientation cache that post-processes the standard recall result set: a heuristic Distiller extracts transferable knowledge from each hit and a priority Evictor enforces a fixed token budget (default 512). The recall response carries the bounded rendered map alongside top-k so the caller has both what is in scope and what is relevant right now in one payload.
Enable via the MCP recall tool param orientation_cache: { namespace?, token_budget?, include_in_response?, distill? }, the REST ?orientation_cache=true&orientation_namespace=…&orientation_token_budget=… query params, the gRPC OrientationCacheRequest message, the pgwire /*+ orientation_cache */ SQL hint comment, or the Rust RecallRequest.orientation_cache = Some(OrientationCacheConfig::new()) field directly. The store is in-process, namespace-scoped ((org_id, agent_id) by default), and lost on restart — persistence is a v0.5.x knob. The default read path is unchanged and orientation rendering only fires when both the caller passes a config AND the engine has an OrientationCacheStore attached via MnemoEngine::with_orientation_cache_store(). See the PEEK-anchored bench at bench/locomo/src/bin/orientation.rs for the repeated-context scenario (K ∈ {3, 6, 10, 15} calls per trial) and the module doc at crates/mnemo-core/src/query/orientation_cache.rs for the contract + the explicit "not a learned summariser / not a context-window extender / not persisted" disclaimers.
Cost-aware, answer-impact-scored recall — evidence budget (v0.4.12)
The default recall path front-loads: it returns the top-limit records by fused score, and an LLM caller pays context tokens for every chunk whether or not it changes the answer. mnemo v0.4.12 ships an opt-in cost-aware evidence budget that runs over the already-ranked candidate set and returns the smallest prefix that clears a configurable sufficiency bar, capped by an optional max_evidence. It is purely subtractive — it only ever returns a prefix of the ranked list, so it can never reorder or silently lower the retrieval's top-k cosine ordering (enforced by a property test).
Relevance is computed through a pluggable EvidenceScorer trait so callers can swap the signal used to decide sufficiency: CosineScorer (default — cosine of candidate vs query embedding, falling back to the fused retrieval score when embeddings are absent/degenerate) and DeltaScorer — an answer-impact scorer that rates a chunk by whether adding it to the evidence already selected would change a downstream answer. The "would the answer change?" judgement is an injectable closure (DeltaScorer::new(|ctx| …)) so the engine core stays model-agnostic; DeltaScorer::stub() ships a deterministic marginal-novelty heuristic for tests and offline use.
Enable via the Rust RecallRequest.evidence_budget = Some(EvidenceBudget { max_evidence, stop_when_sufficient, sufficiency_threshold, scorer }) field. Attach a custom answer-impact scorer with MnemoEngine::with_evidence_scorer(Arc<dyn EvidenceScorer>); when the budget selects ScorerKind::Delta but no scorer is attached, the path falls back to cosine rather than erroring. The recall response carries an evidence_selection diagnostics block (scorer name, examined vs returned counts, cumulative score, early-stop / capped flags). The default read path is unchanged — no evidence_budget means the legacy front-loaded top-limit. See the module doc + property test at crates/mnemo-core/src/query/evidence.rs and the end-to-end integration test at crates/mnemo-core/tests/cost_aware_recall.rs.
Budgeted evidence retention — EMBER-style capsules (arXiv:2606.05894)
Where the v0.4.12 evidence budget caps how many records come back, an agent that must keep evidence resident in a bounded context window has a different problem: raw chunk dumps burn the window fast — a handful of full records and it's full, even though most of each record is filler around one salient fact. EMBER (arXiv:2606.05894) frames this as a writer problem: under a fixed retained-token budget, keep compact, recoverable evidence rather than raw text.
mnemo extends the verified recall surface with an opt-in RecallRequest.retained_token_budget: Option<usize>. When set, the engine packs the recalled hits into at most budget tokens as verbatim evidence capsules — a short verbatim excerpt plus a retrieval key (the record id, so the caller re-fetches the full chunk on demand) — ranked by a v0 recoverability heuristic (recency × retrieval-hit-rate) standing in for EMBER's learned writer. It is purely additive: the memories list is unchanged, and the capsule view rides in RecallResponse.retained_evidence. No new enum, no protocol change — just the one parameter on the existing surface.
The eval at crates/mnemo-core/tests/budgeted_evidence_retention.rs measures the knob's value on a LongMemEval-style fixture (60 gold facts, each behind a noisy chunk) at a fixed 8192-token budget — budgeted capsules vs naive truncation:
| arm | covered | recall@budget | retained tokens |
|-------------------|--------:|--------------:|----------------:|
| naive truncation | 45 | 0.750 | n/a |
| budgeted capsules | 60 | 1.000 | 4380 |
Because each capsule costs a fraction of a raw chunk, every gold fact survives the budget (recall 1.000 vs 0.750) using barely half the tokens. See the module doc + tests at crates/mnemo-core/src/query/retained.rs for the cost model + the full "what this is NOT" block (v0 heuristic, not EMBER's learned writer; read-side projection, nothing mutated).
Agent-controlled memory mode — agent-managed flat store (AutoMEM, arXiv:2606.04315)
Mnemo's default path is an ingestion + retrieval pipeline: content is written, indexed, and recalled by vector + BM25 + graph + RRF. AutoMEM (arXiv:2606.04315) frames a crossover: the fixed pipeline wins single-shot retrieval (it ingested everything, so the fact is in the index), but on long-horizon, multi-session workloads an agent that controls its own writes over a simple flat store can win, because it revises stale facts in place instead of letting every version pile up and pollute retrieval.
So mnemo adds an opt-in agent-controlled memory mode over the MCP tool surface — four tools the agent itself calls to manage a flat store it curates:
These are thin compositions over the verified remember / recall / forget primitives plus a reserved agent-managed tag — no new engine enum or method. The default mnemo.recall pipeline is untouched and remains the fallback for single-shot queries. The point is write-control: the agent (not an ingestion heuristic) decides what persists.
The crossover eval at crates/mnemo-core/tests/agent_managed_crossover.rs measures both directions on a multi-session fixture (12 tracked facts × 3 revisions + 12 incidental details), holding retrieval to BM25 so the measured variable is purely write-control:
| query family | mode | recall@k | precision | F1 |
|---------------------------|----------------|---------:|----------:|------:|
| long-horizon current-fact | fixed-pipeline | 1.000 | 0.333 | 0.500 |
| long-horizon current-fact | agent-managed | 1.000 | 1.000 | 1.000 |
| single-shot incidental | fixed-pipeline | 1.000 | 1.000 | 1.000 |
| single-shot incidental | agent-managed | 0.000 | 0.000 | 0.000 |
The pipeline still wins single-shot retrieval (recall 1.000 vs 0.000 — it ingested the incidental details the agent chose to skip); the agent-managed path wins long-horizon write-control (current-fact F1 1.000 vs 0.500 — it revised in place, so no stale versions dilute precision). The agent-managed mode is for long-horizon write-control, not a replacement for single-shot retrieval. See the tool inputs at crates/mnemo-mcp/src/tools/agent_managed.rs.
Experience-memory tier — cached plan replay (DocTrace, arXiv:2606.10921) — v0.4.14
The four primitives above (REMEMBER / RECALL / FORGET / SHARE) operate on tier 1: the raw memory store. DocTrace (arXiv:2606.10921) adds a tier 2 — an experience memory that caches a successful retrieval/reasoning plan and replays it when a structurally-similar query recurs, instead of re-running full retrieval. mnemo ships this as a mode, not a new store: two new ops on the existing engine surface, gated behind MnemoEngine::with_experience_memory() (or the MNEMO_EXPERIENCE_MEMORY=1 env on the server) so default behaviour is unchanged.
Plans are persisted as ordinary MemoryRecords carrying a reserved tag with the plan payload in metadata, so the tier is backend-agnostic (DuckDB + PostgreSQL, unchanged schema) and RBAC/consent-gated exactly like every other record (private plans are invisible to other agents; shared plans honour the ACL). A query signature is its normalized significant-token set; structural similarity is the Jaccard overlap of two signatures — deterministic and embedder-agnostic for the v0 replay gate. Plan records are excluded from ordinary recall (replayed only via RECALL_PLAN). See crates/mnemo-core/src/query/experience.rs and the contract tests at crates/mnemo-core/tests/experience_memory.rs (store-on-success, replay-on-similar, no-replay-on-dissimilar, RBAC, mode-off).
Domain-scoped recall — anti vector-search-dilution (MASDR-RAG, arXiv:2606.11350) — v0.4.15
RetrievalMode::DomainScoped adds a recall mode that pre-filters to a metadata-defined sub-corpus before the dense similarity step, then runs a single vector pass. The predicate rides on an additive, optional RecallRequest.domain_scope kwarg (a DomainScope { org_id, namespace, doc_class, tags }) — no breaking change to existing callers, and the legacy strategy / typed mode paths are untouched. Over MCP, pass a domain_scope object to mnemo.recall (named domain_scope, not scope, because scope already filters visibility):
{ "query": "indemnification cap", "domain_scope": { "org_id": "acme", "namespace": "legal", "doc_class": "contract" } }
Why scoping beats more-documents. Dense retrieval ranks by semantic similarity, and similarity is not domain-awareness: a query about one tenant's contracts is highly similar to every other tenant's contracts too. As the corpus grows, those off-domain-but-on-topic records crowd into the top-k and push the genuinely relevant ones out — so adding documents makes precision worse, not better. Restricting the candidate set to the right sub-corpus before the vector search removes the dilutants entirely, so precision is independent of how large the rest of the corpus gets. The dilution eval at crates/mnemo-core/tests/domain_scoped_dilution.rs reproduces the curve on a corpus growing 50 → 1,000 docs (10 fixed in-domain gold docs, the rest same-topic off-domain distractors):
| corpus | flat P@10 | domain-scoped P@10 | gap |
|-------:|----------:|-------------------:|-------:|
| 50 | 0.100 | 1.000 | +0.900 |
| 200 | 0.000 | 1.000 | +1.000 |
| 1000 | 0.000 | 1.000 | +1.000 |
Flat semantic recall collapses to 0.000 P@10 by 1,000 docs; domain-scoped holds at 1.000. The metadata predicate is enforced through the existing storage layer, so it works on both the DuckDB and PostgreSQL backends and respects RBAC/scope exactly like ordinary recall.
Active-reconstruction recall — reconstruct (MRAgent, arXiv:2606.06036) — v0.5.1
strategy = "reconstruct" (typed: RetrievalMode::Reconstruct) adds an opt-in recall strategy that, instead of returning only the top-k snippets, (a) retrieves candidates with the default hybrid RRF, (b) walks the existing memory-graph edges from those hits to gather linked/causal context, and (c) synthesises a deterministic belief-state node returned alongside the raw hits — MRAgent's cue → linked-context → reconstruct pattern. It is surfaced as a strategy parameter on the existing recall API across all four protocols (MCP strategy: "reconstruct", REST ?strategy=reconstruct, gRPC RecallRequest.strategy, pgwire SELECT ... /*+ reconstruct */), so the tool surface is unchanged.
{ "query": "Project Apollo owner", "strategy": "reconstruct" }
// → { "memories": [ ...the usual top-k... ],
// "reconstruction": { "cue": "...", "summary": "Reconstructed belief ...",
// "source_ids": [...], "linked_context_ids": [...], "confidence": 0.x } }
The raw memories list is exactly what the default auto path returns — reconstruct is purely additive (the belief node is extra), and the synthesis is rule-based (no LLM), so it is deterministic and reproducible. This is an option to test reconstruction-vs-retrieval on your own data, not a claim that retrieval is wrong. MRAgent reports up to ~23% gains on multi-hop questions; the A/B harness at bench/locomo/src/bin/reconstruct_ab.rs lets you check the mechanism on mnemo itself by measuring gold-coverage@k — whether a graph-linked answer that flat top-k retrieval misses is recovered by the reconstruction's linked context. On its (adversarially multi-hop, by-construction) fixture, reconstruct lifts coverage@5 from 0.083 → 0.208; run it on your own corpus before drawing conclusions. The graph walk uses the same related_to relations the graph strategy traverses, so it works on both the DuckDB and PostgreSQL backends and respects RBAC/scope like ordinary recall.
Offline consolidation — Auto-Dreamer-shaped active-bank shrink (v0.4.8)
Anthropic's Auto-Dreamer consolidation runs offline, away from the agent's interactive loop, and produces a smaller active bank of semantic summaries that should serve subsequent recall at least as well as the raw episodic trace it replaced. mnemo's existing run_decay_pass + run_consolidation path (crates/mnemo-core/src/query/lifecycle.rs, plus the reflection module at crates/mnemo-core/src/query/reflection.rs that mirrors the same offline-housekeeping shape) is the engine-side equivalent: the decay pass marks low effective-importance records as Archived / Forgotten, the consolidation pass replaces tag-overlap clusters of episodic memories with structured [Consolidated from N memories] … semantic bundles, and the originals are flipped to Consolidated rather than deleted (so the chain stays auditable). The new Auto-Dreamer-shaped scenario at bench/locomo/src/bin/auto_dreamer_consolidation.rs exercises both passes end-to-end on a synthetic multi-session trajectory and reports the two axes Auto-Dreamer headlines as its claim: active_bank_ratio = post / pre (expects < 1.0) and held-out recall_post >= recall_pre. A JSON summary lands beside the Markdown report so the headline number is citable here.
Topic-document consolidation (Infini-Memory, arXiv:2606.10677) — v0.5.0
mnemo.consolidate / MnemoEngine::consolidate adds a caller-driven consolidation primitive: it groups a chosen set of member memories into one revisable topic document — a semantic unit that collects related evidence, preserves metadata, and revises facts over time. This is the interactive, by-id sibling of the offline run_consolidation pass above: instead of clustering by tag overlap on a schedule, the caller names the members and the topic.
{ "memory_ids": ["<id1>", "<id2>", "<id3>"], "topic_name": "Acme account" }
The primitive is deterministic (no LLM): absent a caller summary, the document body is a stable join of the member contents ordered by (created_at, id). It preserves provenance — the topic document's metadata records consolidated_from plus each member's source, timestamp, and confidence — and links topic_document --consolidated_from--> member relations so the evidence set is retrievable as a unit. Every consolidation appends a hash-chained MemoryConsolidated audit event.
Fact revision keeps history. Pass supersede: <old_topic_document_id> to revise a fact: the new document becomes version N+1 (prev_version_id → old), the old document is retained (not deleted — deleting a mid-chain record would break verify_integrity) and marked Consolidated with a superseded_by pointer, and a MemoryRevised event is appended alongside. Reuse the same topic_name across revisions so the current-fact resolver (keyed on the topic metadata) collapses to the current view at recall time. The primitive is protocol-agnostic — reachable identically over MCP, REST (POST /v1/consolidate), and gRPC (rpc Consolidate). It is not exposed over pgwire, which stays SQL-only (SELECT/INSERT/DELETE → recall/remember/forget); consolidation is a primitive-RPC operation, not a SQL statement.
Run via cargo run --release --bin auto_dreamer_consolidation -p mnemo-locomo-bench — defaults to 8 sessions × 25 facts × 5 trials with archive/forget thresholds of 0.40 / 0.10 and min_cluster_size = 3; all tunable via CLI flags. The default read path is unchanged — the bench only consumes existing mnemo_core::query::lifecycle::* APIs and adds no public surface. See the bin module rustdoc for the full "what this bin is NOT" block (not a faithful Auto-Dreamer reproduction; not the criterion crate; NoopEmbedding makes the vector lane degenerate by design; single-agent, single-scope).
Embedding-backend selection — SLA-aware recommender (v0.4.9)
arXiv:2605.23618 (GE2 vs local encoders — quality + latency) motivates choosing an embedding backend by measured quality and tail-latency on the operator's workload, not by reputation. mnemo v0.4.9 ships bench/embeddings, a criterion-driven bench + SLA-aware recommender that runs each configured backend (Noop and a bench-local hashing baseline always; OpenAiEmbedding when OPENAI_API_KEY is set; OnnxEmbedding when MNEMO_ONNX_MODEL_PATH is set and mnemo-core was built with the onnx feature) against a 50-document / 10-query labeled fixture and reports nDCG@10, recall@10, p50/p95 single-vector embed latency, and throughput at batch sizes 1/8/32. The recommender then picks the highest-nDCG backend whose p95 ≤ the SLO and reports the explicit nDCG gap vs the absolute best-quality backend (so the operator sees "you give up 0.003 nDCG for 7x lower p95 latency" rather than a black-box choice).
Run via mnemo bench embeddings --slo-ms <N> (built into the mnemo binary) or cargo bench -p mnemo-embeddings-bench (criterion HTML reports at target/criterion/embed_single/). The default read path is unchanged — no retrieval defaults, no RRF weights, no EmbeddingProvider impls are touched. The embedded-first wedge stays: default builds run without OPENAI_API_KEY and the recommender picks a local backend. See bench/embeddings/README.md for the full "what this bench is NOT" block (not a faithful arXiv:2605.23618 reproduction; not a managed-cloud default; hashing-baseline is a bench-local lexical sanity floor, not a production backend).
mnemo as a golem:vector provider (v0.4.6)
mnemo v0.4.6 ships a vertical-slice WASM-component implementation of the golem:[email protected] WIT interface — three load-bearing functions (upsert-vector / search-vectors / delete-vectors) — split across two crates: crates/mnemo-golem-wit (the WASM component, compiled to wasm32-wasip2 via cargo component build) and crates/mnemo-golem-host (the Rust host that owns an Arc<MnemoEngine> and supplies the WIT host imports). The two-crate split is forced by mnemo-core's C++ deps (DuckDB + USearch) which cannot compile to WASM — see docs/research/golem-vector-wit-provider.md for the layering rationale, the per-function gap list (27 of 30 deferred to v0.5.x), and the wasmtime-component-loader wiring step explicitly deferred. The vertical-slice integration is functionally complete as a Rust trait surface today (MnemoGolemProvider + MnemoGolemHost) with 5 integration tests + an end-to-end example showing REMEMBER → RECALL → DELETE through a real MnemoEngine.
Which parts of the MCP spec mnemo implements
The current spec revision is 2026-07-28, which removes
protocol-level sessions and the initialize handshake, adds server/discover,
requires Mcp-Method / Mcp-Name headers and ttlMs / cacheScope on list
results, and deprecates Roots, Sampling, Logging and the HTTP+SSE transport.
mnemo negotiates 2025-11-25, not 2026-07-28. mnemo speaks MCP through
the rmcp = "3.0" workspace dep (CI resolves 3.5.0 as of 2026-10-03),
and follows rmcp's implementation as it lands rather than racing the spec.
rmcp 3.5.0 made 2026-07-28 its ProtocolVersion::LATEST, but mnemo
advertises nothing past 2025-11-25 and 2026-07-28 has no handshake, so
2025-11-25 is what a handshake settles on.
Where mnemo already satisfies the new revision it is because of decisions taken
earlier, not because it tracked the spec. Cross-call state has always travelled
through explicit, server-minted handles passed as ordinary tool arguments
(checkpoint_id, lease_token), which SEP-2567 makes the sanctioned
replacement for sessions; mnemo implements none of the deprecated features; and
tool listings are already deterministically ordered.
Row by row, with what is still open and what waits on rmcp: docs/src/integrations/mcp-2026-07-28.md. It is not a marketing page. Four rows are still open, every one of them carrying a sentence saying what a caller should assume instead, and a CI test refuses to let an open row exist without one. It also states plainly that mnemo implements no OAuth authorization: no RFC 9728 Protected Resource Metadata document is served on any transport, which is a conformant position on stdio (the spec says stdio servers SHOULD NOT follow the authorization spec and should take credentials from the environment, which is what mnemo does) and a thing worth saying out loud rather than leaving a reader to infer from silence.
The MCP 2026 Roadmap (published 2026-03-09) organised the protocol's
direction around four priority areas: Transport Evolution and Scalability, Agent
Communication, Governance Maturation, and Enterprise Readiness. mnemo's
operator-held HMAC keystore, AES-256-GCM at-rest encryption, dual DuckDB /
PostgreSQL backends and mnemo-compliance crate sit under Enterprise
Readiness as an attestable memory layer.
This is kept as history. The July spec release superseded the roadmap as the
statement of current direction, and the conformance table above is where mnemo's
actual state is recorded. The four-priority-area mapping is preserved in
docs/src/integrations/mcp-server.md.
SDKs
Python
pip install mnemo-db
Why
mnemo-dband notmnemo? A 2021 notebook project (last release 2021-07-06, unrelated) holds the unqualifiedmnemoname on PyPI. Our distribution publishes asmnemo-db; the import path staysfrom mnemo import …so existing code is unaffected.
Version line & wire compatibility.
pip install mnemo-dbgivesv0.5.30. The Python SDK is not independently versioned:python/is PyO3 bindings that compilemnemo-coreinto the wheel, so the wheel version names the engine inside it, andworkspace_version_fence.rsfails CI ifpyproject.tomlandmnemo/__init__.pydrift from[workspace.package].version.
- In-process,
MnemoClient(the PyO3 extension).mnemo-dbv0.5.30ismnemo-corev0.5.30. There is no version-skew question to answer: the engine is the wheel.pip install mnemo-dbandcargo add mnemo-coredo not currently resolve the same version. PyPI hasv0.5.30; crates.io hasv0.5.29. The wheel publishes on merge tomainwhile the crates publish on a tag, so the Python side leads inside an open release window. Pin deliberately if you embed both.- Over MCP, the
agno/camel/agno-memoryadapters. These embed no engine; they spawn the externalmnemoserver binary you install and bind to its MCP tool surface (the 23 registered tools), not to amnemo-coreversion. They are wire-compatible with any 0.5.xmnemo-mcp-server. Server properties such as the rmcp 3.0 transport and the tool-catalog attestation come from that binary, not from the SDK, so run a current one to get them.
from mnemo import MnemoClient
client = MnemoClient(db_path="agent.mnemo.db")
result = client.remember("The user prefers dark mode", tags=["preference"])
memories = client.recall("user preferences", limit=5)
client.forget([result["id"]])
# Mem0-compatible aliases also available:
# client.add(), client.search(), client.delete()
Framework Integrations
Mnemo provides native integration modules for 15 agent frameworks:
All integrations are auto-imported via from mnemo import <ClassName> — dependencies fail gracefully if not installed.
Memory-tool servers and shared-memory adapters (v0.3.4 → v0.4.1)
TypeScript
Status: maintenance only, not on the 0.5 train.
npm install @mndfreek/mnemo-sdkgives 0.4.4 (published 2026-05-18) while the Rust line is on 0.5.x. It still works, because it is a thin MCP-over-STDIO client that targets the server's tool surface rather than amnemo-coreversion: the published 0.4.4 package was run against amnemo-mcp-serverbuilt fromv0.5.26, andremember,recallandverifyall succeeded with a valid hash chain. Publishing newer versions (package.jsonis at 0.4.8) is blocked on an expiredNPM_TOKEN, an operator action rather than a code change. What that verification does and does not cover, plus therecalldefault that will bite you first, is insdks/typescript/README.md.
import { MnemoClient } from "@mndfreek/mnemo-sdk";
const client = new MnemoClient({ dbPath: "agent.mnemo.db" });
await client.connect();
const { id } = await client.remember({ content: "User prefers dark mode" });
const { memories } = await client.recall({ query: "user preferences" });
await client.share({ memory_id: id, target_agent_id: "auditor-agent" });
await client.close();
Go
import "github.com/sattyamjjain/mnemo/sdks/go"
client, err := mnemo.NewClient(mnemo.ClientOptions{DbPath: "agent.mnemo.db"})
defer client.Close()
result, _ := client.Remember(mnemo.RememberInput{Content: "User prefers dark mode"})
memories, _ := client.Recall(mnemo.RecallInput{Query: "user preferences"})
_, _ = client.Share(mnemo.ShareInput{MemoryID: result.ID, TargetAgentID: "auditor-agent"})
Storage Backends
Two backends implement the same StorageBackend trait. As of v0.5.7 they are
feature-equivalent for recall — semantic / hybrid / graph / domain-scoped
recall now run real pgvector ANN on PostgreSQL (#99).
The matrix below is explicit about what each backend does.
Backend capability matrix
¹ Postgres vector recall runs a real pgvector ANN query against the
idx_memories_embedding_hnsw HNSW index (cosine <=>, ORDER BY … LIMIT k),
with the same permission-safe oversample-then-filter the USearch backend uses,
so filtered/scoped recall never under-returns. As of v0.5.18 the
VectorIndex::search / filtered_search methods are async (#99 resolved):
the sqlx query is .awaited directly on the caller's ambient Tokio runtime, so
Postgres semantic recall works from inside the server/CLI #[tokio::main]
runtime on any flavor — single- or multi-threaded — with no block_on
bridge and therefore no "runtime within a runtime" panic or deadlock. If the
pgvector extension / <=> operator is genuinely absent at runtime, the vector
lane still fails loud with a typed
Error::BackendUnsupported — never a silent
empty result. Verified end-to-end against a live pgvector Postgres by the
MNEMO_TEST_POSTGRES_URL-gated integration test
crates/mnemo-postgres/tests/pgvector_ann.rs:
nearest-in-rank-order + permission filter under a multi-threaded runtime, and
a current_thread-runtime regression test that the old bridge would have
panicked on.
That integration test skips when MNEMO_TEST_POSTGRES_URL is unset, so on an
ordinary CI run it proved nothing about the fail-loud claim.
crates/mnemo-postgres/tests/semantic_recall_fails_loud.rs
closes that hole: it needs no database, runs on every cargo test --workspace,
and asserts through the engine (not just the index) that semantic, auto,
graph and domain_scoped recall each return BackendUnsupported rather than
Ok(empty) — against a store that provably contains a record, so "no matches"
and "not implemented" cannot be confused. Re-introducing a single
.unwrap_or_default() on any of the four filtered_search call sites in
recall.rs turns it red.
Embedder support matrix — which embedders actually produce semantic results
Semantic recall is only as real as the embedder behind it. A backend's vector index cannot manufacture meaning from a query that was never embedded — so the supported semantic path is a real embedder + either backend, and mnemo now fails loud rather than silently returning empty when the embedder cannot produce vectors (v0.5.13).
The supported semantic path is DuckDB (or PostgreSQL) + a real embedder —
OpenAiEmbedding (HTTP) or, for a fully on-prem deployment, OnnxEmbedding
(local model, onnx feature). With no real embedder configured, mnemo runs with
the no-op embedder, whose query vectors are all-zero; any semantic /
hybrid (auto) / graph / domain-scoped recall then returns a typed
Error::EmbedderNotConfigured — never a
silent empty result set. Lexical (BM25) and exact/filter recall need no
embedder and always work. This mirrors the Postgres BackendUnsupported
fail-loud behaviour above: mnemo will not pretend to do semantic recall it cannot
actually perform.
Compliance profiles — processing-log retention
Same posture, applied to retention: mnemo's agent_events log is append-only
by construction (no forget / TTL / decay / cold-tier path removes an event —
each appends one), so mnemo-compliance
ships a RetentionProfile that
verifies a minimum retention floor held across every deletion path, and fails
loud (ComplianceError::RetentionFloorUnsupported, naming the backend) if a
backend cannot guarantee it. These are conformance checks for the obligation
named — not a certification or a claim of compliance.
Run the reproducible conformance harness (every deletion path × the floor, byte-stable artifact):
cargo run --release -p mnemo-retention-conformance-bench # DPDP, 365-day floor
cargo run --release -p mnemo-retention-conformance-bench -- --profile eu-ai-act-art19
mnemo compliance retention --profile dpdp # print the profile + backend gate
Dates are provisional and depend on the final notified/enacted texts — verify against the primary sources before relying on them. Why this matters, and how mnemo compares on the compliance-audit axis: docs/POSITIONING.md.
Key Features
- Hybrid retrieval — Reciprocal Rank Fusion combining semantic vectors (USearch/pgvector), BM25 keywords (Tantivy), knowledge graph signals, and recency scoring with configurable weights
- Bitemporal graph layer (
mnemo-graph) — Graphiti-inspired temporal edges withvalid_from/valid_to(fact validity) plusrecorded_at(system clock).graph_expand(seed, depth, as_of)walks the graph at any point in time without losing later corrections. New in v0.4.0-rc1. - AES-256-GCM encryption — at-rest content encryption via
MNEMO_ENCRYPTION_KEY - Hash chain integrity — SHA-256 content hashes with chain linking and
verifytool - Memory poisoning detection — anomaly scoring with prompt injection pattern detection; quarantine for flagged content
- Cognitive forgetting — five strategies: soft delete, hard delete, decay, consolidation, archive
- Feedback-driven consolidation trigger — opt-in
ConsolidationPolicy::MaturityDrivengatesrun_consolidationon a per-cluster maturity score (recency × hit-success × edge-degree × redundancy) instead of firing on a fixed schedule. Inherited byforgetand `c