embedded Datalog.
performant graphs.
a substrate for machine memory.
mnestic is a transactional relational–graph–vector database that speaks Datalog, the same engine that called itself “the hippocampus for AI,” now actively maintained and tuned as the recall layer for agents.
MPL-2.0 · Rust · RocksDB / SQLite / in-memory backends
1# every memory reachable from a seed, then the 12 nearest by meaning2recall[to] := *recalls{ from: $seed, to }3recall[to] := recall[via], *recalls{ from: via, to }4 5?[memory, dist] :=6 recall[memory],7 ~memory:embedding{ memory |8 query: $cue, k: 12, ef: 80, bind_distance: dist9 }10:order dist11:limit 12Why a fork
CozoDB went quiet after December 2024. The design is too good to let drift. So we forked it, openly, under MPL-2.0, and pointed it at one job: being the memory an agent can trust. Every divergence is documented, every original copyright preserved.
The engine you inherit
Datalog, not SQL
Queries compose piece by piece. Recursion is first-class, and runs faster than the SQL equivalent. A safe subset of aggregations is allowed inside recursion.
Relational · graph · vector
One engine, one model. The relational algebra handles graph structure implicit several levels deep, with no shoehorning your data into a labelled-property graph.
Embeddable like SQLite
Runs in-process (no server, no setup), yet scales to large data and high concurrency, and can also run client-server when you want it to.
Vector search (HNSW)
Disk-resident HNSW indices unify with Datalog: search by meaning inside a recursive query, filter with the rest of your rules, all in one pass.
Full-text & near-dup
Built-in full-text search and MinHash-LSH for near-duplicate detection: the keyword and dedup legs of retrieval, native to the engine.
Time travel
Relations can carry validity time. Query the graph as it was at any historical point: memory you can rewind, not just overwrite.
Built to be fast
upstream figures · 2020 Mac mini · RocksDB backend
mixed read / write / update transactions
read-only queries
on a 1.6M-vertex, 31M-edge graph
at OLTP load
Backup ≈ 1M rows/s · restore ≈ 400K rows/s · PageRank on 1.6M vertices ≈ 30 s. mnestic keeps these and adds the recall-focused wins below.
Faster, safer, and built for recall.
The query language and semantics are unchanged. What changed is the stuff that bites you in production (lock contention, full scans, seven-rule retrieval pipelines) and the things agents actually need.
Three-way recall in one call
Vector similarity, keyword match, and graph proximity — the three signals behind “what should I recall right now” — fuse in a single typed call, ranked by Reciprocal Rank Fusion and diversified with MMR. It used to take about seven hand-written Datalog rules, and every result can now tell you which signals surfaced it.
→ all three signals, one transaction · ~4× faster than stitching it by hand
Index builds that never block reads
Building a vector (HNSW) or full-text index used to lock the table and stall every reader until it finished. Now the build happens off to the side while reads keep flowing the whole time — and the build itself is up to 15× faster.
→ 40k vectors indexed in ~19 s · 90k reads served mid-build
Full-text search that ranks correctly
Keyword search now scores with Okapi BM25 — the ranking standard behind modern search engines — instead of raw term counts. The keyword half of hybrid recall actually surfaces the right passages.
→ fused recall climbs 0.75 → 0.954 on a 40k-chunk corpus
Reads that don’t wait on writers
Read-only queries now read through a plain snapshot instead of opening a transaction, so a busy writer can never block a reader. For an agent recalling while it ingests, that means steady, predictable latency.
→ keyed point reads −16% p50, −19% p99
Key lookups skip the full scan
Filtering a stored relation by an exact key now compiles to a direct keyed seek instead of scanning every row — the difference between a constant-time lookup and a full table walk on your hottest queries.
→ ~28× faster single-row primary-key lookups
Time-ordered IDs for memory streams
rand_ulid() generates sortable, time-ordered identifiers — ideal for append-only memory you scan by recency, with the creation time recoverable straight from the key.
→ lexicographically sortable · time-ordered scans
Hybrid retrieval,
one typed call.
Vector similarity, keyword match, and graph proximity are three different signals about “what should I remember right now.” As of 0.8.3 mnestic fuses all three natively: a typed graph leg joins the vector and keyword legs in one call, combined by Reciprocal Rank Fusion and de-duplicated by Maximal Marginal Relevance.
- Graph proximity is a typed GraphLeg: bounded-hop, ranked by min distance
- Query vector, text & seeds passed as params, never string-interpolated
- One call, one transaction; generated CozoScript inspectable via hybrid_search_script
1use cozo::{DbInstance, GraphLeg, HybridSearch, MmrParams};2 3// One typed call: HNSW + FTS + graph proximity, fused natively4// with Reciprocal Rank Fusion, then MMR-diversified.5let recalls = db.hybrid_search(&HybridSearch {6 relation: "memory".into(),7 vector_index: "embedding".into(),8 query_vector: cue, // Vec<f32> from your embedder9 vector_k: 24,10 ef: 80,11 fts_index: "summary_fts".into(),12 query_text: "pricing decision",13 fts_k: 24,14 // graph leg: expand 2 hops from a seed over *recalls,15 // rank by min hop distance — fused in the same call.16 graph_legs: vec![GraphLeg {17 edge_relation: "recalls".into(),18 seeds: vec![seed.into()],19 max_hops: 2,20 ..GraphLeg::default()21 }],22 rrf_k: 60.0,23 mmr: Some(MmrParams { lambda: 0.5, k: 12, embedding_col: "embedding".into() }),24 ..HybridSearch::default()25})?;One engine for all three signals.
The task is fusing vector, keyword, and graph proximity into one ranking. Raw latency isn't what separates the field at this scale. Three structural things are, and mnestic is the only embedded engine here that gets all three right.
It has a graph signal at all
Graph proximity is correlated but distinct from vector and keyword: drop it and you lose recall the other two can't recover. It's the single largest effect in the run, with the graph-less engines (LanceDB) landing far below. This is why graph-augmented retrieval exists.
One store, one call, no glue
mnestic serves all three signals from one embedded store and fuses them in a single transactional call. SQLite, DuckDB and Kuzu keep them in one process but fuse in app code (three queries + a hand-rolled RRF); LanceDB fuses natively but needs a second system for graph.
Read-your-writes on every signal
An agent writes a memory and must recall it immediately. mnestic's indexes update in the same transaction, giving 100% fused read-your-writes. DuckDB's full-text index is a build-time snapshot: a new memory is unsearchable by keyword (0%) until a rebuild. A static-corpus drag race hides this entirely.
On quality, mnestic hits recall@10 of 0.954, level with DuckDB's 0.957 and far above the graph-less LanceDB (0.501). It's the only engine here that fuses all three signals in one transaction:
| Engine | recall@10 | Signals | Fusion | Fused read-your-writes |
|---|---|---|---|---|
| mnestic | 0.954 | vec · FTS · graph | native · one call | 100% |
| duckdb 1.5.3 | 0.957 | vec · FTS · graph | app-side glue | 0% full-text † |
| sqlite 0.1.9 | 1.000* | vec · FTS · graph | app-side glue | 100% |
| lancedb 0.33 | 0.501 | vec · FTS | native · no graph | n/a — no graph |
The native call is the fast path
mnestic's one-call 3-way fusion runs at ~42 ms p50, faster than DuckDB's decomposed path and about 4× faster than hand-decomposing it yourself. It's the only engine here that fuses three signals in a single call; LanceDB's native call covers just two.
Latency, in context
mnestic isn't the lowest absolute latency at this scale, but the numbers hold up off the test wheel. Re-measured on the RocksDB backend it actually runs, with real sentence-transformer embeddings, the decomposed path's tail falls (p99 181 ms vs 258 on the wheel) and the native 3-way call stays around 40 ms. What holds is quality and capability: matching the best indexed engine while fusing a signal the others can't.
† DuckDB's full-text index is a build-time snapshot — its fused read-your-writes is 99% overall but 0% for the keyword leg until a rebuild. Source: the mnestic-benchmarks hybrid suite, summarized in the 0.8.5 changelog. Small scale (40k chunks, 10k entities, 50k edges, dim 384) · 1,000 queries, k=10, 2-hop graph · 2026-05-31 · macOS arm64. Numbers are hardware-specific. Recall@10 is the synthetic text-derived-embedding run on the SQLite-backed wheel, where the vector signal is meaningful by construction; latency is additionally validated on the RocksDB backend with real sentence-transformer embeddings. *SQLite's recall reflects an exact brute-force KNN scan (no ANN index), not a like-for-like indexed search. Kuzu did not complete (extension host offline since its Oct-2025 archival).
Add it to your project
1# default = in-memory + SQLite backends2cargo add mnestic3 4# or, with the RocksDB backend:5# mnestic = { version = "0.8", features = ["storage-rocksdb"] }1use cozo::DbInstance;2 3let db = DbInstance::new("mem", "", "")?;4db.run_default("?[x] := x in [1, 2, 3]")?;1pip install mnestic # the engine (abi3 wheels)2pip install langchain-mnestic # LangChain vector store3pip install llama-index-vector-stores-mnesticNaming, on purpose
The published crate is mnestic, but the importable library name stays cozo. Every use cozo::… in your existing code, and in downstream crates, keeps working unchanged. A drop-in, not a rewrite.
Credit where it’s due
mnestic is not the official CozoDB and is not affiliated with or endorsed by its authors. All credit for the original design belongs to Ziyang Hu and the Cozo Project Authors.