memory-mcp
memory-mcp
Personal cross-tool memory: a searchable episodic archive of AI sessions from all devices plus a curated, high-trust semantic facts layer, exposed through an MCP server. Designed for a self-hosted arm64 box, WG-only. See docs/mcp/memory-mcp-spec.md for the full design.
Layout
Package | Purpose |
| memory-core library — stores, chunking, embedding, hybrid retrieval, facts, scrub, distill (no MCP, no HTTP) |
|
|
| memory-mcp MCP server (Streamable HTTP, static bearer) |
| retrieval golden-set harness (recall@5, MRR, p95) |
| compose.prod.yaml, Dockerfile, systemd timers, backup script |
Two layers, two trust levels
Episodic archive (Qdrant
memory_archive): every chunk of every session. Dense (bge-m3, 1024) + sparse (IDF-modified TF) vectors, RRF-fused hybrid search. Searched only on demand (search_archive).Semantic facts (Postgres): curated statements with provenance. Default read surface (
search_memory). Written byremember(sourceuser) and by human-approved distillation (sourcedistilled) — nothing distilled becomes active withoutmemoryctl review.
Features
Everything below is a first-class part of this project — no external services beyond your own Qdrant and an OpenAI-compatible LLM gateway (see Operations).
Cross-device episodic archive. Every AI session from any tool (chatgpt, claude, claude-code, opencode, vscode, markdown) is parsed, scrubbed, chunked, embedded (dense + sparse), and stored in Qdrant — searchable as raw historical transcripts via
search_archive.Curated semantic facts. Beliefs, preferences, decisions, and people facts are distilled from sessions and stored in Postgres with provenance and a
reviewapproval gate. Nothing distilled becomes active truth without a human sign-off (memoryctl review). Written ad hoc viaremember.Semantic search over both layers.
search_memoryis the fast, high-trust default over curated facts;search_archiveis the on-demand, pre-filtered hybrid search over raw transcripts (injection-framed as data, never trusted as instructions).Project scoping. Facts and archive sessions carry an optional
projectlabel.remember,search_memory,search_archive, andlist_recent_sessionstake aprojectfilter so recall can be scoped to one codebase or concern — and project-less results can be excluded. Existing facts can be backfilled from session provenance withmemoryctl backfill-projects(provenance repair only:updated_atis untouched, and sessions with no project leave the fact unscoped rather than guessing wrong).Most-recently-started sessions.
list_recent_sessions(project?, limit)returns the sessions a project was active in, newest first, each with its source tool, project, chunk count, and start time. This is the "what are we doing here lately" index into the archive — useful after a session compacts and drops its own recent context. It is a bounded scan, so on a very large archive an ultra-low-activity session may be missed.Archive timeline.
search_archive_timeline(session_id, around_chunk_id, window)returns the chunks immediately before and after an archive hit in chronological order, so a single match can be zoomed out to its full conversation.Staleness & review.
memoryctl staleness --older-than <months> --recent <n>reports active facts that have not been updated for months plus the most recent superseded facts. Thememory://recent/supersededresource exposes the recent memory-churn log (updated facts and theirsuperseded_bychain) to clients, so an aged or superseded fact never silently lingers as current truth. Wired into the nightly systemd pass.Destructive, two-phase
forget. Forgetting by query first previews (previewForgetByQuery) exactly which facts will be hard-deleted from both stores beforeforgetis confirmed.Fail-closed scrubbing. gitleaks plus custom regexes strip secrets before anything is embedded or stored; unparseable input quarantines; transient infrastructure failures roll back and retry on the next run.
MCP surface
Tools: remember, search_memory, search_archive, search_archive_timeline,
list_recent_sessions, forget (destructive, two-phase by query). Resources:
memory://stats, memory://facts/recent, memory://recent/superseded,
memory://facts/{id}. remember, search_memory, and list_recent_sessions
accept an optional project scope; search_archive (and the timeline) filter
on archive session project metadata.
Development
pnpm install
pnpm build # all packages
pnpm test # core + server suites (stores are mocked; nothing external touched)
pnpm eval # golden-set retrieval harness (needs live stores + ingested data)Config is environment-driven through packages/core/src/config.ts
(loadConfig) — see deploy/.env.example for every variable.
Operations
cd deploy && cp .env.example .env # fill in POSTGRES_PASSWORD, STATIC_BEARER, keys
docker compose -f compose.prod.yaml up -d --build
docker compose -f compose.prod.yaml exec memory-mcp memoryctl init
docker compose -f compose.prod.yaml exec memory-mcp memoryctl verifycompose.prod.yaml assumes Qdrant and an OpenAI-compatible LLM gateway
already run as external containers on your host — edit the networks:
section's name: fields to match your own Docker network names.
Daily ingest+distill and nightly backups are systemd timers
(deploy/systemd/). Backups age out after 14 days so a forget propagates
out of backups within that window.
Security posture
WG-only bind, static bearer (v1); gateway JWT is a planned future auth mode (Step 8), pending a compatible OIDC/JWT gateway in front of the server.
Scrubbing (gitleaks + custom regexes) is fail-closed and runs before anything is embedded or stored. Unparseable input quarantines the whole file (recorded in the ledger); transient infrastructure failures (embedder, Qdrant, gitleaks unavailable) roll back cleanly, record nothing, and the file is retried on the next run.
forgetis a hard delete in both stores.Archive chunks are returned inside untrusted-data delimiters — historical transcripts can contain instruction-like text and must be treated as data.
See SECURITY.md to report a vulnerability.
Contributing
Issues and PRs are welcome — see CONTRIBUTING.md for dev setup and project conventions, and CHANGELOG.md for history. This project follows the Code of Conduct.