A SEALCROFT PROJECT · THE VAULT BENEATH THE HALL

UNDERCROFT

Hardened, local-first AI memory. Your agent's every word, kept verbatim in encrypted vaults — HMAC-chained, tamper-evident, searchable, and never leaving your machine.

Below the hall. Cut from stone. Sealed.

descend
The palace, live

A memory system you can watch working

undercroft — sealed vault · ~/.undercroft
Seal & verify

Try the vault

drawer 5a2f…c91d · wing backend / room decisions ⬡ PLAINTEXT
hmac-sha256 · awaiting write…

Seal: XChaCha20-Poly1305 encrypts content and its embedding. The AAD binds vault + record id — ciphertext can't be replayed anywhere else.

Verify: every record carries an HMAC-SHA256 tag, and every write joins a tamper-evident audit chain. Change one byte on disk and verify names the record.

Keys: derived per-vault with HKDF domain separation. One vault falling tells an attacker nothing about its siblings.

Watch it work

The Palace Monitor

A telemetry build serves a self-contained pixel-art dashboard at GET /monitor. Point it at a running palace and watch the archivist file drawers into wings, searches pulse the halls, the audit chain stamp every write — and the ambulance beacon fire the instant a record fails its HMAC. It reads the live server-sent event stream: metadata only, never drawer contents, and sealed vaults stream aggregate counts with wing names suppressed.

● LIVE · palace-demo
Palace Monitor live view: eight wings named for conversation participants, each filling with filed drawers, an archivist mid-file, gold audit-chain links stamping between wings.
Live. Each wing is a real drawer stream — this palace is built from the LoCoMo long-conversation benchmark, filed verbatim. The archivist files every write, the chain stamps, and the counts climb as it happens.
🚨 INTEGRITY ALERT
Palace Monitor tamper alarm: the whole palace flushed red under a beacon after a real HMAC verify failure.
Tampered. Corrupt one drawer's bytes on disk and the next read fails its HMAC — a genuine hmac-fail event floods the palace red. The beacon only ever fires on a real integrity failure.

Opt-in, zero-egress

Monitor and stream exist only in a --features telemetry build. Default builds carry no telemetry deps and phone nothing home; the page is fully self-contained — same-origin, no external requests.

Metadata only

The stream carries counts, rates, and wing/room labels — never drawer content or keys. Sealed vaults stream aggregate totals with names suppressed server-side.

A beacon you can trust

The tamper light is wired to the same undercroft_hmac_verify_failures_total signal as the metrics: it fires on real verification failure and nothing else. No synthetic alarms.

Operate it

Alerting you can trust

A telemetry build drops into a full Grafana stack — Prometheus metrics, Loki logs, Tempo traces, and Alertmanager. Health and latency are there, but the headline is integrity: the instant a record fails its HMAC, PalaceTamperDetected fires — tagged with the surface that was hit — and routes to your pager with a link to the runbook.

Grafana · Undercroft — Palace
The Undercroft — Palace Grafana dashboard: a red HMAC-failures tamper stat, request/latency/write metrics, tamper broken out by surface, an active-alerts table, structured logs, and traces — all on one board.
One board. Metrics, tamper-by-surface, the active-alerts table, logs, and traces — this palace is a live vault filed from the LoCoMo benchmark, with a drawer corrupted on disk to trip the alarm.
🚨 PalaceTamperDetected · firing
Grafana active-alerts table showing PalaceTamperDetected firing at critical severity on the drawer surface, above HighSearchLatencyP95 firing at warning.
A real alert. Corrupt one drawer's bytes on disk and the next read fails its HMAC — PalaceTamperDetected fires critical, names the drawer surface, and is delivered through Alertmanager. No synthetic alarms.

Alertmanager-routed

Six rules ship out of the box — tamper, chain-stalled, target-down, latency, 5xx, auth. Route them to Slack, email, or PagerDuty; the demo stack logs every delivery.

Logs & traces, metadata only

Loki logs and Tempo spans carry operation, route, and vault — never query text, drawer content, or keys. Full traceability without leaking the palace.

A tamper runbook

The alert links to a runbook: where it happened, and how to confirm, mitigate, fix, and prevent it.

The undercroft is the vaulted chamber beneath the hall — cut from stone, sealed against damp and fire, and built to outlast everything raised above it.
— ON THE NAME
Measured, not promised

Benchmarks that were actually run

Both public long-memory suites, end to end, on the shipped scoring pipeline — full datasets, MemPalace's own protocol and metrics, everything inside Docker. Two configurations: the default hash embedder (deterministic, offline, zero downloads) and all-MiniLM-L6-v2 via ONNX — the same model class MemPalace used, so those rows compare like for like. MemPalace rows are their published numbers.

LongMemEval-S recall@5 · all 500 questions
undercroft · MiniLM
99.4
undercroft · hash
95.0
mempalace · hybrid
98.4
mempalace · raw
96.6
recall@5 of the evidence session · MiniLM + BM25 clears MemPalace's tuned hybrid; zero-model hash reaches 95.0 · already saturated at 99.4, so a reranker only adds noise here
LoCoMo session recall@10 · 1,982 QA
undercroft · MiniLM + reranker
97.7
undercroft · MiniLM
94.6
undercroft · hash
94.6
mempalace · hybrid
88.9
mempalace · raw
60.3
recall@10 of the evidence session · MiniLM + BM25 and zero-model hash converge at 94.6, both far above MemPalace; an optional cross-encoder reranker lifts it to 97.68 (1936/1982)

What was measured

Evidence recall at session granularity, the metric MemPalace reports: LongMemEval-S (xiaowu0162/longmemeval-cleaned — the exact file MemPalace benchmarked) and LoCoMo (snap-research/locomo; 1,982 of 1,986 QA are evaluable, no-evidence questions skipped). Scoring runs the sealed-level pipeline — the same code path production uses.

How it was run

undercroft-bench rebuilds a palace per conversation, files every session verbatim, then checks whether the annotated evidence surfaces in the top-k. Ranking blends cosine with a real BM25 lexical score over the verified candidates; MiniLM inference is pure Rust via tract (256-token truncation, mean pooling), sharded across containers with --skip/--limit. The hash rows need no model at all and run ~95× faster per question.

Honest reading

Matched-model rows are the fair comparison: +2.8 over MemPalace raw on LongMemEval-S and +1.0 over their tuned hybrid (99.4 vs 98.4), and +5.7 over their best on LoCoMo (94.6 vs 88.9). MemPalace numbers come from their own harness; ours from the commands below, reproducible on any machine with Docker. The zero-model hash rows are the honest floor, and honestly split: 94.6 on LoCoMo is above MemPalace's best (88.9) with no model to download, while 95.0 on LongMemEval-S sits below their raw 96.6 — that suite is where the model still earns its keep.

reproduce — benchmarks/RESULTS.md has the exact recipe
docker compose run --rm onnx-build # model rows: build with --features onnx undercroft-bench longmemeval longmemeval_s_cleaned.json --k 5 LongMemEval-S · 500 questions · session R@5 95.0% (hash, zero model) undercroft-bench locomo locomo10.json --k 10 LoCoMo · 1,982 QA · session R@10 94.6% (hash) · 97.68% with reranker
full numbers, per-type breakdowns, exact commands →
Head-to-head

Against the memory-layer market

Competitors, measured — not quoted. Every system ingests byte-identical chunks of the same LoCoMo conversations and is scored by one shared scorer, on the same machine, on one pinned stack — the full corpus, all 1,982 questions. Every row is fully local — no cloud APIs anywhere — and each competitor runs its own published server in its best documented local configuration. Numbers are reported as measured, favorable or not; raw logs ship in the repo, and corrections are accepted by PR.

Retrieval quality session recall@10 · LoCoMo full corpus · 1,982 QA
undercroft · MiniLM + ColBERT, sealed
96.5
undercroft · no model at all, sealed
94.6
mem0 · local, their stack
66.9
mem0 = their published OpenMemory server + qdrant, running with far more model firepower than we gave ourselves: a 35B-parameter LLM (qwen3.6) + neural embeddings (nomic) versus undercroft's 22M-parameter MiniLM — or no model at all. The architecture won, not the hardware budget. All ten conversations, no subsets: mem0's per-conversation recall spans 58.0–70.9%.
The details that decide it same 1,271 chunks, one pinned stack
undercroftmem0 local
ingest, whole corpus16.5 s≈32 h (LLM per write, 92 s/chunk measured)
search latency5.5 ms/query93–210 ms/query
what survives ingestevery word, verbatim55 distilled facts of 177 chunks (measured subset)
at restsealed · AEAD + audit chainplaintext vector store
model runtime needednoneLLM + embedder, every write
the "what survives" row is mem0's design, not a bug: its extraction rubric keeps personal facts and silently discards the rest — the raw LLM traffic proving it ships with the logs
Architecture

Eight rooms of the machine

Sealed vaults

Each namespace gets its own database and its own HKDF-derived keys. Content and embeddings encrypted at rest; structure searchable; nothing plaintext-derived ever touches disk.

Tamper evidence

HMAC tags on every drawer, knowledge-graph fact, and tunnel; an append-only audit chain with a MAC'd manifest. verify replays all of it.

Verbatim recall

Never summarized on the way in. Hybrid semantic + lexical + recency search with one-typo tolerance returns your exact words.

Knowledge graph

Temporal facts with validity windows. kg query --as-of 2024-06 answers what was true then; supersede closes the old fact and opens the new.

Untrusted accelerators

Qdrant, Chroma, pgvector, Milvus, Weaviate — fed only sealed bytes. Their answers are re-verified against local HMACs before you ever see them, then filtered by the same retrieval policy the local path uses — trust floor, quarantine fence, closed vocabularies — off each candidate's verified metadata. An index push is not a route around admission control.

Agent-native

34 MCP tools over stdio or team HTTP (bearer-enforced; --read-only is a posture on the whole process, not a filter on one port), auto-save hooks, per-agent diaries, transcript sweeps — memory that survives the gap between sessions. The tool list is an inventory the build counts itself against, in both directions.

Screened at the door

Opt-in write-path admission, enforced at the one write choke point behind a required argument — a new write path does not compile until its author decides. A deterministic detector diverts injection-shaped writes into a sealed quarantine wing that no read reaches: not search, not wake-up, not the closet index, and MCP refuses to read or destroy the evidence at all. Rulings are chain-audited, a diversion says so on every surface, and an optional local classifier may say suspicious — never admitted. Trust classes and training-draw caps bound what any writer can shape.

Forgetting with a receipt

forget destroys through the audit chain and hands back a verifiable attestation — heads, tombstones, content fingerprints. Retention policies per wing/room enforce by explicit attested sweeps, never by a silent timer. GDPR/RTBF with proof.

0
cargo tests
0
e2e checks
0
live backends
0
mcp tools
0
bytes phoned home
What to build with it

Use cases it covers today

A coding agent that remembers

MCP stdio server + auto-save hooks: every session's decisions survive the crossing. wake-up loads identity and the essential story; search returns the exact words, one-typo tolerant.

A team's shared memory

One serve-http instance, bearer-enforced beyond loopback, with --read-only for consumers — a posture the whole process takes, gated in front of dispatch and failing closed, so a route nobody remembered to guard is refused rather than served. Same MCP tools over HTTP — every teammate's agent shares one sealed palace.

A multi-tenant memory product

The versioned /v1 REST engine: per-vault assertions, external embeddings, count-verified migration — and an optional orchestrator that routes fleets of engines with HMAC-only tenant tokens.

An auditable record of what was said

Verbatim storage, HMAC on every record, a tamper-evident audit chain, and crash-safe key rotation. verify replays it all; a rollback is an alarm, a crash never is. Egress joins the chain too: every export is audited unconditionally, and UNDERCROFT_READ_AUDIT=chain adds a record per search — a keyed fingerprint of the query, never its text.

Backups that never leak

Encrypted export bundles sealed to a hybrid post-quantum recipient — X25519 and ML-KEM-768, both shared secrets feeding one key, because the bundle is the one thing that leaves the machine and harvest-now-decrypt-later only needs it copied. A migration file never exists in plaintext. Restore is import --identity; token matrices ride along as portable artifacts.

Offline retrieval that scales

Deterministic hash embedder by default; opt-in ONNX / ONNX Runtime models, cross-encoder rerank, ColBERT late interaction, PQ/IVF and MUVERA FDE tiers — every number on this page measured, never promised.

Hands on

From zero to a remembering agent

Begin

Give your agent a memory

Local-first, sealed by default, zero bytes phoned home. One binary, one command to start — and a guide written for the AI that will operate it.