@cairnkeep/cli 2.3.0 → 2.4.0

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 (125) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +221 -6
  3. package/bin/cairn +43 -2
  4. package/claude/capability-contract/hooks/capability-command-finish.sh +81 -0
  5. package/claude/capability-contract/hooks/capability-command-start.sh +76 -0
  6. package/claude/hooks/compaction-capture.sh +34 -0
  7. package/claude/hooks/memory-capture.sh +47 -5
  8. package/claude/hooks/memory-wakeup.sh +55 -2
  9. package/docs/building-an-overlay.md +1 -1
  10. package/docs/design/meta-agent.md +439 -0
  11. package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
  12. package/docs/learning/lessons/L01-safe-trial.md +3 -3
  13. package/docs/learning/lessons/L02-installation.md +2 -2
  14. package/docs/learning/lessons/L03-first-project.md +1 -1
  15. package/docs/operating.md +531 -4
  16. package/docs/privacy-and-data-flow.md +417 -0
  17. package/docs/storage.md +252 -0
  18. package/examples/eval/README.md +42 -0
  19. package/examples/eval/adapter.json +12 -0
  20. package/examples/eval/bundled-fake.json +1 -0
  21. package/examples/eval/task-set.json +1 -0
  22. package/mcp-memory-server/dist/artifact-cli.js +184 -0
  23. package/mcp-memory-server/dist/artifact-schema.js +218 -0
  24. package/mcp-memory-server/dist/artifact-store.js +812 -0
  25. package/mcp-memory-server/dist/capability-adapter.js +286 -0
  26. package/mcp-memory-server/dist/capability-cli.js +361 -0
  27. package/mcp-memory-server/dist/capability-config.js +310 -0
  28. package/mcp-memory-server/dist/capability-harness.js +496 -0
  29. package/mcp-memory-server/dist/capability-registry.js +123 -0
  30. package/mcp-memory-server/dist/capability-schema.js +65 -0
  31. package/mcp-memory-server/dist/capability-store.js +431 -0
  32. package/mcp-memory-server/dist/compaction-normalize.js +267 -0
  33. package/mcp-memory-server/dist/eval-cli.js +523 -0
  34. package/mcp-memory-server/dist/eval-plan.js +346 -0
  35. package/mcp-memory-server/dist/eval-process.js +273 -0
  36. package/mcp-memory-server/dist/eval-report.js +609 -0
  37. package/mcp-memory-server/dist/eval-runner.js +827 -0
  38. package/mcp-memory-server/dist/eval-schema.js +368 -0
  39. package/mcp-memory-server/dist/eval-statistics.js +161 -0
  40. package/mcp-memory-server/dist/eval-workspace.js +313 -0
  41. package/mcp-memory-server/dist/failure-signature.js +141 -0
  42. package/mcp-memory-server/dist/index.js +600 -55
  43. package/mcp-memory-server/dist/node-cli.js +84 -0
  44. package/mcp-memory-server/dist/node-schema.js +170 -0
  45. package/mcp-memory-server/dist/node-store.js +455 -0
  46. package/mcp-memory-server/dist/note-cli.js +235 -0
  47. package/mcp-memory-server/dist/note-distiller.js +177 -0
  48. package/mcp-memory-server/dist/note-enrichment.js +93 -0
  49. package/mcp-memory-server/dist/note-schema.js +4 -0
  50. package/mcp-memory-server/dist/note-store.js +985 -0
  51. package/mcp-memory-server/dist/trajectory-cli.js +141 -0
  52. package/mcp-memory-server/dist/trajectory-normalize.js +448 -0
  53. package/mcp-memory-server/dist/trajectory-redaction.js +155 -0
  54. package/mcp-memory-server/dist/trajectory-schema.js +73 -0
  55. package/mcp-memory-server/dist/trajectory-store.js +339 -0
  56. package/mcp-memory-server/package.json +24 -2
  57. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.219.json +9 -0
  58. package/mcp-memory-server/scripts/fixtures/compaction-claude-code-2.1.220.json +9 -0
  59. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-event.json +6 -0
  60. package/mcp-memory-server/scripts/fixtures/compaction-opencode-1.17.20-messages.json +119 -0
  61. package/mcp-memory-server/scripts/fixtures/compaction-unknown-version.json +17 -0
  62. package/opencode/capability-contract/plugins/capability-command.ts +319 -0
  63. package/opencode/command/graphify.md +25 -14
  64. package/opencode/plugins/memory-capture.ts +76 -10
  65. package/opencode/plugins/memory-wakeup.ts +58 -6
  66. package/package.json +9 -4
  67. package/pi/extensions/cairnkeep-trajectory.ts +55 -0
  68. package/schemas/artifact.schema.json +133 -0
  69. package/schemas/capability-callback.schema.json +68 -0
  70. package/schemas/capability-contract.schema.json +126 -0
  71. package/schemas/eval-adapter.schema.json +43 -0
  72. package/schemas/eval-protocol.schema.json +114 -0
  73. package/schemas/eval-report.schema.json +423 -0
  74. package/schemas/eval-task-set.schema.json +114 -0
  75. package/schemas/memory-node.schema.json +90 -0
  76. package/schemas/note.schema.json +112 -0
  77. package/schemas/trajectory-redaction.schema.json +25 -0
  78. package/schemas/trajectory.schema.json +82 -0
  79. package/scripts/bootstrap.sh +4 -0
  80. package/scripts/completion.sh +119 -5
  81. package/scripts/doctor.sh +284 -0
  82. package/scripts/fake-eval-adapter.mjs +120 -0
  83. package/scripts/fixtures/capability-harness-contracts.json +408 -0
  84. package/scripts/install-audit-timer.sh +3 -1
  85. package/scripts/lib/capability-opencode-plugin-harness.mjs +291 -0
  86. package/scripts/lib/trajectory-opencode-plugin-harness.mjs +25 -0
  87. package/scripts/lib/trajectory-pi-extension-harness.mjs +42 -0
  88. package/scripts/memory-wiki-audit.sh +49 -0
  89. package/scripts/smoke-bash32.sh +9 -0
  90. package/scripts/sync-claude-assets.sh +99 -6
  91. package/scripts/sync-opencode-graphify-assets.sh +24 -3
  92. package/scripts/sync-opencode-plugin-assets.sh +31 -5
  93. package/scripts/sync-opencode-security-assets.sh +24 -3
  94. package/scripts/sync-opencode-wiki-assets.sh +24 -3
  95. package/scripts/sync-pi-assets.sh +72 -0
  96. package/scripts/test-audit-timer.sh +98 -0
  97. package/scripts/test-bootstrap-untracked.sh +48 -0
  98. package/scripts/test-cli-dispatch.sh +41 -2
  99. package/scripts/test-compaction-hooks.sh +495 -0
  100. package/scripts/test-completion.sh +77 -0
  101. package/scripts/test-doctor.sh +348 -1
  102. package/scripts/test-eval-cli.sh +175 -0
  103. package/scripts/test-launcher-hooks.sh +602 -3
  104. package/scripts/test-notes-cli.sh +114 -0
  105. package/scripts/test-package-install.sh +251 -0
  106. package/scripts/test-phase16-node-lifecycle.sh +50 -0
  107. package/scripts/test-phase17-artifact-lifecycle.sh +370 -0
  108. package/scripts/test-phase18-capability-lifecycle.sh +504 -0
  109. package/scripts/test-phase18-harness-boundary.sh +1060 -0
  110. package/scripts/test-phase19-eval-lifecycle.sh +1015 -0
  111. package/scripts/test-trajectory-cli.sh +87 -0
  112. package/scripts/test-trajectory-hooks.sh +142 -0
  113. package/scripts/test-uninstall.sh +231 -5
  114. package/scripts/uninstall.sh +37 -5
  115. package/scripts/verify-dependency-security.mjs +121 -0
  116. package/scripts/verify-docs-parity.sh +527 -1
  117. package/scripts/verify-phase18-runtime-evidence.mjs +895 -0
  118. package/scripts/verify-phase19-runtime-evidence.mjs +947 -0
  119. package/templates/capabilities.json.template +7 -0
  120. package/templates/env.example.template +51 -0
  121. package/templates/memory-wiki-audit.service.template +1 -1
  122. package/templates/start-claude.sh.template +36 -0
  123. package/templates/start-opencode.sh.template +47 -5
  124. package/templates/start-pi.sh.template +30 -0
  125. package/templates/trajectory-redaction.json.template +4 -0
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart
5
5
  **Time:** 25 minutes
6
- **Tested with:** Cairnkeep 2.3.0 and Node.js 22 or newer
6
+ **Tested with:** Cairnkeep 2.4.0 and Node.js 22 or newer
7
7
 
8
8
  ## Outcome
9
9
 
@@ -40,7 +40,7 @@ machine-level steps.
40
40
  2. Install Cairnkeep:
41
41
 
42
42
  ```bash
43
- npm install --global @cairnkeep/cli@2.3.0
43
+ npm install --global @cairnkeep/cli@2.4.0
44
44
  cairn --version
45
45
  ```
46
46
 
@@ -3,7 +3,7 @@
3
3
  **Status:** Ready
4
4
  **Track:** Quickstart
5
5
  **Time:** 25 minutes
6
- **Tested with:** Cairnkeep 2.3.0
6
+ **Tested with:** Cairnkeep 2.4.0
7
7
 
8
8
  ## Outcome
9
9
 
package/docs/operating.md CHANGED
@@ -17,7 +17,7 @@ installs. This guide covers all three in order.
17
17
  ## Prerequisites
18
18
 
19
19
  - Node.js 22 or newer (for the memory server) and a supported harness: Claude
20
- Code or OpenCode.
20
+ Code, OpenCode, or Pi.
21
21
  - Optional: the `sqlite3` CLI for `cairn memory export`. Runtime memory and
22
22
  `cairn memory import` do not require it.
23
23
  - Optional: an OpenAI-compatible LLM endpoint for memory extraction and
@@ -93,11 +93,15 @@ Step 3 installs into `~/.claude` (override with `CLAUDE_CONFIG_DIR` or
93
93
  `repo-review`, `graphify`, `context-explore`
94
94
  - **7 agents** → `agents/`: `code-reviewer`, the three `security-*` agents, and
95
95
  the three `wiki-*` agents
96
- - **3 hooks** → `hooks/`, registered in `settings.json`:
96
+ - **5 hooks** → `hooks/`, registered in `settings.json`:
97
97
  - `memory-wakeup.sh` on **SessionStart** — surfaces AgentFS memory + wiki index
98
98
  - `memory-capture.sh` on **SessionEnd** — extracts memory candidates to staging
99
99
  - `memory-recall.sh` on **PreToolUse** (Edit/Write/MultiEdit) — injects
100
100
  file-specific memory before an edit
101
+ - `context-explore-pretask.sh` on **UserPromptSubmit** — inert unless the
102
+ separate context-exploration opt-in is enabled
103
+ - `compaction-capture.sh` on **PostCompact** — inert unless local compaction
104
+ continuity is enabled
101
105
  - **scaffold templates** → `templates/`, used by `/security-audit` and `/wiki-*`
102
106
 
103
107
  Re-running `sync-claude-assets.sh --apply` is idempotent; use `--check` to see
@@ -121,7 +125,7 @@ OpenCode is a secondary path. Steps 1, 4, and 5 are identical (use
121
125
  The operating-layer assets are installed by topic-specific scripts:
122
126
 
123
127
  ```bash
124
- scripts/sync-opencode-plugin-assets.sh --apply # memory-wakeup plugin
128
+ scripts/sync-opencode-plugin-assets.sh --apply # memory wakeup/capture/recall plugins
125
129
  scripts/sync-opencode-memory-assets.sh --apply # memory-sync/review + code-review
126
130
  scripts/sync-opencode-wiki-assets.sh --apply # wiki commands/agents/workflows
127
131
  scripts/sync-opencode-security-assets.sh --apply # security-audit chain
@@ -153,6 +157,33 @@ Model selection stays operator-env-driven through the existing
153
157
  variables (see "Configuration" below) — the harness commits no default model
154
158
  and carries no known-good allowlist.
155
159
 
160
+ ## Setup order (Pi)
161
+
162
+ Pi trajectory capture uses the same project scaffold and store, but its source
163
+ is a native TypeScript extension installed into Pi's agent root:
164
+
165
+ ```bash
166
+ npm install -g @cairnkeep/cli
167
+ cairn sync-pi --apply # default: ~/.pi/agent
168
+ cairn bootstrap /path/to/project
169
+ cp /path/to/project/.ai/env.example /path/to/project/.ai/.env
170
+ cd /path/to/project && cairn doctor && ./.ai/start-pi.sh
171
+ ```
172
+
173
+ This command does not install an MCP bridge. Cairnkeep does not own or select a
174
+ Pi bridge; if you want `cairn memory-server` tools inside Pi as well, configure
175
+ a user-chosen Pi extension/bridge separately.
176
+
177
+ Use `cairn sync-pi --check` to report drift without writing, or
178
+ `--live-root DIR` to target an isolated Pi agent root. The command owns exactly
179
+ `extensions/cairnkeep-trajectory.ts`; `cairn uninstall --pi-live-root DIR`
180
+ removes that file backup-first and leaves every other Pi asset untouched.
181
+
182
+ The extension listens for Pi's native `session_shutdown` event and reads only
183
+ the active branch from the read-only session manager. It returns before doing
184
+ that work unless `CAIRN_TRAJECTORY_CAPTURE` is explicitly enabled. Capture is
185
+ local, fail-open, and capped at three seconds so it cannot hold Pi open.
186
+
156
187
  Before burning a multi-run soak, a preflight probe drives one real tool call
157
188
  and fails fast with a trait-named message if the configured model is not
158
189
  tool-call-reliable. The harness keeps a three-tier speed structure:
@@ -175,6 +206,38 @@ vendor or host.
175
206
  | `CAIRN_MEMORY_EMBEDDING_MODEL` | Embedding model name (required for semantic search) |
176
207
  | `CAIRN_MEMORY_EMBEDDING_TIMEOUT_MS` | Embedding request timeout before substring fallback (default `15000`) |
177
208
  | `CAIRN_AGENTFS_BASE_DIR` | Server-side base dir for named/global memory scopes (default `~/.cairnkeep`); it does not affect `project` scope |
209
+ | `CAIRN_TRAJECTORY_CAPTURE` | Opt in to local structured session capture (`1`, `true`, `yes`, or `on`; unset/default → no capture work) |
210
+ | `CAIRN_TRAJECTORY_SESSION_MAX_BYTES` | Maximum serialized bytes per session (default `5242880`, 5 MiB; minimum 1024) |
211
+ | `CAIRN_TRAJECTORY_STORE_MAX_BYTES` | Logical local trajectory budget (default `268435456`, 256 MiB; must be at least the session maximum) |
212
+ | `CAIRN_TRAJECTORY_RETENTION_DAYS` | Age retention applied on capture/prune (default `30`; `0` removes sessions once they are older than the current instant) |
213
+ | `CAIRN_TRAJECTORY_REDACTION_FILE` | Optional redaction JSON path contained by the project (default `.ai/trajectory-redaction.json` when that file exists) |
214
+ | `CAIRN_REDACTION_FILE` | Optional project-contained redaction JSON shared by trajectories and artifacts; takes precedence over `CAIRN_TRAJECTORY_REDACTION_FILE` |
215
+ | `CAIRN_COMPACTION_CAPTURE` | Opt in to supported local compaction capture and automatic structured recovery (off by default; unset means zero new work) |
216
+ | `CAIRN_ARTIFACT_STORE` | Expose the four local stdio artifact tools (off by default; independent of compaction capture) |
217
+ | `CAIRN_ARTIFACT_HTTP` | Separately expose artifact tools over authenticated HTTP (off by default; also requires `CAIRN_ARTIFACT_STORE`) |
218
+ | `CAIRN_ARTIFACT_MAX_BYTES` | Per-artifact stored-byte cap (default `1048576`, minimum `1024`) |
219
+ | `CAIRN_ARTIFACT_SESSION_MAX_BYTES` | Per-session logical-byte cap (default `16777216`; at least the artifact cap) |
220
+ | `CAIRN_ARTIFACT_STORE_MAX_BYTES` | Total logical-byte cap (default `268435456`; at least the session cap) |
221
+ | `CAIRN_ARTIFACT_RETENTION_DAYS` | Age retention in days (default `30`, minimum `0`) |
222
+ | `CAIRN_COMPACTION_MAX_REVISIONS` | Immutable compaction revisions retained per session (default `8`, minimum `1`) |
223
+ | `CAIRN_ARTIFACT_GENERATED_FILE_SNAPSHOT_MAX_BYTES` | Generated-file inline snapshot cap (default `262144`; effective cap is the lower of this and the artifact cap) |
224
+ | `CAIRN_NOTE_DISTILLATION` | Master opt-in for local one-shot/scheduled hindsight distillation and exact lookup (unset/default → disabled before I/O) |
225
+ | `CAIRN_NOTE_ENRICHMENT` | Separate opt-in for provider prose enrichment; credentials alone never enable it |
226
+ | `CAIRN_NOTE_ENRICHMENT_MODEL` | Explicit OpenAI-compatible chat model for note enrichment (no default) |
227
+ | `CAIRN_NOTE_ENRICHMENT_TIMEOUT_MS` | Enrichment timeout in milliseconds (default `15000`, allowed `100`–`120000`) |
228
+ | `CAIRN_TYPED_MEMORY_NODES` | Add typed metadata, filters, logical note address spaces, and `memory_import` (unset/default off; restart after changing) |
229
+ | `CAIRN_CAPABILITY_CONTRACT` | Enable the managed eight-capability contract (unset/default off) |
230
+ | `CAIRN_CAPABILITY_LOGGING` | Strict per-process callback-logging override (`1`/`0`; compatibility default off) |
231
+ | `CAIRN_CAPABILITY_MEMORY_WRITE` | Strict per-process override for `memory.write` |
232
+ | `CAIRN_CAPABILITY_MEMORY_SEARCH` | Strict per-process override for `memory.search` |
233
+ | `CAIRN_CAPABILITY_NOTES_DISTILL` | Strict per-process override for `notes.distill` |
234
+ | `CAIRN_CAPABILITY_WIKI` | Strict per-process override for `wiki` |
235
+ | `CAIRN_CAPABILITY_GRAPH` | Strict per-process override for `graph` |
236
+ | `CAIRN_CAPABILITY_SECURITY_AUDIT` | Strict per-process override for `security.audit` |
237
+ | `CAIRN_CAPABILITY_ROUTE_CHECK` | Strict per-process override for `route.check` |
238
+ | `CAIRN_CAPABILITY_CONTEXT_EXPLORE` | Strict per-process override for `context.explore` |
239
+ | `CAIRN_EVAL` | Opt in to the local evaluation coordinator (`1`, `true`, `yes`, or `on`; unset/default → stable disabled result before input reads, writes, or adapter execution) |
240
+ | `CAIRN_HARNESS_STATE_DIR` | Optional absolute local root for recoverable native capability leases (default `${XDG_STATE_HOME:-~/.local/state}/cairn/harness`) |
178
241
  | `CAIRN_GIT_PROVIDER` | Git host for collaboration commands: `github`\|`gitlab`\|`codeberg`\|`forgejo`\|`none`. See [git-providers.md](git-providers.md) |
179
242
  | `CAIRN_ROUTE_ENDPOINT` | Base URL of an already-running token-miser routing/tiering proxy (unset → the `route_check` tool is inert) |
180
243
  | `CAIRN_EXPLORE_BINARY` | Absolute path to the `token_miser` binary used by `context_explore` (unset → the tool throws at call time) |
@@ -187,6 +250,208 @@ vendor or host.
187
250
  | `CAIRN_ANYTHINGLLM_PROJECTS_FILE` | Override path to the bundled sync script's project configuration |
188
251
  | `CAIRN_ANYTHINGLLM_STATE_FILE` | Override path to the bundled sync script's incremental state |
189
252
 
253
+ ### Managed capability contract (opt-in)
254
+
255
+ The managed contract is a narrow state and measurement boundary around eight
256
+ existing owners. It adds no runtime dependency and does not move business logic
257
+ from the MCP server, note CLI/timer, installed workflows, or the external
258
+ token-miser delegates.
259
+
260
+ The canonical registry is exactly:
261
+
262
+ | ID | Kind | Owner | Compatibility default | Environment override | Restart required |
263
+ |---|---|---|---:|---|---:|
264
+ | `memory.write` | `mcp-tool` | `mcp-memory-server:index#memory_write` | on | `CAIRN_CAPABILITY_MEMORY_WRITE` | yes |
265
+ | `memory.search` | `mcp-tool` | `mcp-memory-server:index#memory_search` | on | `CAIRN_CAPABILITY_MEMORY_SEARCH` | yes |
266
+ | `notes.distill` | `offline-job` | `mcp-memory-server:note-cli#distill` | `CAIRN_NOTE_DISTILLATION`, otherwise off | `CAIRN_CAPABILITY_NOTES_DISTILL` | no |
267
+ | `wiki` | `operating-workflow` | `operating-layer:wiki` | on | `CAIRN_CAPABILITY_WIKI` | no |
268
+ | `graph` | `operating-workflow` | `operating-layer:graph` | `.planning/config.json` `graphify.enabled`, otherwise off | `CAIRN_CAPABILITY_GRAPH` | no |
269
+ | `security.audit` | `operating-workflow` | `operating-layer:security-audit` | on | `CAIRN_CAPABILITY_SECURITY_AUDIT` | no |
270
+ | `route.check` | `mcp-tool` | `mcp-memory-server:index#route_check` | on | `CAIRN_CAPABILITY_ROUTE_CHECK` | yes |
271
+ | `context.explore` | `mcp-tool` | `mcp-memory-server:index#context_explore` | on | `CAIRN_CAPABILITY_CONTEXT_EXPLORE` | yes |
272
+
273
+ `CAIRN_CAPABILITY_CONTRACT` is the default-off rollout gate. Master off is exact
274
+ legacy behavior: the launchers and sync commands preserve the legacy installed
275
+ assets byte-for-byte, install and invoke no capability hook or plugin, introduce
276
+ no capability block, and create no capability measurement state. Master on is
277
+ not enough to modify the normal harness root. The launcher must also select the
278
+ explicit `capability-overlay` mode; only that two-factor path renders the
279
+ isolated `.ai/capability-contract/` root and installs its native hooks/plugins.
280
+ Normal installed assets remain in place, and toggling never deletes
281
+ configuration, retained data, or installed files.
282
+
283
+ Bootstrap creates a mode-`0600` `.ai/capabilities.json` only if it is absent:
284
+
285
+ ```json
286
+ {
287
+ "schema_version": 1,
288
+ "capabilities": {},
289
+ "logging": { "callbacks": false }
290
+ }
291
+ ```
292
+
293
+ Use the managed CLI; do not hand-edit the file:
294
+
295
+ ```bash
296
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities list
297
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities status
298
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities status --json
299
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities enable wiki
300
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities disable memory.search
301
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities reset memory.search
302
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities reset --all
303
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities logging enable
304
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities logging disable
305
+ CAIRN_CAPABILITY_CONTRACT=1 cairn capabilities logging reset
306
+ ```
307
+
308
+ Precedence is strict: the derived per-capability environment value (or
309
+ `CAIRN_CAPABILITY_LOGGING`) wins over the project value, which wins over the
310
+ compatibility resolver. Every derived key uses the `CAIRN_CAPABILITY_` prefix
311
+ followed by the uppercased ID with dots replaced by underscores. Accepted
312
+ values are `1|true|yes|on` and
313
+ `0|false|no|off`, case-insensitively. An invalid top-level value is reported
314
+ with a fixed value-free issue and falls directly back to compatibility; it does
315
+ not reveal the raw value or consult a lower project override. Unknown IDs and
316
+ invalid project rows are likewise reported without their values. Each bad row
317
+ falls back independently, so it cannot disable another capability or the
318
+ server.
319
+
320
+ `reset` removes the explicit override; `reset --all` clears every capability
321
+ and logging override. The returned schema-v1 status orders all eight rows and
322
+ reports `enabled`, `source` (`environment`, `project`, or `compatibility`),
323
+ `restart_required`, contract/logging state, fixed issue codes, and the SHA-256
324
+ `configuration_digest` of the canonical effective snapshot. The digest is
325
+ evidence, not a signature or secret. MCP state is resolved once before server
326
+ registration, so changes to `memory.write`, `memory.search`, `route.check`, or
327
+ `context.explore` require a new server start; a disabled tool is omitted from
328
+ `tools/list`. Notes and operating workflows resolve current state for the next
329
+ invocation. Disabled notes retain the existing structured `enabled: false`
330
+ offline result.
331
+
332
+ Wiki covers ingest, query, and lint; graph covers its command family; security
333
+ audit covers its command and workflow family. There are no subcommand-level
334
+ switches. The four enabled MCP tools retain their exact names, schemas,
335
+ responses, timeout/error behavior, and owners. In particular, `route_check`
336
+ and `context_explore` remain thin token-miser delegates; Cairnkeep does not gain
337
+ endpoint, model, tier, sandbox, or inference-loop ownership.
338
+
339
+ The five operating command surfaces are still `wiki-ingest`, `wiki-query`,
340
+ `wiki-lint`, `graphify`, and `security-audit`. Their installed commands, agents,
341
+ and workflows remain the owners described by D-10, D-12, and D-16; the native
342
+ boundary admits or blocks an invocation and settles evidence, but does not move
343
+ or duplicate owner logic.
344
+
345
+ #### Native operating lifecycle
346
+
347
+ Claude Code uses two native hooks. `capability-command-start.sh` runs on
348
+ `UserPromptExpansion`, before command or MCP-prompt expansion can enter owner
349
+ I/O. It validates the exact target-command event and binds the canonical
350
+ project root from the event to the process root. The coordinator then creates
351
+ an issued recoverable lease for an eligible measured invocation. That validated
352
+ project identity and lease are immutable for the invocation: terminal events
353
+ carry neither a replacement project path nor a caller-selected handle.
354
+ `capability-command-finish.sh` maps `Stop` to success and maps `StopFailure`
355
+ immediately to an error terminal; error settlement is not deferred to
356
+ `SessionEnd`. `SessionEnd` performs abandonment cleanup only for unfinished
357
+ leases, while `CwdChanged` updates lifecycle state without rebinding the
358
+ start-time project.
359
+
360
+ OpenCode uses the pinned native plugin for OpenCode 1.17.20.
361
+ `command.execute.before` validates the target command, session, output shape,
362
+ and the one canonical project identity supplied at plugin initialization before
363
+ expansion. `session.idle` and `session.status:idle` settle success,
364
+ `session.error` settles error, and `session.deleted` performs abandonment
365
+ cleanup only for an unfinished invocation. Plugin disposal likewise abandons
366
+ only unfinished leases. Both harnesses settle the issued lease atomically and
367
+ idempotently: one terminal can win, replay cannot add another final, and
368
+ crash-recovery cleanup cannot rewrite a settled outcome.
369
+
370
+ The three operating states are exact. With master off, no native capability
371
+ owner is installed or invoked and no measurement state exists. With master on
372
+ and a target disabled, the native boundary returns the fixed block before owner
373
+ I/O regardless of measurement consent. Only when all three consents are on may
374
+ that block settle exactly one D-25/D-26 value-free `disabled` final through an
375
+ issued lease; if either measurement consent is off, it blocks with no state.
376
+ With master on and a target enabled, either measurement consent being off
377
+ preserves owner execution unchanged and creates no measurement state.
378
+
379
+ Deterministic native-boundary tests prove pinned event shapes, admission before
380
+ owner delegation, terminal ordering, and overlay installation. They are not
381
+ exhaustive live real-owner evidence. Phase 18 also requires the complete live
382
+ eight-by-seven matrix: eight capabilities across seven target-disabled cells,
383
+ 56 genuine owner executions in total. Any missing, failed, or unavailable cell
384
+ keeps Phase 18 incomplete.
385
+
386
+ Callback logging is separately default-off. When all three consents are on
387
+ (`CAIRN_CAPABILITY_CONTRACT`, managed callback logging, and
388
+ `CAIRN_TRAJECTORY_CAPTURE`), the native boundary measures only the capability-owned
389
+ invocation. State is resolved first; the timer starts immediately before the
390
+ owned body and stops after its terminal outcome but before final presentation.
391
+ Discovery, configuration resolution, guard work, and unrelated harness overhead
392
+ are excluded. Logging is payload-free, local-only, final-only, and fail-open;
393
+ HTTP callbacks are never persisted. See the privacy and storage guides for the
394
+ exact record and retention contract.
395
+
396
+ Phase 19 must explicitly enable all eight capabilities to establish its
397
+ baseline before disabling one at a time. This contract records status/digest
398
+ and callback evidence only: it does not implement tasks, a runner, turns/tokens,
399
+ statistics, confidence intervals, or a quality/efficiency claim.
400
+
401
+ ### Typed memory nodes and note address spaces (opt-in)
402
+
403
+ Set `CAIRN_TYPED_MEMORY_NODES=1` in the memory-server process and restart it.
404
+ Tool schemas are selected when `createMemoryServer` constructs a stdio server
405
+ or authenticated HTTP session. With the flag unset, Cairnkeep retains the
406
+ existing 14-tool list, required inputs, structured response roots, raw KV
407
+ values, files, processes, network behavior, and output. Enabled mode adds
408
+ optional fields to the existing lifecycle tools and exactly `memory_import`;
409
+ it does not add `memory_edit`, `memory_update`, `memory_remove`, or duplicate
410
+ read/delete tools. Use `memory_supersede` for both content and metadata edits.
411
+
412
+ Types are `memory`, `knowledge`, `hindsight`, `shared`, `provenance`, or a
413
+ lowercase namespaced extension such as `team:runbook`. Tags are trimmed, ASCII
414
+ lowercased, normalize whitespace/underscore runs to one hyphen, collapse
415
+ hyphens, deduplicate, and sort. `memory_list` and `memory_search` accept
416
+ non-empty `node_types`, `tags_all`, and `tags_any`; these are hard eligibility
417
+ filters applied before `top_k`, cache lookup, or embedding. Case-insensitive
418
+ key matches and exact canonical tag/type matches sort before semantic results.
419
+ Endpoint failure or absent embedding configuration falls back to stable local
420
+ substring matching over key, value, type, and tags.
421
+
422
+ Enabled `memory_write` without any Phase 16 field preserves collision-safe
423
+ legacy overwrite behavior. Supplying `address_space`, `node_type`, `tags`, or
424
+ `note` makes it create-only on a differing live node and returns `CONFLICT`;
425
+ use `memory_supersede` to preserve the complete prior value/type/tags snapshot.
426
+ Delete also records the complete final snapshot, and recreation is allowed.
427
+
428
+ `memory_import` takes `schema_version: 1`, one concrete `scope`, optional
429
+ `address_space` (default `memory`), 1–256 `nodes`, optional `import_id`,
430
+ `dry_run`, and `conflict_policy` (default `reject`, or explicit `supersede`).
431
+ Each unique logical key is a contained relative slash path; values are limited
432
+ to 256 KiB UTF-8 each and 5 MiB per batch. The content digest excludes retry
433
+ controls, while `import_id` durably binds to it. Dry-run performs no create,
434
+ lock, cache, journal, or mutation and returns only the digest, sorted actions,
435
+ and counts. Committed and replay results likewise omit values. Stable failures
436
+ include `INVALID_SCHEMA`, `INVALID_SCOPE`, `INVALID_PATH`, `CONFLICT`,
437
+ `IMPORT_ID_REUSE`, and `UNSUPPORTED_TARGET`.
438
+
439
+ `project-notes` and `shared-notes` both require `scope: project`; no named
440
+ memory scope is reserved. Keys such as `knowledge/build-cache` are logical,
441
+ not client filesystem paths. Rich hindsight/shared/provenance imports require
442
+ the complete nested note record; knowledge values must still losslessly encode
443
+ the validated record. Canonical Markdown and its manifest remain authoritative.
444
+ Unmanaged collisions are refused, and bytes after the managed marker are
445
+ preserved exactly. Local stdio derives project identity from its working
446
+ directory. Authenticated HTTP project notes require validated
447
+ `X-Cairn-Project`; the bearer token still defines the trust domain.
448
+
449
+ An interrupted note mutation blocks later note writes with `RECOVERY_REQUIRED`.
450
+ `cairn doctor --repair` rolls prepared/committing journals back to verified
451
+ pre-images, but verifies and finalizes committed-before-cleanup journals without
452
+ undoing the completed operation. Unverifiable committed state remains failed
453
+ and untouched for backup-guided manual recovery.
454
+
190
455
  ### Domain knowledge (RAG via AnythingLLM, opt-in)
191
456
 
192
457
  `domain_knowledge_query` / `domain_knowledge_sync` bridge to an optional
@@ -293,6 +558,7 @@ is guarded and **fails closed**:
293
558
  | `CAIRN_MEMORY_HTTP_TOKEN` | **Required** in HTTP mode — clients send `Authorization: Bearer <token>`; the server refuses to start without it |
294
559
  | `CAIRN_MEMORY_HTTP_ALLOWED_ORIGINS` | Comma-separated browser origins allowed via CORS (default: none — no cross-origin access) |
295
560
  | `CAIRN_MEMORY_HTTP_ALLOWED_HOSTS` | Comma-separated allowed `Host` headers for DNS-rebinding protection (default: the bind host + `localhost` on the chosen port) |
561
+ | `CAIRN_ARTIFACT_HTTP` | Additional consent for artifact tools (default off; requires `CAIRN_ARTIFACT_STORE`, a valid `X-Cairn-Project`, bearer auth, and the normal Host/CORS checks) |
296
562
 
297
563
  Requests without a valid bearer token get `401`; requests with an unexpected
298
564
  `Host` header get `403`. Keep HTTP mode bound to `127.0.0.1` unless you have a
@@ -370,7 +636,7 @@ optional hooks around the harness, each a no-op when absent:
370
636
  | Seam | When | Purpose |
371
637
  |---|---|---|
372
638
  | `.ai/pre-launch.sh` | sourced after `.env`, before launch | export env (e.g. a provider base URL / auth), refresh credentials, or abort by returning non-zero |
373
- | `CAIRN_EXTRA_SETTINGS` | read just before launch | path to a settings file layered on the harness (`--settings` / `--config`); process env still wins over it |
639
+ | `CAIRN_EXTRA_SETTINGS` | read just before launch | path to a settings file layered on the harness (`--settings` / `OPENCODE_CONFIG`); process env still wins over it |
374
640
  | `.ai/post-exit.sh` | sourced after the harness exits | teardown; `CAIRN_EXIT_STATUS` holds the exit code |
375
641
 
376
642
  A wrapper that needs a non-default provider drops a `pre-launch.sh` that renders
@@ -384,10 +650,265 @@ harness's remote HTTP registration. Unconfigured optional dependencies are
384
650
  skipped; it exits non-zero when the local server probe fails or a configured
385
651
  dependency (LLM/embedding endpoint, writable store) is unreachable.
386
652
 
653
+ It also checks an existing project-local trajectory database for SQLite
654
+ integrity, schema compatibility and index consistency. Because capture is
655
+ opt-in, a missing database is skipped. Metadata or indexes can be rebuilt from
656
+ valid full records only through the explicit repair operation:
657
+
387
658
  ```bash
388
659
  cd /path/to/project && cairn doctor
660
+ cd /path/to/project && cairn doctor --repair
389
661
  ```
390
662
 
663
+ The same command checks `.agentfs/artifacts.db`. Absence is a healthy skip
664
+ because both producers are opt-in. A valid schema, full-record digest, index,
665
+ dedupe row, compaction pointer, and retention state passes. `--repair` may
666
+ rebuild only derived indexes/dedupe/pointers from valid authoritative records.
667
+ Unsupported schema, SQLite failure, invalid full records, or digest corruption
668
+ fails untouched with guidance to preserve the database before manual recovery.
669
+
670
+ ### Evaluation harness (opt-in)
671
+
672
+ `cairn eval` is a local, serial experiment coordinator. It is disabled unless
673
+ `CAIRN_EVAL` is explicitly truthy. The disabled path returns before reading a
674
+ task set or adapter configuration, creating a workspace or report, opening a
675
+ database, starting a subprocess, or making a network request. Enabling the
676
+ flag does not choose a harness, model, endpoint, or credential: inference
677
+ remains owned by the operator-configured adapter process.
678
+
679
+ Task sets and adapters are strict schema-v1 JSON. Each task names an immutable
680
+ source commit, fixed input, shell-free preparation and verification commands,
681
+ and limits. An adapter configuration is one program-plus-arguments array and
682
+ an exact turn-semantics ID plus definition. Cairnkeep sends one bounded JSON
683
+ request on stdin and accepts exactly one bounded, strict JSON observation on
684
+ stdout. The adapter may report terminal status, turns under its declared
685
+ semantics, independently optional token components and total, optional
686
+ currency-bearing cost, component identities, capability digest, and local
687
+ trajectory/artifact references. The independent task verifier—not the
688
+ adapter—assigns pass, fail, or unknown. Arbitrary adapter stderr remains live
689
+ operator diagnostics and is not copied into the report.
690
+
691
+ The request's `workspace_path` is the task's Cairnkeep project root for all
692
+ trajectory-producing harness work. An adapter that returns `trajectory_ref`
693
+ must run that work against the supplied root so the closed trajectory is in
694
+ that task's local store. The reference is a bounded session identifier, not a
695
+ filesystem locator. A task manifest with `workspace.path: "."` remains the
696
+ compatible repository-root case; there is no second trajectory-root field.
697
+
698
+ Validate before spending or mutating:
699
+
700
+ ```bash
701
+ export CAIRN_EVAL=1
702
+ cairn eval validate --task-set tasks.json --adapter adapter.json \
703
+ --output .agentfs/eval/experiments --json
704
+
705
+ # Two serial observations per task and repetition: Run 1, then fresh Run 2.
706
+ cairn eval run --task-set tasks.json --adapter adapter.json \
707
+ --output .agentfs/eval/experiments --repetitions 1 --seed trial-1 --yes --json
708
+
709
+ # Four serial observations per task and repetition: all-on and one-off,
710
+ # with Run 1 and Run 2 in each arm. Only one capability may be disabled.
711
+ cairn eval ablate --disable memory.search --task-set tasks.json \
712
+ --adapter adapter.json --output .agentfs/eval/experiments --seed trial-1
713
+ # Inspect the printed invocation estimate and both configuration digests, then
714
+ # repeat with --yes to confirm execution.
715
+
716
+ cairn eval report --experiment EXPERIMENT-ID --json
717
+ cairn eval prune --older-than-days 30 --dry-run --json
718
+ cairn eval prune --older-than-days 30 --json
719
+ cairn eval delete --experiment EXPERIMENT-ID --dry-run --json
720
+ cairn eval delete --experiment EXPERIMENT-ID --json
721
+ ```
722
+
723
+ `validate` resolves task/adapter bytes and digests, immutable revisions,
724
+ commands, limits, output containment, seeds, and the complete deterministic
725
+ schedule without invoking the adapter. `run` requires `--yes` and prints its
726
+ serial invocation estimate. `ablate` first prints the estimate, the explicit
727
+ all-eight-enabled baseline, the exactly-one-disabled treatment, and their
728
+ configuration digests; execution likewise requires `--yes`. There are no
729
+ automatic retries: repetitions and paired seeds are explicit experiment
730
+ inputs.
731
+
732
+ The packaged fake workflow uses `examples/eval/task-set.json` and
733
+ `examples/eval/adapter.json` with the same `validate`, `run`, `report`,
734
+ `prune`, and `delete` commands. Its manifest must match the package-owned ID,
735
+ version, canonical bytes, and digest. It is deterministic and network-free,
736
+ but proves framework behavior only; it is not live performance evidence.
737
+
738
+ Run 1 starts every task from a fresh workspace without evaluation notes. After
739
+ a closed trajectory, the existing offline note distiller may create an
740
+ experiment-owned immutable snapshot. Run 2 uses another fresh workspace and
741
+ can see only its own task's snapshot. Tasks with `no_notes`, failed, or skipped
742
+ distillation still run and remain visible. Reports show the full committed
743
+ population and the note-eligible subset, plus executed, verified, paired, and
744
+ missing populations and reason-coded missingness.
745
+
746
+ Turns are paired or aggregated only when exact turn-semantics IDs match.
747
+ Token fields remain independent and totals are never inferred. Verified pass
748
+ rates exclude unknown verifier outcomes while retaining them in denominators
749
+ and missingness tables. Task-clustered paired bootstrap intervals use recorded
750
+ seeds and streams; fewer than two valid pairs has no interval and fewer than
751
+ 20 emits a small-sample warning. Results are estimates and may be
752
+ inconclusive—reports do not make causal, significance, quality, or efficiency
753
+ claims.
754
+
755
+ SIGINT or SIGTERM stops schedule admission, terminates the active adapter,
756
+ checkpoints a `cancelled` observation, waits for bounded cleanup, and retains
757
+ an inspectable partial report. On POSIX systems the runner targets the child
758
+ process group with TERM then bounded KILL escalation. Windows descendant-tree
759
+ termination is not guaranteed and is not a supported evaluation claim.
760
+
761
+ See [storage](storage.md#evaluation-report-and-note-snapshot-storage) for local
762
+ retention/removal and [privacy](privacy-and-data-flow.md#evaluation-adapter-and-report-flow)
763
+ for the exact request, observation, environment, and persistence boundaries.
764
+
765
+ ### Structured session trajectories (opt-in)
766
+
767
+ Trajectory capture is disabled by default. To enable it for a launched Claude
768
+ Code, OpenCode, or Pi session, set the flag in the project's private `.ai/.env`:
769
+
770
+ ```bash
771
+ CAIRN_TRAJECTORY_CAPTURE=1
772
+ ```
773
+
774
+ The Claude Code SessionEnd hook, OpenCode session-idle plugin, or Pi
775
+ `session_shutdown` extension then normalizes the closed session, redacts it,
776
+ and writes it locally without a model, API key, or remote request. Inspect and
777
+ manage the result from the project root:
778
+
779
+ ```bash
780
+ cairn trajectory list
781
+ cairn trajectory show <session-id-or-unambiguous-prefix>
782
+ cairn trajectory prune --dry-run
783
+ cairn trajectory prune
784
+ ```
785
+
786
+ All commands accept `--json`; `--dry-run` applies to `prune`. Bootstrap creates
787
+ `.ai/trajectory-redaction.json` with an empty custom-pattern list but does not
788
+ enable capture. Add bounded regular-expression entries there only for
789
+ project-specific secrets; built-in credential patterns always apply. See
790
+ [Privacy and data flow](privacy-and-data-flow.md) for the exact capture boundary
791
+ and [Memory storage and deployment](storage.md) for retention and backup.
792
+
793
+ ### Compaction continuity and artifacts (opt-in)
794
+
795
+ The two flags are independent and off by default. Enable either in the private
796
+ project `.ai/.env`, sync the harness assets, and restart both the harness and
797
+ the MCP server when changing tool registration:
798
+
799
+ ```bash
800
+ cd /path/to/project
801
+ printf '%s\n' 'CAIRN_COMPACTION_CAPTURE=1' >> .ai/.env
802
+ printf '%s\n' 'CAIRN_ARTIFACT_STORE=1' >> .ai/.env
803
+ cairn sync --apply # Claude Code PostCompact + SessionStart
804
+ # OpenCode: re-run scripts/sync-opencode-plugin-assets.sh --apply from a clone
805
+ # Restart the harness and cairn-memory after changing flags.
806
+
807
+ # Disable: remove/comment both lines, sync again, and restart.
808
+ ```
809
+
810
+ Compaction continuity accepts only the pinned Claude Code `PostCompact`
811
+ 2.1.219 and 2.1.220 payloads and OpenCode `session.compacted` 1.17.20 payload.
812
+ The Claude hook resolves the local CLI version and passes that exact version to
813
+ the normalizer. Unknown or malformed versions fail open, retain none of the
814
+ unknown payload, and leave only a
815
+ bounded value-free doctor diagnostic. The flag is checked before parsing,
816
+ SDK, subprocess, filesystem, database, network, stdout, stderr, or injection
817
+ work. Capture forwards the harness-produced summary unchanged to the local
818
+ normalizer; it never generates a summary, changes a prompt, or triggers
819
+ compaction.
820
+
821
+ Automatic recovery prefers the exact current/resumed harness session. When a
822
+ fresh session has no match, it chooses the newest valid project compaction. It
823
+ injects provenance, revision, time, age, completeness, and only structured
824
+ goals/decisions/TODOs/critical errors. Old state is still shown with a stale
825
+ warning to validate it against the repository. The redacted raw summary is
826
+ available only through explicit `artifact_read` or `cairn artifact show`.
827
+
828
+ `CAIRN_ARTIFACT_STORE=1` registers exactly `artifact_write`, `artifact_read`,
829
+ `artifact_list`, and `artifact_delete`. The first schema supports exactly
830
+ `compaction_summary`, `diff`, `test_output`, and `generated_file`. Writes are
831
+ bounded inline values; generated-file paths are labels and are never read.
832
+ Operator commands remain available for already-retained local data:
833
+
834
+ ```bash
835
+ cairn artifact list --kind compaction_summary --session SESSION-REF --json
836
+ cairn artifact show ARTIFACT-ID --json
837
+ cairn artifact delete ARTIFACT-ID --dry-run --json
838
+ cairn artifact delete ARTIFACT-ID --json
839
+ cairn artifact prune --dry-run --json
840
+ cairn artifact prune --dry-run --include-protected --json
841
+ ```
842
+
843
+ Defaults are 1 MiB per artifact, 16 MiB per session, 256 MiB total, 30 days,
844
+ eight compaction revisions, and a 256 KiB generated-file snapshot cap. Pruning
845
+ is oldest-first after age/revision eligibility and protects the newest valid
846
+ project compaction unless `--include-protected` or explicit delete is used.
847
+ Hard delete removes the body and derived rows with no tombstone.
848
+
849
+ No key, model, endpoint, or network is required. Stdio access needs only
850
+ `CAIRN_ARTIFACT_STORE`. HTTP requires double consent—both artifact flags—plus
851
+ the existing bearer token, allowed `Host`, CORS policy, and a valid
852
+ `X-Cairn-Project`; the latter derives an isolated server-side artifact root.
853
+ Artifact HTTP does not expose trajectories. See [storage](storage.md#artifact-storage)
854
+ and [privacy](privacy-and-data-flow.md#compaction-and-artifact-flows).
855
+
856
+ ### Hindsight notes (opt-in)
857
+
858
+ Note distillation is a separate asynchronous/one-shot capability. It never runs
859
+ inside SessionEnd, session-idle, `session_shutdown`, or any other online agent
860
+ callback. It consumes only closed, schema-validated trajectories already
861
+ redacted by the capture path. Enable the master flag in the process that runs
862
+ the command:
863
+
864
+ ```bash
865
+ export CAIRN_NOTE_DISTILLATION=1
866
+
867
+ # Incremental current project, or one exact session for deterministic replay
868
+ cd /path/to/project
869
+ cairn notes distill --json
870
+ cairn notes distill --session SESSION-ID --json
871
+
872
+ # Scheduled/manual sweep below one explicit PARA root
873
+ cairn notes distill --all-projects --para-root "$HOME/PARA" --json
874
+
875
+ # Exact signature lookup; stdin avoids quoting a real multiline stack trace
876
+ printf '%s\n' 'TypeError: cache closed' ' at loadCache (/tmp/src/cache.ts:44:2)' \
877
+ | cairn notes search-error --project /path/to/project --json
878
+
879
+ # Validate/rebuild generated indexes, or explicitly promote corroborated notes
880
+ cairn notes doctor --json
881
+ cairn notes doctor --repair --json
882
+ cairn notes promote NOTE-ID --with CORROBORATING-NOTE-ID --confirm --json
883
+ ```
884
+
885
+ The hierarchy is `${CAIRN_AGENTFS_BASE_DIR:-~/.cairnkeep}/notes/README.md`,
886
+ `shared/`, and `projects/<slug--stable-id>/{README.md,knowledge/,hindsight/}`.
887
+ Each Markdown leaf has validated `id`, `title`, `description`, `keywords`,
888
+ `node_type`, and `tags` front matter. Hindsight nodes add a deterministic
889
+ signature and `unresolved`, `resolved`, or `abandoned` lifecycle. A later
890
+ recurrence reopens the same stable node. Generated content is enclosed by
891
+ `cairnkeep:managed:v1` markers; text after that block is preserved. An existing
892
+ unmarked target is treated as a collision and is never overwritten.
893
+
894
+ JSON distillation output separates `created`, `updated`, `already_processed`,
895
+ `enrichment_skipped`, `enrichment_failed`, and session-level `failed` results.
896
+ Project locks reject concurrent writers. Exact lookup normalizes the message,
897
+ causal stack, and component with the same versioned implementation used by the
898
+ index; it runs without embeddings or network access. Promotion requires two
899
+ compatible notes from distinct projects plus `--confirm`, creates one shared
900
+ canonical body, and converts each project note into a provenance reference.
901
+
902
+ Optional prose enrichment requires all of the master flag,
903
+ `CAIRN_NOTE_ENRICHMENT=1`, `CAIRN_LLM_API_KEY`, `CAIRN_LLM_API_URL`, and an
904
+ explicit `CAIRN_NOTE_ENRICHMENT_MODEL`. It sends bounded already-redacted
905
+ evidence to `{CAIRN_LLM_API_URL}/chat/completions`, labels returned prose as
906
+ non-authoritative, and cannot change signature, lifecycle, or provenance. Any
907
+ missing configuration, malformed response, timeout, or provider failure leaves
908
+ the deterministic note usable and is reported as skipped/failed. Review the
909
+ [privacy data flow](privacy-and-data-flow.md#hindsight-note-distillation) before
910
+ enabling it.
911
+
391
912
  ### `cairn memory export|import|path`
392
913
 
393
914
  Relocate named/global memory (one SQLite `.db` per scope under
@@ -414,3 +935,9 @@ cairn audit-timer --on-calendar daily # install + enable the timer
414
935
  cairn audit-timer --render-only ./units # just render the unit files
415
936
  # no systemd? cron: @daily .../scripts/memory-wiki-audit.sh --para-root "$HOME/PARA" --report ...
416
937
  ```
938
+
939
+ The timer inherits its process environment. With `CAIRN_NOTE_DISTILLATION`
940
+ truthy it launches exactly one separate `cairn notes distill --all-projects`
941
+ process after the wiki scan and reports its bounded status. With the flag unset
942
+ it performs no note work. Note failure does not replace wiki findings or change
943
+ their actionable exit status; no credentials are embedded in rendered units.