Long-term memory MCP server for LLMs — hybrid search in a single SQLite file
Project description
rekal
Long-term memory for LLMs. One SQLite file, no cloud, no API keys.
You tell your LLM you prefer Ruff, that deploys go through tags, that the auth service lives in services/auth. Next conversation, blank slate. You repeat yourself. Again. Forever.
rekal is an MCP server that gives LLMs persistent memory. It stores what matters and retrieves it later using hybrid search (BM25 keywords + vector similarity + recency decay). Embeddings run locally via fastembed. Nothing leaves your machine.
pip install rekal
or with uv:
uv tool install rekal
Requires Python 3.11+.
Setup
1. Install and add the MCP server
pip install rekal
# or
uv tool install rekal
Then add rekal to your MCP client:
claude mcp add rekal -- rekal
For other MCP clients, add to your config JSON:
{
"mcpServers": {
"rekal": {
"command": "rekal"
}
}
}
On first run, rekal creates ~/.rekal/memory.db. That single file holds everything. Copy it to back up, drop it to start fresh.
2. (Optional) Claude Code skills
If you use Claude Code, rekal ships as a plugin with three skills for memory management. The plugin talks to the MCP server from step 1, so install that first.
/plugin marketplace add janbjorge/rekal
/plugin install rekal-skills@rekal
| Skill | Trigger | What it does |
|---|---|---|
rekal-init |
/rekal-init, or "bootstrap memory" |
Scans your codebase and bootstraps rekal with project knowledge |
rekal-usage |
/rekal-usage, or "how do I use rekal" |
Tool reference, query patterns, and workflows — teaches agents how to use rekal effectively |
rekal-save |
Auto on session end, or /rekal-save |
Reviews the conversation, deduplicates against existing memories, stores what's worth keeping |
rekal-hygiene |
/rekal-hygiene |
Finds conflicts, duplicates, and stale data. Proposes fixes for your approval, never deletes on its own |
How it works
Your LLM stores things worth remembering:
User: "I prefer Ruff over Black for formatting"
LLM: → memory_store("User prefers Ruff over Black", type="preference")
Weeks later, different conversation:
User: "Set up linting for my new project"
LLM: → memory_search("formatting linting preferences")
← "User prefers Ruff over Black" (score: 0.92)
When facts change, old versions stay linked:
LLM: → memory_supersede(old_id="mem_abc", new_content="API moved from v2 to v3")
When things contradict each other:
LLM: → memory_conflicts(project="backend")
← "use PostgreSQL for everything" contradicts "migrate analytics to ClickHouse"
Search
Three signals, blended into one score:
score = 0.4 · BM25(keyword match)
+ 0.4 · cosine(semantic similarity)
+ 0.2 · exp(-t/half_life)
"deploy auth" and "shipping the login system to pre-prod" both find the same memory. Recent stuff ranks higher, but old memories still surface when relevant.
Tools
16 tools over MCP:
Core
| Tool | Description |
|---|---|
memory_store |
Store a memory with type, project, tags, and conversation scope |
memory_search |
Hybrid search: BM25 + vector + recency in one query |
memory_update |
Update content, tags, or type (re-embeds automatically) |
memory_delete |
Delete a memory by ID |
Smart write
| Tool | Description |
|---|---|
memory_supersede |
Replace a memory while keeping the old one as history |
memory_link |
Link memories: supersedes, contradicts, related_to |
memory_build_context |
Relevant memories + conflicts + timeline for a query, in one call |
Introspection
| Tool | Description |
|---|---|
memory_similar |
Find memories similar to a given one |
memory_topics |
Topic summary grouped by type |
memory_timeline |
Chronological view with optional date range filters |
memory_related |
All links to and from a memory |
memory_health |
Database stats: counts by type, project, date range |
memory_conflicts |
Find memories that contradict each other |
Conversations
| Tool | Description |
|---|---|
conversation_start |
Start a conversation, optionally linked to a previous one |
conversation_tree |
Get the full conversation DAG |
conversation_threads |
List recent conversations with memory counts |
conversation_stale |
Find inactive conversations |
Memory types
| Type | For | Example |
|---|---|---|
fact |
Things that are true | "The API rate limit is 1000 req/min" |
preference |
How you like things | "Prefers dataclasses over hand-written __init__" |
procedure |
Steps to do something | "Deploy: git tag vX.Y.Z && git push --tags" |
context |
Current state | "Currently rewriting the payment service" |
episode |
Things that happened | "Debugged the OOM, root cause was unbounded cache" |
Architecture
One SQLite file, four components:
rekal
│
SQLite ──┬── FTS5 index ──── keyword relevance (BM25)
├── sqlite-vec ──── semantic similarity (384d vectors)
├── recency ─────── exponential decay (30-day half-life)
└── memory links ── supersedes / contradicts / related_to
Conversations form a DAG (follow-ups, branches, merges), navigable like a git log.
CLI
rekal serve # Run as MCP server (default)
rekal health # Database health report
rekal export # Export all memories as JSON
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rekal-1.5.0.tar.gz.
File metadata
- Download URL: rekal-1.5.0.tar.gz
- Upload date:
- Size: 145.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
387f2b8381a201a03684c8b334131c371e930a01e8aad872d88f298c2359baa8
|
|
| MD5 |
86740d2d36ac3cc09d84f2bc913aff6b
|
|
| BLAKE2b-256 |
e7219974a654c0a591b00451217002b24c6d4d604d0d9afe9f3602ac94db6d31
|
Provenance
The following attestation bundles were made for rekal-1.5.0.tar.gz:
Publisher:
release.yml on janbjorge/rekal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rekal-1.5.0.tar.gz -
Subject digest:
387f2b8381a201a03684c8b334131c371e930a01e8aad872d88f298c2359baa8 - Sigstore transparency entry: 1285640443
- Sigstore integration time:
-
Permalink:
janbjorge/rekal@7c4e0de077a0976e5868b8000fc91821e276013e -
Branch / Tag:
refs/tags/v1.5.0 - Owner: https://github.com/janbjorge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c4e0de077a0976e5868b8000fc91821e276013e -
Trigger Event:
push
-
Statement type:
File details
Details for the file rekal-1.5.0-py3-none-any.whl.
File metadata
- Download URL: rekal-1.5.0-py3-none-any.whl
- Upload date:
- Size: 20.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c89ecfb566af54e8e09a439fccd6f7110263d21bf9970d319fffc3a440024498
|
|
| MD5 |
bb6ce5d2a721514a9768955ff8fb8612
|
|
| BLAKE2b-256 |
30ec9fdda43e89db95c192f2e61586c2efd2944540b140ad62a8fe45f702da78
|
Provenance
The following attestation bundles were made for rekal-1.5.0-py3-none-any.whl:
Publisher:
release.yml on janbjorge/rekal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rekal-1.5.0-py3-none-any.whl -
Subject digest:
c89ecfb566af54e8e09a439fccd6f7110263d21bf9970d319fffc3a440024498 - Sigstore transparency entry: 1285640541
- Sigstore integration time:
-
Permalink:
janbjorge/rekal@7c4e0de077a0976e5868b8000fc91821e276013e -
Branch / Tag:
refs/tags/v1.5.0 - Owner: https://github.com/janbjorge
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c4e0de077a0976e5868b8000fc91821e276013e -
Trigger Event:
push
-
Statement type: