{"id":48727008,"url":"https://github.com/janbjorge/rekal","last_synced_at":"2026-04-19T14:07:09.945Z","repository":{"id":350746652,"uuid":"1208114351","full_name":"janbjorge/rekal","owner":"janbjorge","description":"Long-term memory for LLMs. MCP server backed by hybrid search in a single SQLite file.","archived":false,"fork":false,"pushed_at":"2026-04-19T12:38:56.000Z","size":236,"stargazers_count":41,"open_issues_count":0,"forks_count":4,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T13:24:07.882Z","etag":null,"topics":["llm","mcp","mcp-server","memory","python","sqlite","vector-search"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/rekal/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/janbjorge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-11T20:51:41.000Z","updated_at":"2026-04-19T12:38:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/janbjorge/rekal","commit_stats":null,"previous_names":["janbjorge/rekal"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/janbjorge/rekal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janbjorge%2Frekal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janbjorge%2Frekal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janbjorge%2Frekal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janbjorge%2Frekal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/janbjorge","download_url":"https://codeload.github.com/janbjorge/rekal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/janbjorge%2Frekal/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32009243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T20:23:30.271Z","status":"online","status_checked_at":"2026-04-19T02:00:07.110Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["llm","mcp","mcp-server","memory","python","sqlite","vector-search"],"created_at":"2026-04-11T23:07:59.831Z","updated_at":"2026-04-19T14:07:09.936Z","avatar_url":"https://github.com/janbjorge.png","language":"Python","readme":"# rekal\n\n**Long-term memory for LLMs. One SQLite file, no cloud, no API keys.**\n\nrekal is an [MCP](https://modelcontextprotocol.io) server that gives AI coding agents persistent memory across sessions. Memories are stored locally in SQLite and retrieved with hybrid search (BM25 keywords + vector semantics + recency decay). Nothing leaves your machine.\n\nWorks with any MCP-capable agent: [Claude Code](#setup--claude-code), [Codex CLI](#setup--codex-cli), [OpenCode](#setup--opencode).\n\n```\nSession 1:   \"I prefer Ruff over Black\"  → memory_store(...)\nSession 47:  \"Set up linting\"            → memory_search(\"formatting preferences\")\n                                          ← \"User prefers Ruff over Black\" (0.92)\n                                          Sets up Ruff without asking.\n```\n\n## Install\n\n```bash\npip install rekal\n# or\nuv tool install rekal\n```\n\nRequires Python 3.11+. On first run, rekal creates `~/.rekal/memory.db`.\n\n## Setup — Claude Code\n\nThree steps: add the MCP server, install the plugin, and disable built-in memory.\n\n**1. Add the MCP server** — gives Claude Code the memory tools:\n\n```bash\nclaude mcp add rekal rekal\n```\n\n**2. Install the plugin** — teaches Claude Code when to use those tools, and prevents conflicts with built-in memory:\n\n```bash\nclaude plugin marketplace add janbjorge/rekal\nclaude plugin install rekal-skills@rekal\n```\n\n**3. Disable built-in auto memory** — add `\"autoMemoryEnabled\": false` to `~/.claude/settings.json`:\n\n```json\n{\n  \"autoMemoryEnabled\": false\n}\n```\n\n\u003e **Why is this required?** Claude Code's instruction priority is **system prompt \u003e CLAUDE.md \u003e MCP server instructions**. Built-in memory lives in the system prompt and always wins — without disabling it, the agent ignores rekal and writes to a flat file with no search, no deduplication, no ranking. The plugin's SessionStart hook replaces the context injection auto memory normally provides, so you don't lose anything.\n\u003e\n\u003e **What if I forget?** The plugin's `block-memory-writes` hook will catch and block MEMORY.md writes as a safety net, but the agent wastes turns hitting the block. Disabling auto memory is cleaner.\n\u003e\n\u003e **Can the plugin do this automatically?** No — Claude Code doesn't allow plugins to modify user settings. This manual step is the only way.\n\n### What the plugin provides\n\n**Hooks** (automatic, no user action needed):\n\n| Hook | Event | What it does |\n|------|-------|-------------|\n| session-start | `SessionStart` | Reminds agent to call `memory_build_context` before doing anything |\n| block-memory-writes | `PreToolUse` on Edit/Write | Blocks writes to MEMORY.md, redirects to rekal tools |\n\n**Skills** (user-invocable):\n\n| Skill | Trigger | What it does |\n|-------|---------|-------------|\n| `rekal-init` | `/rekal-init` | Scans codebase and bootstraps rekal with project knowledge |\n| `rekal-save` | `/rekal-save` or auto on session end | Deduplicates and stores durable knowledge from the conversation |\n| `rekal-usage` | `/rekal-usage` | Teaches agents how to use rekal effectively |\n| `rekal-hygiene` | `/rekal-hygiene` | Finds conflicts, duplicates, stale data — proposes fixes |\n\n## Setup — Codex CLI\n\nOne step. rekal is a standard MCP stdio server — no plugin system, no competing memory to disable ([Codex memories are off by default](https://developers.openai.com/codex/memories)).\n\nAdd to `~/.codex/config.toml` ([Codex MCP docs](https://developers.openai.com/codex/mcp)):\n\n```toml\n[mcp_servers.rekal]\ncommand = \"rekal\"\n\n# optional: scope all memories to a project automatically\n[mcp_servers.rekal.env]\nREKAL_PROJECT = \"my-project\"\n```\n\nInstruct the agent to call `memory_build_context` at session start. Add to your project's `AGENTS.md`:\n\n```markdown\nCall memory_build_context with your current task before exploring the codebase.\n```\n\n\u003e **If you have enabled Codex memories** (`memories = true` in `~/.codex/config.toml`): disable them to avoid competing memory instructions.\n\u003e ```toml\n\u003e [features]\n\u003e memories = false\n\u003e ```\n\n## Setup — OpenCode\n\nOne step. OpenCode has no built-in memory system — rekal plugs in cleanly with no conflicts.\n\nAdd to `opencode.jsonc` in your project root ([OpenCode MCP docs](https://opencode.ai/docs/mcp-servers/)):\n\n```jsonc\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"mcp\": {\n    \"rekal\": {\n      \"type\": \"local\",\n      \"command\": [\"rekal\"],\n      \"enabled\": true,\n      \"environment\": {\n        \"REKAL_PROJECT\": \"my-project\"\n      }\n    }\n  }\n}\n```\n\nOpenCode does **not** auto-read `AGENTS.md` — you must list instruction files explicitly ([OpenCode config docs](https://opencode.ai/docs/config/)). Add to your `opencode.jsonc`:\n\n```jsonc\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"instructions\": [\"AGENTS.md\"]\n}\n```\n\n## Tools\n\nrekal exposes 16 MCP tools grouped into four categories.\n\n**Core** — read and write memories:\n\n| Tool | Purpose |\n|------|---------|\n| `memory_store` | Store a memory with type, project, and tags |\n| `memory_search` | Hybrid search across all memories |\n| `memory_update` | Edit content, tags, or type of an existing memory |\n| `memory_delete` | Remove a memory by ID |\n\n**Smart write** — manage knowledge over time:\n\n| Tool | Purpose |\n|------|---------|\n| `memory_supersede` | Replace a memory while linking the old one as history |\n| `memory_link` | Connect memories: `supersedes`, `contradicts`, or `related_to` |\n| `memory_build_context` | One call that returns relevant memories + conflicts + timeline |\n\n**Introspection** — explore what's stored:\n\n| Tool | Purpose |\n|------|---------|\n| `memory_similar` | Find memories similar to a given one |\n| `memory_topics` | Topic summary grouped by type |\n| `memory_timeline` | Chronological view with optional date range |\n| `memory_related` | All links to and from a memory |\n| `memory_health` | Database stats: counts by type, project, date range |\n| `memory_conflicts` | Find memories that contradict each other |\n\n**Conversations** — track session threads:\n\n| Tool | Purpose |\n|------|---------|\n| `conversation_start` | Start a conversation, optionally linked to a previous one |\n| `conversation_tree` | Get the full conversation DAG |\n| `conversation_threads` | List recent conversations with memory counts |\n| `conversation_stale` | Find inactive conversations |\n\n## How it works\n\n### Storage\n\nEverything lives in `~/.rekal/memory.db`. Three subsystems share it:\n\n- **memories table** — content, type, project, tags, timestamps, access counts\n- **FTS5 virtual table** — full-text index over content+tags+project, auto-synced via triggers\n- **sqlite-vec virtual table** — 384-dimensional vector index for semantic search\n\nMemory links (`supersedes`, `contradicts`, `related_to`) are stored in a separate table. `memory_supersede` writes the new memory and creates a `supersedes` link in a single operation — old knowledge stays queryable with explicit lineage.\n\n### Embeddings\n\nrekal uses [fastembed](https://github.com/qdrant/fastembed) with `BAAI/bge-small-en-v1.5` (384 dimensions). Runs locally via ONNX — no API calls, no network. The model downloads once on first use (~50MB) and is cached.\n\n### Search\n\nEvery `memory_search` runs two parallel lookups, merges candidates, then scores:\n\n```\nscore = w_fts × sigmoid(-BM25)                       ← keyword relevance    (default 0.4)\n      + w_vec × (1 - cosine_distance)                 ← semantic similarity  (default 0.4)\n      + w_recency × exp(-0.693 × days/half_life)      ← recency              (default 0.2, 30-day half-life)\n```\n\n**Why three signals?** Keywords miss synonyms (\"deploy\" vs \"ship to prod\"). Vectors miss exact identifiers. Recency alone buries important old knowledge. The blend covers all three failure modes.\n\n**Configurable weights.** All weights and half-life are configurable at three levels:\n\n| Priority | Source | Set by | Persists? |\n|----------|--------|--------|-----------|\n| 1 (highest) | Per-search params | `memory_search(..., w_fts=0.8)` | No — single query only |\n| 2 | Database project config | `memory_set_config(key, value, project)` | Yes — SQLite, across sessions |\n| 3 | `.rekal/config.yml` | Checked into version control | Yes — shared with team |\n| 4 (lowest) | Hardcoded defaults | Built into rekal | Always: 0.4 / 0.4 / 0.2, 30-day half-life |\n\nLayers resolve per-key independently. A `.rekal/config.yml` setting `w_fts` and a DB override for `half_life` combine — each key uses its highest-priority source.\n\n```yaml\n# .rekal/config.yml\nscoring:\n  w_fts: 0.6\n  w_vec: 0.3\n  w_recency: 0.1\n  half_life: 14.0\n```\n\n### Why SQLite?\n\n- **Single file** — copy, back up, version-control, or delete to start fresh\n- **Zero config** — no daemon, no port, no connection string\n- **FTS5 built-in** — BM25 ranking without an external search engine\n- **sqlite-vec extension** — vector search in the same process, no separate vector DB\n- **Sub-millisecond** — local disk I/O, no network round-trips\n\n## Troubleshooting — Claude Code\n\n### Agent still writes to MEMORY.md\n\n1. Check `autoMemoryEnabled` is `false` in `~/.claude/settings.json`\n2. Check the plugin is installed: `claude plugin list` should show `rekal-skills`\n\n### Agent doesn't call memory_build_context at session start\n\nThe `SessionStart` hook injects a reminder. If the agent ignores it, add to your project's `CLAUDE.md`:\n\n```markdown\nCall memory_build_context before exploring the codebase.\n```\n\n### Memories not being stored\n\nCheck the MCP server is running: `claude mcp list` should show `rekal`. If missing:\n\n```bash\nclaude mcp add rekal rekal\n```\n\n### Updating the plugin\n\nClaude Code's plugin system may serve a stale cache after `plugin install`. If hooks or skills are missing after an update, clear the marketplace cache first:\n\n```bash\nrm -rf ~/.claude/plugins/marketplaces/rekal\nclaude plugin marketplace add janbjorge/rekal\nclaude plugin install rekal-skills@rekal\n```\n\n## Architecture (for contributors)\n\n```\nPlugin (hooks + skills)\n  │\n  ├── hooks/\n  │   ├── handlers/session-start.py       ← SessionStart: inject context reminder\n  │   └── handlers/block-memory-writes.py ← PreToolUse: block MEMORY.md writes\n  │\n  └── skills/\n      ├── rekal-init/    ← /rekal-init: bootstrap project knowledge\n      ├── rekal-save/    ← /rekal-save: end-of-session capture\n      ├── rekal-usage/   ← /rekal-usage: operational guide for tools\n      └── rekal-hygiene/ ← /rekal-hygiene: maintenance\n\nMCP Server (rekal)\n  │ stdio (JSON-RPC)\n  │\n  mcp_adapter.py          ← FastMCP server, lifespan, instructions\n  │\n  ├── tools/core.py       ─┐\n  ├── tools/introspection.py│─ thin @mcp.tool() wrappers\n  ├── tools/smart_write.py  │\n  └── tools/conversations.py┘\n                            │\n                    sqlite_adapter.py ← all SQL lives here\n                            │\n                            ├── SQLite (memories, conversations, tags, conflicts)\n                            ├── FTS5 (full-text index)\n                            └── sqlite-vec (vector index)\n```\n\n**Instruction flow** (single source per concern):\n\n| What | Where | Why |\n|------|-------|-----|\n| \"Use rekal tools, not MEMORY.md\" | MCP server instructions + PreToolUse hook | Instructions guide, hook enforces |\n| \"Call memory_build_context first\" | SessionStart hook | Automatic, every session |\n| \"How to store/search/supersede\" | MCP server instructions | Always present next to the tools |\n| \"Capture session knowledge\" | rekal-save skill | Explicit trigger, detailed procedure |\n| \"Bootstrap project\" | rekal-init skill | Explicit trigger |\n| \"Clean up database\" | rekal-hygiene skill | Explicit trigger |\n\n## CLI\n\n```bash\nrekal serve    # Run as MCP server (default)\nrekal health   # Database health report\nrekal export   # Export all memories as JSON\n```\n\n## License\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanbjorge%2Frekal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjanbjorge%2Frekal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjanbjorge%2Frekal/lists"}