English · 中文
The embedded memory engine for AI agents — memory that doesn't rot, stays current, and proves where every fact came from — plus Deja Loop, built-in governed self-improvement: evidence-cited, undoable, measured.
Named for déjà vu — French for "already seen." That's what your agent's memory is for: recognizing what it has encountered before.
Embed it in-process, store memories as immutable content-addressed grains, query them with CAL (the Context Assembly Language), and hand the results straight to a model — on the default embedded backend: no server, no sidecars, no network hop in the recall path. Recall in microseconds — fast enough to run inside a real-time voice agent's turn, where a network memory call can't. Your agent's memory is a file you own. And when the deployment has nowhere to put a file — stateless containers, multi-instance services — the same engine runs over a PostgreSQL schema instead, same semantics, millisecond-class recall.
git for your agent's memory: log, diff, time-travel, forks with explicit merges, and encrypted incremental sync — built into the data model, because grains are content-addressed immutable objects.
Status: 1.1.1 — the .mg format and CAL are stable and documented (conformant
with the Open Memory Spec, OMS).
Grains → context assembly → CAL → the agent loop → Deja Loop, in one animated pass.
The web console — browse memories, inspect the graph, and run CAL with a live grain inspector (click to enlarge):
Agent memory today is a vector store plus an extraction pipeline — and audited deployments keep finding the same failure: the store fills with duplicates and stale values nobody can trace. DejaDB is a different shape: an engine you embed, built so memory can't rot silently.
- Doesn't rot — measured, not promised: memories are immutable,
content-addressed grains, so byte-identical re-writes collapse to one
grain; updates are supersessions, so recall returns 1 current value, 0
stale with the full history kept; 100% of grains trace to when and how
they entered. All deterministic, no LLM in the loop:
cargo run -p dejadb-bench --bin honesty_metrics. - Safe for agents that learn: in a self-improvement loop, rot compounds — an agent that keeps stale lessons and duplicates gets worse, not better. Supersession (revisions replace, never co-rank), lessons structurally linked to the experience that taught them, replay-idempotent sync, and point-in-time rollback of the memory file make the loop auditable and reversible: build an agent that learns.
- Self-improvement with governance — Deja Loop, built in: eleven deterministic analyzers turn the agent's own history into recommendations — "this tool failed 71% of its calls", "these two facts contradict" — each citing the grains it was computed from, gated propose → review → apply → verify, undoable, and re-measured after apply. Zero model calls required; attach an LLM and its findings are grounded against the evidence and independently verified before a human ever sees them.
- CAL-native:
RECALL/ASSEMBLE/EXISTS/HISTORY/ADD/SUPERSEDE— a query language with no bulk destruction:DELETEandDROPare not tokens in the grammar, and the one destructive statement —FORGET <hash>, a single-grain tombstone — is gated and can be disabled per process. - Fast where it matters (measured, Apple M4 Max): structural recall ~30µs,
entity_latest~9µs, 50ms-cadence voice loop with live write-back 79µs p50 / 152µs p99 per frame recall. - Hybrid recall: structural + BM25 + vector legs fused with RRF; multilingual
by construction (Arabic and English ride every leg; unspaced CJK rides the
vector leg). Bring any embedder: the
EmbedBackendtrait in Rust, a callback in Python (set_embedder), or a command on every surface (--embed-cmd 'my-embedder'— text on stdin, JSON vector on stdout). - Distributed the git way: op-log streaming with generations and point-in-time restore; pull subscriptions for fleet-wide knowledge distribution; concurrent edits become branches with a deterministic provisional head — surfaced, merged explicitly, never silently lost.
- Private by design: local-first, no telemetry; optional AES-256-GCM encryption at rest with an Argon2id-derived key; deletion is a tombstone or crypto-erasure (destroy the key, destroy the memory). See Security.
- Model-native: built-in MCP server, Anthropic memory-tool backend adapter, budget-aware context rendering (SML / Markdown / TOON / JSON), tool-schema rendering for 9 provider formats, Python and Node bindings.
- A format you keep, with a paved road in: the
.mgformat is fully documented and OMS-conformant (byte-exact test vectors), so your memory outlives this engine — anddeja migrateimports what you have today from mem0 (keeping its full edit history as supersession chains), Zep/Graphiti, Letta, LangMem/LangGraph, Basic Memory, or any store via generic JSONL.
DejaDB ships on all three registries — install the surface you need:
cargo install dejadb # the `deja` CLI
pip install dejadb # Python bindings
npm install dejadb # Node bindingsNo Rust toolchain? Every release also carries prebuilt deja binaries for
Linux (x86_64 / aarch64), macOS (Intel / Apple Silicon) and Windows x86_64:
curl -fsSL https://raw.githubusercontent.com/AreevAI/dejadb/main/scripts/install.sh | shIt installs to ~/.local/bin (/usr/local/bin as root; override with
DEJA_INSTALL), pins with DEJA_VERSION=v1.1.1, and verifies the download
against the release's SHA256SUMS. Or grab an archive straight from the
Releases page — handy in a
notebook, where the wheel covers the memory and the loop but deja ui (the
web console, including the review queue) lives in the binary.
Embedding the store in a Rust project? Add the library crates instead of the CLI:
cargo add dejadb-store dejadb-coreOr build from source (Rust 1.90+):
git clone https://github.com/AreevAI/dejadb
cd dejadb
cargo build --release # builds the `deja` binary
./target/release/deja --help
# Python bindings (maturin): maturin develop -m crates/dejadb-py/Cargo.toml
# Node bindings (napi-rs): cd crates/dejadb-js && npm ci && npm run buildStore a fact, recall it, hand it to a model — three commands, no ceremony
(--db is optional; it falls back to $DEJADB_DB, then ~/.dejadb/default.db):
deja add john prefers "window seat" # subject relation object
deja recall john # → the stored fact, one JSON grain per line
deja recall john --render sml # → "john prefers window seat" as a model-ready blockPoint it at a specific file with -d mem.db (or export DEJADB_DB=mem.db).
Then explore: deja cal '<QUERY>' runs the query language, deja ui opens the
web console (http://127.0.0.1:7437), and deja repl is an interactive CAL shell.
claude mcp add deja -- deja serve --mcp --db ~/.dejadb/code.db --ns claude-codedeja serve --mcp speaks newline-delimited JSON-RPC 2.0 on stdio and works
with any MCP client — see docs/mcp-reference.md.
Bring your memories with you — including their edit history:
deja migrate --from mem0 --file export.json --history history.json --db mine.db
deja migrate --from basic-memory --file ~/basic-memory --db mine.dbmem0 history events replay as real supersession chains (ADD → add, UPDATE →
supersede, DELETE → forget) with their original timestamps, so HISTORY
shows your memory's pre-import evolution; note-shaped sources land as live
memory-tool files under /memories. Re-running an import skips what's already
there. Per-source export one-liners: docs/migrate.md.
Memory rot compounds in a self-improvement loop: an agent that re-learns duplicates and keeps stale lessons doesn't plateau, it gets worse. DejaDB's write path is the safety mechanism for that loop — log raw experience, distill lessons into facts, track proficiency as a supersession chain:
deja remember --observer executor --content "Attempt 2: isolated the tempdir per test - PASSED."
deja cal 'ADD fact SET subject = "fix_flaky_tests" SET relation = "lesson"
SET object = "Shared tempdirs need per-test isolation." REASON "distilled from session 41"'
deja cal 'HISTORY WHERE subject = "fix_flaky_tests" AND relation = "proficiency"' # the learning curve
deja restore --db rewound.db --from ./checkpoints --until-hlc <T> # roll back a bad learning episodeDistilling the lessons is a model call, and it is yours to own: no model runs
unless you point DejaDB at one (--model provider:name or --llm-cmd, key
from the environment). Point remember at one and it extracts the facts for
you — stamped verification_status="unverified" with the model named on the
grain, after the raw text is already stored, so a hallucinated extraction is
reviewable and never costs you the source
(cookbook §9).
What the write path guarantees either way: revised lessons replace instead of
co-ranking, every lesson links back to the experience that taught it
(derived_from),
synced/replayed writes can't double-store, and a bad episode rewinds with
point-in-time restore (checkpoint first — the recipe shows the flow). Even a
paraphrased re-learning is caught: deja novelty reports the nearest existing
lesson so the harness supersedes it instead of adding a near-duplicate
(advise-only — it never drops a write itself). Full loop:
cookbook §10.
The section above is the loop by hand. Deja Loop governs it: it turns your agent's history into recommendations — evidence-cited, reviewable, undoable, measured — starting with zero model calls. The fastest way to see it needs no agent and no waiting:
import dejadb, json
db = dejadb.DejaDB("proof.db", actor="user:me")
for _ in range(5): db.record_tool_call("stripe_refund", '{"error":"rate_limited"}', is_error=True)
for _ in range(2): db.record_tool_call("stripe_refund", '{"ok":true}', is_error=False)
db.loop_run() # deterministic; never gated when bare
for r in json.loads(db.recommendations('{"status":"pending"}')): print(r["severity"], r["summary"])
# → high Tool "stripe_refund" failed 5 times (71% of calls): rate_limited
db.apply_recommendation(<hash>, because="retries belong in the client") # audited, undoableWhat that buys you:
- Your agent stops repeating what fails. Eleven deterministic analyzers
(ten default-on) cluster recurring tool failures into lessons, catch
duplicate and contradictory facts, flag stale grains, and surface forks —
computed over typed grains, never raw prose. With the recall-telemetry
sidecar on, three of them see memory utility, not just hygiene: facts
never recalled (
cold_grains), questions that keep coming back empty (coverage_gap), context budgets overflowing (budget_pressure). Precision is measured, not asserted: 1.00 on the labeled fixture, CI-gated at 0.90 (cargo run -p dejadb-bench --bin loop_precision). - Nothing changes behind your back. Four gates — propose → review → apply → verify — with separation of duties, a mandatory reason on every decision, a hash-chained audit grain per transition, and a stored inverse for every apply. Auto-apply is off unless a host policy file explicitly grants it, and never for destructive or LLM-originated changes.
- It proves whether its own advice worked. A recommendation that carries
a metric is re-measured after you apply it — at 1d / 7d / 30d checkpoints,
against what actually happened (did that tool failure recur?); a late
regression proposes a revert.
deja loop outcomesis the receipt. - Add an LLM for what determinism can't see — verified, never trusted.
deja loop run --model claude-sonnet(oropenai:gpt-5,ollama:llama3.1, any OpenAI-compatible endpoint, or--llm-cmd 'CMD') lets a model discover cross-fact issues like a semantic contradiction — but every draft must ground against the cited grains and survive an independent verifier (the proposer never grades itself) before it reaches the queue, andorigin = llmcan never auto-apply. "Nothing to report" is a first-class answer, so it doesn't invent findings to look busy. - It runs where you already run things — no daemon. A cheap, idempotent
command with watermark gates (
--min-new,--if-stale): a Claude CodeSessionEndhook, cron, CI (deja loop list --fail-on highexits 2 — a build gate), or thedejadb_loopMCP tool. And the loop closes into the agent:deja recall-hook --with-looprides the pending queue into the context Claude Code injects, so the agent sees its own recommendations without polling. The console (deja ui) shows the queue, recall sessions, and measured outcomes.
From a fresh install: deja init --db demo.db --template demo seeds a demo
corpus, deja loop run proposes across analyzers (deja loop reflect
sweeps the whole memory), and the Deja Loop tab in deja ui is the governed
review queue. Full guide: docs/loop.md · why the LLM layer
is verified, never trusted: docs/loop-reflection.md.
Embed the store in-process. Add it to your Cargo.toml:
[dependencies]
dejadb-store = "1"
dejadb-core = "1"Most agent hosts are async (Tokio, axum). Use AsyncDejaDB there — it runs each
operation on the blocking pool and tears the store down off the async worker, so
neither a call nor a drop can panic inside a runtime:
use dejadb_store::AsyncDejaDB;
use dejadb_core::types::Fact;
let db = AsyncDejaDB::open("agent.db").await?;
db.add(Fact::new("john", "prefers", "dark mode")).await?;
let latest = db.latest("caller", "john", "prefers").await?;In synchronous code (a CLI, a script, a test) use DejaDB directly:
use dejadb_store::DejaDB;
use dejadb_core::types::Fact;
let mut db = DejaDB::open("agent.db")?;
db.add(&Fact::new("john", "prefers", "dark mode"))?;
DejaDBis blocking and drives its own runtime, so it must not be called — or dropped — from inside an async runtime. Reach forAsyncDejaDBin async code.
import dejadb, json
m = dejadb.DejaDB("john.db", ns="caller")
m.add_fact("john", "prefers", "tea", confidence=0.95)
m.recall("john") # JSON string, newest-first — needs a subject
m.search("tea", k=5) # free text, when you don't have a subject.
# BM25-only out of the box, so it matches
# words that are present; install an
# embedder (below) for semantic hits like
# "hot drinks".
m.cal('RECALL facts WHERE subject = "john"')
m.memory_tool(json.dumps({"command": "view", "path": "/memories"})) # Anthropic memory-tool backendDejaDB(..., index_text=False) turns the BM25 index off for this file (a
deliberate re-stamp, reported by open_warnings()). That trades search()'s
text leg — keep it working by installing an embedder — for write latency that
stays flat as the file grows. add_batch(...) writes many grains in one
transaction; to load another system's export, prefer migrate().
const { DejaDb } = require('dejadb')
const mem = new DejaDb('john.db', 'caller') // 3rd arg: passphrase for AES-256 at rest
await mem.addFact('john', 'prefers', 'tea', 0.95)
await mem.recall('john') // JSON string, newest-first
await mem.cal('RECALL facts WHERE subject = "john"')
await mem.memoryTool('{"command": "view", "path": "/memories"}') // Anthropic memory-tool backendEvery method returns a promise — store calls run on libuv's thread pool rather than blocking the event loop. The constructor is the exception, so opening a file still fails at the line that opened it. Await your writes: promises settle in completion order, not call order.
One memory = one file is the edge story. In stateless deployments (Cloud Run,
autoscaled containers) there is no durable disk — so the same store runs over
one PostgreSQL schema per memory instead, behind the non-default
postgres cargo feature:
cargo install dejadb --features postgres
deja add luis prefers window_seat --db 'postgres://user:pass@host/db?schema=memory_luis'
deja recall --db 'postgres://user:pass@host/db?schema=memory_luis' --subject luisThe bindings ship with the backend built in — the same class takes a DSN where it takes a path:
m = dejadb.DejaDB("postgres://user:pass@host/db?schema=memory_luis")
dejadb.drop_postgres_schema(url, "memory_luis") # memory-level erasureconst m = new DejaDb('postgres://user:pass@host/db?schema=memory_luis')
dropPostgresSchema(url, 'memory_luis') // memory-level erasurelet mut m = DejaDB::open_postgres("postgres://user:pass@host/db", "memory_luis")?;Identical semantics by construction — the same store logic (fork election, supersession, op-log, BM25, hybrid recall) runs over either backend, pinned by a conformance suite that executes the same case list against both. The differences are deliberate and explicit:
- Latency class: point reads are microseconds embedded, milliseconds over a network. The voice frame path stays on the embedded backend by design.
- Multiple concurrent writers per memory: any number of app instances can
hold handles on the same schema. Write transactions claim their id blocks
from an in-schema counters row, which serializes them briefly — so the
op-log stays gapless and ordered for followers, racing supersedes of one
head produce one winner and one clean
SupersessionConflict, and readers never block (MVCC). One instance can likewise hold handles to many memories (the schema-per-tenant shape). - Vectors use pgvector; the
vector(dim)column is created when the first embedder is installed, and a dimension mismatch is a hard refusal rather than a degraded leg. - Erasure and portability map to schema operations:
pg_dump -n <schema>exports a memory,DROP SCHEMA … CASCADEerases one (exposed asdrop_postgres_schema). Recall telemetry rides the memory's schema too. Page-level crypto-erasure remains a file-backend capability; encrypt at the deployment layer (TDE/pgcrypto) instead. - Right to erasure and retention (both backends):
forget_subjecterases every structured reference to one identity — full history, object references, thread events, the dictionary entry itself — with replicating tombstones;forget_older_thanis the age-based retention sweep. Both are host-level operations, deliberately not reachable from CAL; see docs/erasure.md for the scope contract and the documented OMS deviation. - HA is inherited: run it on a regionally-replicated Postgres and the memory inherits the failover, PITR, and backup story your ops team already drilled.
export DEJADB_KEY="correct horse battery staple"
deja add --db secret.db --ns caller --subject john --relation prefers \
--object "window seat" --passphrase-env DEJADB_KEY # AES-256-GCM, Argon2id keydeja stream --db john.db --to s3-mounted/john/ # continuous op-log shipping (~Litestream, grain-level)
deja restore --db new.db --from s3-mounted/john/ [--until-hlc T] # incl. point-in-time
deja follow --db org-replica.db --from org-pub/ # subscribe: org knowledge → every edge
deja verify --db john.db # integrity + full content-address recheckOne memory = one file: the unit of erasure (crypto-erase = key destruction), sync, portability, and write parallelism. Partition by user, org, category, or conversation — your call.
Reproducible harnesses in crates/dejadb-bench (accuracy, honesty, transport)
and crates/dejadb-store/examples (bench, voice_loop — the in-process
latency gates) — full methodology and raw data in
RESULTS.md; committed transcripts in
results/.
Memory quality — LoCoMo (10 conversations, 5,882 turns, 1,982 QAs), a plain retrieve-then-read pipeline with no task-specific tuning:
| retrieval leg | DejaDB |
|---|---|
hit@10 / hit@20 — OpenAI text-embedding-3-small |
74.5% / 81.6% |
End-to-end answer accuracy is 54.2% across all 1,982 QAs (gpt-4o-mini reader,
gpt-4o judge, k=20) — a cheap, untuned reader over that retrieval, where the
reader (not recall) is the ceiling; a stronger reader lifts it. Bring your own
models ($DEJADB_LLM_CMD / $DEJADB_JUDGE_CMD) and embedder (the EmbedBackend
trait; the no-API TF-IDF floor still scores 40.7% hit@10). Every answer and judge
verdict is committed for audit — the category has a history of unreproducible
claims, so we publish the receipts:
transcripts
(summary).
Memory integrity — honesty metrics (structural, deterministic, no LLM):
byte-identical writes settle to one grain (idempotent import, sync replay,
and retries — paraphrase dedup is host-side); after 20 updates recall returns
1 current value, 0 stale with full history kept; writes cost ~136µs and
0 LLM calls (text index off or deferred; a live FTS index adds ~140ms/write
— RESULTS.md finding #1); 100% of grains trace to when/how they entered.
cargo run -p dejadb-bench --bin honesty_metrics.
Latency (Apple M4 Max) — the microseconds that make an embedded engine a different shape from a memory service:
| recall operation | p50 | p99 |
|---|---|---|
entity_latest (in-process) |
~9 µs | — |
| structural recall (in-process) | ~30 µs | — |
| inside a 50 ms voice frame, live write-back | 79 µs | 152 µs |
| same recall via localhost HTTP sidecar | 158 µs | 264 µs |
| same recall via MCP stdio (agent host) | 129 µs | 205 µs |
Every surface above fits inside 0.6% of a 50 ms audio frame; the two transport rows show the cost is the network hop, not the store — the whole argument for embedding it.
On edge hardware — benchmarked on the devices themselves, not extrapolated.
A $35 Raspberry Pi 3 B from 2016 (1 GB RAM, 1.2 GHz Cortex-A53, consumer
microSD) serves recall at ~361 µs, flat from 500 to 8,000 grains; an
Intel NUC8i3BEH from 2018 (i3-8109U, NVMe) does the same in ~30 µs —
matching the M4 Max figure above, through the Python binding's FFI. Both install
with pip install dejadb in 16 seconds, no compiler. 16× the corpus, same
latency: a device can accumulate memory for months and answer as fast on day 200
as on day 1. The write path is the one thing to design for (bulk-load at
0.4–4 ms/grain vs 24–201 ms with a live FTS index). Clock-certified per phase,
with a projection for current Pi hardware:
RESULTS.md §6.
| Doc | For |
|---|---|
ARCHITECTURE.md |
How DejaDB works: grains, .mg format, CAL, recall, sync |
docs/loop.md |
Deja Loop — governed self-improvement (analyzers, four gates, policy, CLI/bindings/MCP/API) |
docs/loop-reflection.md |
The reflection engine — how LLM proposals are grounded, verified, and measured |
docs/cal-reference.md |
The CAL query language reference |
docs/mcp-reference.md |
The MCP server + its 8 tools |
docs/migrate.md |
Importing from mem0, Zep, Letta, LangMem, Basic Memory, JSONL |
docs/memory-tool.md |
The Anthropic memory-tool backend (Python / Node / CLI) |
docs/cookbook.md |
Task-oriented recipes |
FAQ.md |
Questions & answers (also LLM-friendly) |
SECURITY.md · docs/security-model.md |
Security policy & threat model |
AGENTS.md · llms.txt |
For AI agents working in / with this repo |
CONTRIBUTING.md |
How to contribute (DCO sign-off) |
DejaDB is local-first and collects no telemetry. Optional AES-256-GCM encryption at rest protects the database (key derived from a passphrase via Argon2id); deleting a memory is a tombstone or crypto-erasure. The web console binds loopback with no auth by design and refuses to expose itself to the network without an explicit opt-in.
Read the honest threat model before deploying beyond a local machine, and report vulnerabilities per our security policy — please don't open public issues for them.
| Crate | What |
|---|---|
dejadb-core |
.mg format, canonical serialization, content addressing, 12 grain types, tool-schema rendering |
dejadb-store |
Turso-backed store: dictionary-encoded triples, hybrid recall, heads/forks, blobs (CAS), bundles/streaming, memory-tool adapter |
dejadb-cal |
CAL lexer/parser/executor, multi-source ASSEMBLE, saved queries, DejaDbFacade (+ read-only mounts) |
dejadb-context |
Budget-aware provider-optimal rendering (SML/TOON/Markdown/JSON) |
deja-loop |
The self-improvement engine — substrate-agnostic: analyzers, four gates, recommendation lifecycle, LLM verifier (no DejaDB deps) |
dejadb-loop |
DejaDB substrate adapter for Deja Loop + the recall-telemetry sidecar |
dejadb-llm |
Out-of-box LLM backends for Deja Loop reflection (OpenAI-compatible / Anthropic / Ollama) |
dejadb-mcp |
Stdio MCP server (dejadb_recall/add/supersede/forget/remember/cal + dejadb_loop/recommendations) |
dejadb-server |
Local web console (memories / graph / query / Deja Loop queue / sessions, light + dark) + dejad hub mode (segment push/pull, bearer auth) |
dejadb |
The deja binary |
dejadb-py |
Python bindings (import dejadb) |
dejadb-js |
Node bindings (napi-rs native addon, require('dejadb')) |
Built on Turso Database (MIT) — see
THIRD-PARTY-NOTICES.md.
Contributions are welcome under the DCO — see CONTRIBUTING.md and our Code of Conduct. Questions and ideas: GitHub Discussions.
Licensed under either of Apache License 2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution you intentionally submit for inclusion is dual-licensed as above, with no additional terms. The OMS specification itself is CC0.



