Encrypted-first embedded database and memory engine, faster than unencrypted SQLite.

Every page is encrypted and authenticated before it touches disk. A full SQL engine that wins all 50 head-to-head benchmarks against SQLite - and, on the same encrypted pages, a memory engine with vector recall, an MCP server, and cryptographic forgetting.

Argon2id/AES-256-CTR/HMAC-SHA256/BLAKE3 Merkle/PRISM ANN
$cargo add citadeldb citadeldb-sql

A 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.

my.dbsealed
1 page / 8,208 B / IV ‖ ciphertext ‖ MAC
$
Argon2id / AES-256-CTR / HMAC-SHA256 / enter passphrase to decrypt
// proven in
50/50 wins vs SQLite362× on full outer join5,200+ tests, 20 crates85.5% LoCoMo score
One substrate, two faces

An encrypted file format that is both a database and a memory.

The same AES-256-CTR + HMAC pages, copy-on-write B+ tree, and shadow-paging commit power both halves. Use the SQL engine, the memory engine, or both in one file.

citadeldb / citadeldb-sql

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
citadeldb-vector / -mem / -ai / -mcp

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.5% on the LoCoMo long-memory benchmark
Features

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.

Quickstart

Open a database in six lines.

The API is small on purpose. One builder, one connection, one passphrase.

1

Add the crates

Engine + SQL frontend from crates.io.

2

Open with a passphrase

Argon2id derives the master key in memory. Keys live in {dbname}.citadel-keys, not inside the database.

3

Run SQL, or use the KV API

Both are first class. Mix them in the same transaction.

4

Ship a single file

Embedded, zero-config. No servers, no daemons, no runtime dependencies.

src/main.rs
How the seal works

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.

citadel-crypto  /  page seal pipeline What happens when Citadel writes one page to disk.
stdin + keyfileGather inputs
$passphrase:
+
salt-16 B
Read 16-byte salt from my.db.citadel-keys. The passphrase stays in memory only; never touches disk.
memory-hard KDFArgon2id
params: t=3, m=64 MiB, p=4filling memory...
Fills 64 MiB of memory across three passes. Slow by design: a GPU attacker pays the same cost you do.
master key-32 B
RFC 3394AES-KW unwrap
wrapped REK40 B blob
master key
REK-32 B
Unwraps the root encryption key. Rekeying only rewraps this 40-byte blob; no pages re-encrypted.
RFC 5869 / once per openHKDF-SHA256 split
REK32 B
salt = zeros(32)
info="citadel-dek-v1"
DEK-32 B
info="citadel-mac-key-v1"
MAC key-32 B
Domain-separated expansion: one call per label. DEK, MAC key, and an audit key live in memory for the session.
AES-256-CTR + HMAC-SHA256Seal the page
IV-16 B
plaintext8,160 B
AES-256-CTR encrypts with a fresh random IV per page. HMAC signs 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.
citadel-iofsync to disk
my.db appended page #42 / IV ‖ CT ‖ MAC+ 8,208 B
my.db.citadel-keys unchanged / salt + wrapped REK + file MAC172 B
Two files, two secrets. Steal my.db alone and you get opaque ciphertext. Lose the passphrase and nothing opens.
One derivation chain, one sealed page. The passphrase never touches disk. Argon2id re-derives the master key in memory on every open.
keys derived once / seals page 42
Memory engine

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.

remember
Store an atom with its embedding, payload, score, and TTL.
recall
Hybrid retrieval: ANN + BM25 + recency + optional reranker.
fetch
Deterministic listing by kind, no ranking.
update
Replace an atom's payload in place.
link
Typed directed edges between atoms; cycles rejected on DAG kinds.
evolve
Recompute neighbors and decay scores over time.
summarize
Per-kind digest of a region.
evict
Selective forgetting by policy.
forget
Cryptographic erasure with a verifiable receipt.

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 (BGE small/base/large, MiniLM, E5-large) and a cross-encoder reranker, pulled on demand. CUDA optional.

85.5%
LoCoMo score (3-run mean)
95.1%
retrieval ceiling
7
typed edge kinds
9
memory operations

LoCoMo long-term conversational-memory benchmark, encrypted regions, gpt-4o-mini reader and judge - zero LLM at ingest or retrieval. 90.6% (3-run mean) with a gemini-3.5-flash reader. Protocol and full numbers in citadeldb-membench.

Model Context Protocol

Plug encrypted memory into Claude Desktop.

citadel-mcp serves one memory region over MCP (JSON-RPC 2.0 on stdio) with 13 tools. Encrypted by default, with per-atom sealing and cryptographic erasure. Any MCP client - Claude Desktop, an IDE - can read and write it.

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
claude_desktop_config.json
{
  "mcpServers": {
    "citadel": {
      "command": "citadel-mcp",
      "args": [
        "--db", "memory.citadel",
        "--region", "default",
        "--embedder", "bge-small"
      ],
      "env": { "CITADEL_KEY": "your-passphrase" }
    }
  }
}

One-time setup: citadel-mcp pull bge-small downloads the embedder (models are never fetched automatically; omit --embedder for keyword-only recall). Recall hits carry provenance, per-hit integrity verdicts, and memory://atom/{id} resource links. mem_forget returns an erasure receipt.

Benchmarks

50 of 50 head-to-head, won.

Single-threaded, 100K rows, (id INTEGER PK, name TEXT, age INTEGER). Equal cache budgets (~32 MB). Ratio is SQLite / Citadel time; higher means Citadel wins by more. Geometric mean ~2.8×.

362×
full_outer_join
318×
correlated_in
144×
count
1.03×
slowest (still wins)
Benchmark
Relative speed
Citadel
SQLite
Ratio
SQLite: journal_mode=OFF, synchronous=OFF, cache_size=8192. Citadel: SyncMode::Off, cache_size=4096. Reproduce: cargo bench -p citadeldb-sql --bench h2h_bench.
Architecture

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.

citadel-cliinteractive shell
citadel-pythonPython wheel
citadel-ffi37 C-ABI functions
citadel-wasmbrowser builds
citadel-mcpMCP server
citadel-aiagent runtime
citadel-membenchLoCoMo harness
citadel-swemini-SWE harness
citadel-memregions / atoms / edges
citadel-sqlparser / planner / executor
citadel-vectorVECTOR / ANN
sql-json-pathSQL/JSON paths
citadeldatabase API / builder / sync
citadel-txntransactions
citadel-syncreplication
citadel-cryptokeys / ciphers
citadel-bufferSIEVE buffer pool
citadel-pagepage codec
citadel-iofile I/O / fsync / io_uring
citadel-coretypes / errors / constants

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.

Commit Protocol
1 / 4  /  Copy-on-write new pages
Two dirty pages in the buffer pool are ready to commit. Before a single byte touches disk, the active slot stays untouched, so a crash right now leaves the last good snapshot intact.
citadel-buffer  in memory
page #42modified
page #43clean
page #44modified
page #45clean
SIEVE pool / 4,096 frames
encode / seal
my.db  on disk
god byte / 1 B
00000000
bit 0 / active slot A / B
slot A / 240 Bmerkle: 8a1c...e4f2active
#42
#43
#44
#45
slot B / 240 Bmerkle: -shadow
#42#43#44#45h₁₂h₃₄root
SQL

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
Security

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.

Distribution

Rust, Python, C, WebAssembly, and a CLI.

Rust

crates.io / citadeldb

The reference API. Builder-style open, typed transactions, no hidden allocation. 15 crates published.

$ cargo add citadeldb citadeldb-sql

Python

PyPI / citadeldb

The full engine in one wheel: encrypted SQL, vector search, the memory engine, and the MCP server. Typed, with numpy vectors.

$ pip install citadeldb

C / C++

cbindgen / citadel.h

37 panic-safe functions, static or dynamic linkage. Drop into any toolchain that speaks C ABI.

> #include <citadel.h>

WebAssembly

npm / @citadeldb/wasm

A real encrypted database in your browser tab. Compiled with wasm-pack. Powers the playground.

$ npm install @citadeldb/wasm

CLI

crates.io / citadeldb-cli

An interactive SQL shell with tab completion, syntax highlighting, and 26 dot-commands.

$ cargo install citadeldb-cli
Writing

From the blog.

Start with the browser build.

No install, no signup. Run real SQL against an encrypted database compiled to WebAssembly.