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.
- kirok_mcp-1.4.0/.env.example +14 -0
- kirok_mcp-1.4.0/.github/workflows/publish.yml +40 -0
- kirok_mcp-1.4.0/.github/workflows/test.yml +32 -0
- kirok_mcp-1.4.0/.gitignore +37 -0
- kirok_mcp-1.4.0/CHANGELOG.md +126 -0
- kirok_mcp-1.4.0/CONTRIBUTING.md +78 -0
- kirok_mcp-1.4.0/LICENSE +21 -0
- kirok_mcp-1.4.0/PKG-INFO +261 -0
- kirok_mcp-1.4.0/README.ja.md +233 -0
- kirok_mcp-1.4.0/README.md +233 -0
- kirok_mcp-1.4.0/docs/architecture.md +229 -0
- kirok_mcp-1.4.0/docs/tools-reference.md +281 -0
- kirok_mcp-1.4.0/pyproject.toml +62 -0
- kirok_mcp-1.4.0/scripts/reembed.py +244 -0
- kirok_mcp-1.4.0/scripts/search_eval.example.json +17 -0
- kirok_mcp-1.4.0/scripts/search_eval.py +186 -0
- kirok_mcp-1.4.0/skills/kirok/SKILL.md +86 -0
- kirok_mcp-1.4.0/src/kirok_mcp/__init__.py +3 -0
- kirok_mcp-1.4.0/src/kirok_mcp/backup.py +516 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/__init__.py +27 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/banks.py +318 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/base.py +177 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/core.py +61 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/memories.py +356 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/models.py +175 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/observations.py +309 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/schema.py +547 -0
- kirok_mcp-1.4.0/src/kirok_mcp/db/search.py +507 -0
- kirok_mcp-1.4.0/src/kirok_mcp/diagnostics.py +300 -0
- kirok_mcp-1.4.0/src/kirok_mcp/embeddings.py +196 -0
- kirok_mcp-1.4.0/src/kirok_mcp/llm.py +483 -0
- kirok_mcp-1.4.0/src/kirok_mcp/retry.py +80 -0
- kirok_mcp-1.4.0/src/kirok_mcp/server.py +1303 -0
- kirok_mcp-1.4.0/tests/test_backup.py +393 -0
- kirok_mcp-1.4.0/tests/test_bank_edges.py +75 -0
- kirok_mcp-1.4.0/tests/test_consolidation_atomicity.py +250 -0
- kirok_mcp-1.4.0/tests/test_db.py +248 -0
- kirok_mcp-1.4.0/tests/test_diagnostics.py +105 -0
- kirok_mcp-1.4.0/tests/test_embedding_dim.py +14 -0
- kirok_mcp-1.4.0/tests/test_embeddings.py +145 -0
- kirok_mcp-1.4.0/tests/test_failure_log.py +116 -0
- kirok_mcp-1.4.0/tests/test_fts_trigram.py +267 -0
- kirok_mcp-1.4.0/tests/test_hybrid_improvements.py +281 -0
- kirok_mcp-1.4.0/tests/test_llm.py +77 -0
- kirok_mcp-1.4.0/tests/test_llm_retry.py +115 -0
- kirok_mcp-1.4.0/tests/test_retry.py +124 -0
- kirok_mcp-1.4.0/tests/test_search_eval.py +117 -0
- kirok_mcp-1.4.0/tests/test_search_quality.py +224 -0
- kirok_mcp-1.4.0/tests/test_server.py +684 -0
- kirok_mcp-1.4.0/tests/test_server_startup.py +57 -0
- kirok_mcp-1.4.0/tests/test_stage3.py +328 -0
- kirok_mcp-1.4.0/tests/test_vec_crud.py +196 -0
- kirok_mcp-1.4.0/tests/test_vec_load.py +45 -0
- kirok_mcp-1.4.0/tests/test_vec_migration.py +148 -0
- kirok_mcp-1.4.0/tests/test_vec_search.py +236 -0
- 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.
|
kirok_mcp-1.4.0/LICENSE
ADDED
|
@@ -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.
|
kirok_mcp-1.4.0/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/TadFuji/kirok-mcp/actions/workflows/test.yml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](https://www.python.org)
|
|
36
|
+
[](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)
|