recall-kit
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@recall-kit what did we decide about Harbor Lane's payment terms, and where'd that come from?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
recall-kit
Persistent memory and project continuity for AI agents, shared across Claude Code, Claude Desktop, Codex and any MCP client, with a test suite that proves it works across fresh sessions.
The problem
People who work with several AI tools hit the same wall. ChatGPT remembers one thing, Claude another, Codex nothing. Project status lives in a Notion page that no agent reads. When a fact changes, the old version keeps coming back. When you ask an assistant to "forget that", you can't verify it did. And nobody can say where a remembered "fact" came from.
recall-kit is a small, self-hostable memory layer that fixes this with boring, inspectable parts:
Markdown files are the source of truth: one file per memory, with YAML frontmatter (type, source, created/updated, confidence, supersedes, tags).
git versions every change, so history, diffs and restore come free.
SQLite FTS5 + vectors give hybrid (BM25 + embedding) retrieval. The index is derived data and can be rebuilt.
MCP (stdio and Streamable HTTP) lets every client share the same memory and project state.
A continuity test suite runs each "session" as a brand-new process and checks recall, correction, deletion, citation and project resume, plus the ways agent memory typically fails.
All names, clients and data in this repo are fictional.
Related MCP server: MCP Shared Memory Hub
Demo (real terminal output)
Each rk line below is a separate process, so a fresh session every time. The only thing they share
is the store on disk.
Architecture
flowchart LR
subgraph Clients
CC[Claude Code]
CD[Claude Desktop]
CX[Codex]
VA[VPS agent / any MCP client]
end
CC & CD & CX -- stdio --> S1[rk serve]
VA -- "HTTPS + Bearer token (scoped)" --> CA[Caddy TLS] --> S2[rk serve --http]
S1 & S2 --> API[MemoryStore + ProjectBoard]
API -->|write| MD[(memories/*.md\nprojects/*.md)]
API -->|commit| GIT[(.git history)]
API -->|index| IDX[(index.sqlite\nFTS5 + vectors + conflicts)]
API -->|append| AUD[(audit.jsonl\nids only)]
IDX -. rank .-> API
MD -. "serve + verify each hit" .-> API
BK[rk backup] -->|AES-256-GCM| OFF[(encrypted .rkb\noff-site)]
MD & GIT --> BKThree rules hold everything together:
Files are the truth, the index only ranks. Recall re-reads every hit from its markdown file, so a stale, rolled-back or tampered index cannot serve deleted, superseded or altered text. It heals itself instead.
Nothing is overwritten.
correctcreates a new version thatsupersedesthe old one. The old version stays in history but is never returned as current.Deletion is physical.
forgetremoves the file, rewrites git history, deletes the FTS rows and vectors, then runs FTSoptimize+VACUUMwithsecure_delete. The audit log records that something was deleted, never what.
How memory flows between sessions
sequenceDiagram
participant S1 as Session 1 (Claude Code)
participant St as Store (files + git + index)
participant S2 as Session 2 (Codex, days later)
S1->>St: memory_remember("Harbor Lane pays net-30", source="user, call 11-03", key=client.harbor-lane.terms)
St-->>S1: created mem_a1 (file + commit + index)
Note over S1: session ends, process exits
S2->>St: memory_recall("Harbor Lane payment terms")
St-->>S2: mem_a1 + source {origin, file, commit}
S2->>St: memory_correct(mem_a1, "net-45 from December", source="email 11-09")
St-->>S2: mem_b2 supersedes mem_a1
S2->>St: project_resume("harbor-portal")
St-->>S2: status, blockers, next steps, follow-ups, decisions (cited)Test matrix (real results)
Run on macOS arm64 / Python 3.12, uv run pytest, 2026-11-10: 42 passed, 1 skipped (the LLM eval,
because no API key is set). CI runs the same suite on Ubuntu for every push.
Area | What it proves | Result |
Continuity: fresh sessions (each step is a new | ||
two sessions really are different processes (different server PIDs) | ✅ | |
a fact agreed in session 1 is recalled in session 2 from a differently worded question, with its source | ✅ | |
correcting stale info: new version supersedes the old; old never returned as current; history keeps both; same | ✅ | |
delete on request: all versions gone from markdown, index table, FTS, raw SQLite bytes, | ✅ | |
every recalled item cites a source that matches: file exists, id + content match, cited git commit contains the content, origin attributed to the right memory | ✅ | |
resume a project: status, open blockers, ordered next steps, follow-up due date, decision + rationale, last note; identical via the CLI (another tool); unblocking flips status | ✅ | |
Failure modes (each one reproduced, then detected or prevented) | ||
stale wins over fresh: stale text is a far better keyword match → still not returned | ✅ | |
stale resurrected by index corruption ( | ✅ | |
stale re-asserted from an old chat export → | ✅ | |
duplicates across sessions (case/whitespace variants) → merged, 1 memory | ✅ | |
deleted item resurfacing from an old index file restored after deletion → detected by | ✅ | |
deleted item resurfacing from orphan embeddings → detected, not served | ✅ | |
wrong source: index content tampered → file content + correct source served, | ✅ | |
memory without a source → refused | ✅ | |
prompt-injected memory content: 6 attack variants (override, fake | ✅ | |
…and 4 benign look-alikes ("Ignore the AC noise", "System requirements…") → not quarantined | ✅ | |
an approved risky memory still arrives inside a "data, not instructions" envelope; quarantine shows up in the daily brief | ✅ | |
contradicting memories (15th vs 28th) → flagged, newer ranked first, cleared after resolution | ✅ | |
two clients writing concurrently (Claude Code + Codex simulation) → no lost writes, | ✅ | |
unrelated query → empty result, not "nearest noise" | ✅ | |
HTTP + security | scopes enforced per tool (read/write/forget); missing, wrong and revoked tokens → 401; server refuses to start without tokens; stdio-written memory recalled over HTTP | ✅ |
Ops | encrypted backup → restore (git history intact, same commit SHAs); wrong passphrase and tampered file rejected; restore refuses to overwrite; full disaster-recovery runbook via CLI; prune; daily brief (overdue / due soon / stale / blocked); ICS parses with | ✅ |
LLM-in-the-loop ( | same scenarios driven by a real Claude agent ( | ⏭ not run: no key set (skips cleanly, tested) |
Full per-test list: CI writes it to the job summary (scripts/test_matrix.py).
What was verified outside the test suite
Docker: image built; container run read-only;
/healthzOK;POST /mcpwithout a token → 401;memory_remember+memory_recallover Streamable HTTP with a scoped token → stored at/data/store.Compose:
docker compose configvalidates. Caddy/TLS needs a real domain, so it was not run here.Real agent client: see docs/verification/ for the status of the Claude Code session check.
Quick start
git clone https://github.com/sibrahimpa1/recall-kit && cd recall-kit
uv sync
uv run rk demo # fictional sample data in ~/.recall-kit
uv run rk recall "Tallow & Tide invoice terms"
uv run rk resume northwind-relaunch
uv run rk brief
uv run pytest # ~1 minute; the continuity suite is `-m continuity`Core CLI:
rk remember TEXT --source S [--type fact|preference|decision|project] [--key K] [--project P] [--tag T]
rk recall QUERY [-k 5] [--type] [--project] [--all-versions] [--json]
rk correct ID TEXT --source S [--reason R] rk forget ID --yes rk history ID
rk conflicts | approve ID | list | doctor | reindex | stats
rk project update|add|close|decide|list ... rk resume SLUG rk brief
rk ics --out followups.ics rk backup create|verify|restore|prune
rk token new --name N --scopes read,write rk serve [--http --port 8765]MCP tools
Tool | Scope | Purpose |
| write | save a fact / preference / decision / project memory (source required; optional |
| read | hybrid search; every result has |
| write | supersede a stale memory |
| forget | hard delete across every layer ( |
| read | versions with commits, disagreeing pairs, store info |
| write | status, blockers, next steps, follow-ups (due dates), decisions with rationale |
| read | the briefing a fresh session needs |
Connect your clients
Ready-to-paste configs are in clients/: Claude Code (claude mcp add … or .mcp.json),
Claude Desktop (claude_desktop_config.json), Codex (~/.codex/config.toml), and the remote HTTPS
variant with a bearer token. integrations/claude-code/ adds a
memory-protocol skill (when to recall, remember, correct, forget) and a permissions file that
auto-allows reads but asks before correct and forget. integrations/AGENTS.md
is the same protocol for Codex and other agents.
Security and operations
Tokens: per-client, scoped (
read,write,forget,admin), stored on the server only as SHA-256 hashes inRECALL_TOKENS. Revoke = delete the line + restart. HTTP mode won't start without tokens. Host-header allow-list against DNS rebinding.Secrets only in env:
.env(git-ignored), see.env.example.Backups:
rk backup create= tar of files + full git history, scrypt-derived key, AES-256-GCM, hash manifest checked on restore.rk backup verifyproves a backup restores. Cron example indeploy/crontab.example.Deploy:
deploy/docker-compose.yml: read-only container, non-root, no published port, Caddy in front with automatic TLS.Runbooks: docs/handover.md covers operate, troubleshoot, back up/restore, rotate credentials, revoke access, and extend.
When is this worth it vs. built-in memory? See docs/tool-comparison.md, including rough costs and an honest list of limits.
LLM-in-the-loop eval (optional)
export ANTHROPIC_API_KEY=... # your own key; never committed
uv run --extra eval python evals/llm_eval.pyFive scenarios (recall, correction, deletion, resume, injected content). Each session is a new MCP
server process and a new conversation with no history. Model: claude-opus-5-5 (adaptive thinking,
effort: medium, server-side refusal fallback). A deterministic grader checks the final answer and
the store on disk. For example, after "update the sync to Thursday" there must be exactly one current
sync memory and one superseded one. Results go to evals/results/. Without a key it prints SKIPPED
and exits 0. In CI it runs only if the repo has an ANTHROPIC_API_KEY secret.
Project layout
src/recall_kit/ store.py (lifecycle + recall) · index.py (FTS5/vectors) · gitrepo.py · projects.py
admin.py (brief, ICS) · backup.py · auth.py · safety.py · embed.py · server.py (MCP) · cli.py
tests/ continuity, failure modes, HTTP auth, ops (fresh process per session)
evals/ LLM-in-the-loop eval
clients/ Claude Code / Desktop / Codex configs integrations/ skill, permissions, AGENTS.md
deploy/ Docker Compose + Caddy + cron docs/ handover, tool comparison, verificationAuthor
Senka Ibrahimpašić GitHub @sibrahimpa1
Licensed under the MIT License.
Available Tools
14 toolsdaily_briefC
Overdue and upcoming follow-ups, stale and blocked projects, memory hygiene.
Inputs: today. Returns: object
memory_conflictsB
Pairs of current memories that appear to disagree, newer first, with a suggested fix.
Inputs: no named parameters. Returns: result
memory_correctA
Replace a stale memory. The new version supersedes the old one, which is kept in history but never returned as current.
Inputs: id (required), source (required), content (required), reason. Returns: object
memory_forgetA
Permanently delete a memory and all its versions from files, git history, search index and embeddings. Only on the user's explicit request; requires confirm=true.
Inputs: id (required), confirm. Returns: object
memory_historyB
All versions of a memory (oldest first) with their git commits.
Inputs: id (required). Returns: object
memory_recallB
Hybrid (BM25 + vector) search over current memories. Every result carries its source (origin, file, git commit) and a citation like [mem:mem_ab12cd34ef].
Inputs: query (required), k, type, project, +1 more. Returns: object
memory_rememberB
Save a durable memory. type: fact | preference | decision | project. source = where it came from (e.g. "user in Claude Code, 2026-11-10"). key = optional stable slot for single-valued facts; a new value for the same key supersedes the old one.
Inputs: source (required), content (required), key, tags, +3 more. Returns: object
memory_statsB
Store location and counts (plus the server process id, useful to prove fresh sessions).
Inputs: no named parameters. Returns: object
project_addB
Add an item to a project. kind: blocker | next_step | followup (follow-ups need due=YYYY-MM-DD).
Inputs: kind (required), slug (required), text (required), due, +1 more. Returns: object
project_closeA
Resolve a blocker (b1), complete a next step (n1) or a follow-up (f1).
Inputs: slug (required), item_id (required). Returns: object
project_decideC
Record a project decision together with WHY it was made.
Inputs: slug (required), source (required), decision (required), rationale (required). Returns: object
project_listA
All projects with status and open item counts.
Inputs: no named parameters. Returns: result
project_resumeB
Briefing to pick a project back up: status, blockers, next steps, follow-ups, decisions.
Inputs: slug (required). Returns: object
project_updateC
Create or update a project. status: active | blocked | paused | done. note = what changed.
Inputs: slug (required), name, note, status, +1 more. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 14 tools
The set splits cleanly into a project cluster, a memory cluster, and daily_brief, and within each cluster verbs distinguish intent well (recall vs history vs conflicts, remember vs correct vs forget). The weak spot is project_add vs project_update and project_close: despite the 'project_' prefix, add/close operate on items (blockers, next steps, follow-ups) while update operates on projects, so an agent can misread which resource is being mutated. Descriptions do resolve the ambiguity once read, so no more than a modest penalty.
Almost every tool follows a consistent domain_verb snake_case pattern (project_*, memory_*), and the verb positions are predictable across both clusters. The single deviation is daily_brief, which has no domain prefix and leads with a noun, breaking the otherwise uniform scheme.
Fourteen tools is comfortably within the well-scoped range, and each one maps to a distinct lifecycle operation (add/close/decide/resume/list/update for projects; remember/recall/correct/forget/history/conflicts/stats for memory) plus one aggregate briefing. No filler or near-duplicate tools are present.
Memory coverage is close to a full lifecycle (create, search, supersede, delete, history, conflict detection), and projects support creation, item intake, resolution, decisions and resumption briefing. Gaps remain minor but real: there is no way to enumerate all memories without a search query (no memory_list), no way to edit or delete an individual project item short of closing it, and no way to remove a project entirely.
Maintenance
Related MCP Connectors
Team memory for AI coding agents and code reviews, reviewed by humans and served over MCP.
One memory, every AI. A shared, user-owned markdown memory your AI clients read and write over MCP.
Hosted MCP memory for coding agents: persistent across sessions, editable markdown, team sharing.
Persistent AI memory shared across Claude, ChatGPT, coding agents, and compatible MCP clients.
Related MCP Servers
- AlicenseAqualityBmaintenanceAudit-grade, git-versioned memory for AI coding agents that enables saving, searching, editing, and rolling back facts through markdown files served via MCP.3959 PyPI46MIT
- AlicenseNot gradedqualityCmaintenanceEnables multiple MCP-compatible AI clients to share persistent, versioned project knowledge across sessions with conflict-safe updates, provenance, hybrid retrieval, stale-memory handling, and context-budgeted recall.MIT
- AlicenseNot gradedqualityCmaintenanceProvides persistent, shared memory for AI agents via MCP, enabling retrieval of relevant memories instead of loading entire context. Allows agents across projects, machines, and tools to share a single auditable, markdown-native brain.4AGPL 3.0
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to maintain Git-backed long-term project memory over MCP, supporting on-demand retrieval, curated writes, conflict-safe versioning, idempotency, and crash recovery so new sessions or machines can recover context and continue working.6Apache 2.0