{
  "markdown": "# Kirok\n\n<!-- mcp-name: io.github.TadFuji/kirok-mcp -->\n\nEnglish | [日本語](README.ja.md)\n\n[![tests](https://github.com/TadFuji/kirok-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/TadFuji/kirok-mcp/actions/workflows/test.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org)\n[![Version 1.4.2](https://img.shields.io/badge/version-1.4.2-blue.svg)](CHANGELOG.md)\n\n**Persistent memory for AI agents, over MCP.** Kirok (記録, \"record\") is a [Model Context Protocol](https://modelcontextprotocol.io) server that gives an agent a durable, searchable memory: **Retain** what matters, **Recall** it with hybrid semantic + keyword search, and **Reflect** to distil accumulated memories into reusable insights. A background consolidation loop turns raw memories into higher-level *observations* on its own.\n\n<img src=\"https://raw.githubusercontent.com/TadFuji/kirok-mcp/main/assets/demo.svg\" alt=\"Kirok demo: retain a memory, then recall it later with hybrid search — consolidated observations shown first\" width=\"760\">\n\n\n## Why Kirok\n\nMost \"agent memory\" is either a flat vector store (recall is a bare cosine top-k, no keyword grounding, no forgetting) or a pile of markdown the agent has to re-read every turn. Kirok is a small, self-hostable server that does the retrieval engineering properly:\n\n- **Hybrid retrieval, not just vectors.** Semantic KNN and FTS5 BM25 are fused with Reciprocal Rank Fusion, so an exact keyword match and a semantic match reinforce each other instead of competing.\n- **A calibrated relevance floor.** Naive cosine thresholds don't work on real embedding distributions (see [Search quality](#-search-quality)); Kirok's floor is measured against live data, and there's an evaluation harness to keep it honest.\n- **Autonomous consolidation.** Memories are periodically synthesised into observations, and destructive LLM decisions are soft-deleted with an audit trail rather than executed blindly.\n- **Reliability first.** Atomic writes, soft deletes, startup auto-snapshots, and a fail-open background pipeline that never loses a `retain`.\n\n**Not local-first:** storage is a local SQLite file you own, but embedding and LLM inference are sent to Google's Gemini API. If everything must stay on-device, Kirok is not for you (yet).\n\n## Architecture\n\n```mermaid\nflowchart TB\n    client[\"MCP Client<br/>(Claude Desktop / Claude Code / Cursor / …)\"]\n    subgraph server[\"Kirok MCP Server (FastMCP)\"]\n        direction TB\n        tools[\"19 MCP tools<br/>Retain · Recall · Reflect · consolidate · CRUD\"]\n        pipeline[\"Hybrid search (RRF) · Smart dedup<br/>Consolidation · Auto-refresh\"]\n    end\n    subgraph storage[\"Local SQLite (WAL)\"]\n        direction LR\n        fts[\"FTS5 trigram<br/>(BM25 keyword)\"]\n        vec[\"sqlite-vec<br/>(KNN, brute-force fallback)\"]\n        tables[\"memories · observations<br/>mental_models · banks · system_events\"]\n    end\n    gemini[\"Google Gemini API<br/>gemini-embedding-001 (3072-d)<br/>gemini-2.5-flash-lite\"]\n\n    client <-->|\"stdio (JSON-RPC 2.0)\"| tools\n    tools --> pipeline\n    pipeline <--> storage\n    pipeline <-->|embeddings · entity extraction<br/>reflection · consolidation| gemini\n```\n\nStorage is a single SQLite database at `~/.kirok/memory.db`. `sqlite-vec` provides per-bank vector KNN; if the native extension can't load, Kirok falls back to a NumPy brute-force scan with identical results. See [docs/architecture.md](docs/architecture.md) for the full design.\n\n## 🚀 Quick start\n\n**Requirements:** Python 3.12+, [uv](https://docs.astral.sh/uv/) (for `uvx`), and a [Gemini API key](https://aistudio.google.com/apikey) (free tier is plenty).\n\nKirok ships on [PyPI](https://pypi.org/project/kirok-mcp/) — nothing to clone. Put your key in `~/.kirok/.env` (one line: `GEMINI_API_KEY=AIza...`), then verify the setup:\n\n```bash\nuvx --from kirok-mcp kirok-doctor   # offline sanity check\n```\n\n### Connect an MCP client\n\n**Claude Code CLI:**\n\n```bash\nclaude mcp add kirok -s user -- uvx kirok-mcp\n```\n\n**Claude Desktop** — edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\\Claude\\`):\n\n```json\n{\n  \"mcpServers\": {\n    \"kirok\": { \"command\": \"uvx\", \"args\": [\"kirok-mcp\"] }\n  }\n}\n```\n\nThen restart the client. The server reads `GEMINI_API_KEY` from `~/.kirok/.env`; an `env` block in the client config also works and takes precedence.\n\n### From source (development)\n\n```bash\ngit clone https://github.com/TadFuji/kirok-mcp.git\ncd kirok-mcp\nuv sync                       # installs deps, including sqlite-vec\ncp .env.example .env          # then put your key in it: GEMINI_API_KEY=AIza...\nuv run kirok-doctor           # offline sanity check of the whole setup\n```\n\nPoint your MCP client at the checkout with `uv run --directory /absolute/path/to/kirok-mcp kirok-mcp` instead of `uvx kirok-mcp`.\n\n> [!TIP]\n> **If `uv run` fails to launch the server** (common on Windows or cloud-synced folders — `uv run` re-syncs on every launch and can hit locked `.venv` files or an in-use entry-point `.exe`), invoke the venv's Python directly to skip the sync entirely:\n>\n> ```json\n> {\n>   \"mcpServers\": {\n>     \"kirok\": {\n>       \"command\": \"/absolute/path/to/kirok-mcp/.venv/bin/python\",\n>       \"args\": [\"-m\", \"kirok_mcp.server\"],\n>       \"env\": { \"PYTHONPATH\": \"/absolute/path/to/kirok-mcp/src\" }\n>     }\n>   }\n> }\n> ```\n>\n> On Windows use `.venv\\\\Scripts\\\\python.exe` and double-backslash paths in JSON.\n\nA bundled agent skill in [`skills/kirok/`](skills/kirok/) teaches the agent when and how to use the memory tools on its own — point your client at `skills/kirok/SKILL.md` to enable it.\n\n## 🛠️ Tools\n\n19 MCP tools. One-line summaries below; full parameter tables in [docs/tools-reference.md](docs/tools-reference.md).\n\n**Core**\n\n| Tool | Purpose |\n|------|---------|\n| `KIROK_retain` | Store a memory: entity/keyword extraction + embedding + smart ADD/UPDATE/NOOP dedup |\n| `KIROK_recall` | Hybrid semantic + keyword search (RRF), observations shown first |\n| `KIROK_reflect` | Synthesise memories into a mental model (insight), optionally auto-refreshing |\n| `KIROK_smart_retain` | Score importance (1–10) first, then retain only if it clears a threshold |\n| `KIROK_consolidate` | Manually run observation consolidation for a bank |\n\n**Memory management**\n\n| Tool | Purpose |\n|------|---------|\n| `KIROK_get_memory` / `KIROK_list_memories` | Fetch one memory / browse a bank with pagination |\n| `KIROK_update_memory` | Edit content or context (re-extracts and re-embeds on content change) |\n| `KIROK_forget` | Delete a single memory (irreversible) |\n\n**Mental models**\n\n| Tool | Purpose |\n|------|---------|\n| `KIROK_list_mental_models` / `KIROK_get_mental_model` | List / inspect insights from Reflect |\n| `KIROK_refresh_mental_model` | Re-analyse against current memories |\n| `KIROK_delete_mental_model` | Delete a mental model (irreversible) |\n\n**Banks**\n\n| Tool | Purpose |\n|------|---------|\n| `KIROK_list_banks` / `KIROK_stats` | List banks with counts / detailed per-bank stats incl. background failures |\n| `KIROK_clear_bank` | Delete a bank's memories + observations (requires `confirm=true`; previews otherwise) |\n| `KIROK_delete_bank` | Delete a bank entirely (requires `confirm=true`; previews otherwise) |\n\n**Config**\n\n| Tool | Purpose |\n|------|---------|\n| `KIROK_set_bank_config` / `KIROK_get_bank_config` | Set / view a bank's retain & observation \"missions\" (what to focus on) |\n\n## ⚙️ Configuration\n\nEverything is set via environment variables (typically in `.env`). Only `GEMINI_API_KEY` is required.\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `GEMINI_API_KEY` | — | **Required.** Google Gemini API key. |\n| `KIROK_DB_PATH` | `~/.kirok/memory.db` | SQLite database location. |\n| `KIROK_DEDUP_THRESHOLD` | `0.85` | Cosine similarity above which retain invokes the LLM dedup (ADD/UPDATE/NOOP) decision. |\n| `KIROK_RECALL_MIN_SIMILARITY` | `0.62` | Similarity floor for semantic memory hits in recall. Keyword/FTS hits are exempt. |\n| `KIROK_OBS_MIN_SIMILARITY` | `0.62` | Similarity floor for observation hits in recall. |\n| `KIROK_CONSOLIDATION_BATCH_SIZE` | `5` | Run auto-consolidation only once this many memories are pending (`1` = every retain). |\n| `KIROK_CONSOLIDATION_TIMEOUT` | `120` | Consolidation timeout, seconds. |\n| `KIROK_REFLECT_TIMEOUT` | `300` | Reflect timeout, seconds. |\n| `KIROK_AUTO_SNAPSHOT_HOURS` | `24` | Min hours between startup auto-snapshots (`0` disables). |\n| `KIROK_SNAPSHOT_KEEP` | `5` | Auto-snapshot generations to keep before rotating out the oldest. |\n\n## 🔍 Search quality\n\nRecall runs semantic KNN and FTS5 BM25 in parallel and fuses them with Reciprocal Rank Fusion (`k=60`). Short Japanese keyword queries get special handling: 1–2 character kanji/katakana tokens fall below the trigram tokenizer's 3-char window and can never `MATCH`, so they're rescued by an exact-substring `LIKE` supplement appended after the BM25 hits (hiragana-only short tokens stay excluded — function words would substring-match half a bank; tokens are OR-joined, matching the MATCH side).\n\nThree details keep the hybrid honest: each source is fetched deeper than the final page (`max(limit*3, 30)`) so RRF can promote an item ranked just outside the cut in both lists; all FTS text is NFKC-normalized on both the index and query side, so width variants (ＭＣＰ vs MCP, ﾊﾞｸﾞ vs バグ) actually match; and observations get the same hybrid treatment as memories — semantic hits floored, keyword hits floor-exempt — instead of being reachable only through the semantic floor.\n\n**The similarity floor is calibrated on real data.** A naive cosine threshold doesn't work here: on live `gemini-embedding-001` vectors the distribution is narrow — off-topic queries score **0.55–0.62** against unrelated banks while true hits score **0.66–0.73**. So the usable floor sits *just* above the off-topic ceiling, at **0.62**. Without it, an unrelated query still returns a full page of memories from any non-empty bank (context pollution); much lower and the floor filters nothing (the old hardcoded `0.4` sat below even off-topic scores). FTS keyword hits bypass the floor entirely — a literal term match is independent evidence, not a weak vector score.\n\nSearch parameters aren't tuned by vibes. [`scripts/search_eval.py`](scripts/search_eval.py) runs a golden query set through the *exact* recall pipeline the server uses (extracted as `hybrid_search_memories`, so the harness can't drift from production) and reports hit@1/hit@5/hit@k and MRR:\n\n```bash\ncp scripts/search_eval.example.json my_golden.json   # add 30–50 real cases\nuv run python scripts/search_eval.py my_golden.json --limit 10\n```\n\n## 🛡️ Reliability\n\n- **Atomic consolidation.** Every create/update embedding is generated *before* any DB write; all observation changes plus the \"consolidated\" mark commit in a single transaction. A failure at any step leaves the database exactly as it was, with the source memories still pending for a later retry — never a half-applied batch.\n- **Failures surface, never fake success.** A consolidation LLM failure raises and is recorded to `system_events` — the batch stays pending for a later retry, instead of being silently marked consolidated with nothing produced. Runs are serialized per bank, so two retains landing together cannot double-process the same batch into duplicate observations.\n- **Soft deletes with audit trail.** An observation the consolidation LLM decides to remove is stamped `deprecated_at` (excluded from search/list/stats) instead of destroyed, and a dedup UPDATE records the pre-merge content in the same transaction as the merge itself — both logged to `system_events` so a bad LLM decision is recoverable, not silent data loss.\n- **Startup auto-snapshot.** On launch, if the newest auto-snapshot is older than `KIROK_AUTO_SNAPSHOT_HOURS`, a `VACUUM INTO` + `integrity_check` snapshot is written under `~/.kirok/backups/`, keeping the newest `KIROK_SNAPSHOT_KEEP` generations. A snapshot that fails partway leaves no broken file behind, and manual backups are never rotated.\n- **Concurrency.** Connections set `PRAGMA busy_timeout=30000`, so a second MCP client waits out a busy writer instead of failing with `database is locked`.\n- **Fail-open background work.** Auto-consolidation and mental-model refresh run behind `retain` and can never fail it — errors are swallowed, recorded to `system_events`, and surfaced via `KIROK_stats` so silent degradation stays visible.\n\n## 💾 Backup & restore\n\nAll state is one SQLite file. The offline `kirok-backup` CLI needs no API key:\n\n```bash\nuv run kirok-backup snapshot        # byte-level DB copy (safe while server runs)\nuv run kirok-backup export          # portable JSON of all banks + memories + observations + models\nuv run kirok-backup import ~/.kirok/backups/kirok-export-YYYYMMDD-HHMMSS.json\n```\n\n`snapshot` and `export` write timestamped files under `~/.kirok/backups/` and refuse to overwrite. `import` runs in one transaction (all-or-nothing), skips existing IDs rather than overwriting, and rebuilds the FTS + vector indexes so search works immediately. Use `--db` to target a different database file.\n\n## 🩺 Diagnostics\n\n```bash\nuv run kirok-doctor            # offline: Python version, .env, key presence (never printed),\n                               # required modules, FTS5, sqlite-vec, DB writability\nuv run kirok-doctor --json     # machine-readable, for automation\nuv run kirok-doctor --online   # adds one live embedding call to verify Gemini connectivity\n```\n\n## 🧑‍💻 Development\n\n```bash\nuv sync\nuv run --no-sync pytest        # 164 offline tests; no API key or network needed\n```\n\nThe suite is fully offline — importing `kirok_mcp.server` is side-effect-free (the API key is checked at startup, not import) and tests swap in fake Gemini clients. CI runs the same suite on Ubuntu and Windows on every push ([.github/workflows/test.yml](.github/workflows/test.yml)). See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a PR.\n\n## 📚 Documentation\n\n- [docs/architecture.md](docs/architecture.md) — internal design, data model, consolidation engine\n- [docs/tools-reference.md](docs/tools-reference.md) — full parameter reference for all 19 tools\n- [CHANGELOG.md](CHANGELOG.md) — version history (current: 1.4.2)\n\n## 📄 License\n\nMIT — see [LICENSE](LICENSE).\n\n## Acknowledgements\n\n- [Model Context Protocol](https://modelcontextprotocol.io) and the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) (FastMCP)\n- [Google Gemini API](https://ai.google.dev/) for embeddings and LLM\n- [Mem0](https://github.com/mem0ai/mem0) — inspiration for smart deduplication and the knowledge layer\n- [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) (Cormack et al., 2009)\n",
  "bytes": 14917,
  "sha": "156d6a6c066aa5c016b4b885ab5486e0b24db035fa429784a24634bab79867b3",
  "repo_slug": "tadfuji/kirok-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_tadfuji_kirok_mcp_e2a9663d/readme"
}