nlm-memory 0.5.0 → 0.5.2

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 (257) hide show
  1. package/README.md +89 -34
  2. package/dist/cli/digest.d.ts +20 -0
  3. package/dist/cli/digest.js +142 -0
  4. package/dist/cli/digest.js.map +1 -0
  5. package/dist/cli/nlm.d.ts +1 -0
  6. package/dist/cli/nlm.js +25 -1
  7. package/dist/cli/nlm.js.map +1 -1
  8. package/dist/core/digest/compose.d.ts +38 -0
  9. package/dist/core/digest/compose.js +93 -0
  10. package/dist/core/digest/compose.js.map +1 -0
  11. package/dist/core/digest/hook-liveness.d.ts +32 -0
  12. package/dist/core/digest/hook-liveness.js +54 -0
  13. package/dist/core/digest/hook-liveness.js.map +1 -0
  14. package/dist/http/app.js +2 -1
  15. package/dist/http/app.js.map +1 -1
  16. package/dist/mcp/server.js +20 -1
  17. package/dist/mcp/server.js.map +1 -1
  18. package/dist/ui/assets/{index-C8cpwbYJ.css → index-Beo8psd-.css} +1 -1
  19. package/dist/ui/assets/{index-CB50QnL-.js → index-CSPTTeeM.js} +8 -8
  20. package/dist/ui/index.html +2 -2
  21. package/package.json +26 -1
  22. package/.agents/plugins/marketplace.json +0 -20
  23. package/.github/workflows/ci.yml +0 -30
  24. package/docs/methodology/re-derivation-rate.md +0 -112
  25. package/docs/methodology/useful-hit-rate.md +0 -79
  26. package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
  27. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
  28. package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
  29. package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
  30. package/docs/plans/desktop-product.md +0 -69
  31. package/docs/plans/factstore-design.md +0 -236
  32. package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1575
  33. package/logs/CHANGELOG/CHANGELOG.md +0 -209
  34. package/migrations/000_initial_schema.sql +0 -174
  35. package/migrations/001_entity_type_rename.sql +0 -17
  36. package/migrations/002_adapter_state_extend.sql +0 -12
  37. package/migrations/003_session_embeddings.sql +0 -11
  38. package/migrations/004_facts.sql +0 -46
  39. package/migrations/005_sources.sql +0 -31
  40. package/migrations/006_providers.sql +0 -33
  41. package/migrations/007_source_tokens.sql +0 -17
  42. package/migrations/008_fts_rebuild.sql +0 -9
  43. package/migrations/009_session_embedding_chunks.sql +0 -46
  44. package/migrations/010_sources_opencode.sql +0 -30
  45. package/migrations/011_sources_hermes_agent.sql +0 -30
  46. package/migrations/012_sources_aider.sql +0 -30
  47. package/migrations/013_adapter_state_failure_count.sql +0 -12
  48. package/migrations/014_sources_cursor.sql +0 -30
  49. package/migrations/015_sources_windsurf.sql +0 -30
  50. package/plugin-hermes-agent/README.md +0 -49
  51. package/plugin-hermes-agent/__init__.py +0 -75
  52. package/plugin-hermes-agent/plugin.yaml +0 -15
  53. package/scripts/backfill-citations.mjs +0 -0
  54. package/scripts/build-codex-plugin.mjs +0 -61
  55. package/scripts/deepseek-probe.mjs +0 -67
  56. package/scripts/extract-triples.mjs +0 -207
  57. package/scripts/longmemeval/embedding-cache.ts +0 -77
  58. package/scripts/longmemeval/fetch-dataset.sh +0 -25
  59. package/scripts/longmemeval/run-harness.ts +0 -315
  60. package/scripts/longmemeval/scorer.ts +0 -99
  61. package/scripts/longmemeval/tsconfig.json +0 -9
  62. package/scripts/longmemeval/types.ts +0 -35
  63. package/scripts/nlm-daily-digest.py +0 -239
  64. package/scripts/nlm-daily-digest.sh +0 -28
  65. package/src/cli/classify-parity.ts +0 -257
  66. package/src/cli/launchctl-helpers.ts +0 -49
  67. package/src/cli/nlm.ts +0 -1078
  68. package/src/core/actions/actions-log.ts +0 -118
  69. package/src/core/actions/overlay.ts +0 -117
  70. package/src/core/adapters/aider.ts +0 -205
  71. package/src/core/adapters/claude-code.ts +0 -293
  72. package/src/core/adapters/common.ts +0 -54
  73. package/src/core/adapters/cursor.ts +0 -486
  74. package/src/core/adapters/from-source.ts +0 -67
  75. package/src/core/adapters/hermes-agent.ts +0 -240
  76. package/src/core/adapters/hermes.ts +0 -277
  77. package/src/core/adapters/jsonl-generic.ts +0 -208
  78. package/src/core/adapters/opencode.ts +0 -281
  79. package/src/core/adapters/pi.ts +0 -264
  80. package/src/core/adapters/windsurf.ts +0 -386
  81. package/src/core/classifier/prompt.ts +0 -200
  82. package/src/core/dataset/build-dataset.ts +0 -463
  83. package/src/core/embedding/chunk-body.ts +0 -76
  84. package/src/core/embedding/embed-backfill.ts +0 -210
  85. package/src/core/embedding/embed-normalize.ts +0 -135
  86. package/src/core/facts/backfill-facts.ts +0 -254
  87. package/src/core/facts/extract-facts.ts +0 -50
  88. package/src/core/hook/citation-detect.ts +0 -124
  89. package/src/core/hook/cite-memo.ts +0 -68
  90. package/src/core/hook/claude-settings.ts +0 -187
  91. package/src/core/hook/gate.ts +0 -25
  92. package/src/core/hook/hook-log.ts +0 -41
  93. package/src/core/hook/memo-sweep.ts +0 -164
  94. package/src/core/hook/memo.ts +0 -67
  95. package/src/core/hook/pointer-block.ts +0 -26
  96. package/src/core/hook/select.ts +0 -32
  97. package/src/core/hook/transcript.ts +0 -121
  98. package/src/core/ingest/ingest-session.ts +0 -111
  99. package/src/core/providers/provider-models.ts +0 -100
  100. package/src/core/providers/provider-registry.ts +0 -196
  101. package/src/core/recall/citation-log.ts +0 -108
  102. package/src/core/recall/filter.ts +0 -27
  103. package/src/core/recall/index.ts +0 -6
  104. package/src/core/recall/match-fields.ts +0 -40
  105. package/src/core/recall/query-log.ts +0 -149
  106. package/src/core/recall/query-shape.ts +0 -66
  107. package/src/core/recall/recall-service.ts +0 -320
  108. package/src/core/recall/recent-log.ts +0 -59
  109. package/src/core/recall/tokenize.ts +0 -18
  110. package/src/core/recall/useful-scan.ts +0 -336
  111. package/src/core/recall-facts/fact-query-log.ts +0 -150
  112. package/src/core/recall-facts/fact-recall-service.ts +0 -327
  113. package/src/core/scheduler/scan-once.ts +0 -142
  114. package/src/core/scheduler/scheduler.ts +0 -225
  115. package/src/core/sources/source-registry.ts +0 -278
  116. package/src/core/storage/db-restore.ts +0 -133
  117. package/src/core/storage/live-status.ts +0 -45
  118. package/src/core/storage/migrate.ts +0 -72
  119. package/src/core/storage/sqlite-fact-store.ts +0 -304
  120. package/src/core/storage/sqlite-session-store.ts +0 -810
  121. package/src/hook/hook-auth.ts +0 -18
  122. package/src/hook/prompt-recall-hook.ts +0 -180
  123. package/src/hook/session-end-hook.ts +0 -81
  124. package/src/hook/session-start-hook.ts +0 -168
  125. package/src/hook/stop-hook.ts +0 -239
  126. package/src/http/app.ts +0 -1215
  127. package/src/install/claude-code.ts +0 -128
  128. package/src/install/codex.ts +0 -367
  129. package/src/install/cursor.ts +0 -68
  130. package/src/install/hermes-agent.ts +0 -76
  131. package/src/install/hermes.ts +0 -78
  132. package/src/install/nlm-dir-perms.ts +0 -55
  133. package/src/install/ollama.ts +0 -284
  134. package/src/install/setup.ts +0 -489
  135. package/src/install/windsurf.ts +0 -68
  136. package/src/llm/classifier-box.ts +0 -64
  137. package/src/llm/deepseek-client.ts +0 -150
  138. package/src/llm/env-autoload.ts +0 -55
  139. package/src/llm/ollama-client.ts +0 -189
  140. package/src/mcp/server.ts +0 -534
  141. package/src/ports/fact-store.ts +0 -102
  142. package/src/ports/llm-client.ts +0 -52
  143. package/src/ports/logger.ts +0 -16
  144. package/src/ports/session-store.ts +0 -45
  145. package/src/ports/transcript-adapter.ts +0 -55
  146. package/src/shared/types.ts +0 -149
  147. package/src/ui/App.tsx +0 -58
  148. package/src/ui/components/PromoteOpenButton.tsx +0 -65
  149. package/src/ui/components/SessionDrawer.tsx +0 -199
  150. package/src/ui/components/SideNav.tsx +0 -162
  151. package/src/ui/components/Skeleton.tsx +0 -107
  152. package/src/ui/index.html +0 -13
  153. package/src/ui/lib/actions.ts +0 -30
  154. package/src/ui/lib/api.ts +0 -92
  155. package/src/ui/lib/dataset.ts +0 -141
  156. package/src/ui/lib/registries.ts +0 -155
  157. package/src/ui/lib/view-settings.ts +0 -41
  158. package/src/ui/main.tsx +0 -15
  159. package/src/ui/pages/Live.tsx +0 -229
  160. package/src/ui/pages/Pulse.tsx +0 -415
  161. package/src/ui/pages/Recall.tsx +0 -190
  162. package/src/ui/pages/River.tsx +0 -354
  163. package/src/ui/pages/Search.tsx +0 -386
  164. package/src/ui/pages/Stub.tsx +0 -9
  165. package/src/ui/pages/Thread.tsx +0 -473
  166. package/src/ui/pages/settings/Classifier.tsx +0 -227
  167. package/src/ui/pages/settings/Data.tsx +0 -190
  168. package/src/ui/pages/settings/Index.tsx +0 -65
  169. package/src/ui/pages/settings/Labels.tsx +0 -224
  170. package/src/ui/pages/settings/Providers.tsx +0 -305
  171. package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
  172. package/src/ui/pages/settings/Sources.tsx +0 -326
  173. package/src/ui/pages/settings/Views.tsx +0 -96
  174. package/src/ui/styles.css +0 -1890
  175. package/src/ui/tsconfig.json +0 -21
  176. package/src/ui/vite.config.ts +0 -19
  177. package/tests/fixtures/claude_code/short_session.jsonl +0 -2
  178. package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
  179. package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
  180. package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
  181. package/tests/fixtures/facts.ts +0 -17
  182. package/tests/fixtures/golden-corpus.ts +0 -85
  183. package/tests/fixtures/hermes/paired_request_dump.json +0 -24
  184. package/tests/fixtures/hermes/paired_session.json +0 -23
  185. package/tests/fixtures/hermes/request_dump.json +0 -28
  186. package/tests/fixtures/hermes/session_iso.json +0 -38
  187. package/tests/fixtures/hermes/session_unix.json +0 -38
  188. package/tests/fixtures/hermes/system_only.json +0 -18
  189. package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
  190. package/tests/fixtures/pi/short-successful.jsonl +0 -5
  191. package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
  192. package/tests/fixtures/sessions.ts +0 -22
  193. package/tests/integration/backfill-facts.test.ts +0 -362
  194. package/tests/integration/citation-explicit.test.ts +0 -111
  195. package/tests/integration/cite-event.test.ts +0 -169
  196. package/tests/integration/cite-memo.test.ts +0 -87
  197. package/tests/integration/db-restore.test.ts +0 -153
  198. package/tests/integration/embed-backfill.test.ts +0 -176
  199. package/tests/integration/fact-supersedence.test.ts +0 -313
  200. package/tests/integration/fts-index.test.ts +0 -60
  201. package/tests/integration/getbyids-sqlite.test.ts +0 -100
  202. package/tests/integration/hermes-agent-hooks.test.ts +0 -248
  203. package/tests/integration/hook-claude-settings.test.ts +0 -218
  204. package/tests/integration/hook-log.test.ts +0 -54
  205. package/tests/integration/hook-memo.test.ts +0 -68
  206. package/tests/integration/hook-pre-compact.test.ts +0 -105
  207. package/tests/integration/hook-subagent-start.test.ts +0 -102
  208. package/tests/integration/http.test.ts +0 -401
  209. package/tests/integration/keyword-search-fts.test.ts +0 -66
  210. package/tests/integration/mcp-recall-logging.test.ts +0 -88
  211. package/tests/integration/mcp.test.ts +0 -260
  212. package/tests/integration/memo-sweep.test.ts +0 -91
  213. package/tests/integration/prompt-recall-hook.test.ts +0 -88
  214. package/tests/integration/provider-registry.test.ts +0 -107
  215. package/tests/integration/recall-golden.test.ts +0 -59
  216. package/tests/integration/recall-sqlite.test.ts +0 -169
  217. package/tests/integration/scheduler.test.ts +0 -391
  218. package/tests/integration/session-end-hook.test.ts +0 -48
  219. package/tests/integration/session-start-hook.test.ts +0 -126
  220. package/tests/integration/source-registry.test.ts +0 -122
  221. package/tests/integration/sqlite-fact-store.test.ts +0 -346
  222. package/tests/integration/stop-hook.test.ts +0 -560
  223. package/tests/integration/wal-checkpoint.test.ts +0 -49
  224. package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
  225. package/tests/unit/core/adapters/aider.test.ts +0 -230
  226. package/tests/unit/core/adapters/claude-code.test.ts +0 -118
  227. package/tests/unit/core/adapters/cursor.test.ts +0 -485
  228. package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
  229. package/tests/unit/core/adapters/hermes.test.ts +0 -81
  230. package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
  231. package/tests/unit/core/adapters/opencode.test.ts +0 -354
  232. package/tests/unit/core/adapters/pi.test.ts +0 -110
  233. package/tests/unit/core/adapters/windsurf.test.ts +0 -416
  234. package/tests/unit/core/classifier/prompt.test.ts +0 -126
  235. package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
  236. package/tests/unit/core/facts/extract-facts.test.ts +0 -117
  237. package/tests/unit/core/filter.test.ts +0 -40
  238. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
  239. package/tests/unit/core/hook/citation-detect.test.ts +0 -124
  240. package/tests/unit/core/hook/gate.test.ts +0 -29
  241. package/tests/unit/core/hook/pointer-block.test.ts +0 -22
  242. package/tests/unit/core/hook/select.test.ts +0 -66
  243. package/tests/unit/core/match-fields.test.ts +0 -39
  244. package/tests/unit/core/mcp-cite-session.test.ts +0 -51
  245. package/tests/unit/core/providers/provider-models.test.ts +0 -101
  246. package/tests/unit/core/query-shape.test.ts +0 -92
  247. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
  248. package/tests/unit/core/recall-service.test.ts +0 -200
  249. package/tests/unit/core/storage/live-status.test.ts +0 -54
  250. package/tests/unit/core/tokenize.test.ts +0 -32
  251. package/tests/unit/core/useful-scan.test.ts +0 -537
  252. package/tests/unit/llm/embed.test.ts +0 -93
  253. package/tests/unit/llm/ollama-client.test.ts +0 -124
  254. package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
  255. package/tsconfig.json +0 -31
  256. package/tsconfig.test.json +0 -11
  257. package/vitest.config.ts +0 -22
@@ -1,131 +0,0 @@
1
- # NLM Auto-Inject Recall Hook — Design
2
-
3
- **Status:** Approved design, ready for implementation planning
4
- **Date:** 2026-05-20
5
- **Tracking:** NLM open task #144
6
- **Repo:** nlm-memory-ts
7
-
8
- ## Problem
9
-
10
- NLM's write side works — the daemon ingests sessions automatically, the corpus is substantial, recall *coverage* is good (~92% hit rate). The unproven side is read adoption. The Recall page telemetry showed near-zero real agent recall traffic; fact recall had zero MCP calls ever. That number is confounded (the MCP server was broken until it was rewired on 2026-05-20), so adoption is untested rather than proven-bad — but the structural weakness is real regardless:
11
-
12
- Read-side recall depends on the agent *deciding* a prompt is backward-looking and *choosing* to call the recall tool. That is a soft contract — a model judgment call on every prompt, with no guarantee. A memory system that is only read when the agent remembers to ask is not reliably a memory system.
13
-
14
- This feature removes that dependency: relevant prior-session context is surfaced automatically, gated for relevance, via a Claude Code `UserPromptSubmit` hook.
15
-
16
- ## Scope
17
-
18
- - **In scope:** A Claude Code `UserPromptSubmit` hook that surfaces relevant prior sessions automatically.
19
- - **Out of scope (v1):** Hermes/Codex/Gemini equivalents (`UserPromptSubmit` is a Claude Code mechanism; other runtimes need their own integration — separate future work). Local-LLM relevance gating. Injecting full session content (v1 is pointer-only). Multi-machine sync.
20
-
21
- ## Design decisions
22
-
23
- Four decisions were settled during brainstorming:
24
-
25
- 1. **Rollout posture: shadow mode first.** Build the full hook now; it evaluates every prompt and logs what it *would* surface, but injects nothing. After 1–2 weeks the relevance gate is tuned on real logged data, then a single flag flips it live. This resolves the task #144 wait-vs-build tension: the feature is built now and measured by the real artifact, and going live is a low-risk one-flag change.
26
-
27
- 2. **Relevance gate: heuristic prefilter + recall-score threshold.** No per-prompt LLM call. A cheap heuristic excludes obviously generative prompts; everything else queries recall and is gated on the top hit's score.
28
-
29
- 3. **Injection payload: pointer-only.** When the gate fires, inject a short pointer block (matched session ids + labels), not full content. The agent pulls detail with the existing `recall_sessions` / `get_session` MCP tools. Rationale: the structural problem is the agent never *realizing memory is relevant* — pointer-only fixes exactly that (surfaces awareness automatically) while leaving retrieval depth to the agent, at minimal token cost. If shadow/live data later shows agents see pointers but don't pull, escalating to compact content injection is a follow-up.
30
-
31
- 4. **Repeat control: per-conversation dedup memo.** Each relevant session is surfaced at most once per conversation. Chosen over a turn-based rate limit because (a) both approaches require per-conversation state, so the rate limit is not actually simpler; (b) the real waste is *redundancy* (re-surfacing the same session), which the memo eliminates and a rate limit only throttles; (c) a rate limit can suppress a genuinely new, highly relevant hit purely for timing reasons, which a memory feature should never do.
32
-
33
- ## Components
34
-
35
- | Component | File | Responsibility |
36
- |---|---|---|
37
- | Gate (pure) | `src/core/hook/gate.ts` | `classifyPrompt(prompt) → "generative" \| "evaluate"`. Pure function, no I/O. Unit-tested. |
38
- | Hook entrypoint | `src/hook/prompt-recall-hook.ts` → built to `dist/hook/prompt-recall-hook.js` | Invoked by Claude Code per prompt. Reads hook JSON from stdin, runs the gate, queries recall, injects (live) or logs (shadow). |
39
- | CLI subcommand | `nlm hook install` / `nlm hook uninstall` (in `src/cli/nlm.ts`) | Adds/removes the `UserPromptSubmit` entry in `~/.claude/settings.json`. Idempotent and reversible. **Not** part of `nlm install`. |
40
- | Shadow log | `~/.nlm/hook-log.jsonl` | Append-only. One line per prompt seen: timestamp, conversation id, truncated prompt, gate decision, recall hits + scores, would-inject flag, estimated token cost. |
41
- | Per-conversation memo | `~/.nlm/hook-state/<conversation-id>.json` | The set of session ids already surfaced in this conversation. Drives dedup. |
42
-
43
- ## Gate logic
44
-
45
- The heuristic is a **conservative generative *excluder***, not a backward-looking detector. The default classification is `evaluate`; only obviously generative prompts short-circuit to `generative`.
46
-
47
- - `generative` signals (short-circuit, inject nothing, no API call): the prompt is dominated by generative intent — e.g. opens with or strongly centers on "write", "draft", "create", "compose", "brainstorm", "name", "generate", "design a", "come up with", "ideas for", "suggest a". The exact pattern set is seeded from the `workflows.md` recall trigger/non-trigger examples and refined against shadow-mode logs.
48
- - `evaluate` (default — everything else): proceed to a recall query.
49
-
50
- Rationale for the asymmetry: a false `evaluate` is cheap — recall returns weak hits and the score threshold discards them. A false `generative` (missing a backward-looking prompt) is the exact failure this feature exists to fix. So the heuristic is deliberately biased toward `evaluate`; the recall-score threshold does the real relevance filtering.
51
-
52
- ## Data flow (per prompt)
53
-
54
- 1. Claude Code fires `UserPromptSubmit` → runs `node dist/hook/prompt-recall-hook.js`, passing the hook payload as JSON on stdin (includes the user `prompt` and the conversation `session_id`).
55
- 2. The hook parses stdin and runs `classifyPrompt`.
56
- - `generative` → shadow: write a log line; live: nothing. Emit nothing. Exit 0.
57
- - `evaluate` → `GET http://localhost:3940/api/recall?q=<prompt>&mode=keyword&limit=5` with header `x-recall-source: hook`. (Keyword/FTS5, not hybrid: hybrid's Ollama embedding round-trip is ~5s, too slow for a hook that blocks prompt submission.)
58
- 3. Filter the returned hits to those with score ≥ the relevance threshold.
59
- 4. Load the per-conversation memo; drop hits whose session id was already surfaced.
60
- 5. Apply the per-conversation cap (see Token discipline).
61
- 6. Branch on mode:
62
- - **shadow** → append a log line (gate decision, surviving hits + scores, estimated token cost, would-inject flag). Emit nothing.
63
- - **live** → if any new hits remain, emit the pointer block to stdout and record those session ids in the memo. If none remain, emit nothing.
64
- 7. **Always exit 0.** Any error — daemon unreachable, malformed stdin, timeout — is caught and results in no output and a clean exit. The hook must never block or fail a prompt (fail open).
65
-
66
- ### Pointer block format (live mode)
67
-
68
- ```
69
- ## Possibly-relevant prior sessions (nlm-memory)
70
- - sess_a1b2 · FTS5 vs pgvector decision (2026-05-15)
71
- - sess_c3d4 · Semantic recall via sqlite-vec (2026-05-17)
72
- Pull detail with the recall_sessions / get_session MCP tools if relevant.
73
- ```
74
-
75
- ## Token discipline
76
-
77
- Context-bloat control is a first-class constraint, since the hook fires on every prompt.
78
-
79
- - **Pointer-only payload.** ~3 lines. Hard cap of **3 sessions per fire**, target ≤ ~60 tokens. One terse line per session: `sess_id · label (date)`.
80
- - **Each session surfaced at most once per conversation** (the dedup memo). `UserPromptSubmit` fires on every prompt; without dedup a long conversation would stack a pointer block every turn. The memo bounds total footprint by the count of *distinct relevant sessions*, not by conversation length.
81
- - **Suppress empty fires.** If the gate passes but no hit clears the score threshold, or every surviving hit was already surfaced, inject nothing.
82
- - **Per-conversation cap.** A hard ceiling of **10 distinct sessions surfaced per conversation**, total. A guardrail, not the primary mechanism — the memo plus the score threshold keep normal conversations well under it. Retained so a topic-roaming conversation cannot grow unbounded.
83
- - **Score threshold** starts conservative and is tuned in shadow mode against the logged score distribution. Under-injecting is preferred to poisoning the well (a noisy gate trains the agent to ignore injected context).
84
- - **Shadow log records estimated token cost per fire**, so the real per-conversation footprint is observed — not guessed — before going live.
85
-
86
- Expected steady state: most prompts cost zero tokens (excluded by the gate, or no new hits). A conversation injects at most a handful of ~60-token pointer blocks total, each once.
87
-
88
- ## Mode flag
89
-
90
- `NLM_HOOK_MODE` environment variable, read by the hook script. Default `shadow`. Values:
91
-
92
- - `shadow` — evaluate, log, inject nothing.
93
- - `live` — evaluate, log, inject pointer blocks.
94
-
95
- The env var is set in the hook's command entry in `~/.claude/settings.json` (so `nlm hook install` writes it). Flipping to live after the review window is a one-line settings edit; a future `nlm hook enable` convenience subcommand is possible but out of scope for v1.
96
-
97
- ## Distribution
98
-
99
- An explicit `nlm hook install` subcommand, **separate from `nlm install`**. Silently editing a user's `~/.claude/settings.json` during the main daemon install is too invasive. `nlm hook install`:
100
-
101
- - Reads `~/.claude/settings.json` (creates it if absent).
102
- - Adds a `UserPromptSubmit` hook entry pointing at the built hook script, with `NLM_HOOK_MODE=shadow` in its environment.
103
- - Is idempotent — re-running does not duplicate the entry.
104
-
105
- `nlm hook uninstall` removes exactly that entry and nothing else.
106
-
107
- ## Failure modes
108
-
109
- | Condition | Behavior |
110
- |---|---|
111
- | Daemon down / `/api/recall` unreachable | Catch, emit nothing, exit 0. |
112
- | Malformed or empty stdin | Catch, emit nothing, exit 0. |
113
- | Recall query slow | Bounded by a short timeout (≈1s); on timeout, emit nothing, exit 0. |
114
- | Memo file missing/corrupt | Treat as empty memo; continue. |
115
- | `~/.claude/settings.json` malformed during `nlm hook install` | Abort with a clear error; do not write a broken file. |
116
-
117
- The invariant: the hook never blocks, delays meaningfully, or fails a prompt. Every abnormal path is fail-open.
118
-
119
- ## Testing
120
-
121
- - **`gate.ts`** — unit tests over a fixture set of generative vs retrospective prompts, seeded from the `workflows.md` recall trigger/non-trigger examples.
122
- - **Hook script** — integration tests with a stubbed recall fetch: asserts shadow-mode logs and injects nothing; asserts live-mode injects the pointer block; asserts the dedup memo suppresses a repeat surfacing within one conversation; asserts fail-open on a simulated daemon-down.
123
- - **`nlm hook install` / `uninstall`** — integration tests against a temp `settings.json`: install adds the entry and is idempotent on re-run; uninstall removes exactly its own entry and leaves the rest of the file intact.
124
-
125
- ## Open calibration items (resolved in shadow mode, not now)
126
-
127
- - The exact generative-excluder pattern set.
128
- - The recall-score threshold value.
129
- - Whether the per-conversation cap of 10 is ever approached in practice.
130
-
131
- These are intentionally left to empirical tuning against `~/.nlm/hook-log.jsonl` during the shadow window.