Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

451 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

image

Undercroft

Hardened, local-first AI memory: encrypted, integrity-verified memory vaults with verbatim recall.

Website · Documentation · Agents implementation guide · Security model

Implementing with an AI agent? Point it at docs/AGENTS.md — a scenario-driven guide (personal agent memory, team server, multi-tenant engine, fleet orchestration, retrieval tiers, security operations) written so an agent can pick the right deployment shape and implement it correctly, with the full tool/route/env reference and a verification checklist.


Why "Undercroft"?

An undercroft is the vaulted chamber beneath a hall — cut into stone, built to resist damp and fire, and used for the charters, plate and records that had to outlast the building above them. It was never the room anyone was shown. It is the room the contents survived in.

That is the job description:

Undercroft (the room) Undercroft (this project)
Beneath the hall, not part of it Sits under your agent, outside any single session
Stone vaulting, built for the load above Verbatim storage — nothing summarized on the way in
Proof against damp and fire Sealed vaults: AEAD at rest, HMAC per record, a tamper-evident chain
Locked, and the lock is the point Per-vault keys, screened writes, receipted deletion
What is kept there outlasts the building Memory that survives sessions, context compressions and machines

The word is exact rather than ornamental. vault is this system's load-bearing noun — the crypto boundary, the CLI subcommand (undercroft vault create), the isolation unit — and an undercroft is a vault in the literal sense before it is one in the banking sense. The structure inside is inherited from MemPalace: content is filed into wings and rooms as drawers.

Published under Sealcroft.

What it is

Undercroft stores conversation history and project knowledge as verbatim text (never summarized on the way in) and retrieves it with hybrid semantic + lexical + recency search. The index keeps MemPalace's structure — people and projects are wings, topics are rooms, original content lives in drawers — and adds a security-first memory management layer:

The vault layer (original to this project)

Every memory namespace is a vault — a hard isolation boundary:

  • Separation — each vault has its own directory and its own SQLite database. There is no shared table space to leak across, and vault names are validated against path traversal.
  • Key isolation — per-vault encryption and MAC keys are derived from one palace master key via HKDF-SHA256 domain separation. Vault A's keys are cryptographically useless against vault B's data. The master key is either a 0600 key file or derived from a passphrase with Argon2id (64 MiB, t=3); keys are zeroized in memory on drop.
  • Encryption — in sealed vaults (the default), drawer content and its embedding are encrypted with XChaCha20-Poly1305. The AEAD associated data binds vault id + record id, so ciphertext cannot be replayed into another vault or another record slot. Nothing content-derived is written to disk in plaintext — a default vault searches by decrypt-scan, and the optional index tiers below (PQ codes and codebooks, ColBERT token matrices, FDE vectors) are sealed under their own AAD domains and read through decrypt-once RAM caches rather than in the clear.
  • HMAC integrity — every record carries an HMAC-SHA256 tag (independent MAC key) over its id, metadata, and at-rest content; reads verify before returning data. An append-only audit table feeds a tamper-evident HMAC chain whose head lives in the vault manifest — and the manifest itself is MAC'd, so offline edits (chain resets, security-level downgrades) are caught at unlock. undercroft verify walks all of it.
  • Choice of levelsealed (encrypt everything) or hmac-only (plaintext + full-text indexing, but still integrity-tagged and chained) for memories where searchability outweighs confidentiality.
  • Screened writes, receipted deletions — opt-in admission control diverts injection-shaped writes into a sealed quarantine wing, with chain-audited allow/deny rulings (deny hands back an attestation). The screen sits at the write choke point rather than at each call site, behind an argument every write path must state, so a save, a dedup-refresh, a caller-supplied-vector import and a backup restore are all screened by construction; a diverted save says so on every save surface — CLI, MCP and /v1 alike — and hands back the id the drawer actually landed under, instead of reporting success under the id you aimed at. Quarantined drawers answer no one but their reviewer: excluded from search, from wake-up and the closet index, and from drawer listings — and MCP, the agent surface, may neither read them back nor delete them. Beside it, forget destroys through the audit chain and emits a verifiable receipt; retention policies per wing/room enforce by explicit attested sweeps; wings carry operator-assigned trust classes consumed as a retrieval floor; every export leaves an audit-chain record binding its own manifest digest, with no flag to set (a read-only replica cannot write one and says so instead), and reads can be audited too (UNDERCROFT_READ_AUDIT=chain — a keyed query fingerprint, never the query text). All operator surfaces — deliberately never MCP.

Threat model: protects memories at rest against disk theft, cross-vault bleed, and offline tampering of the database or manifest. It does not defend against an attacker who can read process memory while a vault is unlocked.

Nothing leaves your machine by default. The default embedder is a deterministic local hashed n-gram model — no downloads, no API calls, no network at all.

Storage & retrieval backends

The bundled SQLite store is the system of record — keys, HMAC tags, audit chain, and knowledge graph always live there. Remote vector databases are supported as untrusted search accelerators:

Backend Role Configure with
SQLite (bundled) System of record + local search (default)
qdrant Remote ANN index (REST) UNDERCROFT_QDRANT_URL
chroma Remote ANN index (REST v2, server mode) UNDERCROFT_CHROMA_URL
pgvector Remote ANN index (Postgres) UNDERCROFT_PGVECTOR_DSN
milvus Remote ANN index (REST v2, standalone) UNDERCROFT_MILVUS_URL
weaviate Remote ANN index (REST + GraphQL) UNDERCROFT_WEAVIATE_URL

Unlike MemPalace — which stores plaintext documents in these databases — Undercroft uploads only the sealed content blob plus the embedding and wing/room labels. Remote search returns candidate ids; every candidate is re-loaded from the local palace, HMAC-verified, decrypted, and re-ranked locally. A compromised index can hide results but cannot forge, alter, or inject them. Retrieval policy is the local path's, from the same code: the trust floor, the quarantine fence and the closed-vocabulary filters are applied per candidate off the verified metadata, so --backend qdrant is not a route around admission control. The trade-off that remains: embeddings are visible server-side (ANN cannot work otherwise) — if embedding-inversion leakage is unacceptable, use local search. Remotely the floor can only bound what came back rather than what was generated, which costs availability, never integrity.

undercroft index push qdrant            # upload sealed records
undercroft search "query" --backend qdrant
undercroft index status qdrant

Languages

A query finds a word's other forms — running from run, Kinder from Kind, libri from libro, бумаги from бумага, مكتوب from كتب. Measured end to end at realistic drawer length over 191 paradigm pairs in 19 languages: 100% on the lexical channel, with nothing left to the embedder to rescue.

Which language applies is resolved three ways, strongest first: what you declared on the request; else what the script settles (Greek, Georgian and Hangul are one language apiece); else what the drawer says it is — a text carrying der, die, und is German. Only closed-class function words vote, and only decisively. You do not have to declare anything, though declaring is stronger and worth doing when you know.

Five pairwise rules do it — suffix, substitutive inflection, agglutinative stacking, Arabic root identity, and a table of irregular forms. None builds an equivalence class, which is why a stemmer is deliberately not used: one false friend poisons a whole class, and measured, Snowball Greek merges πολύ (much) with πόλη (city).

Morphology admits, so every rule has a price and each one is a pinned test row — declaring German merges flow/flower, Italian merges pesca/pesce. 58 control rows in eight languages guard them, run end to end through the real search at realistic drawer length: 49 pairs that must stay apart, plus 9 that already meet and are pinned as the known price, so a cost that disappears gets reported rather than absorbed. See docs/agents.html.

Note this is within-language. Cross-lingual retrieval needs one thing: a multilingual model via onnx/ort/http — the default hashed embedder matches on shared surface forms, so an EN/AR translation pair scores below an unrelated sentence. With one installed, cross-script pairs are served at the default configuration (the script-disjoint fusion reweight; measured 95–100% R@5 on FLORES-200 — tables in the CHANGELOG).

Embedders

The Embedder trait is pluggable and identity-tracked: the model name and dimension are recorded per vault on first write, and a mismatch is refused (silent model swaps degrade recall) unless UNDERCROFT_FORCE_EMBEDDER=1 is set, after which undercroft repair re-embeds every drawer.

  • hash (default) — deterministic hashed n-gram embedder, zero dependencies, fully offline.
  • onnx — MiniLM-class sentence-transformer ONNX exports via tract (pure Rust, no native binaries). Build with --features onnx, then point UNDERCROFT_ONNX_MODEL and UNDERCROFT_ONNX_TOKENIZER at a user-supplied model.onnx + tokenizer.json and set UNDERCROFT_EMBEDDER=onnx. Undercroft never downloads models itself.
  • ort — the same models through ONNX Runtime (~2.5× faster per forward, int8/VNNI support, ~4–5× faster ingest embed). Build with --features ort and set UNDERCROFT_EMBEDDER=ort; reads the same UNDERCROFT_ONNX_* variables, so switching backends is one env change. Opt-in because it links ONNX Runtime's C++ library — tract stays the pure-Rust default. Releases ship it ready-made at full parity with the default artifacts: a smoke-probed -ort binary for all five targets (Linux x86_64/arm64, macOS Intel/Apple Silicon, Windows) and a multi-arch :tag-ort container image.
  • http — a model served by Ollama, llama.cpp server, LM Studio, vLLM or TEI (UNDERCROFT_EMBEDDER=http + UNDERCROFT_EMBED_URL): no export, no feature build. Transport is TLS or loopback only — cleartext http to a non-loopback host is refused at construction with no override, and UNDERCROFT_EMBED_CA pins a self-signed root (the compose embeddings-tls terminator ships the infra). The stated trade: the endpoint reads your text in plaintext — the in-process backends above close that. The full posture guide is docs/EMBEDDERS.md.

Cross-encoder reranker (optional, onnx / ort features)

A second retrieval stage: after hybrid search surfaces a candidate pool, a cross-encoder re-scores the top-N with the full (query, passage) pair and re-orders them. Point UNDERCROFT_RERANK_MODEL / UNDERCROFT_RERANK_TOKENIZER at a user-supplied cross-encoder ONNX export (a BERT-family model such as cross-encoder/ms-marco-MiniLM-L-6-v2; note tract 0.22 does not run DeBERTa-based rerankers) and set UNDERCROFT_RERANKER=onnx (tract) or UNDERCROFT_RERANKER=ort (ONNX Runtime: one batched forward for the whole pool + a session-pool fan-out, --features ort). Pairs with either embedder; UNDERCROFT_RERANK_TOP_N (default 50) bounds the added latency. Applies to search, serve-mcp, the daemon, and the multi-tenant /v1 surface (one shared model across vaults). Measured: LoCoMo R@10 94.6 → 97.68% at 101–327 ms/query on 24 cores (ONNX Runtime backend + int8).

ColBERT late interaction (optional, onnx feature; ort runtime available)

The core-count-independent second stage: drawers are encoded once at ingest into per-token matrices (PQ-compressed to ~16 bytes/token on disk, AEAD-sealed in sealed vaults) and a search runs one query forward plus a MaxSim re-score — no transformer per candidate. Measured: LoCoMo R@10 94.6 → 96.5–96.8% at a flat ~93 ms/query on any core count with the pure-Rust tract runtime, ~70 ms/query (and 3.3× faster ingest) on the opt-in ONNX Runtime backend — recall identical across runtimes. Set UNDERCROFT_RERANKER=colbert (tract) or colbert-ort (ONNX Runtime, --features ort) + UNDERCROFT_COLBERT_MODEL (doc export) / _QUERY_MODEL / _TOKENIZER (fixed-shape ONNX exports; recipe in docs/RETRIEVAL_SCALING.md). Token matrices ride export bundles as portable artifacts (restore = copy, not re-encode); repair --tokens backfills palaces that predate the encoder. MUVERA FDE candidates (UNDERCROFT_RETRIEVAL=fde) make the candidate stage token-aware too: each matrix compresses to one fixed-dimensional vector (sealed at rest, built with zero extra forwards) whose dot product approximates MaxSim — measured on LoCoMo: recall identical to fusion, question-for-question, at −25% search latency; at N=200k synthetic docs the exact top-10 survives the FDE top-100 100% of the time at 40× below exact-scan cost. Above a few hundred drawers the FDEs PQ-compress 32× (256 B/drawer, 51 MB RAM at N=200k) with containment still perfect and the scan ~8× faster — bounded RAM like every other index here.

Scaling retrieval (PQ / IVF, both vault levels)

Large corpora can cut candidate generation from a full scan to a bounded-RAM product-quantization index with IVF inverted lists (UNDERCROFT_RETRIEVAL=pq): ~48 bytes/vector on disk, recall flat in corpus size (99+% R@5 at N=50k). Sealed vaults get it too — code rows, codebook, and centroids are AEAD-sealed and scanned via a decrypt-once RAM cache; measured sealed search went from 2.1 → 33.4 q/s at N=20k (×16), parity with the plaintext index. Full numbers: benchmarks/RESULTS.md.

Quickstart (Docker — recommended)

Everything persists under /data, so mount a volume there:

docker pull ghcr.io/sealcroft/undercroft:latest   # published image
docker tag ghcr.io/sealcroft/undercroft:latest undercroft
# or build it yourself:
docker build -t undercroft .

docker run --rm -v undercroft-data:/data undercroft init
docker run --rm -v undercroft-data:/data undercroft remember \
  "We chose GraphQL over REST for the mobile API" --wing backend --room decisions
docker run --rm -v undercroft-data:/data undercroft search "why graphql"
docker run --rm -v undercroft-data:/data undercroft verify
docker run -i --rm -v undercroft-data:/data undercroft serve-mcp   # MCP stdio server

Wire it into an MCP client (e.g. Claude Code):

{
  "mcpServers": {
    "undercroft": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-v", "undercroft-data:/data", "undercroft", "serve-mcp"]
    }
  }
}

No Docker? Prebuilt binaries for Linux (x86_64 + arm64), macOS (Intel + Apple Silicon), and Windows are attached to every release (undercroft + undercroft-orchestrator, SHA-256 checksums included). Or build natively: cargo build --releasetarget/release/undercroft.

CLI

undercroft init                       # master key + 'default' sealed vault
undercroft vault create work          # new isolated vault (own keys, own DB)
undercroft vault list | status <name>
undercroft vault rotate <name>        # fresh derived keys; re-seals everything, crash-safe
undercroft remember <text> [--vault --wing --room --kind]  # --kind: the label search --kind filters on
undercroft mine <dir> [--mode files|convos]  # documents, or Claude Code/Codex JSONL sessions
undercroft sweep <dir>                # one verbatim drawer per transcript message (idempotent)
undercroft search <query> [--vault --wing --room --kind --min-trust -n N]
undercroft search <query> --language de   # declared morphology (en de nl it es fr pt tr ru el hi ka ko)
undercroft search <query> --offset N --ranked-at <rfc3339>  # page one ranking, clock pinned
undercroft search <query> --room-cap N    # spread hits across rooms, not the most verbose one
undercroft wake-up [--vault --wing]   # L0 identity + L1 essential story
undercroft drawer get|list|update|delete|delete-by-source|check-dup
undercroft kg add|query|rel|invalidate|supersede|timeline|stats
undercroft kg authority|canonical|receipts  # golden-values tier + its receipts
undercroft diary write|read|agents    # per-agent diaries in their own wings
undercroft tunnel create|list|follow|delete|traverse   # cross-wing links
undercroft hallways <wing>            # within-wing entity co-occurrence
undercroft closets [--wing]           # compact LLM-scannable index (AAAK port)
undercroft refine [--dry-run]         # local-LLM extraction into the KG (UNDERCROFT_LLM_URL)
undercroft stats | taxonomy           # palace shape
undercroft dedup [--apply]            # exact-duplicate detection (keyed fingerprints)
undercroft backup create|list|restore # verified snapshots, keeps last 10
undercroft repair                     # backfill + vacuum + re-verify
undercroft verify [--vault]           # HMAC every record + replay audit chain
undercroft admission list|allow|deny  # review writes the ingest screen quarantined
undercroft trust set|list <wing>      # deployment-assigned wing trust (candidate floor)
undercroft retention set|list|clear|sweep  # per wing/room max age; sweep is explicit
undercroft forget <id...> [--sign]    # destroy + chain-attested receipt (RTBF)
undercroft verify-forgetting <receipt># replay a receipt against this vault
undercroft export [--vault]           # decrypted JSONL to stdout
undercroft export --to <pub> --out f  # sealed bundle only that recipient can open
undercroft import <file.jsonl>        # migrate from undercroft or mempalace exports
undercroft import <bundle> --identity <key>  # open + import an encrypted bundle
undercroft bundle keygen|recipient    # hybrid X25519+ML-KEM-768 identities for sealed exports
undercroft bundle sign-keygen|sender  # Ed25519 sender-attestation identities (export --sign)
undercroft transcript render <f.jsonl># pretty-print an agent transcript
undercroft daemon run [--watch --interval --once]  # background auto-save loop
undercroft hooks claude-code          # auto-save hook settings snippet
undercroft serve-mcp [--vault]        # MCP stdio server (34 tools)
undercroft serve-http [--host --port --read-only]  # MCP /mcp + multi-tenant REST /v1
                                     # --read-only is a posture on the whole
                                     # process: both stores open read-only and
                                     # the route gate fails closed
undercroft assert-header <vault>      # mint an X-Vault-Assertion (per-tenant auth)

serve-http is both the shared team server (MCP over HTTP, bearer auth) and a multi-tenant memory engine: a versioned /v1 REST surface with vault lifecycle, per-vault HMAC assertions (UNDERCROFT_ASSERTION_SECRET), caller-supplied embeddings, dedup-refresh on save, the operator plane (trust, admission rulings, retention, forget, rotate, verify), and lossless export/import for migrating a tenant between instances — every one of those write doors screened by the same admission control, and every read of them answering with the same trust floor and quarantine exclusion as the CLI. See the remote-server guide.

It also serves a vault admin console at GET /ui — one static, dependency-free page (every build, no telemetry feature needed): vault lifecycle, stats, a live monitor, a knowledge-graph browser, one-click HMAC

  • chain verification, key rotation, a taxonomy-driven drawer browser with verbatim view/edit/delete, search, export/import, and an ops tab carrying the operator plane the agent surface deliberately lacks — the admission review queue (allow re-files, deny destroys with a receipt, both audited), wing-trust assignment, retention, and attested forgetting. Credentials stay in the browser tab (assertions are minted client-side via WebCrypto), and destructive operations require typing the target's name.

Fleets of engines get the optional orchestrator (undercroft-orchestrator): instance registry, tenant creation with one-time token minting, a routing proxy that maps each tenant token to exactly its own vault, and count-verified live migration between instances — a separate control plane speaking only the public /v1 surface, with engine credentials sealed at rest and tenant tokens stored only as HMACs. It carries its own fleet console at GET /ui — instances, tenants, token rotation, migration — in the same self-contained style as the engine's admin console. Read routing scales horizontally with read replicas (serve --read-replica): a replica opens the state database read-only and serves only the /t/* data plane, with /healthz reporting mode + last_write so replication lag is observable. Design + surface: docs/MULTI_TENANCY.md.

Palace location: $UNDERCROFT_HOME (default ~/.undercroft; /data in Docker). Passphrase mode: set UNDERCROFT_PASSPHRASE before init and every command.

MCP tools (34)

Category Tools
Palace core save, search, wake_up, verify, status, history, get_closet_index
Drawers get_drawer, add_drawer, update_drawer, delete_drawer, list_drawers, delete_by_source, check_duplicate
Navigation list_wings, list_rooms, get_taxonomy, create_tunnel, list_tunnels, follow_tunnel, delete_tunnel, traverse, list_hallways
Knowledge graph kg_add, kg_query, kg_invalidate, kg_supersede, kg_timeline, kg_stats, lookup_canonical
Agent diaries diary_write, diary_read, list_agents
Maintenance dedup

Deliberately absent from MCP: admission rulings, wing trust, retention, forgetting, key rotation, and placing a fact on the authority tier — operator surfaces (CLI + /v1) only, because an agent must not rule on its own quarantined writes, raise its own standing, shorten the life of the memory it reads, or make its own fact the single answer lookup_canonical returns. Both halves of that sentence are enforced by a test rather than by this table: the tool list above is inventoried in code and counted against the server in both directions (a tool without an entry fails the build, an entry without a tool fails it too), and the operator-only capabilities are asserted absent from MCP by the same mechanism — so the boundary cannot quietly become a gap, and the list cannot rot. An agent also cannot read or delete another agent's quarantined evidence: no MCP tool may name the review wing or a drawer sitting in it.

All tool names are prefixed undercroft_. The knowledge graph stores temporal facts with validity windows — kg_query --as-of 2024-06-15 answers "what was true then", kg_supersede closes the old fact and opens the new one, and kg_timeline replays history. KG facts live in the vault too: objects are sealed in encrypted vaults, and every triple is HMAC-tagged and audit-chained.

Testing (all in Docker)

docker compose run --rm test              # unit + integration tests (cargo)
docker compose run --rm e2e               # end-to-end UI/UX suite against the real binary
docker compose run --rm orchestrator-e2e  # two engines + the control plane
docker compose run --rm e2e-telemetry     # telemetry build + /metrics gating
docker compose run --rm backends-e2e      # remote-index suite (five live vector DBs)
docker compose run --rm onnx-build        # compile check for the ONNX embedder feature

The e2e suite drives the actual CLI the way a user would — help text, happy paths, exit codes, vault isolation, plaintext-leak checks against the raw DB file, deliberate on-disk tampering (must be detected), a scripted attacker whose injection-shaped writes must land in quarantine and stay unreadable, and a scripted MCP JSON-RPC session. The backends suite runs the full push → remote search → verify flow against real Qdrant, Chroma, Postgres+pgvector, Milvus, and Weaviate servers.

Architecture

crates/
  undercroft-core/    domain model: drawers, chunking, ids, normalization,
                     deterministic hashed n-gram embedder
  undercroft-vault/   security layer: VaultManager, HKDF key derivation,
                     XChaCha20-Poly1305 sealing, HMAC tags + audit chain,
                     hybrid PQ export bundles + signed manifests
  undercroft-store/   SQLite per-vault storage, hybrid search, PQ/IVF + FDE
                     index tiers, admission control, forgetting, retention
  undercroft-cli/     `undercroft` binary: CLI, MCP stdio, HTTP + /v1, admin UI
  undercroft-index/   remote vector backends as untrusted accelerators
  undercroft-llm/     local LLM runtimes + the HTTP-served embedder
  undercroft-obs/     observability shim: no-op and zero-dep by default
  undercroft-orchestrator/  optional multi-tenant control plane (own binary)
  undercroft-bench/   retrieval benchmark + synthetic-instrument harnesses
  undercroft-embed-onnx/, undercroft-embed-ort/
                     feature-gated in-process model backends (built explicitly)

Drawer metadata (wing, room, source_file, chunk_index, added_by, filed_at, normalize_version, id_recipe, …) mirrors MemPalace's schema, and drawer ids use the same deterministic-recipe idea (idempotent re-mining).

Relationship to MemPalace

Undercroft began as a fork of the MemPalace project (MIT-licensed, Python) and its feature surface was ported to Rust; no Python remains and no MemPalace source code is present. Everything since — the vault and security layer, the retrieval stack, the language layer and the orchestrator — is original work with no MemPalace counterpart.

Ported: the palace model and miners (files + conversation transcripts + sweep), wake-up layers, knowledge graph, tunnels/hallways navigation, agent diaries, drawer management, dedup/stats/backups/repair, hooks output, the MCP tool surface, remote vector backends (Qdrant, Chroma, pgvector — with client-side sealing, where MemPalace uploads plaintext), and model-based embeddings (ONNX via tract, feature-gated). Milvus is MemPalace's gRPC-only opt-in extra and appears here as a REST v2 client instead, tested against a live standalone server; Weaviate exists only here. Absent by choice: embedded ChromaDB (a Python library; the bundled SQLite store fills that role).

Benchmarks (measured, not inherited)

Full methodology and reproduce commands: benchmarks/RESULTS.md. All figures below are under the shipped default (bm25 fusion). Matched-model conditions (all-MiniLM-L6-v2, the class MemPalace used): LoCoMo session R@10 94.6% (MemPalace: 60.3% raw / 88.9% hybrid) and LongMemEval-S R@5 99.4% on the full 500 — clearing not just MemPalace's raw 96.6% but their tuned hybrid 98.4%. The zero-model hash embedder — no download, ~95x faster — holds 94.6% / 95.0% respectively, converging with the model on LoCoMo. An optional cross-encoder reranker lifts LoCoMo to 97.68% (1936/1982).

(Until 2026-08-05 this paragraph quoted 93.8 / 97.4 / 92.7 / 90.4 — the pre-BM25 legacy-fusion numbers, which had not been the default for several releases and contradicted the RESULTS.md this sentence links to.)

Storage that doesn't balloon

  • Sealed content is zstd-compressed before encryption (compress-then- encrypt — ciphertext can't be compressed after the fact), with a raw fallback when compression doesn't pay. Legacy records stay readable.
  • Embeddings are int8-quantized (4× smaller than f32; the vector is usually bigger than the text it embeds) with per-vector scaling — ranking-neutral (cosine drift < 0.1%) and covered by tests.
  • Exact-duplicate detection (keyed fingerprints), dedup --apply, and repair (vacuum + re-embed) keep the palace tight.

More

License

Business Source License 1.1 — see LICENSE. In practice:

  • Free for almost everything: use, modify, self-host, and run in production — personal, internal, and commercial — at no cost.
  • The one carve-out: you may not offer Undercroft itself to third parties as a paid hosted or embedded product that competes with the Licensor's commercial offerings.
  • Time-limited by design: each release automatically converts to the open-source MPL 2.0 four years after publication.

Undercroft began as a fork of the MIT-licensed MemPalace project, was ported to Rust, and contains no code from it — see NOTICE for the heritage attribution and docs/PARITY.md for the full feature-by-feature relationship.

About

Undercroft — hardened local-first AI memory in Rust: encrypted vaults, HMAC integrity, verbatim recall, knowledge graph, MCP server.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages