nlm-memory 0.5.0 → 0.5.1
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.
- package/README.md +72 -34
- package/dist/cli/nlm.js +2 -1
- package/dist/cli/nlm.js.map +1 -1
- package/dist/http/app.js +2 -1
- package/dist/http/app.js.map +1 -1
- package/dist/mcp/server.js +20 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/ui/assets/{index-C8cpwbYJ.css → index-Beo8psd-.css} +1 -1
- package/dist/ui/assets/{index-CB50QnL-.js → index-CSPTTeeM.js} +8 -8
- package/dist/ui/index.html +2 -2
- package/package.json +26 -1
- package/.agents/plugins/marketplace.json +0 -20
- package/.github/workflows/ci.yml +0 -30
- package/docs/methodology/re-derivation-rate.md +0 -112
- package/docs/methodology/useful-hit-rate.md +0 -79
- package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
- package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
- package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
- package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
- package/docs/plans/desktop-product.md +0 -69
- package/docs/plans/factstore-design.md +0 -236
- package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1575
- package/logs/CHANGELOG/CHANGELOG.md +0 -209
- package/migrations/000_initial_schema.sql +0 -174
- package/migrations/001_entity_type_rename.sql +0 -17
- package/migrations/002_adapter_state_extend.sql +0 -12
- package/migrations/003_session_embeddings.sql +0 -11
- package/migrations/004_facts.sql +0 -46
- package/migrations/005_sources.sql +0 -31
- package/migrations/006_providers.sql +0 -33
- package/migrations/007_source_tokens.sql +0 -17
- package/migrations/008_fts_rebuild.sql +0 -9
- package/migrations/009_session_embedding_chunks.sql +0 -46
- package/migrations/010_sources_opencode.sql +0 -30
- package/migrations/011_sources_hermes_agent.sql +0 -30
- package/migrations/012_sources_aider.sql +0 -30
- package/migrations/013_adapter_state_failure_count.sql +0 -12
- package/migrations/014_sources_cursor.sql +0 -30
- package/migrations/015_sources_windsurf.sql +0 -30
- package/plugin-hermes-agent/README.md +0 -49
- package/plugin-hermes-agent/__init__.py +0 -75
- package/plugin-hermes-agent/plugin.yaml +0 -15
- package/scripts/backfill-citations.mjs +0 -0
- package/scripts/build-codex-plugin.mjs +0 -61
- package/scripts/deepseek-probe.mjs +0 -67
- package/scripts/extract-triples.mjs +0 -207
- package/scripts/longmemeval/embedding-cache.ts +0 -77
- package/scripts/longmemeval/fetch-dataset.sh +0 -25
- package/scripts/longmemeval/run-harness.ts +0 -315
- package/scripts/longmemeval/scorer.ts +0 -99
- package/scripts/longmemeval/tsconfig.json +0 -9
- package/scripts/longmemeval/types.ts +0 -35
- package/scripts/nlm-daily-digest.py +0 -239
- package/scripts/nlm-daily-digest.sh +0 -28
- package/src/cli/classify-parity.ts +0 -257
- package/src/cli/launchctl-helpers.ts +0 -49
- package/src/cli/nlm.ts +0 -1078
- package/src/core/actions/actions-log.ts +0 -118
- package/src/core/actions/overlay.ts +0 -117
- package/src/core/adapters/aider.ts +0 -205
- package/src/core/adapters/claude-code.ts +0 -293
- package/src/core/adapters/common.ts +0 -54
- package/src/core/adapters/cursor.ts +0 -486
- package/src/core/adapters/from-source.ts +0 -67
- package/src/core/adapters/hermes-agent.ts +0 -240
- package/src/core/adapters/hermes.ts +0 -277
- package/src/core/adapters/jsonl-generic.ts +0 -208
- package/src/core/adapters/opencode.ts +0 -281
- package/src/core/adapters/pi.ts +0 -264
- package/src/core/adapters/windsurf.ts +0 -386
- package/src/core/classifier/prompt.ts +0 -200
- package/src/core/dataset/build-dataset.ts +0 -463
- package/src/core/embedding/chunk-body.ts +0 -76
- package/src/core/embedding/embed-backfill.ts +0 -210
- package/src/core/embedding/embed-normalize.ts +0 -135
- package/src/core/facts/backfill-facts.ts +0 -254
- package/src/core/facts/extract-facts.ts +0 -50
- package/src/core/hook/citation-detect.ts +0 -124
- package/src/core/hook/cite-memo.ts +0 -68
- package/src/core/hook/claude-settings.ts +0 -187
- package/src/core/hook/gate.ts +0 -25
- package/src/core/hook/hook-log.ts +0 -41
- package/src/core/hook/memo-sweep.ts +0 -164
- package/src/core/hook/memo.ts +0 -67
- package/src/core/hook/pointer-block.ts +0 -26
- package/src/core/hook/select.ts +0 -32
- package/src/core/hook/transcript.ts +0 -121
- package/src/core/ingest/ingest-session.ts +0 -111
- package/src/core/providers/provider-models.ts +0 -100
- package/src/core/providers/provider-registry.ts +0 -196
- package/src/core/recall/citation-log.ts +0 -108
- package/src/core/recall/filter.ts +0 -27
- package/src/core/recall/index.ts +0 -6
- package/src/core/recall/match-fields.ts +0 -40
- package/src/core/recall/query-log.ts +0 -149
- package/src/core/recall/query-shape.ts +0 -66
- package/src/core/recall/recall-service.ts +0 -320
- package/src/core/recall/recent-log.ts +0 -59
- package/src/core/recall/tokenize.ts +0 -18
- package/src/core/recall/useful-scan.ts +0 -336
- package/src/core/recall-facts/fact-query-log.ts +0 -150
- package/src/core/recall-facts/fact-recall-service.ts +0 -327
- package/src/core/scheduler/scan-once.ts +0 -142
- package/src/core/scheduler/scheduler.ts +0 -225
- package/src/core/sources/source-registry.ts +0 -278
- package/src/core/storage/db-restore.ts +0 -133
- package/src/core/storage/live-status.ts +0 -45
- package/src/core/storage/migrate.ts +0 -72
- package/src/core/storage/sqlite-fact-store.ts +0 -304
- package/src/core/storage/sqlite-session-store.ts +0 -810
- package/src/hook/hook-auth.ts +0 -18
- package/src/hook/prompt-recall-hook.ts +0 -180
- package/src/hook/session-end-hook.ts +0 -81
- package/src/hook/session-start-hook.ts +0 -168
- package/src/hook/stop-hook.ts +0 -239
- package/src/http/app.ts +0 -1215
- package/src/install/claude-code.ts +0 -128
- package/src/install/codex.ts +0 -367
- package/src/install/cursor.ts +0 -68
- package/src/install/hermes-agent.ts +0 -76
- package/src/install/hermes.ts +0 -78
- package/src/install/nlm-dir-perms.ts +0 -55
- package/src/install/ollama.ts +0 -284
- package/src/install/setup.ts +0 -489
- package/src/install/windsurf.ts +0 -68
- package/src/llm/classifier-box.ts +0 -64
- package/src/llm/deepseek-client.ts +0 -150
- package/src/llm/env-autoload.ts +0 -55
- package/src/llm/ollama-client.ts +0 -189
- package/src/mcp/server.ts +0 -534
- package/src/ports/fact-store.ts +0 -102
- package/src/ports/llm-client.ts +0 -52
- package/src/ports/logger.ts +0 -16
- package/src/ports/session-store.ts +0 -45
- package/src/ports/transcript-adapter.ts +0 -55
- package/src/shared/types.ts +0 -149
- package/src/ui/App.tsx +0 -58
- package/src/ui/components/PromoteOpenButton.tsx +0 -65
- package/src/ui/components/SessionDrawer.tsx +0 -199
- package/src/ui/components/SideNav.tsx +0 -162
- package/src/ui/components/Skeleton.tsx +0 -107
- package/src/ui/index.html +0 -13
- package/src/ui/lib/actions.ts +0 -30
- package/src/ui/lib/api.ts +0 -92
- package/src/ui/lib/dataset.ts +0 -141
- package/src/ui/lib/registries.ts +0 -155
- package/src/ui/lib/view-settings.ts +0 -41
- package/src/ui/main.tsx +0 -15
- package/src/ui/pages/Live.tsx +0 -229
- package/src/ui/pages/Pulse.tsx +0 -415
- package/src/ui/pages/Recall.tsx +0 -190
- package/src/ui/pages/River.tsx +0 -354
- package/src/ui/pages/Search.tsx +0 -386
- package/src/ui/pages/Stub.tsx +0 -9
- package/src/ui/pages/Thread.tsx +0 -473
- package/src/ui/pages/settings/Classifier.tsx +0 -227
- package/src/ui/pages/settings/Data.tsx +0 -190
- package/src/ui/pages/settings/Index.tsx +0 -65
- package/src/ui/pages/settings/Labels.tsx +0 -224
- package/src/ui/pages/settings/Providers.tsx +0 -305
- package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
- package/src/ui/pages/settings/Sources.tsx +0 -326
- package/src/ui/pages/settings/Views.tsx +0 -96
- package/src/ui/styles.css +0 -1890
- package/src/ui/tsconfig.json +0 -21
- package/src/ui/vite.config.ts +0 -19
- package/tests/fixtures/claude_code/short_session.jsonl +0 -2
- package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
- package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
- package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
- package/tests/fixtures/facts.ts +0 -17
- package/tests/fixtures/golden-corpus.ts +0 -85
- package/tests/fixtures/hermes/paired_request_dump.json +0 -24
- package/tests/fixtures/hermes/paired_session.json +0 -23
- package/tests/fixtures/hermes/request_dump.json +0 -28
- package/tests/fixtures/hermes/session_iso.json +0 -38
- package/tests/fixtures/hermes/session_unix.json +0 -38
- package/tests/fixtures/hermes/system_only.json +0 -18
- package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
- package/tests/fixtures/pi/short-successful.jsonl +0 -5
- package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
- package/tests/fixtures/sessions.ts +0 -22
- package/tests/integration/backfill-facts.test.ts +0 -362
- package/tests/integration/citation-explicit.test.ts +0 -111
- package/tests/integration/cite-event.test.ts +0 -169
- package/tests/integration/cite-memo.test.ts +0 -87
- package/tests/integration/db-restore.test.ts +0 -153
- package/tests/integration/embed-backfill.test.ts +0 -176
- package/tests/integration/fact-supersedence.test.ts +0 -313
- package/tests/integration/fts-index.test.ts +0 -60
- package/tests/integration/getbyids-sqlite.test.ts +0 -100
- package/tests/integration/hermes-agent-hooks.test.ts +0 -248
- package/tests/integration/hook-claude-settings.test.ts +0 -218
- package/tests/integration/hook-log.test.ts +0 -54
- package/tests/integration/hook-memo.test.ts +0 -68
- package/tests/integration/hook-pre-compact.test.ts +0 -105
- package/tests/integration/hook-subagent-start.test.ts +0 -102
- package/tests/integration/http.test.ts +0 -401
- package/tests/integration/keyword-search-fts.test.ts +0 -66
- package/tests/integration/mcp-recall-logging.test.ts +0 -88
- package/tests/integration/mcp.test.ts +0 -260
- package/tests/integration/memo-sweep.test.ts +0 -91
- package/tests/integration/prompt-recall-hook.test.ts +0 -88
- package/tests/integration/provider-registry.test.ts +0 -107
- package/tests/integration/recall-golden.test.ts +0 -59
- package/tests/integration/recall-sqlite.test.ts +0 -169
- package/tests/integration/scheduler.test.ts +0 -391
- package/tests/integration/session-end-hook.test.ts +0 -48
- package/tests/integration/session-start-hook.test.ts +0 -126
- package/tests/integration/source-registry.test.ts +0 -122
- package/tests/integration/sqlite-fact-store.test.ts +0 -346
- package/tests/integration/stop-hook.test.ts +0 -560
- package/tests/integration/wal-checkpoint.test.ts +0 -49
- package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
- package/tests/unit/core/adapters/aider.test.ts +0 -230
- package/tests/unit/core/adapters/claude-code.test.ts +0 -118
- package/tests/unit/core/adapters/cursor.test.ts +0 -485
- package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
- package/tests/unit/core/adapters/hermes.test.ts +0 -81
- package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
- package/tests/unit/core/adapters/opencode.test.ts +0 -354
- package/tests/unit/core/adapters/pi.test.ts +0 -110
- package/tests/unit/core/adapters/windsurf.test.ts +0 -416
- package/tests/unit/core/classifier/prompt.test.ts +0 -126
- package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
- package/tests/unit/core/facts/extract-facts.test.ts +0 -117
- package/tests/unit/core/filter.test.ts +0 -40
- package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
- package/tests/unit/core/hook/citation-detect.test.ts +0 -124
- package/tests/unit/core/hook/gate.test.ts +0 -29
- package/tests/unit/core/hook/pointer-block.test.ts +0 -22
- package/tests/unit/core/hook/select.test.ts +0 -66
- package/tests/unit/core/match-fields.test.ts +0 -39
- package/tests/unit/core/mcp-cite-session.test.ts +0 -51
- package/tests/unit/core/providers/provider-models.test.ts +0 -101
- package/tests/unit/core/query-shape.test.ts +0 -92
- package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
- package/tests/unit/core/recall-service.test.ts +0 -200
- package/tests/unit/core/storage/live-status.test.ts +0 -54
- package/tests/unit/core/tokenize.test.ts +0 -32
- package/tests/unit/core/useful-scan.test.ts +0 -537
- package/tests/unit/llm/embed.test.ts +0 -93
- package/tests/unit/llm/ollama-client.test.ts +0 -124
- package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
- package/tsconfig.json +0 -31
- package/tsconfig.test.json +0 -11
- 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.
|