memkernel 0.5.0__tar.gz → 0.7.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 (50) hide show
  1. {memkernel-0.5.0 → memkernel-0.7.0}/PKG-INFO +70 -23
  2. {memkernel-0.5.0 → memkernel-0.7.0}/README.md +68 -21
  3. {memkernel-0.5.0 → memkernel-0.7.0}/pyproject.toml +3 -3
  4. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/__init__.py +1 -1
  5. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/config.py +9 -0
  6. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/core/models.py +47 -0
  7. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/core/ports.py +24 -1
  8. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/core/services.py +262 -26
  9. memkernel-0.7.0/src/memkernel/lifecycle.py +421 -0
  10. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/mcp/tools/memory.py +7 -0
  11. memkernel-0.7.0/src/memkernel/mcp/tools/wiki.py +177 -0
  12. memkernel-0.7.0/src/memkernel/session_spool.py +154 -0
  13. memkernel-0.7.0/src/memkernel/storage/chroma.py +474 -0
  14. memkernel-0.7.0/src/memkernel/wiki/curation.py +254 -0
  15. memkernel-0.7.0/src/memkernel/wiki/ledger.py +282 -0
  16. memkernel-0.7.0/src/memkernel/wiki/studio.py +728 -0
  17. memkernel-0.7.0/src/memkernel/wiki/sync.py +1064 -0
  18. memkernel-0.5.0/src/memkernel/lifecycle.py +0 -221
  19. memkernel-0.5.0/src/memkernel/mcp/tools/wiki.py +0 -84
  20. memkernel-0.5.0/src/memkernel/storage/chroma.py +0 -290
  21. memkernel-0.5.0/src/memkernel/wiki/studio.py +0 -193
  22. memkernel-0.5.0/src/memkernel/wiki/sync.py +0 -425
  23. {memkernel-0.5.0 → memkernel-0.7.0}/LICENSE +0 -0
  24. {memkernel-0.5.0 → memkernel-0.7.0}/NOTICE +0 -0
  25. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/adapters/__init__.py +0 -0
  26. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/adapters/rlmstudio.py +0 -0
  27. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/api/__init__.py +0 -0
  28. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/api/app.py +0 -0
  29. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/api/routes/__init__.py +0 -0
  30. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/api/routes/admin.py +0 -0
  31. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/core/__init__.py +0 -0
  32. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/factory.py +0 -0
  33. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/mcp/__init__.py +0 -0
  34. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/mcp/server.py +0 -0
  35. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/mcp/tools/__init__.py +0 -0
  36. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/memory/__init__.py +0 -0
  37. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/memory/dedup.py +0 -0
  38. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/memory/lifecycle.py +0 -0
  39. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/memory/types.py +0 -0
  40. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/py.typed +0 -0
  41. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/retrieval/__init__.py +0 -0
  42. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/retrieval/chunking.py +0 -0
  43. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/retrieval/embeddings.py +0 -0
  44. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/retrieval/reasoner.py +0 -0
  45. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/retrieval/search.py +0 -0
  46. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/retrieval/sparse.py +0 -0
  47. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/storage/__init__.py +0 -0
  48. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/storage/documents.py +0 -0
  49. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/wiki/__init__.py +0 -0
  50. {memkernel-0.5.0 → memkernel-0.7.0}/src/memkernel/wiki/pages.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memkernel
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Code-aware memory server for AI coding copilots
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -23,7 +23,7 @@ Requires-Dist: rank-bm25 (>=0.2)
23
23
  Requires-Dist: sentence-transformers (>=4.0,<6)
24
24
  Requires-Dist: tree-sitter (>=0.22,<1)
25
25
  Requires-Dist: tree-sitter-python (>=0.23)
26
- Requires-Dist: wiki-studio (>=0.3.1,<1) ; extra == "wiki"
26
+ Requires-Dist: wiki-studio (>=0.7.0,<1) ; extra == "wiki"
27
27
  Project-URL: Documentation, https://memkernel.dev
28
28
  Project-URL: Homepage, https://memkernel.dev
29
29
  Project-URL: Repository, https://github.com/gosha70/memkernel
@@ -65,7 +65,7 @@ MemKernel gives Claude Code (and any MCP-compatible copilot) persistent memory a
65
65
  - **An index of the whole repository.** `code` memories are the snapshots the agent chose to retain, not an automatic scan of every file.
66
66
  - **A cross-project knowledge base.** One store per project id. Sharing is deliberate: a second server with a shared id, or the wiki bridge.
67
67
 
68
- **Evidence.** `eval/run_eval.py` measures retrieval (recall@k, precision@k, MRR). The 0.5 lifecycle evaluation, a **maintainer-run supplemental evaluation** (memkernel's own runner, preregistered and announced in [#17](https://github.com/gosha70/memkernel/issues/17) before the run), ran the same agent in seven arms on 20 tasks: [report](eval/lifecycle-suite/report-0.5.0.md). **No gain is claimed.** Against files alone (`claude_md`), memkernel's memory store was ahead on 6 tasks, behind on none and tied on 14 (Holm-adjusted p 0.0625: "no detectable difference"; with so many ties this task set could not have reached significance); against earlier transcripts (`fs_grep`) it was equivalent. Two of the four task groups hit a ceiling, every arm passing every trial. The report's disclosures list its deviations and known defects. Results are published per minor release, including the cases with no gain.
68
+ **Evidence.** `eval/run_eval.py` measures retrieval (recall@k, precision@k, MRR). The 0.5 lifecycle evaluation, a **maintainer-run supplemental evaluation** (memkernel's own runner, preregistered and announced in [#17](https://github.com/gosha70/memkernel/issues/17) before the run), ran the same agent in seven arms on 20 tasks: [report](eval/lifecycle-suite/report-0.5.0.md). **No gain is claimed.** Against files alone (`claude_md`), memkernel's memory store was ahead on 6 tasks, behind on none and tied on 14 (Holm-adjusted p 0.0625: "no detectable difference"; with so many ties this task set could not have reached significance); against earlier transcripts (`fs_grep`) it was equivalent. Two of the four task groups hit a ceiling, every arm passing every trial. The report's disclosures list its deviations and known defects. Results are published with the cases that show no gain; the 0.5 report is the latest evaluation, and 0.6.0 had no new measured run.
69
69
 
70
70
  ---
71
71
 
@@ -88,7 +88,7 @@ pipx install memkernel
88
88
  pipx install --python python3.11 memkernel
89
89
  # optional: wiki-studio lint and health findings on wiki_export (ADR-010)
90
90
  pipx install "memkernel[wiki]"
91
- pipx inject memkernel "wiki-studio>=0.3.1,<1" # add it to an existing install
91
+ pipx inject memkernel "wiki-studio>=0.7.0,<1" # add it to an existing install
92
92
  ```
93
93
 
94
94
  **As a library inside a project**, install it into that project's virtual environment:
@@ -147,9 +147,12 @@ Copy the hook scripts from [`templates/hooks/`](templates/hooks/) to `.claude/ho
147
147
 
148
148
  | Hook | Claude Code event | What it does |
149
149
  |------|-------------------|--------------|
150
- | `session-start.py` | `SessionStart` | Consolidates, recovers the latest checkpoint and long-term context, lists promotion candidates |
151
- | `pre-compact.py` | `PreCompact` | Retains a checkpoint episode, runs a consolidation pass |
152
- | `post-compact.py` | `PostCompact` | Re-injects the checkpoint, long-term context and promotion candidates |
150
+ | `session-start.py` | `SessionStart` | By `source`: on `startup` and `clear`, consolidates, recovers the latest checkpoint and long-term context, lists promotion candidates; on `compact`, the recovery after a compaction (the latest checkpoint the agent saved, with its age, and long-term context; no consolidation); on `resume` and `fork`, nothing |
151
+ | `pre-compact.py` | `PreCompact` | Runs a consolidation pass (Claude Code passes no summary here, so no checkpoint is retained) |
152
+ | `post-compact.py` | `PostCompact` | Retains Claude Code's compaction summary (`compact_summary`) as a checkpoint for the next session; prints nothing for the model |
153
+ | `session-end.py` | `SessionEnd` | Writes one line to the session spool in `MEMKERNEL_STATE_DIR` (no store, no model, within SessionEnd's 1.5-second budget); the next session start says when a session ended without a saved checkpoint. A session that never recorded an end (a crash, or this hook not registered) is dropped from the spool after 7 days, and the next start says how many |
154
+
155
+ Each injected memory line ends with its `ref_id`, so `get(ref_id)` returns the full text; a block never exceeds 9,000 characters (Claude Code caps hook output at 10,000), and a cut checkpoint says which `get` holds the rest. At session start the long-term context is the most important decisions, conventions, procedures and facts, not the matches of a fixed query; after a compaction it is recalled with the checkpoint's text. Register `SessionStart` without a matcher, so it runs for every `source`, and `session-end.py` under `SessionEnd`. Claude Code adds only `SessionStart`'s output to the model's context, not `PreCompact`'s or `PostCompact`'s, so the recovery after a compaction comes from `SessionStart` with `source: "compact"`. After upgrading memkernel, copy the three scripts again.
153
156
 
154
157
  See the Prerequisites section inside each script for the Python environment requirement. Each script is a thin wrapper over [`MemoryLifecycle`](#embedding-memkernel-in-your-own-harness), so an in-process harness gets the same behaviour without the scripts.
155
158
 
@@ -174,14 +177,14 @@ Kiro (and the Kiro CLI, formerly Amazon Q Developer CLI) connects to MemKernel a
174
177
  | Tool | What it does |
175
178
  |------|-------------|
176
179
  | `retain(content, type, session_id?, checkpoint?, source?, importance?, tags?, supersedes?)` | Store a memory. Returns `ref_id`. Idempotent — identical content + type returns the same `ref_id`. `supersedes` hides the memory it replaces. |
177
- | `recall(query, type?, tier?, session_id?, checkpoint_only?, top_k?, include_needs_review?)` | Search by natural language or identifier. Returns lightweight `ArtifactRef` list ranked by relevance + recency + importance. Records an access (reinforces retention). Code memories flagged `needs_review` are hidden unless `include_needs_review=true`. |
180
+ | `recall(query, type?, tier?, session_id?, checkpoint_only?, top_k?, include_needs_review?, order?)` | Search by natural language or identifier. Returns lightweight `ArtifactRef` list ranked by relevance + recency + importance. Records an access (reinforces retention). Code memories flagged `needs_review` are hidden unless `include_needs_review=true`. |
178
181
  | `get(ref_id)` | Fetch full content of a specific memory. Records an access. |
179
182
  | `forget(ref_id)` | Delete a memory from all indexes. |
180
183
  | `promote(ref_id, type, content?, importance?, tags?)` | Turn a short-term/working memory into a long-term one (you supply the distilled text). Keeps provenance via `promoted_from`. |
181
184
  | `consolidate(session_id?, limit?, synthesize?, revalidate?)` | Purge expired short-term memories and list the ones most worth promoting (ranked by importance × how often they were recalled). `synthesize=true` also asks an external reasoner to draft long-term statements (`suggested`); `revalidate=true` first checks every code memory against its source file (`revalidated`). The report always carries a `health` summary. |
182
185
  | `ask(ref_id, question, mode?)` | Answer a question about one memory without loading it — for a `document` the full body is consulted. `mode`: `direct` \| `rag` \| `rlm` \| `auto`, passed to the reasoner unchanged; the built-in reasoner answers in `rag` mode with the best-matching chunks. Returns `answer`, `mode`, `citations`, `usage`. |
183
186
  | `wiki_export(wiki_dir?)` | Compile long-term memories without a wiki page into draft markdown pages (`index.md`, `log.md` updated). |
184
- | `wiki_import(wiki_dir?)` | Index curated wiki pages as long-term memories (idempotent; changed pages supersede). |
187
+ | `wiki_import(wiki_dir?, adopt_legacy?, adopt_pages?, adopt_manifest?)` | Index wiki pages as long-term memories, each wiki keeping to its own: approved revisions only with wiki-studio ≥ 0.7.0, every page without it (idempotent; changed pages supersede; `corrects` supersedes the memories a page corrects). |
185
188
 
186
189
  ### Memory types and tiers
187
190
 
@@ -201,13 +204,13 @@ Retention: `ttl = base_ttl × (1 + access_boost × access_count)`, measured from
201
204
 
202
205
  ### Session protocol
203
206
 
204
- 1. **Session start** — `recall(tier="long_term", query=<task>)`, plus the latest checkpoint (`checkpoint_only=true`). The `session-start.py` hook does this and prints promotion candidates.
207
+ 1. **Session start** — `recall(tier="long_term", query=<task>)`, plus the latest checkpoint (`checkpoint_only=true, order="newest"`: newest first, not the most relevant). The `session-start.py` hook does this and prints promotion candidates; after a compaction it prints a shorter recovery block instead.
205
208
  2. **While working** — `retain(type="task")` when the plan changes; `retain(type="observation")` for findings; durable knowledge as `decision` / `convention` / `fact` / `procedure`.
206
- 3. **Before compaction** — `retain(type="episode", checkpoint=true)` (the `pre-compact.py` hook) and `consolidate()`.
209
+ 3. **Before compaction** — `retain(type="episode", checkpoint=true)` with your own summary of where the work stands; the `pre-compact.py` hook runs `consolidate()`, and the `post-compact.py` hook keeps Claude Code's compaction summary as a checkpoint.
207
210
  4. **After compaction / end of phase** — `promote(ref_id, type, content)` for the candidates worth keeping.
208
211
  5. **Large artefacts** — `retain(type="document", content=<body>, source=<name>)` then `ask(ref_id, question)`; never `get` a document to read it.
209
212
  7. **Code snapshots** — `retain(type="code", content=<file>, source=<path>)`; retain the file again after editing it (the new snapshot supersedes the old one). The session-start hook revalidates snapshots against their files and lists the ones that need re-retaining.
210
- 6. **Curate** — `wiki_export` → review the drafts → `wiki_import`.
213
+ 6. **Curate** — `wiki_export` → review and approve the drafts in wiki-studio → `wiki_import`.
211
214
 
212
215
  ### Quick examples
213
216
 
@@ -220,7 +223,7 @@ retain(
220
223
 
221
224
  recall(query="database choice", top_k=5)
222
225
 
223
- recall(query="session checkpoint", checkpoint_only=True)
226
+ recall(query="session checkpoint", checkpoint_only=True, order="newest", top_k=1) # the latest checkpoint
224
227
 
225
228
  retain(content="pytest hangs unless MEMKERNEL_PROJECT_ID is set", type="observation", tags=["ci"])
226
229
 
@@ -251,7 +254,7 @@ setting is logged as a warning at startup.
251
254
  |----------|---------|-------------|
252
255
  | `MEMKERNEL_PROJECT_ID` | `default` | Project namespace — memories are isolated per project |
253
256
  | `MEMKERNEL_DEFAULT_SESSION_ID` | `default` | Session tag applied when `session_id` is omitted from `retain` |
254
- | `MEMKERNEL_CHROMA_PERSIST_PATH` | `.memkernel/chroma` | ChromaDB data directory. Empty string = in-memory (ephemeral) |
257
+ | `MEMKERNEL_CHROMA_PERSIST_PATH` | `.memkernel/chroma` | ChromaDB data directory. Empty string = in-memory (ephemeral). On POSIX systems (Linux, macOS), opening it takes a short lock on `.memkernel-open.lock` inside it, so the MCP server and a hook opening a new store at once do not collide; where `fcntl` is unavailable (Windows) the store opens without the lock, and two processes creating a new store at the same moment can still fail |
255
258
  | `MEMKERNEL_CHROMA_COLLECTION_PREFIX` | `memkernel` | Prefix for ChromaDB collection names (`{prefix}_{project_id}`) |
256
259
  | `MEMKERNEL_EMBEDDING_MODEL` | `all-MiniLM-L6-v2` | Sentence-transformers model name |
257
260
  | `MEMKERNEL_EMBEDDING_DIMENSION` | `384` | Output dimension of the embedding model |
@@ -277,7 +280,10 @@ setting is logged as a warning at startup.
277
280
  | `MEMKERNEL_RECALL_WEIGHT_RECENCY` | `0.3` | Weight of recency in the final recall score |
278
281
  | `MEMKERNEL_RECALL_WEIGHT_IMPORTANCE` | `0.3` | Weight of importance in the final recall score |
279
282
  | `MEMKERNEL_WIKI_DIR` | `` | Directory of the LLM wiki used by `wiki_export` / `wiki_import` (empty = pass `wiki_dir` explicitly) |
283
+ | `MEMKERNEL_STATE_DIR` | `.memkernel/state` | memkernel's own state outside ChromaDB: the wiki sync ledger. Resolved like `MEMKERNEL_CHROMA_PERSIST_PATH` (a relative path is relative to the process's working directory, so set it absolute when the persist path is); keep it out of git |
280
284
  | `MEMKERNEL_WIKI_PROPOSALS_DIR` | `""` | Where wiki-studio patch-sets are written before promote (`memkernel[wiki]`); empty means `<project root>/.memkernel/wiki-proposals`. Setting it requests the gate: without wiki-studio installed, a warning is logged. |
285
+ | `MEMKERNEL_WIKI_MODE` | `auto` | `auto`: wiki-studio's gate and review state when wiki-studio 0.7.0 or later is installed, the direct writer and file-reading import when it is not. `governed`: `wiki_export` and `wiki_import` refuse, writing nothing, unless wiki-studio is installed with every capability memkernel uses and the wiki's manifest is `governed` (`approval_required: true`) without schema drift. `ungated`: wiki-studio is never loaded. Responses report `mode` (`gated` or `ungated`, the path that ran) and `configured_mode` (this setting). |
286
+ | `MEMKERNEL_WIKI_EXPORT_BATCH` | `10` | New pages one `wiki_export` writes at most, the most important memories first; the response's `remaining` counts the rest (`null` when the gate's outcome is uncertain or `governed` refused before reading the wiki). `0` = no limit; a negative value is refused |
281
287
  | `MEMKERNEL_TRANSPORT` | `stdio` | `stdio` or `streamable-http` |
282
288
  | `MEMKERNEL_HOST` | `127.0.0.1` | Host for `streamable-http` transport |
283
289
  | `MEMKERNEL_PORT` | `8765` | Port for `streamable-http` transport |
@@ -319,11 +325,13 @@ for ref in svc.recall("database"):
319
325
  report = svc.consolidate() # {"purged": n, "candidates": [...]}
320
326
  ```
321
327
 
322
- `build_service()` is the supported entry point; the MCP server, the admin API and the hook templates all use it. Integration notes for `code-copilot-team` and `haa` are in [`docs/integration/harness-integration.md`](docs/integration/harness-integration.md).
328
+ `build_service()` is the supported entry point; the MCP server, the admin API and the hook templates all use it. If you pass a `PersistentClient` as `chroma_client` and also set `MEMKERNEL_CHROMA_PERSIST_PATH` (in the environment or in the settings), both must name the same directory, or `ValueError` is raised; with the default setting, the client's own directory is used.
329
+
330
+ **Store locks.** Processes that share a store (the MCP server, a hook, a script) coordinate through locks keyed by the store itself: lock files in the persist directory, `.memkernel-<name>-<key>.lock`, whatever each process's `MEMKERNEL_STATE_DIR` is; an in-memory store's locks are per process. Only one writer changes a record's tags, under the store's `tags` lock, so a tag added by one process is never dropped by another's write. Locks are not re-entrant (taking one the thread already holds raises `LockReentryError`), a wait longer than 10 seconds is logged once, and spelling the persist directory with different letter case in two processes gives two locks, so use one spelling. The locks use `fcntl` and are verified on POSIX (Linux, macOS) only; on Windows they run unlocked across processes, with one logged warning per lock. Integration notes for `code-copilot-team` and `haa` are in [`docs/integration/harness-integration.md`](docs/integration/harness-integration.md).
323
331
 
324
332
  ### The session protocol as a component
325
333
 
326
- `MemoryLifecycle` packages what the hook scripts do, so a harness calls four methods instead of re-implementing the protocol. Every method returns markdown ready for injection and does nothing but talk to the service:
334
+ `MemoryLifecycle` packages what the hook scripts do, so a harness calls five methods instead of re-implementing the protocol. Every method returns markdown ready for injection and does nothing but talk to the service:
327
335
 
328
336
  ```python
329
337
  from memkernel.factory import build_service
@@ -332,9 +340,13 @@ from memkernel.lifecycle import MemoryLifecycle
332
340
  memory = MemoryLifecycle(build_service())
333
341
  system_context = memory.on_session_start() # checkpoint, long-term context, promotion candidates, wiki pointer
334
342
  ...
335
- memory.on_pre_compact(summary) # checkpoint episode + consolidation, before the context is compacted
336
- recovered = memory.on_post_compact() # what to re-inject after compaction
343
+ memory.on_pre_compact(summary) # checkpoint episode (only if summary is not blank) + consolidation
344
+ recovered = memory.on_post_compact() # what to re-inject after your own compaction
337
345
  memory.on_session_end(summary) # final checkpoint + consolidation
346
+
347
+ # Driving it from Claude Code's hook events instead (what templates/hooks/ do):
348
+ memory.on_session_start(source) # source "startup"/"clear": full block; "compact": recovery block; "resume"/"fork": ""
349
+ memory.capture_compact_summary(compact_summary, session_id=..., trigger=...) # keep Claude Code's own summary as a checkpoint
338
350
  ```
339
351
 
340
352
  ### Documents and `ask`
@@ -354,12 +366,41 @@ memory.on_session_end(summary) # final checkpoint + consolidat
354
366
 
355
367
  Long-term memory has a human-readable twin: a markdown wiki in the [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern (`index.md`, `log.md`, typed pages with cited sources — the same schema as `code-copilot-team/knowledge/wiki`).
356
368
 
357
- - `wiki_export` compiles every long-term memory that has no page yet into a **draft** page under `decisions/`, `concepts/` or `workflows/`, with the required sections and a `memkernel://<project>/<ref_id>` citation. Existing pages are never overwritten.
358
- - `wiki_import` indexes curated pages as long-term memories so `recall` finds the reviewed version; a changed page supersedes its earlier memory.
369
+ To see the whole loop on a clean install, follow wiki-studio's [memkernel composition quickstart](https://github.com/gosha70/wiki-studio/blob/main/docs/quickstart-memkernel.md): retain a memory, export it through wiki-studio's gate, correct and approve the page, import only the approved revision, and see the next `recall` return it. CI runs the quickstart against memkernel **0.6.0**.
359
370
 
371
+ - `wiki_export` compiles every long-term memory that has no page yet into a **draft** page under `decisions/`, `concepts/` or `workflows/`, with the required sections and a `memkernel://<project>/<ref_id>` citation. Existing pages are never overwritten.
372
+ - `wiki_import` indexes pages as long-term memories so `recall` finds the reviewed version; a changed page supersedes its earlier memory. With wiki-studio 0.7.0 or later installed it is **governed** (`"governed": true`): it imports only pages wiki-studio's review state reports approved, and only when the page's bytes are still the approved revision, so drafts, self-declared approvals and pages edited after approval are not imported (`not_approved` says why); an edited page keeps its last approved memory until it is approved again. memkernel never approves a page; a curator does, with `wiki-studio approve`. Without wiki-studio the import reads the page files, drafts included, and reports `"governed": false`. An installed wiki-studio that is broken or older than 0.7.0 imports nothing and reports `import_error`.
373
+ - **Withdrawal (governed import, 0.7).** A page's memory leaves the current answers, its record kept for history, when:
374
+ - the page is **deleted**. Only memories this wiki's sync ledger recorded as imported are withdrawn this way; a memory imported before 0.7, or without wiki-studio, never is.
375
+ - the page is **deprecated**, once the `status: deprecated` revision is approved.
376
+ - the page is **superseded** (`superseded_by: <page path>`). Its memory is replaced by the successor page's memory; if the successor has none yet, the old memory is withdrawn and linked once it does (`pending` until then).
377
+
378
+ The response lists `withdrawn`, `superseded`, `pending` and `sync_failed` (retried at the next import). The ledger is one JSON file per project and wiki under `MEMKERNEL_STATE_DIR/wiki-sync/`; a corrupt one is kept aside, reported as `ledger_error`, and rebuilt.
379
+ - **Corrections.** An approved page can name the memories it corrects in its frontmatter: `corrects: <ref_id>, <ref_id>` (one line; tokens of letters, digits, `-` and `_`). On import each named memory that did not come from the wiki is superseded by the page's memory: it stops being a current answer, and its record is kept. References that cannot be applied are listed in `unresolved` with a reason (unknown, another project's, already superseded, or a wiki memory: correct that page instead); a value that breaks the grammar is listed in `invalid_corrects`, and the page is still imported. An approved page that is already current is listed in `unchanged`, and its corrections are reconciled again on every import, so a reference that could not be applied earlier takes effect once it can; corrections already applied are not repeated.
380
+
381
+ - **Each wiki keeps to its own memories (0.7).** Every memory `wiki_import` writes, or finds current, carries its wiki's identity: `wiki-path:<hash>` (the wiki directory's path relative to the project root) and, when the wiki has a wiki-studio manifest, `wiki-id:<uuid>`. A page's previous memory, its corrections, its supersession and its deprecation are only that wiki's, so two wikis in one store may share page paths and `page_id`s. These tags are reserved: `retain` refuses them, and an ordinary `retain` whose text matches a wiki memory gets its own record instead of merging into it; `promote` drops every wiki tag from its copy.
382
+ - **A wiki that may move, or that shares a store with another wiki, needs a manifest.** The path a wiki is known by is relative to its project root: two project roots sharing one store, with wikis at the same relative path, get the same `wiki-path:`, so the second import is refused (memories at this path belong to another wiki) even with manifests. Use one store per project, or different relative paths. Without one, a wiki *is* its path: moved, it starts afresh, and whatever wiki is later placed at the old path continues its memories. A copied wiki directory (two directories, one `wiki_id`) is one wiki: each import moves its memories to the directory imported last.
383
+ - **Upgrading a 0.6 store:** memories written before 0.7 carry no identity, so the first import writes nothing for their pages and lists them under `ambiguous` (`{"path", "ref_ids", "reason"}`). Import once with `adopt_legacy=true` to adopt each page's sole such memory; a page with several stays `ambiguous` (forget the ones that are not its own). With several wikis in one store, name the records instead: `adopt_pages=[{"path", "ref_id"}, …]` copied from `ambiguous`; a named record that changed meanwhile is not adopted (`candidate changed`).
384
+ - **Identity changes stop the import before any write, with the remedy:** a wiki that gained a manifest needs `adopt_manifest=true` once (its sync ledger moves to the `wiki_id` key; if both ledger files exist, delete the stale one); memories at this path that belong to another `wiki_id` (the manifest removed or replaced, or a moved wiki not yet imported from its new place) stop it until the manifest is restored, the moved wiki is imported first, or those memories are forgotten. A wiki with a manifest that moves keeps its memories; the next import moves their path.
385
+ - One import runs at a time per store (the `wiki-import` store lock). On Windows the import runs unlocked with one warning, as in 0.6: two concurrent imports of one wiki can leave two records for a page (the next import reports it as `duplicate records`; forget one), and two wikis adopting the same record at once can tag it for one while the other supersedes it. Give processes that share a store the same `MEMKERNEL_STATE_DIR`, so they share the sync ledger.
360
386
  Set `MEMKERNEL_WIKI_DIR` (for example `knowledge/wiki`) or pass `wiki_dir` to the tools. The standalone maintainer that owns this page schema is [wiki-studio](https://github.com/gosha70/wiki-studio). With the `memkernel[wiki]` extra installed, every `wiki_export` response carries wiki-studio's lint and health findings for the whole wiki after the export, curated pages included, under `lint` (`violations`, `audit_errors`, `pages_checked`, `health` items with `kind`, `severity`, `pages`, `description`); without it `lint` is `null` and `lint_hint` says how to enable it; if the lint pass itself fails, `lint` is `null` and `lint_error` says why. The files written are the same in every case.
361
387
 
362
- With wiki-studio 0.3.1 or later installed, `wiki_export` writes through wiki-studio's promote gate: it first writes `index.md` and `log.md` if they are missing (the gate cannot create them), then proposes every new page with its index and log lines as one patch-set and promotes it. A rejected export writes nothing and the response carries `gate_error`; so does an installed wiki-studio that fails to import or to build the patch-set, rather than falling back to the direct writer. A failed export leaves its patch-set under the proposals directory for inspection; delete it once the cause is fixed. Patch-sets go to `MEMKERNEL_WIKI_PROPOSALS_DIR`, by default `.memkernel/wiki-proposals/` under the project root. Commit `<wiki>/.audit/`, wiki-studio's audit trail of promoted proposals; ignore `.memkernel/wiki-proposals/`.
388
+ With wiki-studio 0.7.0 or later installed, `wiki_export` writes through wiki-studio's promote gate: wiki-studio's `init` creates `index.md` and `log.md` if they are missing (wiki-studio's own scaffold, `status: stable`), then every new page goes with its index and log lines into one patch-set that is proposed and promoted. Other writers can work in the same wiki: wiki-studio's commit writes only the paths the patch-set names, under its lock, and refuses a page someone else created in the meantime. A rejected export writes no page and the response carries `gate_error` (a new wiki keeps the `index.md` and `log.md` that `init` created); so does an installed wiki-studio that is broken or older than 0.7.0, rather than falling back to the direct writer. A promoted export's response adds `proposal`, `submission_digest` and `archived_dir` from wiki-studio's receipt. If `archived_dir` is `null`, the pages landed but wiki-studio did not finish: it could not archive the proposal, or it failed after committing the pages (memkernel checks wiki-studio's receipt: while it still says `committing`, every planned write, index and log included, must be at its post-write sha256; a `committed` receipt needs memkernel's pages at theirs; an unusable receipt falls back to comparing the pages' bytes). `archive_hint` gives the `promote` command that tries to finish it, with absolute paths, so it runs from any directory. It applies nothing twice, but if the wiki or the proposal changed since, for example another writer appended to `index.md`, it may refuse with `uncertain_outcome` or `payload_changed` and write nothing; then compare the wiki with the proposal's `receipt.json` by hand. memkernel never runs it itself. If wiki-studio failed during its commit and its receipt shows neither a commit nor a rollback (say the page was written, the `index.md` write failed and removing the page failed too), the response carries `gate_error` with `uncertain_outcome` and `proposal`: `written` is empty, but the pages it names may be on disk; compare the wiki with the `receipt.json` it names before exporting again. `receipt_error` says the receipt could not be read or used. A failed export leaves its patch-set under the proposals directory for inspection; delete it once the cause is fixed. Patch-sets go to `MEMKERNEL_WIKI_PROPOSALS_DIR`, by default `.memkernel/wiki-proposals/` under the project root. Commit `<wiki>/.audit/`, wiki-studio's audit trail of promoted proposals; ignore `.memkernel/wiki-proposals/`, `.memkernel/state/` (memkernel's sync ledger) and wiki-studio's files next to the wiki directory, `.<wiki-dir>.wiki-studio.*` (for `knowledge/wiki`, `knowledge/.wiki.wiki-studio.*`: its lock file and, from wiki-studio 0.8, its search cache).
389
+
390
+ **Wiki modes.** `MEMKERNEL_WIKI_MODE` is a property of the deployment, not of a call. `governed` runs one preflight before any page is written or read, for export and import alike: wiki-studio 0.7.0 or later installed and working; its `info` listing the capabilities `wiki-lock`, `receipts`, `root-manifest`, `page-id`, `approval-record`, `superseded-by` and `findings`; the wiki's manifest mode `governed`; no schema drift. A `managed` or `legacy` wiki is refused with the remedy: run `wiki-studio init` if there is no manifest, then set `approval_required: true` in the manifest's policy. A refusal reports `gate_error` (export) or `import_error` (import) and writes nothing; `update_index=False` is refused too, since the direct writer is never used. A gate rejection after the preflight commits no drafted page, but a new wiki keeps the `index.md` and `log.md` that wiki-studio's `init` created: memkernel keeps no outer rollback. `ungated` never loads wiki-studio (no gate, lint, review state or findings): `lint` is `null` and `lint_hint` names the mode.
391
+
392
+ **Export batches and grounding.** One `wiki_export` writes at most `MEMKERNEL_WIKI_EXPORT_BATCH` new pages (by importance, highest first, then oldest, then `ref_id`), so a curator reviews a small batch at a time; `remaining` counts the eligible memories still without a page, all of them after a rejected batch. Every exported page carries `memkernel_sha256`, the memory's content hash, beside `memkernel_ref`; a `path:` source gains `sha:`, the commit it is grounded at (`git log -1 --format=%h`, wiki-studio's citation rule), only when the project's `git ls-files` lists exactly that file and `git status` shows no change to it: a modified, staged or untracked file, a file in a submodule, or no git gets none. Pages created through the gate carry wiki-studio's `page_id`, so a page renamed or moved under its `page_id` keeps its memory: the next import supersedes the memory at the old path with one at the new path, also when the moved page's bytes are unchanged.
393
+
394
+ **Curation debt at session start.** With `MEMKERNEL_WIKI_DIR` set and wiki-studio available (in `governed`, only after the preflight passes), the session-start block (`startup`, `clear`) gains `### Curation debt`: one line of wiki-studio's `findings` counts in a fixed order (review kinds first) plus the sync ledger's unfinished actions (`sync pending`, `sync failed`), up to three example pages, and a hint to run `wiki-studio findings`. It uses at most 500 characters, taken from the long-term context's share, so the block stays under 9,000 characters. The check runs in a child process bounded to 2.5 seconds once it has started (a fork or exec the operating system holds up is not covered); it reads the sync ledger without changing it and never waits for an import that holds it. Any failure is one `Curation debt unavailable: <reason>` line. Not on `compact`, `resume` or `fork`, nor in `ungated`.
395
+
396
+ **wiki-studio's files beside the wiki, its ingest log, and wiki-studio 0.8.** When memkernel exports through the gate, wiki-studio creates its lock file, `.<wiki-dir>.wiki-studio.lock`, beside the wiki directory, and from 0.8 a curator's `find` or `query` adds a search cache, `.<wiki-dir>.wiki-studio.search.sqlite`, there too: for a wiki kept in git, ignore both with `.<wiki-dir>.wiki-studio.*` (for `knowledge/wiki`, `knowledge/.wiki.wiki-studio.*`). memkernel creates no file of its own there. memkernel reads no wiki-studio ingest log, so the log's `hash_recipe` has no consumer in memkernel. From wiki-studio 0.8.0 a page that is not valid UTF-8 fails lint for the whole wiki, so every promote, memkernel's gated export included, is refused with a `gate_error` naming that page until it is re-encoded or removed. From 0.8.0 an unknown `WIKI_STUDIO_RANKER` value makes wiki-studio's `info` fail, so a `governed` export or import, a gated import and the curation debt refuse with wiki-studio's message naming the variable. memkernel needs wiki-studio 0.7.0 or later and is tested at 0.7.0; an informational CI job runs the wiki tests against the newest release below 1.0 on every pull request and weekly.
397
+
398
+ wiki-studio's MCP server (`wiki-studio-mcp`, from wiki-studio 0.8.0) needs `mcp>=2.3`, which memkernel's `mcp<2` excludes. Run it in its own environment, at the same wiki-studio version as memkernel's:
399
+
400
+ 1. In memkernel's environment, read the version: `python -c "import importlib.metadata as m; print(m.version('wiki-studio'))"`. If it is below 0.8.0, upgrade it there first: `pip install 'wiki-studio>=0.8,<1'`.
401
+ 2. Install exactly that version for the server, for example `pipx install 'wiki-studio[mcp]==0.8.1'`, and register it beside memkernel's server.
402
+
403
+ On one machine and one filesystem, wiki-studio's lock serializes the two servers' writes to the same wiki. It does not coordinate other hosts, and network filesystems are outside its scope.
363
404
 
364
405
  Rationale: [`docs/adr/ADR-008-llm-wiki-as-semantic-layer.md`](docs/adr/ADR-008-llm-wiki-as-semantic-layer.md) and [`docs/adr/ADR-010-wiki-studio-as-wiki-maintainer.md`](docs/adr/ADR-010-wiki-studio-as-wiki-maintainer.md).
365
406
 
@@ -404,9 +445,15 @@ CI runs on every push and PR: lint, type check, tests (≥ 80% coverage), securi
404
445
 
405
446
  Releases are published to PyPI by [`.github/workflows/release.yml`](.github/workflows/release.yml) using trusted publishing (no token in the repo). To cut a release:
406
447
 
407
- 1. Set `version` in `pyproject.toml` and move the `[Unreleased]` entries in [`CHANGELOG.md`](CHANGELOG.md) under the new version.
448
+ 1. Set `version` in `pyproject.toml` and `__version__` in `src/memkernel/__init__.py` to `X.Y.Z`, and move the `[Unreleased]` entries in [`CHANGELOG.md`](CHANGELOG.md) under `[X.Y.Z] - <tag date>`.
408
449
  2. Merge to `main` with CI green.
409
- 3. Tag and push: `git tag -a v0.4.0 -m "memkernel 0.4.0" && git push origin v0.4.0`.
450
+ 3. Tag and push: `git tag -a vX.Y.Z -m "memkernel X.Y.Z" && git push origin vX.Y.Z`.
451
+ 4. Create the GitHub Release from the tag, with the version's CHANGELOG section as its notes:
452
+
453
+ ```bash
454
+ awk -v v=X.Y.Z 'index($0, "## [" v "]") == 1 {f=1; next} f && /^## \[/ {exit} f' CHANGELOG.md > release-notes.md
455
+ gh release create vX.Y.Z --title X.Y.Z --notes-file release-notes.md --verify-tag
456
+ ```
410
457
 
411
458
  The workflow refuses a tag that does not match the `pyproject.toml` version, builds the sdist and wheel, smoke-installs the wheel, and uploads through the `pypi` environment.
412
459
 
@@ -34,7 +34,7 @@ MemKernel gives Claude Code (and any MCP-compatible copilot) persistent memory a
34
34
  - **An index of the whole repository.** `code` memories are the snapshots the agent chose to retain, not an automatic scan of every file.
35
35
  - **A cross-project knowledge base.** One store per project id. Sharing is deliberate: a second server with a shared id, or the wiki bridge.
36
36
 
37
- **Evidence.** `eval/run_eval.py` measures retrieval (recall@k, precision@k, MRR). The 0.5 lifecycle evaluation, a **maintainer-run supplemental evaluation** (memkernel's own runner, preregistered and announced in [#17](https://github.com/gosha70/memkernel/issues/17) before the run), ran the same agent in seven arms on 20 tasks: [report](eval/lifecycle-suite/report-0.5.0.md). **No gain is claimed.** Against files alone (`claude_md`), memkernel's memory store was ahead on 6 tasks, behind on none and tied on 14 (Holm-adjusted p 0.0625: "no detectable difference"; with so many ties this task set could not have reached significance); against earlier transcripts (`fs_grep`) it was equivalent. Two of the four task groups hit a ceiling, every arm passing every trial. The report's disclosures list its deviations and known defects. Results are published per minor release, including the cases with no gain.
37
+ **Evidence.** `eval/run_eval.py` measures retrieval (recall@k, precision@k, MRR). The 0.5 lifecycle evaluation, a **maintainer-run supplemental evaluation** (memkernel's own runner, preregistered and announced in [#17](https://github.com/gosha70/memkernel/issues/17) before the run), ran the same agent in seven arms on 20 tasks: [report](eval/lifecycle-suite/report-0.5.0.md). **No gain is claimed.** Against files alone (`claude_md`), memkernel's memory store was ahead on 6 tasks, behind on none and tied on 14 (Holm-adjusted p 0.0625: "no detectable difference"; with so many ties this task set could not have reached significance); against earlier transcripts (`fs_grep`) it was equivalent. Two of the four task groups hit a ceiling, every arm passing every trial. The report's disclosures list its deviations and known defects. Results are published with the cases that show no gain; the 0.5 report is the latest evaluation, and 0.6.0 had no new measured run.
38
38
 
39
39
  ---
40
40
 
@@ -57,7 +57,7 @@ pipx install memkernel
57
57
  pipx install --python python3.11 memkernel
58
58
  # optional: wiki-studio lint and health findings on wiki_export (ADR-010)
59
59
  pipx install "memkernel[wiki]"
60
- pipx inject memkernel "wiki-studio>=0.3.1,<1" # add it to an existing install
60
+ pipx inject memkernel "wiki-studio>=0.7.0,<1" # add it to an existing install
61
61
  ```
62
62
 
63
63
  **As a library inside a project**, install it into that project's virtual environment:
@@ -116,9 +116,12 @@ Copy the hook scripts from [`templates/hooks/`](templates/hooks/) to `.claude/ho
116
116
 
117
117
  | Hook | Claude Code event | What it does |
118
118
  |------|-------------------|--------------|
119
- | `session-start.py` | `SessionStart` | Consolidates, recovers the latest checkpoint and long-term context, lists promotion candidates |
120
- | `pre-compact.py` | `PreCompact` | Retains a checkpoint episode, runs a consolidation pass |
121
- | `post-compact.py` | `PostCompact` | Re-injects the checkpoint, long-term context and promotion candidates |
119
+ | `session-start.py` | `SessionStart` | By `source`: on `startup` and `clear`, consolidates, recovers the latest checkpoint and long-term context, lists promotion candidates; on `compact`, the recovery after a compaction (the latest checkpoint the agent saved, with its age, and long-term context; no consolidation); on `resume` and `fork`, nothing |
120
+ | `pre-compact.py` | `PreCompact` | Runs a consolidation pass (Claude Code passes no summary here, so no checkpoint is retained) |
121
+ | `post-compact.py` | `PostCompact` | Retains Claude Code's compaction summary (`compact_summary`) as a checkpoint for the next session; prints nothing for the model |
122
+ | `session-end.py` | `SessionEnd` | Writes one line to the session spool in `MEMKERNEL_STATE_DIR` (no store, no model, within SessionEnd's 1.5-second budget); the next session start says when a session ended without a saved checkpoint. A session that never recorded an end (a crash, or this hook not registered) is dropped from the spool after 7 days, and the next start says how many |
123
+
124
+ Each injected memory line ends with its `ref_id`, so `get(ref_id)` returns the full text; a block never exceeds 9,000 characters (Claude Code caps hook output at 10,000), and a cut checkpoint says which `get` holds the rest. At session start the long-term context is the most important decisions, conventions, procedures and facts, not the matches of a fixed query; after a compaction it is recalled with the checkpoint's text. Register `SessionStart` without a matcher, so it runs for every `source`, and `session-end.py` under `SessionEnd`. Claude Code adds only `SessionStart`'s output to the model's context, not `PreCompact`'s or `PostCompact`'s, so the recovery after a compaction comes from `SessionStart` with `source: "compact"`. After upgrading memkernel, copy the three scripts again.
122
125
 
123
126
  See the Prerequisites section inside each script for the Python environment requirement. Each script is a thin wrapper over [`MemoryLifecycle`](#embedding-memkernel-in-your-own-harness), so an in-process harness gets the same behaviour without the scripts.
124
127
 
@@ -143,14 +146,14 @@ Kiro (and the Kiro CLI, formerly Amazon Q Developer CLI) connects to MemKernel a
143
146
  | Tool | What it does |
144
147
  |------|-------------|
145
148
  | `retain(content, type, session_id?, checkpoint?, source?, importance?, tags?, supersedes?)` | Store a memory. Returns `ref_id`. Idempotent — identical content + type returns the same `ref_id`. `supersedes` hides the memory it replaces. |
146
- | `recall(query, type?, tier?, session_id?, checkpoint_only?, top_k?, include_needs_review?)` | Search by natural language or identifier. Returns lightweight `ArtifactRef` list ranked by relevance + recency + importance. Records an access (reinforces retention). Code memories flagged `needs_review` are hidden unless `include_needs_review=true`. |
149
+ | `recall(query, type?, tier?, session_id?, checkpoint_only?, top_k?, include_needs_review?, order?)` | Search by natural language or identifier. Returns lightweight `ArtifactRef` list ranked by relevance + recency + importance. Records an access (reinforces retention). Code memories flagged `needs_review` are hidden unless `include_needs_review=true`. |
147
150
  | `get(ref_id)` | Fetch full content of a specific memory. Records an access. |
148
151
  | `forget(ref_id)` | Delete a memory from all indexes. |
149
152
  | `promote(ref_id, type, content?, importance?, tags?)` | Turn a short-term/working memory into a long-term one (you supply the distilled text). Keeps provenance via `promoted_from`. |
150
153
  | `consolidate(session_id?, limit?, synthesize?, revalidate?)` | Purge expired short-term memories and list the ones most worth promoting (ranked by importance × how often they were recalled). `synthesize=true` also asks an external reasoner to draft long-term statements (`suggested`); `revalidate=true` first checks every code memory against its source file (`revalidated`). The report always carries a `health` summary. |
151
154
  | `ask(ref_id, question, mode?)` | Answer a question about one memory without loading it — for a `document` the full body is consulted. `mode`: `direct` \| `rag` \| `rlm` \| `auto`, passed to the reasoner unchanged; the built-in reasoner answers in `rag` mode with the best-matching chunks. Returns `answer`, `mode`, `citations`, `usage`. |
152
155
  | `wiki_export(wiki_dir?)` | Compile long-term memories without a wiki page into draft markdown pages (`index.md`, `log.md` updated). |
153
- | `wiki_import(wiki_dir?)` | Index curated wiki pages as long-term memories (idempotent; changed pages supersede). |
156
+ | `wiki_import(wiki_dir?, adopt_legacy?, adopt_pages?, adopt_manifest?)` | Index wiki pages as long-term memories, each wiki keeping to its own: approved revisions only with wiki-studio ≥ 0.7.0, every page without it (idempotent; changed pages supersede; `corrects` supersedes the memories a page corrects). |
154
157
 
155
158
  ### Memory types and tiers
156
159
 
@@ -170,13 +173,13 @@ Retention: `ttl = base_ttl × (1 + access_boost × access_count)`, measured from
170
173
 
171
174
  ### Session protocol
172
175
 
173
- 1. **Session start** — `recall(tier="long_term", query=<task>)`, plus the latest checkpoint (`checkpoint_only=true`). The `session-start.py` hook does this and prints promotion candidates.
176
+ 1. **Session start** — `recall(tier="long_term", query=<task>)`, plus the latest checkpoint (`checkpoint_only=true, order="newest"`: newest first, not the most relevant). The `session-start.py` hook does this and prints promotion candidates; after a compaction it prints a shorter recovery block instead.
174
177
  2. **While working** — `retain(type="task")` when the plan changes; `retain(type="observation")` for findings; durable knowledge as `decision` / `convention` / `fact` / `procedure`.
175
- 3. **Before compaction** — `retain(type="episode", checkpoint=true)` (the `pre-compact.py` hook) and `consolidate()`.
178
+ 3. **Before compaction** — `retain(type="episode", checkpoint=true)` with your own summary of where the work stands; the `pre-compact.py` hook runs `consolidate()`, and the `post-compact.py` hook keeps Claude Code's compaction summary as a checkpoint.
176
179
  4. **After compaction / end of phase** — `promote(ref_id, type, content)` for the candidates worth keeping.
177
180
  5. **Large artefacts** — `retain(type="document", content=<body>, source=<name>)` then `ask(ref_id, question)`; never `get` a document to read it.
178
181
  7. **Code snapshots** — `retain(type="code", content=<file>, source=<path>)`; retain the file again after editing it (the new snapshot supersedes the old one). The session-start hook revalidates snapshots against their files and lists the ones that need re-retaining.
179
- 6. **Curate** — `wiki_export` → review the drafts → `wiki_import`.
182
+ 6. **Curate** — `wiki_export` → review and approve the drafts in wiki-studio → `wiki_import`.
180
183
 
181
184
  ### Quick examples
182
185
 
@@ -189,7 +192,7 @@ retain(
189
192
 
190
193
  recall(query="database choice", top_k=5)
191
194
 
192
- recall(query="session checkpoint", checkpoint_only=True)
195
+ recall(query="session checkpoint", checkpoint_only=True, order="newest", top_k=1) # the latest checkpoint
193
196
 
194
197
  retain(content="pytest hangs unless MEMKERNEL_PROJECT_ID is set", type="observation", tags=["ci"])
195
198
 
@@ -220,7 +223,7 @@ setting is logged as a warning at startup.
220
223
  |----------|---------|-------------|
221
224
  | `MEMKERNEL_PROJECT_ID` | `default` | Project namespace — memories are isolated per project |
222
225
  | `MEMKERNEL_DEFAULT_SESSION_ID` | `default` | Session tag applied when `session_id` is omitted from `retain` |
223
- | `MEMKERNEL_CHROMA_PERSIST_PATH` | `.memkernel/chroma` | ChromaDB data directory. Empty string = in-memory (ephemeral) |
226
+ | `MEMKERNEL_CHROMA_PERSIST_PATH` | `.memkernel/chroma` | ChromaDB data directory. Empty string = in-memory (ephemeral). On POSIX systems (Linux, macOS), opening it takes a short lock on `.memkernel-open.lock` inside it, so the MCP server and a hook opening a new store at once do not collide; where `fcntl` is unavailable (Windows) the store opens without the lock, and two processes creating a new store at the same moment can still fail |
224
227
  | `MEMKERNEL_CHROMA_COLLECTION_PREFIX` | `memkernel` | Prefix for ChromaDB collection names (`{prefix}_{project_id}`) |
225
228
  | `MEMKERNEL_EMBEDDING_MODEL` | `all-MiniLM-L6-v2` | Sentence-transformers model name |
226
229
  | `MEMKERNEL_EMBEDDING_DIMENSION` | `384` | Output dimension of the embedding model |
@@ -246,7 +249,10 @@ setting is logged as a warning at startup.
246
249
  | `MEMKERNEL_RECALL_WEIGHT_RECENCY` | `0.3` | Weight of recency in the final recall score |
247
250
  | `MEMKERNEL_RECALL_WEIGHT_IMPORTANCE` | `0.3` | Weight of importance in the final recall score |
248
251
  | `MEMKERNEL_WIKI_DIR` | `` | Directory of the LLM wiki used by `wiki_export` / `wiki_import` (empty = pass `wiki_dir` explicitly) |
252
+ | `MEMKERNEL_STATE_DIR` | `.memkernel/state` | memkernel's own state outside ChromaDB: the wiki sync ledger. Resolved like `MEMKERNEL_CHROMA_PERSIST_PATH` (a relative path is relative to the process's working directory, so set it absolute when the persist path is); keep it out of git |
249
253
  | `MEMKERNEL_WIKI_PROPOSALS_DIR` | `""` | Where wiki-studio patch-sets are written before promote (`memkernel[wiki]`); empty means `<project root>/.memkernel/wiki-proposals`. Setting it requests the gate: without wiki-studio installed, a warning is logged. |
254
+ | `MEMKERNEL_WIKI_MODE` | `auto` | `auto`: wiki-studio's gate and review state when wiki-studio 0.7.0 or later is installed, the direct writer and file-reading import when it is not. `governed`: `wiki_export` and `wiki_import` refuse, writing nothing, unless wiki-studio is installed with every capability memkernel uses and the wiki's manifest is `governed` (`approval_required: true`) without schema drift. `ungated`: wiki-studio is never loaded. Responses report `mode` (`gated` or `ungated`, the path that ran) and `configured_mode` (this setting). |
255
+ | `MEMKERNEL_WIKI_EXPORT_BATCH` | `10` | New pages one `wiki_export` writes at most, the most important memories first; the response's `remaining` counts the rest (`null` when the gate's outcome is uncertain or `governed` refused before reading the wiki). `0` = no limit; a negative value is refused |
250
256
  | `MEMKERNEL_TRANSPORT` | `stdio` | `stdio` or `streamable-http` |
251
257
  | `MEMKERNEL_HOST` | `127.0.0.1` | Host for `streamable-http` transport |
252
258
  | `MEMKERNEL_PORT` | `8765` | Port for `streamable-http` transport |
@@ -288,11 +294,13 @@ for ref in svc.recall("database"):
288
294
  report = svc.consolidate() # {"purged": n, "candidates": [...]}
289
295
  ```
290
296
 
291
- `build_service()` is the supported entry point; the MCP server, the admin API and the hook templates all use it. Integration notes for `code-copilot-team` and `haa` are in [`docs/integration/harness-integration.md`](docs/integration/harness-integration.md).
297
+ `build_service()` is the supported entry point; the MCP server, the admin API and the hook templates all use it. If you pass a `PersistentClient` as `chroma_client` and also set `MEMKERNEL_CHROMA_PERSIST_PATH` (in the environment or in the settings), both must name the same directory, or `ValueError` is raised; with the default setting, the client's own directory is used.
298
+
299
+ **Store locks.** Processes that share a store (the MCP server, a hook, a script) coordinate through locks keyed by the store itself: lock files in the persist directory, `.memkernel-<name>-<key>.lock`, whatever each process's `MEMKERNEL_STATE_DIR` is; an in-memory store's locks are per process. Only one writer changes a record's tags, under the store's `tags` lock, so a tag added by one process is never dropped by another's write. Locks are not re-entrant (taking one the thread already holds raises `LockReentryError`), a wait longer than 10 seconds is logged once, and spelling the persist directory with different letter case in two processes gives two locks, so use one spelling. The locks use `fcntl` and are verified on POSIX (Linux, macOS) only; on Windows they run unlocked across processes, with one logged warning per lock. Integration notes for `code-copilot-team` and `haa` are in [`docs/integration/harness-integration.md`](docs/integration/harness-integration.md).
292
300
 
293
301
  ### The session protocol as a component
294
302
 
295
- `MemoryLifecycle` packages what the hook scripts do, so a harness calls four methods instead of re-implementing the protocol. Every method returns markdown ready for injection and does nothing but talk to the service:
303
+ `MemoryLifecycle` packages what the hook scripts do, so a harness calls five methods instead of re-implementing the protocol. Every method returns markdown ready for injection and does nothing but talk to the service:
296
304
 
297
305
  ```python
298
306
  from memkernel.factory import build_service
@@ -301,9 +309,13 @@ from memkernel.lifecycle import MemoryLifecycle
301
309
  memory = MemoryLifecycle(build_service())
302
310
  system_context = memory.on_session_start() # checkpoint, long-term context, promotion candidates, wiki pointer
303
311
  ...
304
- memory.on_pre_compact(summary) # checkpoint episode + consolidation, before the context is compacted
305
- recovered = memory.on_post_compact() # what to re-inject after compaction
312
+ memory.on_pre_compact(summary) # checkpoint episode (only if summary is not blank) + consolidation
313
+ recovered = memory.on_post_compact() # what to re-inject after your own compaction
306
314
  memory.on_session_end(summary) # final checkpoint + consolidation
315
+
316
+ # Driving it from Claude Code's hook events instead (what templates/hooks/ do):
317
+ memory.on_session_start(source) # source "startup"/"clear": full block; "compact": recovery block; "resume"/"fork": ""
318
+ memory.capture_compact_summary(compact_summary, session_id=..., trigger=...) # keep Claude Code's own summary as a checkpoint
307
319
  ```
308
320
 
309
321
  ### Documents and `ask`
@@ -323,12 +335,41 @@ memory.on_session_end(summary) # final checkpoint + consolidat
323
335
 
324
336
  Long-term memory has a human-readable twin: a markdown wiki in the [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern (`index.md`, `log.md`, typed pages with cited sources — the same schema as `code-copilot-team/knowledge/wiki`).
325
337
 
326
- - `wiki_export` compiles every long-term memory that has no page yet into a **draft** page under `decisions/`, `concepts/` or `workflows/`, with the required sections and a `memkernel://<project>/<ref_id>` citation. Existing pages are never overwritten.
327
- - `wiki_import` indexes curated pages as long-term memories so `recall` finds the reviewed version; a changed page supersedes its earlier memory.
338
+ To see the whole loop on a clean install, follow wiki-studio's [memkernel composition quickstart](https://github.com/gosha70/wiki-studio/blob/main/docs/quickstart-memkernel.md): retain a memory, export it through wiki-studio's gate, correct and approve the page, import only the approved revision, and see the next `recall` return it. CI runs the quickstart against memkernel **0.6.0**.
328
339
 
340
+ - `wiki_export` compiles every long-term memory that has no page yet into a **draft** page under `decisions/`, `concepts/` or `workflows/`, with the required sections and a `memkernel://<project>/<ref_id>` citation. Existing pages are never overwritten.
341
+ - `wiki_import` indexes pages as long-term memories so `recall` finds the reviewed version; a changed page supersedes its earlier memory. With wiki-studio 0.7.0 or later installed it is **governed** (`"governed": true`): it imports only pages wiki-studio's review state reports approved, and only when the page's bytes are still the approved revision, so drafts, self-declared approvals and pages edited after approval are not imported (`not_approved` says why); an edited page keeps its last approved memory until it is approved again. memkernel never approves a page; a curator does, with `wiki-studio approve`. Without wiki-studio the import reads the page files, drafts included, and reports `"governed": false`. An installed wiki-studio that is broken or older than 0.7.0 imports nothing and reports `import_error`.
342
+ - **Withdrawal (governed import, 0.7).** A page's memory leaves the current answers, its record kept for history, when:
343
+ - the page is **deleted**. Only memories this wiki's sync ledger recorded as imported are withdrawn this way; a memory imported before 0.7, or without wiki-studio, never is.
344
+ - the page is **deprecated**, once the `status: deprecated` revision is approved.
345
+ - the page is **superseded** (`superseded_by: <page path>`). Its memory is replaced by the successor page's memory; if the successor has none yet, the old memory is withdrawn and linked once it does (`pending` until then).
346
+
347
+ The response lists `withdrawn`, `superseded`, `pending` and `sync_failed` (retried at the next import). The ledger is one JSON file per project and wiki under `MEMKERNEL_STATE_DIR/wiki-sync/`; a corrupt one is kept aside, reported as `ledger_error`, and rebuilt.
348
+ - **Corrections.** An approved page can name the memories it corrects in its frontmatter: `corrects: <ref_id>, <ref_id>` (one line; tokens of letters, digits, `-` and `_`). On import each named memory that did not come from the wiki is superseded by the page's memory: it stops being a current answer, and its record is kept. References that cannot be applied are listed in `unresolved` with a reason (unknown, another project's, already superseded, or a wiki memory: correct that page instead); a value that breaks the grammar is listed in `invalid_corrects`, and the page is still imported. An approved page that is already current is listed in `unchanged`, and its corrections are reconciled again on every import, so a reference that could not be applied earlier takes effect once it can; corrections already applied are not repeated.
349
+
350
+ - **Each wiki keeps to its own memories (0.7).** Every memory `wiki_import` writes, or finds current, carries its wiki's identity: `wiki-path:<hash>` (the wiki directory's path relative to the project root) and, when the wiki has a wiki-studio manifest, `wiki-id:<uuid>`. A page's previous memory, its corrections, its supersession and its deprecation are only that wiki's, so two wikis in one store may share page paths and `page_id`s. These tags are reserved: `retain` refuses them, and an ordinary `retain` whose text matches a wiki memory gets its own record instead of merging into it; `promote` drops every wiki tag from its copy.
351
+ - **A wiki that may move, or that shares a store with another wiki, needs a manifest.** The path a wiki is known by is relative to its project root: two project roots sharing one store, with wikis at the same relative path, get the same `wiki-path:`, so the second import is refused (memories at this path belong to another wiki) even with manifests. Use one store per project, or different relative paths. Without one, a wiki *is* its path: moved, it starts afresh, and whatever wiki is later placed at the old path continues its memories. A copied wiki directory (two directories, one `wiki_id`) is one wiki: each import moves its memories to the directory imported last.
352
+ - **Upgrading a 0.6 store:** memories written before 0.7 carry no identity, so the first import writes nothing for their pages and lists them under `ambiguous` (`{"path", "ref_ids", "reason"}`). Import once with `adopt_legacy=true` to adopt each page's sole such memory; a page with several stays `ambiguous` (forget the ones that are not its own). With several wikis in one store, name the records instead: `adopt_pages=[{"path", "ref_id"}, …]` copied from `ambiguous`; a named record that changed meanwhile is not adopted (`candidate changed`).
353
+ - **Identity changes stop the import before any write, with the remedy:** a wiki that gained a manifest needs `adopt_manifest=true` once (its sync ledger moves to the `wiki_id` key; if both ledger files exist, delete the stale one); memories at this path that belong to another `wiki_id` (the manifest removed or replaced, or a moved wiki not yet imported from its new place) stop it until the manifest is restored, the moved wiki is imported first, or those memories are forgotten. A wiki with a manifest that moves keeps its memories; the next import moves their path.
354
+ - One import runs at a time per store (the `wiki-import` store lock). On Windows the import runs unlocked with one warning, as in 0.6: two concurrent imports of one wiki can leave two records for a page (the next import reports it as `duplicate records`; forget one), and two wikis adopting the same record at once can tag it for one while the other supersedes it. Give processes that share a store the same `MEMKERNEL_STATE_DIR`, so they share the sync ledger.
329
355
  Set `MEMKERNEL_WIKI_DIR` (for example `knowledge/wiki`) or pass `wiki_dir` to the tools. The standalone maintainer that owns this page schema is [wiki-studio](https://github.com/gosha70/wiki-studio). With the `memkernel[wiki]` extra installed, every `wiki_export` response carries wiki-studio's lint and health findings for the whole wiki after the export, curated pages included, under `lint` (`violations`, `audit_errors`, `pages_checked`, `health` items with `kind`, `severity`, `pages`, `description`); without it `lint` is `null` and `lint_hint` says how to enable it; if the lint pass itself fails, `lint` is `null` and `lint_error` says why. The files written are the same in every case.
330
356
 
331
- With wiki-studio 0.3.1 or later installed, `wiki_export` writes through wiki-studio's promote gate: it first writes `index.md` and `log.md` if they are missing (the gate cannot create them), then proposes every new page with its index and log lines as one patch-set and promotes it. A rejected export writes nothing and the response carries `gate_error`; so does an installed wiki-studio that fails to import or to build the patch-set, rather than falling back to the direct writer. A failed export leaves its patch-set under the proposals directory for inspection; delete it once the cause is fixed. Patch-sets go to `MEMKERNEL_WIKI_PROPOSALS_DIR`, by default `.memkernel/wiki-proposals/` under the project root. Commit `<wiki>/.audit/`, wiki-studio's audit trail of promoted proposals; ignore `.memkernel/wiki-proposals/`.
357
+ With wiki-studio 0.7.0 or later installed, `wiki_export` writes through wiki-studio's promote gate: wiki-studio's `init` creates `index.md` and `log.md` if they are missing (wiki-studio's own scaffold, `status: stable`), then every new page goes with its index and log lines into one patch-set that is proposed and promoted. Other writers can work in the same wiki: wiki-studio's commit writes only the paths the patch-set names, under its lock, and refuses a page someone else created in the meantime. A rejected export writes no page and the response carries `gate_error` (a new wiki keeps the `index.md` and `log.md` that `init` created); so does an installed wiki-studio that is broken or older than 0.7.0, rather than falling back to the direct writer. A promoted export's response adds `proposal`, `submission_digest` and `archived_dir` from wiki-studio's receipt. If `archived_dir` is `null`, the pages landed but wiki-studio did not finish: it could not archive the proposal, or it failed after committing the pages (memkernel checks wiki-studio's receipt: while it still says `committing`, every planned write, index and log included, must be at its post-write sha256; a `committed` receipt needs memkernel's pages at theirs; an unusable receipt falls back to comparing the pages' bytes). `archive_hint` gives the `promote` command that tries to finish it, with absolute paths, so it runs from any directory. It applies nothing twice, but if the wiki or the proposal changed since, for example another writer appended to `index.md`, it may refuse with `uncertain_outcome` or `payload_changed` and write nothing; then compare the wiki with the proposal's `receipt.json` by hand. memkernel never runs it itself. If wiki-studio failed during its commit and its receipt shows neither a commit nor a rollback (say the page was written, the `index.md` write failed and removing the page failed too), the response carries `gate_error` with `uncertain_outcome` and `proposal`: `written` is empty, but the pages it names may be on disk; compare the wiki with the `receipt.json` it names before exporting again. `receipt_error` says the receipt could not be read or used. A failed export leaves its patch-set under the proposals directory for inspection; delete it once the cause is fixed. Patch-sets go to `MEMKERNEL_WIKI_PROPOSALS_DIR`, by default `.memkernel/wiki-proposals/` under the project root. Commit `<wiki>/.audit/`, wiki-studio's audit trail of promoted proposals; ignore `.memkernel/wiki-proposals/`, `.memkernel/state/` (memkernel's sync ledger) and wiki-studio's files next to the wiki directory, `.<wiki-dir>.wiki-studio.*` (for `knowledge/wiki`, `knowledge/.wiki.wiki-studio.*`: its lock file and, from wiki-studio 0.8, its search cache).
358
+
359
+ **Wiki modes.** `MEMKERNEL_WIKI_MODE` is a property of the deployment, not of a call. `governed` runs one preflight before any page is written or read, for export and import alike: wiki-studio 0.7.0 or later installed and working; its `info` listing the capabilities `wiki-lock`, `receipts`, `root-manifest`, `page-id`, `approval-record`, `superseded-by` and `findings`; the wiki's manifest mode `governed`; no schema drift. A `managed` or `legacy` wiki is refused with the remedy: run `wiki-studio init` if there is no manifest, then set `approval_required: true` in the manifest's policy. A refusal reports `gate_error` (export) or `import_error` (import) and writes nothing; `update_index=False` is refused too, since the direct writer is never used. A gate rejection after the preflight commits no drafted page, but a new wiki keeps the `index.md` and `log.md` that wiki-studio's `init` created: memkernel keeps no outer rollback. `ungated` never loads wiki-studio (no gate, lint, review state or findings): `lint` is `null` and `lint_hint` names the mode.
360
+
361
+ **Export batches and grounding.** One `wiki_export` writes at most `MEMKERNEL_WIKI_EXPORT_BATCH` new pages (by importance, highest first, then oldest, then `ref_id`), so a curator reviews a small batch at a time; `remaining` counts the eligible memories still without a page, all of them after a rejected batch. Every exported page carries `memkernel_sha256`, the memory's content hash, beside `memkernel_ref`; a `path:` source gains `sha:`, the commit it is grounded at (`git log -1 --format=%h`, wiki-studio's citation rule), only when the project's `git ls-files` lists exactly that file and `git status` shows no change to it: a modified, staged or untracked file, a file in a submodule, or no git gets none. Pages created through the gate carry wiki-studio's `page_id`, so a page renamed or moved under its `page_id` keeps its memory: the next import supersedes the memory at the old path with one at the new path, also when the moved page's bytes are unchanged.
362
+
363
+ **Curation debt at session start.** With `MEMKERNEL_WIKI_DIR` set and wiki-studio available (in `governed`, only after the preflight passes), the session-start block (`startup`, `clear`) gains `### Curation debt`: one line of wiki-studio's `findings` counts in a fixed order (review kinds first) plus the sync ledger's unfinished actions (`sync pending`, `sync failed`), up to three example pages, and a hint to run `wiki-studio findings`. It uses at most 500 characters, taken from the long-term context's share, so the block stays under 9,000 characters. The check runs in a child process bounded to 2.5 seconds once it has started (a fork or exec the operating system holds up is not covered); it reads the sync ledger without changing it and never waits for an import that holds it. Any failure is one `Curation debt unavailable: <reason>` line. Not on `compact`, `resume` or `fork`, nor in `ungated`.
364
+
365
+ **wiki-studio's files beside the wiki, its ingest log, and wiki-studio 0.8.** When memkernel exports through the gate, wiki-studio creates its lock file, `.<wiki-dir>.wiki-studio.lock`, beside the wiki directory, and from 0.8 a curator's `find` or `query` adds a search cache, `.<wiki-dir>.wiki-studio.search.sqlite`, there too: for a wiki kept in git, ignore both with `.<wiki-dir>.wiki-studio.*` (for `knowledge/wiki`, `knowledge/.wiki.wiki-studio.*`). memkernel creates no file of its own there. memkernel reads no wiki-studio ingest log, so the log's `hash_recipe` has no consumer in memkernel. From wiki-studio 0.8.0 a page that is not valid UTF-8 fails lint for the whole wiki, so every promote, memkernel's gated export included, is refused with a `gate_error` naming that page until it is re-encoded or removed. From 0.8.0 an unknown `WIKI_STUDIO_RANKER` value makes wiki-studio's `info` fail, so a `governed` export or import, a gated import and the curation debt refuse with wiki-studio's message naming the variable. memkernel needs wiki-studio 0.7.0 or later and is tested at 0.7.0; an informational CI job runs the wiki tests against the newest release below 1.0 on every pull request and weekly.
366
+
367
+ wiki-studio's MCP server (`wiki-studio-mcp`, from wiki-studio 0.8.0) needs `mcp>=2.3`, which memkernel's `mcp<2` excludes. Run it in its own environment, at the same wiki-studio version as memkernel's:
368
+
369
+ 1. In memkernel's environment, read the version: `python -c "import importlib.metadata as m; print(m.version('wiki-studio'))"`. If it is below 0.8.0, upgrade it there first: `pip install 'wiki-studio>=0.8,<1'`.
370
+ 2. Install exactly that version for the server, for example `pipx install 'wiki-studio[mcp]==0.8.1'`, and register it beside memkernel's server.
371
+
372
+ On one machine and one filesystem, wiki-studio's lock serializes the two servers' writes to the same wiki. It does not coordinate other hosts, and network filesystems are outside its scope.
332
373
 
333
374
  Rationale: [`docs/adr/ADR-008-llm-wiki-as-semantic-layer.md`](docs/adr/ADR-008-llm-wiki-as-semantic-layer.md) and [`docs/adr/ADR-010-wiki-studio-as-wiki-maintainer.md`](docs/adr/ADR-010-wiki-studio-as-wiki-maintainer.md).
334
375
 
@@ -373,9 +414,15 @@ CI runs on every push and PR: lint, type check, tests (≥ 80% coverage), securi
373
414
 
374
415
  Releases are published to PyPI by [`.github/workflows/release.yml`](.github/workflows/release.yml) using trusted publishing (no token in the repo). To cut a release:
375
416
 
376
- 1. Set `version` in `pyproject.toml` and move the `[Unreleased]` entries in [`CHANGELOG.md`](CHANGELOG.md) under the new version.
417
+ 1. Set `version` in `pyproject.toml` and `__version__` in `src/memkernel/__init__.py` to `X.Y.Z`, and move the `[Unreleased]` entries in [`CHANGELOG.md`](CHANGELOG.md) under `[X.Y.Z] - <tag date>`.
377
418
  2. Merge to `main` with CI green.
378
- 3. Tag and push: `git tag -a v0.4.0 -m "memkernel 0.4.0" && git push origin v0.4.0`.
419
+ 3. Tag and push: `git tag -a vX.Y.Z -m "memkernel X.Y.Z" && git push origin vX.Y.Z`.
420
+ 4. Create the GitHub Release from the tag, with the version's CHANGELOG section as its notes:
421
+
422
+ ```bash
423
+ awk -v v=X.Y.Z 'index($0, "## [" v "]") == 1 {f=1; next} f && /^## \[/ {exit} f' CHANGELOG.md > release-notes.md
424
+ gh release create vX.Y.Z --title X.Y.Z --notes-file release-notes.md --verify-tag
425
+ ```
379
426
 
380
427
  The workflow refuses a tag that does not match the `pyproject.toml` version, builds the sdist and wheel, smoke-installs the wheel, and uploads through the `pypi` environment.
381
428
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "memkernel"
3
- version = "0.5.0"
3
+ version = "0.7.0"
4
4
  description = "Code-aware memory server for AI coding copilots"
5
5
  authors = ["EGOGE"]
6
6
  license = "Apache-2.0"
@@ -32,7 +32,7 @@ pydantic = ">=2.0,<3"
32
32
  pydantic-settings = ">=2.0,<3"
33
33
  fastapi = ">=0.115,<1"
34
34
  # Optional integrations (ADR-010): installed only through an extra; imported lazily.
35
- wiki-studio = { version = ">=0.3.1,<1", optional = true }
35
+ wiki-studio = { version = ">=0.7.0,<1", optional = true }
36
36
 
37
37
  [tool.poetry.extras]
38
38
  wiki = ["wiki-studio"]
@@ -45,7 +45,7 @@ ruff = ">=0.9"
45
45
  mypy = ">=1.13"
46
46
  httpx = ">=0.27"
47
47
  pip-audit = "^2.10.0"
48
- wiki-studio = ">=0.3.1,<1"
48
+ wiki-studio = ">=0.7.0,<1"
49
49
 
50
50
  [build-system]
51
51
  requires = ["poetry-core"]
@@ -2,4 +2,4 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """MemKernel — Code-aware memory server for AI coding copilots."""
4
4
 
5
- __version__ = "0.5.0"
5
+ __version__ = "0.7.0"
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
  import logging
8
8
  from typing import TYPE_CHECKING, Any, Literal
9
9
 
10
+ from pydantic import Field
10
11
  from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict
11
12
 
12
13
  if TYPE_CHECKING:
@@ -34,6 +35,10 @@ class MemKernelSettings(BaseSettings):
34
35
  chroma_persist_path: str = ".memkernel/chroma"
35
36
  chroma_collection_prefix: str = "memkernel"
36
37
 
38
+ # memkernel's own state outside Chroma (memkernel-0-7): the wiki sync ledger. Resolved like
39
+ # chroma_persist_path: a relative path is relative to the working directory of the process.
40
+ state_dir: str = ".memkernel/state"
41
+
37
42
  # Embedding
38
43
  embedding_model: str = "all-MiniLM-L6-v2"
39
44
  embedding_dimension: int = 384
@@ -79,6 +84,10 @@ class MemKernelSettings(BaseSettings):
79
84
  # Where wiki-studio patch-sets are written before promote (memkernel[wiki], ADR-010).
80
85
  # Empty = <project_root>/.memkernel/wiki-proposals; set it to request the gate explicitly.
81
86
  wiki_proposals_dir: str = ""
87
+ # Release B part 2c (G1): auto = the gate when wiki-studio is installed, the direct writer otherwise; governed =
88
+ # wiki-studio and a governed wiki required, refused otherwise; ungated = wiki-studio is never loaded.
89
+ wiki_mode: Literal["auto", "governed", "ungated"] = "auto"
90
+ wiki_export_batch: int = Field(default=10, ge=0) # new pages per wiki_export, most important first (G3); 0 = none
82
91
 
83
92
  # Server
84
93
  transport: Literal["stdio", "streamable-http"] = "stdio"