kirok-mcp 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. kirok_mcp-1.4.0/.env.example +14 -0
  2. kirok_mcp-1.4.0/.github/workflows/publish.yml +40 -0
  3. kirok_mcp-1.4.0/.github/workflows/test.yml +32 -0
  4. kirok_mcp-1.4.0/.gitignore +37 -0
  5. kirok_mcp-1.4.0/CHANGELOG.md +126 -0
  6. kirok_mcp-1.4.0/CONTRIBUTING.md +78 -0
  7. kirok_mcp-1.4.0/LICENSE +21 -0
  8. kirok_mcp-1.4.0/PKG-INFO +261 -0
  9. kirok_mcp-1.4.0/README.ja.md +233 -0
  10. kirok_mcp-1.4.0/README.md +233 -0
  11. kirok_mcp-1.4.0/docs/architecture.md +229 -0
  12. kirok_mcp-1.4.0/docs/tools-reference.md +281 -0
  13. kirok_mcp-1.4.0/pyproject.toml +62 -0
  14. kirok_mcp-1.4.0/scripts/reembed.py +244 -0
  15. kirok_mcp-1.4.0/scripts/search_eval.example.json +17 -0
  16. kirok_mcp-1.4.0/scripts/search_eval.py +186 -0
  17. kirok_mcp-1.4.0/skills/kirok/SKILL.md +86 -0
  18. kirok_mcp-1.4.0/src/kirok_mcp/__init__.py +3 -0
  19. kirok_mcp-1.4.0/src/kirok_mcp/backup.py +516 -0
  20. kirok_mcp-1.4.0/src/kirok_mcp/db/__init__.py +27 -0
  21. kirok_mcp-1.4.0/src/kirok_mcp/db/banks.py +318 -0
  22. kirok_mcp-1.4.0/src/kirok_mcp/db/base.py +177 -0
  23. kirok_mcp-1.4.0/src/kirok_mcp/db/core.py +61 -0
  24. kirok_mcp-1.4.0/src/kirok_mcp/db/memories.py +356 -0
  25. kirok_mcp-1.4.0/src/kirok_mcp/db/models.py +175 -0
  26. kirok_mcp-1.4.0/src/kirok_mcp/db/observations.py +309 -0
  27. kirok_mcp-1.4.0/src/kirok_mcp/db/schema.py +547 -0
  28. kirok_mcp-1.4.0/src/kirok_mcp/db/search.py +507 -0
  29. kirok_mcp-1.4.0/src/kirok_mcp/diagnostics.py +300 -0
  30. kirok_mcp-1.4.0/src/kirok_mcp/embeddings.py +196 -0
  31. kirok_mcp-1.4.0/src/kirok_mcp/llm.py +483 -0
  32. kirok_mcp-1.4.0/src/kirok_mcp/retry.py +80 -0
  33. kirok_mcp-1.4.0/src/kirok_mcp/server.py +1303 -0
  34. kirok_mcp-1.4.0/tests/test_backup.py +393 -0
  35. kirok_mcp-1.4.0/tests/test_bank_edges.py +75 -0
  36. kirok_mcp-1.4.0/tests/test_consolidation_atomicity.py +250 -0
  37. kirok_mcp-1.4.0/tests/test_db.py +248 -0
  38. kirok_mcp-1.4.0/tests/test_diagnostics.py +105 -0
  39. kirok_mcp-1.4.0/tests/test_embedding_dim.py +14 -0
  40. kirok_mcp-1.4.0/tests/test_embeddings.py +145 -0
  41. kirok_mcp-1.4.0/tests/test_failure_log.py +116 -0
  42. kirok_mcp-1.4.0/tests/test_fts_trigram.py +267 -0
  43. kirok_mcp-1.4.0/tests/test_hybrid_improvements.py +281 -0
  44. kirok_mcp-1.4.0/tests/test_llm.py +77 -0
  45. kirok_mcp-1.4.0/tests/test_llm_retry.py +115 -0
  46. kirok_mcp-1.4.0/tests/test_retry.py +124 -0
  47. kirok_mcp-1.4.0/tests/test_search_eval.py +117 -0
  48. kirok_mcp-1.4.0/tests/test_search_quality.py +224 -0
  49. kirok_mcp-1.4.0/tests/test_server.py +684 -0
  50. kirok_mcp-1.4.0/tests/test_server_startup.py +57 -0
  51. kirok_mcp-1.4.0/tests/test_stage3.py +328 -0
  52. kirok_mcp-1.4.0/tests/test_vec_crud.py +196 -0
  53. kirok_mcp-1.4.0/tests/test_vec_load.py +45 -0
  54. kirok_mcp-1.4.0/tests/test_vec_migration.py +148 -0
  55. kirok_mcp-1.4.0/tests/test_vec_search.py +236 -0
  56. kirok_mcp-1.4.0/uv.lock +1051 -0
@@ -0,0 +1,14 @@
1
+ # Gemini API Key (required for embeddings and LLM operations)
2
+ GEMINI_API_KEY=your-api-key-here
3
+
4
+ # Database path (optional, defaults to ~/.kirok/memory.db)
5
+ # KIROK_DB_PATH=~/.kirok/memory.db
6
+
7
+ # Deduplication similarity threshold (optional, default: 0.85)
8
+ # KIROK_DEDUP_THRESHOLD=0.85
9
+
10
+ # Timeout for reflect operations in seconds (optional, default: 300)
11
+ # KIROK_REFLECT_TIMEOUT=300
12
+
13
+ # Timeout for consolidation operations in seconds (optional, default: 120)
14
+ # KIROK_CONSOLIDATION_TIMEOUT=120
@@ -0,0 +1,40 @@
1
+ name: publish
2
+
3
+ # Publish to PyPI via Trusted Publishing (OIDC) β€” no long-lived token stored
4
+ # in the repo. Runs when a GitHub Release is published, or manually.
5
+ on:
6
+ release:
7
+ types: [published]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v5
16
+ - name: Run the offline test suite
17
+ run: |
18
+ uv sync
19
+ uv run --no-sync pytest -q
20
+ - name: Build sdist and wheel
21
+ run: uv build
22
+ - uses: actions/upload-artifact@v4
23
+ with:
24
+ name: dist
25
+ path: dist/
26
+
27
+ publish:
28
+ needs: build
29
+ runs-on: ubuntu-latest
30
+ environment:
31
+ name: pypi
32
+ url: https://pypi.org/p/kirok-mcp
33
+ permissions:
34
+ id-token: write
35
+ steps:
36
+ - uses: actions/download-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,32 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os: [ubuntu-latest, windows-latest]
14
+ runs-on: ${{ matrix.os }}
15
+ # Tests run fully offline and need no GEMINI_API_KEY: importing
16
+ # kirok_mcp.server is side-effect-free (the key is checked at startup,
17
+ # not import) and tests swap in fake clients.
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+
21
+ - name: Install uv
22
+ # setup-uv publishes no floating "v8" major tag (unlike v7 and earlier);
23
+ # pin the exact release.
24
+ uses: astral-sh/setup-uv@v8.2.0
25
+ with:
26
+ enable-cache: true
27
+
28
+ - name: Install dependencies
29
+ run: uv sync
30
+
31
+ - name: Run tests
32
+ run: uv run pytest tests/ -v
@@ -0,0 +1,37 @@
1
+ # Environment & Secrets
2
+ .env
3
+ .env.local
4
+ .env.*.local
5
+
6
+ # Database
7
+ *.db
8
+ *.db-wal
9
+ *.db-shm
10
+
11
+ # Python
12
+ __pycache__/
13
+ *.pyc
14
+ *.pyo
15
+ *.egg-info/
16
+ dist/
17
+ build/
18
+ .venv/
19
+ venv/
20
+ .pytest_cache/
21
+
22
+ # Editor
23
+ .vscode/
24
+ .idea/
25
+ *.swp
26
+ *.swo
27
+
28
+ # OS
29
+ .DS_Store
30
+ Thumbs.db
31
+
32
+ # AI agent handoff files
33
+ AI_HANDOFF.md
34
+
35
+ # Internal planning/spec artifacts (kept local, not for downstream consumers)
36
+ docs/superpowers/
37
+ archive/
@@ -0,0 +1,126 @@
1
+ # Changelog
2
+
3
+ All notable changes to Kirok will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.4.0] - 2026-07-19
11
+
12
+ ### Fixed
13
+ - **A consolidation LLM failure can no longer silently retire memories.** `LLMClient.consolidate` used to swallow every error (API failure after retries, unparseable response) and return an empty action list; the consolidation engine then marked the whole batch `consolidated_at` with zero observations produced and no failure recorded β€” those memories were permanently skipped. It now raises: the auto path records an `auto_consolidation` failure (visible in `KIROK_stats`) and leaves the batch pending for the next run; manual `KIROK_consolidate` reports the failure and records `manual_consolidation`
14
+ - **Concurrent retains can no longer double-consolidate the same batch.** Two retains passing the debounce together could both read the same unconsolidated memories and produce near-duplicate observations. Consolidation is now serialized per bank with an `asyncio.Lock`; the second run re-reads the (now empty) pending set and exits
15
+ - **The dedup merge and its audit event now commit as one transaction.** Previously the merged content committed first and the `memory_dedup_update` audit row (holding the pre-merge content) committed separately β€” a crash between the two persisted the merge with no recoverable trace of the original, violating the audit guarantee
16
+ - A failure in the consolidation-debounce count query is now recorded to `system_events` (`consolidation_count`) instead of only being logged, matching every other background failure
17
+ - `scripts/search_eval.py`: `--limit 5` no longer computes hit@5 twice, and `--limit 3` no longer prints a hit@5 column that was really hit@3; its docstring now states that it measures the memory fusion layer, not the full observation-aware recall reply
18
+
19
+ ### Changed
20
+ - **Observations are now reachable by keyword.** The `fts_observations` index was written on every insert/update but never queried β€” consolidated knowledge was only findable through the semantic floor, so an observation whose cosine landed at e.g. 0.60 was invisible even on an exact keyword match. `KIROK_recall` now runs a keyword search (BM25 + short-CJK LIKE rescue, floor-exempt like the memory side) and fuses it with the semantic hits via RRF
21
+ - **FTS text is NFKC-normalized on both the index and the query side**, so width variants now match: full-width ASCII (οΌ­οΌ£οΌ° vs MCP) and half-width katakana (バグ vs バグ) were distinct code points that could never MATCH or LIKE each other. Existing databases rebuild their FTS tables once on the next startup (gated by `PRAGMA user_version`); source-table content is stored untouched
22
+ - **RRF now fuses deeper candidate pools** (`max(limit*3, 30)` per source instead of `limit`): an item ranked just outside `limit` in both sources β€” a classic true hit that fusion exists to promote β€” was previously truncated out of both lists before RRF could see it
23
+ - **The short-CJK LIKE rescue is now OR-joined across tokens**, matching the OR-joined FTS MATCH semantics. Previously it required every short token to appear (`京都 ε€§ι˜ͺ` matched only rows containing both), while 3+ char queries used OR β€” an inconsistency that hid partial matches
24
+ - `vec_search` no longer fetches and deserializes each hit's embedding BLOB (top_k Γ— 3072 floats per call) β€” no consumer of its results reads it
25
+ - New partial index `idx_memories_unconsolidated` makes the per-retain consolidation-debounce count and `get_unconsolidated_memories` index-only instead of scanning the bank's rows
26
+ - `set_bank_config` uses an upsert (`ON CONFLICT DO UPDATE`) instead of SELECT-then-INSERT, removing a cross-process race on first config write
27
+ - `KIROK_retain`'s dedup-UPDATE path now runs its re-extraction and re-embedding concurrently (`asyncio.gather`), like the ADD path already did
28
+
29
+ ### Removed
30
+ - `MemoryDB.search_by_timestamp` β€” dead code since 1.3.0 made time-filtered recall filter FTS hits directly; nothing called it
31
+
32
+ ## [1.3.0] - 2026-07-10
33
+
34
+ ### Changed
35
+ - **Short Japanese keyword queries now work**: 1-2 character kanji/katakana tokens (京都, 会議, バグ β€” below the trigram tokenizer's 3-char window, so they could never MATCH) are now served by an exact-substring LIKE supplement over the FTS text, appended after the BM25-ranked hits. Hiragana-only short tokens stay excluded (function words would substring-match half the bank). Previously these queries silently degraded hybrid search to semantic-only. The rescue now also runs whenever FTS already filled the results with 3+ char matches, instead of only when FTS came back short β€” a genuine short-token hit could otherwise be dropped
36
+ - `KIROK_clear_bank` and `KIROK_delete_bank` now require `confirm=true`. Without it they change nothing and return a preview of what would be deleted β€” a single mistaken tool call can no longer wipe a bank
37
+ - **FTS5 multi-word queries now use OR instead of implicit AND**: a document matching any query token is a hit, with BM25 naturally ranking documents that match more tokens higher β€” instead of requiring every token to match, which silently hid partial keyword overlaps
38
+ - FTS hits now carry `timestamp` and `entities` (joined from `memories`), fixing the `[unknown]` placeholder that FTS-only recall results used to show and letting time-filtered recall filter FTS hits by their real timestamp directly, instead of intersecting with a separate `search_by_timestamp` call capped at `limit * 2` β€” which could silently drop in-range but relatively old FTS hits
39
+ - RRF merging (`reciprocal_rank_fusion`) now merges matching items field-by-field instead of letting one list's dict fully overwrite the other's, so an FTS-only hit keeps its semantic metadata and vice versa
40
+ - `vec_search`'s unused `candidate_multiplier` parameter is removed (per-bank KNN needs no over-fetch window; it stopped affecting results after the 1.1.0 per-bank partitioning)
41
+ - `KIROK_retain`'s embedding and entity-extraction calls now run concurrently (`asyncio.gather`) instead of sequentially, cutting retain latency
42
+ - `KIROK_recall` no longer lists an observation's own source memories under "Supporting Memories" β€” they were duplicated there and double-counted against `limit`
43
+ - `kirok-doctor` gains an `--online` flag that adds one live embedding call to verify Gemini connectivity; default (offline) behavior is unchanged
44
+
45
+ ### Added
46
+ - `scripts/search_eval.py` (+ `search_eval.example.json`): measures recall quality (hit@1/5/k, MRR) against a golden query set, through the exact recall pipeline the server uses (extracted as `hybrid_search_memories`). This is the yardstick for tuning search parameters β€” before it, no search change could be shown to help or hurt
47
+ - SQLite connections now set `PRAGMA busy_timeout=30000` (30s), so concurrent MCP client instances wait out a busy writer instead of failing immediately with `database is locked`
48
+ - Consolidation is now atomic: every create/update embedding is generated before any database write, and all observation changes plus `mark_memories_consolidated` commit as a single transaction. A failure (embedding or DB) leaves the database exactly as it was, with the source memories still unconsolidated for a later retry β€” instead of a partially-applied batch
49
+ - Observations are now soft-deleted: consolidation's LLM-decided deletes stamp a new `observations.deprecated_at` column instead of removing the row (idempotent migration for existing databases). Deprecated observations are excluded from search, listing, and stats, but the row and an `observation_deprecated` audit event (in `system_events`) survive for recovery
50
+ - Startup auto-snapshot: on server startup, a `VACUUM INTO` snapshot of the live database is taken to `~/.kirok/backups/memory-auto-*.db` if the newest existing auto-snapshot is older than `KIROK_AUTO_SNAPSHOT_HOURS` (default 24; `0` disables it), rotating to keep the newest `KIROK_SNAPSHOT_KEEP` (default 5). Only auto-snapshot files are ever rotated β€” manual `kirok-backup snapshot`/`export` files are untouched
51
+ - A snapshot that fails partway (VACUUM INTO error or integrity check failure) no longer leaves a broken output file behind
52
+ - JSON export/import now reads every table inside a single transaction (consistent cross-table snapshot even under concurrent WAL writers) and carries `deprecated_at` for observations; importing an older export without that field still works
53
+ - A similarity floor for recall: `KIROK_RECALL_MIN_SIMILARITY` (default `0.62`) drops semantic (vector) memory hits below this cosine similarity, and `KIROK_OBS_MIN_SIMILARITY` (default `0.62`, replacing a hardcoded `0.4` that was below even off-topic scores) does the same for observations. FTS/keyword hits are exempt β€” a literal term match is independent evidence. 0.62 is calibrated on live `gemini-embedding-001` data: off-topic queries score 0.55-0.62 against unrelated banks, true hits score 0.66-0.73
54
+ - Embedding calls now guard against dimension drift: a returned vector whose length doesn't match `EMBEDDING_DIM` (3072) raises `ValueError` instead of being silently stored
55
+ - `KIROK_stats` now reports `API calls this session: embeddings=N, llm=M`, a session-lifetime counter on the embedding/LLM clients (resets on restart)
56
+ - New offline tests: `test_llm_retry.py`, `test_search_eval.py`, `test_consolidation_atomicity.py`, `test_search_quality.py`, `test_stage3.py`, short-CJK rescue and confirm-guard cases
57
+
58
+ ### Fixed
59
+ - `consolidate`, `evaluate_importance`, and `deduplicate` now actually retry transient Gemini failures (5xx/429/network). The 1.2.0 notes claimed all LLM calls retried, but these three fell straight to their fail-open defaults on a single transient error β€” e.g. a duplicate memory stored because deduplication "failed"
60
+ - Deduplication's UPDATE path now records the pre-merge content as a `memory_dedup_update` audit event before overwriting it, so a bad LLM merge can be reconstructed instead of silently erasing the original memory
61
+ - `observation_deprecated` and `memory_dedup_update` audit events no longer show up in `KIROK_stats`'s background-failure list β€” they are an audit trail, not failures, and were crowding out real background failures
62
+
63
+ ## [1.2.0] - 2026-07-03
64
+
65
+ ### Changed
66
+ - Importing `kirok_mcp.server` is now side-effect-free: the database connection and Gemini clients are created at server startup (`main()`), and the `GEMINI_API_KEY` check moved from import time to startup β€” importing the module (tests, tooling) no longer opens the database or exits the process
67
+ - CI no longer needs a dummy `GEMINI_API_KEY`
68
+ - `db.py` (2000 lines) is now the `kirok_mcp.db` package, split by domain (schema, memories, search, observations, models, banks) and composed into the unchanged `MemoryDB` facade; the import surface is identical
69
+ - The six copies of the vec-index sync (delete-then-insert with size guard) are consolidated into one `_sync_vec_row` helper
70
+ - `KIROK_stats` now reports recent background failures (see Added)
71
+ - Embedding and LLM calls now use the async Gemini client (`client.aio`) instead of blocking calls, so concurrent requests overlap and `KIROK_REFLECT_TIMEOUT` / `KIROK_CONSOLIDATION_TIMEOUT` actually take effect (they previously could not interrupt a blocking SDK call)
72
+ - Auto-consolidation is now debounced: it runs once at least `KIROK_CONSOLIDATION_BATCH_SIZE` (default 5) memories are pending, instead of after every retain β€” cutting `KIROK_retain` latency and Gemini token usage. The consolidation report is no longer appended to the retain reply
73
+ - `KIROK_recall` output is compact by default (content + ID only); pass `verbose=true` to include RRF/Sim relevance scores
74
+ - **Japanese keyword search**: FTS5 now uses the `trigram` tokenizer instead of the default (which split CJK into single characters, leaving BM25 ineffective for Japanese). Existing databases rebuild their FTS index automatically on the next connect (rebuilt from the source tables, in a transaction that rolls back on failure). Queries shorter than 3 characters are served by semantic search instead (trigram cannot index them)
75
+ - **Embeddings use asymmetric task types**: stored content uses `RETRIEVAL_DOCUMENT`, search queries use `RETRIEVAL_QUERY`, improving retrieval relevance. Dimension stays 3072 (pre-normalized). Existing data must be re-embedded once via `scripts/reembed.py` to gain the benefit
76
+ - Observation recall now uses a per-bank sqlite-vec `vec0` KNN (`vec_observations`) with transparent brute-force fallback, mirroring memory search
77
+ - Time-range `KIROK_recall` now filters by timestamp in SQL before scoring, instead of loading the whole bank into memory
78
+ - `KIROK_stats` counts observations and unconsolidated memories via `COUNT(*)` (accurate beyond 1000 rows; previously capped)
79
+ - `KIROK_update_memory` now re-extracts entities/keywords under the bank's `retain_mission`, consistent with the retain pipeline
80
+ - Gemini embedding and LLM calls retry transient failures (5xx, 429, network) with bounded exponential backoff; structured errors (auth/bad-request) still fail fast
81
+ - `semantic_search` is vectorized (single numpy matrix op) for faster brute-force/observation/time-range scoring
82
+
83
+ ### Added
84
+ - Background failures (auto-consolidation, mental-model auto-refresh) are now recorded in a capped `system_events` table and surfaced by `KIROK_stats`, instead of being visible only in server logs
85
+ - `kirok-backup` command: `snapshot` (verified `VACUUM INTO` copy of the live database), `export` (portable JSON of all banks, embeddings included), and `import` (transactional restore that skips existing IDs and rebuilds FTS/vector indexes). Fully offline β€” no API key required
86
+ - `KIROK_CONSOLIDATION_BATCH_SIZE` environment variable (default 5) to tune auto-consolidation debouncing (set to 1 to restore per-retain consolidation)
87
+ - `verbose` parameter on `KIROK_recall`
88
+ - `MemoryDB.count_unconsolidated_memories` helper
89
+ - `scripts/reembed.py`: idempotent, resumable re-embedding of all memories and observations (with `--dry-run`, backup-required gate, and a post-run integrity check)
90
+ - `kirok-doctor` now checks that the sqlite-vec extension loads (the actual KNN backend), not just FTS5
91
+ - `vec_observations` table plus `MemoryDB.vec_search_observations`, `get_embeddings_in_range`, `update_memory_embedding`, and `update_observation_embedding` helpers
92
+ - New offline tests: `test_retry.py`, `test_fts_trigram.py`, `test_server_startup.py` (import is side-effect-free; missing API key fails at startup), and observation-vector / task-type / stats cases
93
+
94
+ ### Fixed
95
+ - `KIROK_smart_retain` now honors `threshold` values below 5. Previously a hardcoded `score >= 5` floor in importance evaluation silently overrode lower thresholds, so `threshold=3` could never retain a score-3/4 item (e.g. a subtle preference)
96
+ - `clear_bank`, `delete_bank`, `delete_observation`, and `clear_observations` now keep the observation vector index in sync
97
+
98
+ ## [1.1.0] - 2026-06-06
99
+
100
+ ### Added
101
+ - Per-bank sqlite-vec `vec0` KNN for memory semantic search, with transparent fallback to brute-force cosine when the native extension is unavailable
102
+ - `sqlite-vec` runtime dependency, an `EMBEDDING_DIM = 3072` constant, and a startup migration that backfills and reconciles the `vec_memories` table from stored embeddings
103
+ - Offline setup diagnostics via the new `kirok-doctor` command
104
+ - `auto_refresh` and `source_query` options for `KIROK_reflect` mental models
105
+ - Test suite covering db, server, diagnostics, embeddings, and vector search (`test_vec_*`); `pytest` dev dependency
106
+
107
+ ### Changed
108
+ - `KIROK_smart_retain` now routes through the shared retain pipeline
109
+ - Bank clear/delete logic refactored to share a `_delete_bank_data` helper
110
+
111
+ ### Fixed
112
+ - Clearing or deleting a bank now consistently removes its observations, FTS index rows, and bank configuration
113
+
114
+ ## [1.0.0] - 2026-04-09
115
+
116
+ ### Added
117
+ - **Core Memory Operations**: Retain, Recall, Reflect β€” the three pillars of agent memory
118
+ - **Smart Deduplication**: Mem0-inspired ADD/UPDATE/NOOP pipeline with configurable similarity threshold
119
+ - **Hybrid Search**: Semantic (cosine similarity) + Keyword (FTS5 BM25) merged via Reciprocal Rank Fusion
120
+ - **Observation Consolidation**: Autonomous pattern extraction from accumulated memories
121
+ - **Mental Models**: LLM-generated insights with optional auto-refresh
122
+ - **Smart Retain**: Importance-scored ingestion for bulk/automatic content
123
+ - **Bank Configuration**: Per-bank retain and observations missions
124
+ - **19 MCP tools**: Full CRUD for memories, mental models, observations, and bank management
125
+ - **FTS5 query sanitization**: Safe handling of special characters in search queries
126
+ - **MIT License**: Open-source under the MIT License
@@ -0,0 +1,78 @@
1
+ # Contributing to Kirok
2
+
3
+ Thank you for your interest in contributing to Kirok! This document provides guidelines for contributing to the project.
4
+
5
+ ## Getting Started
6
+
7
+ 1. **Fork** the repository
8
+ 2. **Clone** your fork:
9
+ ```bash
10
+ git clone https://github.com/YOUR_USERNAME/kirok-mcp.git
11
+ cd kirok-mcp
12
+ ```
13
+ 3. **Install** dependencies with [uv](https://docs.astral.sh/uv/):
14
+ ```bash
15
+ uv sync
16
+ ```
17
+ 4. **Set up** your environment:
18
+ ```bash
19
+ cp .env.example .env
20
+ # Edit .env and add your GEMINI_API_KEY
21
+ ```
22
+
23
+ ## Development Workflow
24
+
25
+ ### Making Changes
26
+
27
+ 1. Create a new branch for your feature or fix:
28
+ ```bash
29
+ git checkout -b feature/your-feature-name
30
+ ```
31
+ 2. Make your changes in the `src/kirok_mcp/` directory
32
+ 3. Test your changes locally by running the MCP server
33
+ 4. Commit your changes with clear, descriptive messages
34
+
35
+ ### Code Style
36
+
37
+ - **Python 3.12+** β€” use modern Python features (type hints, `|` union, etc.)
38
+ - **Docstrings** β€” all public functions must have docstrings
39
+ - **Logging** β€” use `logging.getLogger("kirok.module_name")` for all log output
40
+ - **Error handling** β€” fail gracefully, never crash the MCP server
41
+
42
+ ### Running Tests
43
+
44
+ Run the full test suite (unit tests plus the pytest-style vector-search tests) with:
45
+
46
+ ```bash
47
+ uv run pytest
48
+ ```
49
+
50
+ The `sqlite-vec` tests skip automatically on platforms where the native
51
+ extension can't load β€” the brute-force fallback path is exercised separately.
52
+
53
+ ### Commit Messages
54
+
55
+ Follow [Conventional Commits](https://www.conventionalcommits.org/):
56
+
57
+ ```
58
+ feat: add new tool for memory tagging
59
+ fix: handle empty query in recall
60
+ docs: update architecture diagram
61
+ refactor: simplify deduplication logic
62
+ ```
63
+
64
+ ## Pull Requests
65
+
66
+ 1. Ensure your code follows the style guidelines above
67
+ 2. Update documentation if you've changed any public interfaces
68
+ 3. Add an entry to `CHANGELOG.md` under `[Unreleased]`
69
+ 4. Submit your PR with a clear description of the changes
70
+
71
+ ## Reporting Issues
72
+
73
+ - Use GitHub Issues for bug reports and feature requests
74
+ - Include your Python version, OS, and steps to reproduce
75
+
76
+ ## License
77
+
78
+ By contributing to Kirok, you agree that your contributions will be licensed under the MIT License.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tadahiro Fujikawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,261 @@
1
+ Metadata-Version: 2.4
2
+ Name: kirok-mcp
3
+ Version: 1.4.0
4
+ Summary: Persistent memory for AI agents β€” Retain, Recall, Reflect via MCP
5
+ Project-URL: Homepage, https://github.com/TadFuji/kirok-mcp
6
+ Project-URL: Repository, https://github.com/TadFuji/kirok-mcp
7
+ Project-URL: Changelog, https://github.com/TadFuji/kirok-mcp/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/TadFuji/kirok-mcp/issues
9
+ Author: Tadahiro Fujikawa
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,ai,embeddings,gemini,mcp,memory,semantic-search
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: google-genai>=1.0.0
22
+ Requires-Dist: mcp[cli]>=1.0.0
23
+ Requires-Dist: numpy>=1.26.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: python-dotenv>=1.0.0
26
+ Requires-Dist: sqlite-vec>=0.1.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Kirok
30
+
31
+ English | [ζ—₯本θͺž](README.ja.md)
32
+
33
+ [![tests](https://github.com/TadFuji/kirok-mcp/actions/workflows/test.yml/badge.svg)](https://github.com/TadFuji/kirok-mcp/actions/workflows/test.yml)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
35
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org)
36
+ [![Version 1.4.0](https://img.shields.io/badge/version-1.4.0-blue.svg)](CHANGELOG.md)
37
+
38
+ **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.
39
+
40
+ ## Why Kirok
41
+
42
+ Most "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:
43
+
44
+ - **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.
45
+ - **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.
46
+ - **Autonomous consolidation.** Memories are periodically synthesised into observations, and destructive LLM decisions are soft-deleted with an audit trail rather than executed blindly.
47
+ - **Reliability first.** Atomic writes, soft deletes, startup auto-snapshots, and a fail-open background pipeline that never loses a `retain`.
48
+
49
+ **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).
50
+
51
+ ## Architecture
52
+
53
+ ```mermaid
54
+ flowchart TB
55
+ client["MCP Client<br/>(Claude Desktop / Claude Code / Cursor / …)"]
56
+ subgraph server["Kirok MCP Server (FastMCP)"]
57
+ direction TB
58
+ tools["19 MCP tools<br/>Retain Β· Recall Β· Reflect Β· consolidate Β· CRUD"]
59
+ pipeline["Hybrid search (RRF) Β· Smart dedup<br/>Consolidation Β· Auto-refresh"]
60
+ end
61
+ subgraph storage["Local SQLite (WAL)"]
62
+ direction LR
63
+ fts["FTS5 trigram<br/>(BM25 keyword)"]
64
+ vec["sqlite-vec<br/>(KNN, brute-force fallback)"]
65
+ tables["memories Β· observations<br/>mental_models Β· banks Β· system_events"]
66
+ end
67
+ gemini["Google Gemini API<br/>gemini-embedding-001 (3072-d)<br/>gemini-2.5-flash-lite"]
68
+
69
+ client <-->|"stdio (JSON-RPC 2.0)"| tools
70
+ tools --> pipeline
71
+ pipeline <--> storage
72
+ pipeline <-->|embeddings Β· entity extraction<br/>reflection Β· consolidation| gemini
73
+ ```
74
+
75
+ Storage 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.
76
+
77
+ ## πŸš€ Quick start
78
+
79
+ **Requirements:** Python 3.12+, [uv](https://docs.astral.sh/uv/), and a [Gemini API key](https://aistudio.google.com/apikey) (free tier is plenty).
80
+
81
+ ```bash
82
+ git clone https://github.com/TadFuji/kirok-mcp.git
83
+ cd kirok-mcp
84
+ uv sync # installs deps, including sqlite-vec
85
+ cp .env.example .env # then put your key in it: GEMINI_API_KEY=AIza...
86
+ uv run kirok-doctor # offline sanity check of the whole setup
87
+ ```
88
+
89
+ ### Connect an MCP client
90
+
91
+ **Claude Desktop** β€” edit `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`):
92
+
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "kirok": {
97
+ "command": "uv",
98
+ "args": ["run", "--directory", "/absolute/path/to/kirok-mcp", "kirok-mcp"]
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ **Claude Code CLI:**
105
+
106
+ ```bash
107
+ claude mcp add kirok -s user -- uv run --directory /absolute/path/to/kirok-mcp kirok-mcp
108
+ ```
109
+
110
+ Then restart the client. `GEMINI_API_KEY` is read from `.env`, so it need not go in the config.
111
+
112
+ > [!TIP]
113
+ > **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:
114
+ >
115
+ > ```json
116
+ > {
117
+ > "mcpServers": {
118
+ > "kirok": {
119
+ > "command": "/absolute/path/to/kirok-mcp/.venv/bin/python",
120
+ > "args": ["-m", "kirok_mcp.server"],
121
+ > "env": { "PYTHONPATH": "/absolute/path/to/kirok-mcp/src" }
122
+ > }
123
+ > }
124
+ > }
125
+ > ```
126
+ >
127
+ > On Windows use `.venv\\Scripts\\python.exe` and double-backslash paths in JSON.
128
+
129
+ A 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.
130
+
131
+ ## πŸ› οΈ Tools
132
+
133
+ 19 MCP tools. One-line summaries below; full parameter tables in [docs/tools-reference.md](docs/tools-reference.md).
134
+
135
+ **Core**
136
+
137
+ | Tool | Purpose |
138
+ |------|---------|
139
+ | `KIROK_retain` | Store a memory: entity/keyword extraction + embedding + smart ADD/UPDATE/NOOP dedup |
140
+ | `KIROK_recall` | Hybrid semantic + keyword search (RRF), observations shown first |
141
+ | `KIROK_reflect` | Synthesise memories into a mental model (insight), optionally auto-refreshing |
142
+ | `KIROK_smart_retain` | Score importance (1–10) first, then retain only if it clears a threshold |
143
+ | `KIROK_consolidate` | Manually run observation consolidation for a bank |
144
+
145
+ **Memory management**
146
+
147
+ | Tool | Purpose |
148
+ |------|---------|
149
+ | `KIROK_get_memory` / `KIROK_list_memories` | Fetch one memory / browse a bank with pagination |
150
+ | `KIROK_update_memory` | Edit content or context (re-extracts and re-embeds on content change) |
151
+ | `KIROK_forget` | Delete a single memory (irreversible) |
152
+
153
+ **Mental models**
154
+
155
+ | Tool | Purpose |
156
+ |------|---------|
157
+ | `KIROK_list_mental_models` / `KIROK_get_mental_model` | List / inspect insights from Reflect |
158
+ | `KIROK_refresh_mental_model` | Re-analyse against current memories |
159
+ | `KIROK_delete_mental_model` | Delete a mental model (irreversible) |
160
+
161
+ **Banks**
162
+
163
+ | Tool | Purpose |
164
+ |------|---------|
165
+ | `KIROK_list_banks` / `KIROK_stats` | List banks with counts / detailed per-bank stats incl. background failures |
166
+ | `KIROK_clear_bank` | Delete a bank's memories + observations (requires `confirm=true`; previews otherwise) |
167
+ | `KIROK_delete_bank` | Delete a bank entirely (requires `confirm=true`; previews otherwise) |
168
+
169
+ **Config**
170
+
171
+ | Tool | Purpose |
172
+ |------|---------|
173
+ | `KIROK_set_bank_config` / `KIROK_get_bank_config` | Set / view a bank's retain & observation "missions" (what to focus on) |
174
+
175
+ ## βš™οΈ Configuration
176
+
177
+ Everything is set via environment variables (typically in `.env`). Only `GEMINI_API_KEY` is required.
178
+
179
+ | Variable | Default | Description |
180
+ |----------|---------|-------------|
181
+ | `GEMINI_API_KEY` | β€” | **Required.** Google Gemini API key. |
182
+ | `KIROK_DB_PATH` | `~/.kirok/memory.db` | SQLite database location. |
183
+ | `KIROK_DEDUP_THRESHOLD` | `0.85` | Cosine similarity above which retain invokes the LLM dedup (ADD/UPDATE/NOOP) decision. |
184
+ | `KIROK_RECALL_MIN_SIMILARITY` | `0.62` | Similarity floor for semantic memory hits in recall. Keyword/FTS hits are exempt. |
185
+ | `KIROK_OBS_MIN_SIMILARITY` | `0.62` | Similarity floor for observation hits in recall. |
186
+ | `KIROK_CONSOLIDATION_BATCH_SIZE` | `5` | Run auto-consolidation only once this many memories are pending (`1` = every retain). |
187
+ | `KIROK_CONSOLIDATION_TIMEOUT` | `120` | Consolidation timeout, seconds. |
188
+ | `KIROK_REFLECT_TIMEOUT` | `300` | Reflect timeout, seconds. |
189
+ | `KIROK_AUTO_SNAPSHOT_HOURS` | `24` | Min hours between startup auto-snapshots (`0` disables). |
190
+ | `KIROK_SNAPSHOT_KEEP` | `5` | Auto-snapshot generations to keep before rotating out the oldest. |
191
+
192
+ ## πŸ” Search quality
193
+
194
+ Recall 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).
195
+
196
+ Three 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.
197
+
198
+ **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.
199
+
200
+ Search 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:
201
+
202
+ ```bash
203
+ cp scripts/search_eval.example.json my_golden.json # add 30–50 real cases
204
+ uv run python scripts/search_eval.py my_golden.json --limit 10
205
+ ```
206
+
207
+ ## πŸ›‘οΈ Reliability
208
+
209
+ - **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.
210
+ - **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.
211
+ - **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.
212
+ - **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.
213
+ - **Concurrency.** Connections set `PRAGMA busy_timeout=30000`, so a second MCP client waits out a busy writer instead of failing with `database is locked`.
214
+ - **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.
215
+
216
+ ## πŸ’Ύ Backup & restore
217
+
218
+ All state is one SQLite file. The offline `kirok-backup` CLI needs no API key:
219
+
220
+ ```bash
221
+ uv run kirok-backup snapshot # byte-level DB copy (safe while server runs)
222
+ uv run kirok-backup export # portable JSON of all banks + memories + observations + models
223
+ uv run kirok-backup import ~/.kirok/backups/kirok-export-YYYYMMDD-HHMMSS.json
224
+ ```
225
+
226
+ `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.
227
+
228
+ ## 🩺 Diagnostics
229
+
230
+ ```bash
231
+ uv run kirok-doctor # offline: Python version, .env, key presence (never printed),
232
+ # required modules, FTS5, sqlite-vec, DB writability
233
+ uv run kirok-doctor --json # machine-readable, for automation
234
+ uv run kirok-doctor --online # adds one live embedding call to verify Gemini connectivity
235
+ ```
236
+
237
+ ## πŸ§‘β€πŸ’» Development
238
+
239
+ ```bash
240
+ uv sync
241
+ uv run --no-sync pytest # 164 offline tests; no API key or network needed
242
+ ```
243
+
244
+ The 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.
245
+
246
+ ## πŸ“š Documentation
247
+
248
+ - [docs/architecture.md](docs/architecture.md) β€” internal design, data model, consolidation engine
249
+ - [docs/tools-reference.md](docs/tools-reference.md) β€” full parameter reference for all 19 tools
250
+ - [CHANGELOG.md](CHANGELOG.md) β€” version history (current: 1.4.0)
251
+
252
+ ## πŸ“„ License
253
+
254
+ MIT β€” see [LICENSE](LICENSE).
255
+
256
+ ## Acknowledgements
257
+
258
+ - [Model Context Protocol](https://modelcontextprotocol.io) and the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) (FastMCP)
259
+ - [Google Gemini API](https://ai.google.dev/) for embeddings and LLM
260
+ - [Mem0](https://github.com/mem0ai/mem0) β€” inspiration for smart deduplication and the knowledge layer
261
+ - [Reciprocal Rank Fusion](https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf) (Cormack et al., 2009)