Local-first encrypted memory for AI agents.
Store an agent's memory on-device, recall it with hybrid vector search, and erase it by destroying its key, with a verifiable erasure receipt. Zero-LLM ingest and an MCP server, all on one encrypted SQL/vector file. No API keys; your data stays on your machine.
cargo add citadeldb citadeldb-memA live model of Citadel's on-disk format: my.db is opaque ciphertext until the passphrase decrypts the page. Nothing is stored - to run real SQL, open the playground.
An encrypted file that is both an agent memory and a database.
The same AES-256-CTR + HMAC pages, copy-on-write B+ tree, and shadow-paging commit power both halves. Use the memory engine, the SQL engine, or both in one file.
Memory engine
Memory that lives encrypted at rest. Typed atoms and edges, hybrid recall, and a Model Context Protocol server so Claude Desktop or any MCP client can read and write it.
VECTOR(N)type with a PRISM-backed filtered ANN index- Hybrid recall: vector ANN + BM25 keyword + recency + optional cross-encoder reranker
- Cryptographic forgetting: erase an atom or region by destroying its key
- 13-tool MCP server; 85.7% on LoCoMo and 86.2% on LongMemEval-S (gpt-4o)
Encrypted database
A complete embedded SQL + key-value engine. ACID without a WAL, snapshot isolation, and a single self-contained file - faster than unencrypted SQLite at equal cache budgets.
- SQL: FULL OUTER & LATERAL joins, recursive CTEs, window functions, triggers, materialized views
- JSON / JSONB with 14 PostgreSQL operators, GIN indexes, full-text search
- Key-value API and SQL share one transaction
- P2P encrypted sync over Noise; Python, CLI, C FFI, and WebAssembly bindings
A complete engine in a single file.
Full SQL, real ACID, encrypted sync, and a memory engine. Key hierarchy built around RFC 3394, RFC 5869, and a Rust-only crypto core.
Encrypted at rest
AES-256-CTR + HMAC-SHA256 per page, verified before decryption. Fresh random IV on every write. ChaCha20 available.
Real SQL
FULL OUTER + LATERAL joins, recursive CTEs (with-DML), window functions, triggers, materialized views, UPSERT, RETURNING, JSON/JSONB.
ACID, no WAL
Copy-on-Write B+ tree with shadow paging. Snapshot isolation with concurrent readers, atomic single-byte commits.
Vector + memory engine
VECTOR(N) with a PRISM filtered ANN index, plus a memory engine of typed atoms with hybrid vector + keyword recall.
MCP server
Expose encrypted memory to Claude Desktop or any MCP client over stdio. 13 tools for recall, remember, link, evolve, and forget.
Cryptographic forgetting
Erase data by destroying its key, not by overwriting. Whole-store, per-region, and per-atom, with verifiable erasure receipts.
P2P encrypted sync
Merkle-based table diffing over Noise (NNpsk0_25519_ChaChaPoly_BLAKE2s) with ephemeral forward secrecy.
Three-tier key hierarchy
Passphrase → Argon2id → Master Key → AES-KW → REK → HKDF → DEK + MAC. Instant rekey, no page re-encryption.
Cross-platform bindings
Windows, Linux, macOS. Python, C FFI (37 functions), and WebAssembly from one Rust core. Tamper-evident HMAC-chained audit log.
Open a database in six lines.
The API is small on purpose. One builder, one connection, one passphrase.
Add the crates
Engine + SQL frontend from crates.io.
Open with a passphrase
Argon2id derives the master key in memory. Keys live in {dbname}.citadel-keys, not inside the database.
Run SQL, or use the KV API
Both are first class. Mix them in the same transaction.
Ship a single file
Embedded, zero-config. No servers, no daemons, no runtime dependencies.
What "open my.db" actually does.
Six stages from passphrase to a sealed 8,208-byte page on disk. Real algorithm names, real byte counts, drawn from the citadel-crypto source.
my.db.citadel-keys. The passphrase stays in memory only; never touches disk.zeros(32)"citadel-dek-v1"→"citadel-mac-key-v1"→epoch ‖ page_id ‖ IV ‖ ciphertext, so a forged page id or replay from another epoch fails the tag. Page = 16 + 8,160 + 32 = 8,208 bytes.my.db alone and you get opaque ciphertext. Lose the passphrase and nothing opens.Encrypted memory engine.
citadel-mem stores memory as typed atoms grouped into regions and connected by typed edges. Recall blends vector similarity, keyword scoring, and recency; forgetting destroys keys, not just rows.
Hybrid fusion recall. A query over-fetches ANN candidates, then scores each on normalized semantic distance, BM25 keyword overlap, recency (30-day half-life), and importance. An optional cross-encoder reranker (replace or reciprocal-rank-fusion) sharpens the top results, and a graph walk pulls in linked neighbors.
Forgetting that is real. On encrypted regions every atom is sealed under its own key, wrapped by a per-region key. forget and evict destroy those keys and return an erasure receipt; the ciphertext that remains is unrecoverable. Re-verify any atom off disk: authentic, tampered, or key-erased.
Bring your own embedder. A keyword-only mock for tests, or Candle models (E5-large, MiniLM, BGE small/base/large) and a cross-encoder reranker, pulled on demand. CUDA optional.
Two long-term-memory benchmarks, each on its official prompt and judge. LoCoMo: encrypted regions, gpt-4o-mini reader and judge, zero LLM at ingest or retrieval. LongMemEval-S (full-haystack, ~40-50 sessions/question): 86.2% with a gpt-4o reader and the official gpt-4o judge. Protocol and full numbers in citadeldb-membench.
Plug encrypted memory into Claude Desktop.
citadeldb-mcp exposes a memory region as 13 MCP tools over JSON-RPC 2.0 on stdio, so Claude Desktop, an IDE, or any other MCP client can read and write it directly.
Read tools
- mem_recall
- mem_fetch
- mem_edges
- mem_profile
- mem_summarize
- mem_verify
Write & forget tools
- mem_remember
- mem_remember_batch
- mem_update
- mem_link
- mem_evolve
- mem_evict
- mem_forget
{
"mcpServers": {
"citadel": {
"command": "citadeldb-mcp",
"args": [
"--db", "memory.cdl",
"--embedder", "e5-large",
"--reranker", "ms-marco-minilm"
],
"env": { "CITADEL_KEY": "your-passphrase" }
}
}
}One-time setup: citadeldb-mcp pull e5-large and pull ms-marco-minilm download the embedder and reranker (the best-recall config; models are never fetched automatically - omit both for keyword-only recall). Recall hits carry provenance, per-hit integrity verdicts, and memory://atom/{id} resource links.
Faster on all 58 head-to-head benchmarks.
Single-threaded, 100K rows, (id INTEGER PK, name TEXT, age INTEGER). Equal cache budgets (~32 MB). Ratio is SQLite / Citadel time; higher means a larger Citadel speedup. Two-run medians, two regimes: execution speed (every iteration computes its result; geometric mean ~3.4×) and memoized repeat-reads (identical parameters against unchanged data, served from a generation-keyed result cache that any commit invalidates; SQLite has no result cache and re-executes every query).
Execution speed
Memoized repeat-reads
journal_mode=OFF, synchronous=OFF, cache_size=8192. Citadel: SyncMode::Off, cache_size=4096. Reproduce: cargo bench -p citadeldb-sql --bench h2h_bench.20 crates, one file format.
A layered engine: each crate has one job, depends only on the layers beneath it, and can be read on its own.
Page layout is 8,208 bytes. Sixteen bytes of random IV, 8,160 bytes of ciphertext, 32 bytes of HMAC-SHA256. Corrupt pages fail loud, not silent.
Commit protocol is shadow paging. Dirty pages go to new locations, BLAKE3 Merkle hashes climb bottom-up, the inactive 240-byte commit slot is updated, then one byte in the file header flips to publish the new root. No write-ahead log.
The memory layers reuse the same pages. Vectors, atoms, and edges are stored, encrypted, and committed exactly like table rows - one file format, one crypto path. citadel-membench is the LoCoMo harness that measures recall on top.
A SQL dialect that doesn't disappoint.
FULL OUTER and LATERAL joins, recursive and DML-bearing CTEs, triggers, materialized views, window frames, JSON/JSONB with PostgreSQL operators, full-text search, and a native VECTOR type.
Statements
- CREATE / DROP / ALTER TABLE
- CREATE INDEX (partial / expr)
- CREATE VIEW / MATERIALIZED VIEW
- CREATE TRIGGER
- UPSERT (ON CONFLICT)
- RETURNING (OLD / NEW)
- TRUNCATE / SAVEPOINT
- PREPARED / $1, $2, ...
- EXPLAIN
Clauses & joins
- INNER / LEFT / RIGHT / CROSS
- FULL OUTER / LATERAL
- Subq.: scalar / IN / EXISTS / ANY/ALL
- Correlated subqueries
- WITH / WITH RECURSIVE
- WITH-DML (RETURNING)
- UNION / INTERSECT / EXCEPT
- GROUP BY / HAVING
- Window frames (ROWS / RANGE)
JSON / JSONB & search
- 14 PostgreSQL operators
->/->>/#>/#>>@>/<@/?/?|/?&- 16 scalar / 4 aggregate fns
- GIN indexes on JSONB
- FTS: tsvector / ts_rank / phrase
VECTOR(N)+ ANN index<->L2 /<#>inner /<=>cosine
Types / constraints / windows
- INT / REAL / TEXT / BLOB / BOOL
- DATE / TIME / TIMESTAMP(TZ)
- INTERVAL / IANA zones (jiff)
- Generated cols (STORED / VIRTUAL)
- STRICT tables
- COLLATE BINARY / NOCASE / RTRIM
- FK: CASCADE / SET NULL / DEFERRABLE
- ROW_NUMBER / RANK / LAG / LEAD
- SUM / AVG OVER / PARTITION BY
Keys are handled like they matter.
No plaintext on disk
Every page is encrypted before writing, authenticated before reading. The database file is indistinguishable from random.
Keys live outside the database
Encryption material lives in {dbname}.citadel-keys. The passphrase derives a master key in memory via Argon2id and never touches disk.
Instant rekey
Changing a passphrase re-wraps the root key only. No pages are re-encrypted. The operation is instant regardless of database size.
Cryptographic erasure
citadel-mem seals each atom and region under its own key. Forgetting destroys the key and issues a receipt; the remaining ciphertext cannot be recovered.
Forward-secret sync
Noise NNpsk0_25519_ChaChaPoly_BLAKE2s with a 256-bit PSK. Ephemeral Curve25519 keys per session; compromise of one doesn't leak prior traffic.
FIPS 140-3 mode
A feature flag swaps Argon2id for PBKDF2-HMAC-SHA256 (600,000+ iterations) with AES-256-CTR only, for compliance environments.
Rust, Python, C, WebAssembly, and a CLI.
Rust
crates.io / citadeldbThe reference API. Builder-style open, typed transactions, no hidden allocation. 15 crates published.
Python
PyPI / citadeldbThe full engine in one wheel: encrypted SQL, vector search, the memory engine, and the MCP server. Typed, with numpy vectors.
C / C++
cbindgen / citadel.h37 panic-safe functions, static or dynamic linkage. Drop into any toolchain that speaks C ABI.
WebAssembly
npm / @citadeldb/wasmA real encrypted database in your browser tab. Compiled with wasm-pack. Powers the playground.
CLI
crates.io / citadeldb-cliAn interactive SQL shell with tab completion, syntax highlighting, and 26 dot-commands.
From the blog.
- Introducing Citadel
An embedded database that encrypts every page, with a built-in memory engine.
Start with the browser build.
No install, no signup. Run real SQL against an encrypted database compiled to WebAssembly.