nlm-memory 0.4.2 → 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.
Files changed (285) hide show
  1. package/README.md +72 -34
  2. package/dist/cli/nlm.js +223 -33
  3. package/dist/cli/nlm.js.map +1 -1
  4. package/dist/core/adapters/cursor.d.ts +45 -0
  5. package/dist/core/adapters/cursor.js +397 -0
  6. package/dist/core/adapters/cursor.js.map +1 -0
  7. package/dist/core/adapters/from-source.js +10 -0
  8. package/dist/core/adapters/from-source.js.map +1 -1
  9. package/dist/core/adapters/windsurf.d.ts +44 -0
  10. package/dist/core/adapters/windsurf.js +299 -0
  11. package/dist/core/adapters/windsurf.js.map +1 -0
  12. package/dist/core/hook/claude-settings.d.ts +12 -5
  13. package/dist/core/hook/claude-settings.js +21 -6
  14. package/dist/core/hook/claude-settings.js.map +1 -1
  15. package/dist/core/sources/source-registry.d.ts +1 -1
  16. package/dist/core/sources/source-registry.js +18 -0
  17. package/dist/core/sources/source-registry.js.map +1 -1
  18. package/dist/core/storage/sqlite-session-store.d.ts +2 -0
  19. package/dist/core/storage/sqlite-session-store.js +38 -2
  20. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  21. package/dist/hook/hook-auth.d.ts +13 -0
  22. package/dist/hook/hook-auth.js +19 -0
  23. package/dist/hook/hook-auth.js.map +1 -0
  24. package/dist/hook/prompt-recall-hook.js +7 -1
  25. package/dist/hook/prompt-recall-hook.js.map +1 -1
  26. package/dist/hook/session-start-hook.js +4 -1
  27. package/dist/hook/session-start-hook.js.map +1 -1
  28. package/dist/hook/stop-hook.js +4 -1
  29. package/dist/hook/stop-hook.js.map +1 -1
  30. package/dist/http/app.d.ts +2 -0
  31. package/dist/http/app.js +76 -1
  32. package/dist/http/app.js.map +1 -1
  33. package/dist/install/claude-code.js +1 -1
  34. package/dist/install/claude-code.js.map +1 -1
  35. package/dist/install/cursor.d.ts +25 -0
  36. package/dist/install/cursor.js +43 -0
  37. package/dist/install/cursor.js.map +1 -0
  38. package/dist/install/nlm-dir-perms.d.ts +19 -0
  39. package/dist/install/nlm-dir-perms.js +43 -0
  40. package/dist/install/nlm-dir-perms.js.map +1 -0
  41. package/dist/install/ollama.d.ts +18 -1
  42. package/dist/install/ollama.js +62 -7
  43. package/dist/install/ollama.js.map +1 -1
  44. package/dist/install/setup.d.ts +4 -0
  45. package/dist/install/setup.js +141 -18
  46. package/dist/install/setup.js.map +1 -1
  47. package/dist/install/windsurf.d.ts +25 -0
  48. package/dist/install/windsurf.js +43 -0
  49. package/dist/install/windsurf.js.map +1 -0
  50. package/dist/mcp/server.js +20 -1
  51. package/dist/mcp/server.js.map +1 -1
  52. package/dist/shared/types.d.ts +4 -0
  53. package/dist/ui/assets/{index-BA6IpU8g.css → index-Beo8psd-.css} +1 -1
  54. package/dist/ui/assets/index-CSPTTeeM.js +69 -0
  55. package/dist/ui/index.html +2 -2
  56. package/package.json +26 -1
  57. package/plugin/scripts/prompt-recall-hook.mjs +55 -4
  58. package/plugin/scripts/stop-hook.mjs +57 -6
  59. package/.agents/plugins/marketplace.json +0 -20
  60. package/.github/workflows/ci.yml +0 -30
  61. package/dist/ui/assets/index-B_qIVV0k.js +0 -69
  62. package/docs/methodology/re-derivation-rate.md +0 -112
  63. package/docs/methodology/useful-hit-rate.md +0 -79
  64. package/docs/plans/2026-05-20-fts5-lexical-recall.md +0 -1088
  65. package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +0 -662
  66. package/docs/plans/2026-05-20-recall-hook-design.md +0 -131
  67. package/docs/plans/2026-05-20-recall-hook-implementation.md +0 -1222
  68. package/docs/plans/desktop-product.md +0 -69
  69. package/docs/plans/factstore-design.md +0 -236
  70. package/logs/CHANGELOG/CHANGELOG-2026.md +0 -1389
  71. package/logs/CHANGELOG/CHANGELOG.md +0 -337
  72. package/migrations/000_initial_schema.sql +0 -174
  73. package/migrations/001_entity_type_rename.sql +0 -17
  74. package/migrations/002_adapter_state_extend.sql +0 -12
  75. package/migrations/003_session_embeddings.sql +0 -11
  76. package/migrations/004_facts.sql +0 -46
  77. package/migrations/005_sources.sql +0 -31
  78. package/migrations/006_providers.sql +0 -33
  79. package/migrations/007_source_tokens.sql +0 -17
  80. package/migrations/008_fts_rebuild.sql +0 -9
  81. package/migrations/009_session_embedding_chunks.sql +0 -46
  82. package/migrations/010_sources_opencode.sql +0 -30
  83. package/migrations/011_sources_hermes_agent.sql +0 -30
  84. package/migrations/012_sources_aider.sql +0 -30
  85. package/migrations/013_adapter_state_failure_count.sql +0 -12
  86. package/plugin-hermes-agent/README.md +0 -49
  87. package/plugin-hermes-agent/__init__.py +0 -75
  88. package/plugin-hermes-agent/plugin.yaml +0 -15
  89. package/scripts/backfill-citations.mjs +0 -0
  90. package/scripts/build-codex-plugin.mjs +0 -61
  91. package/scripts/deepseek-probe.mjs +0 -67
  92. package/scripts/extract-triples.mjs +0 -207
  93. package/scripts/longmemeval/embedding-cache.ts +0 -77
  94. package/scripts/longmemeval/fetch-dataset.sh +0 -25
  95. package/scripts/longmemeval/run-harness.ts +0 -315
  96. package/scripts/longmemeval/scorer.ts +0 -99
  97. package/scripts/longmemeval/tsconfig.json +0 -9
  98. package/scripts/longmemeval/types.ts +0 -35
  99. package/scripts/nlm-daily-digest.py +0 -239
  100. package/scripts/nlm-daily-digest.sh +0 -28
  101. package/src/cli/classify-parity.ts +0 -257
  102. package/src/cli/launchctl-helpers.ts +0 -49
  103. package/src/cli/nlm.ts +0 -885
  104. package/src/core/actions/actions-log.ts +0 -118
  105. package/src/core/actions/overlay.ts +0 -117
  106. package/src/core/adapters/aider.ts +0 -205
  107. package/src/core/adapters/claude-code.ts +0 -293
  108. package/src/core/adapters/common.ts +0 -54
  109. package/src/core/adapters/from-source.ts +0 -57
  110. package/src/core/adapters/hermes-agent.ts +0 -240
  111. package/src/core/adapters/hermes.ts +0 -277
  112. package/src/core/adapters/jsonl-generic.ts +0 -208
  113. package/src/core/adapters/opencode.ts +0 -281
  114. package/src/core/adapters/pi.ts +0 -264
  115. package/src/core/classifier/prompt.ts +0 -200
  116. package/src/core/dataset/build-dataset.ts +0 -463
  117. package/src/core/embedding/chunk-body.ts +0 -76
  118. package/src/core/embedding/embed-backfill.ts +0 -210
  119. package/src/core/embedding/embed-normalize.ts +0 -135
  120. package/src/core/facts/backfill-facts.ts +0 -254
  121. package/src/core/facts/extract-facts.ts +0 -50
  122. package/src/core/hook/citation-detect.ts +0 -124
  123. package/src/core/hook/cite-memo.ts +0 -68
  124. package/src/core/hook/claude-settings.ts +0 -166
  125. package/src/core/hook/gate.ts +0 -25
  126. package/src/core/hook/hook-log.ts +0 -41
  127. package/src/core/hook/memo-sweep.ts +0 -164
  128. package/src/core/hook/memo.ts +0 -67
  129. package/src/core/hook/pointer-block.ts +0 -26
  130. package/src/core/hook/select.ts +0 -32
  131. package/src/core/hook/transcript.ts +0 -121
  132. package/src/core/ingest/ingest-session.ts +0 -111
  133. package/src/core/providers/provider-models.ts +0 -100
  134. package/src/core/providers/provider-registry.ts +0 -196
  135. package/src/core/recall/citation-log.ts +0 -108
  136. package/src/core/recall/filter.ts +0 -27
  137. package/src/core/recall/index.ts +0 -6
  138. package/src/core/recall/match-fields.ts +0 -40
  139. package/src/core/recall/query-log.ts +0 -149
  140. package/src/core/recall/query-shape.ts +0 -66
  141. package/src/core/recall/recall-service.ts +0 -320
  142. package/src/core/recall/recent-log.ts +0 -59
  143. package/src/core/recall/tokenize.ts +0 -18
  144. package/src/core/recall/useful-scan.ts +0 -336
  145. package/src/core/recall-facts/fact-query-log.ts +0 -150
  146. package/src/core/recall-facts/fact-recall-service.ts +0 -327
  147. package/src/core/scheduler/scan-once.ts +0 -142
  148. package/src/core/scheduler/scheduler.ts +0 -225
  149. package/src/core/sources/source-registry.ts +0 -260
  150. package/src/core/storage/db-restore.ts +0 -133
  151. package/src/core/storage/live-status.ts +0 -45
  152. package/src/core/storage/migrate.ts +0 -72
  153. package/src/core/storage/sqlite-fact-store.ts +0 -304
  154. package/src/core/storage/sqlite-session-store.ts +0 -765
  155. package/src/hook/prompt-recall-hook.ts +0 -174
  156. package/src/hook/session-end-hook.ts +0 -81
  157. package/src/hook/session-start-hook.ts +0 -165
  158. package/src/hook/stop-hook.ts +0 -236
  159. package/src/http/app.ts +0 -1137
  160. package/src/install/claude-code.ts +0 -128
  161. package/src/install/codex.ts +0 -367
  162. package/src/install/hermes-agent.ts +0 -76
  163. package/src/install/hermes.ts +0 -78
  164. package/src/install/ollama.ts +0 -211
  165. package/src/install/setup.ts +0 -368
  166. package/src/llm/classifier-box.ts +0 -64
  167. package/src/llm/deepseek-client.ts +0 -150
  168. package/src/llm/env-autoload.ts +0 -55
  169. package/src/llm/ollama-client.ts +0 -189
  170. package/src/mcp/server.ts +0 -534
  171. package/src/ports/fact-store.ts +0 -102
  172. package/src/ports/llm-client.ts +0 -52
  173. package/src/ports/logger.ts +0 -16
  174. package/src/ports/session-store.ts +0 -45
  175. package/src/ports/transcript-adapter.ts +0 -55
  176. package/src/shared/types.ts +0 -145
  177. package/src/ui/App.tsx +0 -58
  178. package/src/ui/components/PromoteOpenButton.tsx +0 -65
  179. package/src/ui/components/SessionDrawer.tsx +0 -136
  180. package/src/ui/components/SideNav.tsx +0 -162
  181. package/src/ui/components/Skeleton.tsx +0 -107
  182. package/src/ui/index.html +0 -13
  183. package/src/ui/lib/actions.ts +0 -30
  184. package/src/ui/lib/api.ts +0 -92
  185. package/src/ui/lib/dataset.ts +0 -141
  186. package/src/ui/lib/registries.ts +0 -155
  187. package/src/ui/lib/view-settings.ts +0 -41
  188. package/src/ui/main.tsx +0 -15
  189. package/src/ui/pages/Live.tsx +0 -229
  190. package/src/ui/pages/Pulse.tsx +0 -415
  191. package/src/ui/pages/Recall.tsx +0 -190
  192. package/src/ui/pages/River.tsx +0 -308
  193. package/src/ui/pages/Search.tsx +0 -93
  194. package/src/ui/pages/Stub.tsx +0 -9
  195. package/src/ui/pages/Thread.tsx +0 -262
  196. package/src/ui/pages/settings/Classifier.tsx +0 -227
  197. package/src/ui/pages/settings/Data.tsx +0 -190
  198. package/src/ui/pages/settings/Index.tsx +0 -65
  199. package/src/ui/pages/settings/Labels.tsx +0 -224
  200. package/src/ui/pages/settings/Providers.tsx +0 -305
  201. package/src/ui/pages/settings/SettingsSubnav.tsx +0 -28
  202. package/src/ui/pages/settings/Sources.tsx +0 -326
  203. package/src/ui/pages/settings/Views.tsx +0 -96
  204. package/src/ui/styles.css +0 -1766
  205. package/src/ui/tsconfig.json +0 -21
  206. package/src/ui/vite.config.ts +0 -19
  207. package/tests/fixtures/claude_code/short_session.jsonl +0 -2
  208. package/tests/fixtures/claude_code/standard_iso.jsonl +0 -4
  209. package/tests/fixtures/claude_code/tool_heavy.jsonl +0 -8
  210. package/tests/fixtures/claude_code/with_subagent.jsonl +0 -7
  211. package/tests/fixtures/facts.ts +0 -17
  212. package/tests/fixtures/golden-corpus.ts +0 -85
  213. package/tests/fixtures/hermes/paired_request_dump.json +0 -24
  214. package/tests/fixtures/hermes/paired_session.json +0 -23
  215. package/tests/fixtures/hermes/request_dump.json +0 -28
  216. package/tests/fixtures/hermes/session_iso.json +0 -38
  217. package/tests/fixtures/hermes/session_unix.json +0 -38
  218. package/tests/fixtures/hermes/system_only.json +0 -18
  219. package/tests/fixtures/pi/error-connection-abort.jsonl +0 -8
  220. package/tests/fixtures/pi/short-successful.jsonl +0 -5
  221. package/tests/fixtures/pi/with-custom-message.jsonl +0 -6
  222. package/tests/fixtures/sessions.ts +0 -22
  223. package/tests/integration/backfill-facts.test.ts +0 -362
  224. package/tests/integration/citation-explicit.test.ts +0 -111
  225. package/tests/integration/cite-event.test.ts +0 -169
  226. package/tests/integration/cite-memo.test.ts +0 -87
  227. package/tests/integration/db-restore.test.ts +0 -153
  228. package/tests/integration/embed-backfill.test.ts +0 -176
  229. package/tests/integration/fact-supersedence.test.ts +0 -313
  230. package/tests/integration/fts-index.test.ts +0 -60
  231. package/tests/integration/getbyids-sqlite.test.ts +0 -60
  232. package/tests/integration/hermes-agent-hooks.test.ts +0 -248
  233. package/tests/integration/hook-claude-settings.test.ts +0 -205
  234. package/tests/integration/hook-log.test.ts +0 -54
  235. package/tests/integration/hook-memo.test.ts +0 -68
  236. package/tests/integration/hook-pre-compact.test.ts +0 -105
  237. package/tests/integration/hook-subagent-start.test.ts +0 -102
  238. package/tests/integration/http.test.ts +0 -401
  239. package/tests/integration/keyword-search-fts.test.ts +0 -66
  240. package/tests/integration/mcp-recall-logging.test.ts +0 -88
  241. package/tests/integration/mcp.test.ts +0 -248
  242. package/tests/integration/memo-sweep.test.ts +0 -91
  243. package/tests/integration/prompt-recall-hook.test.ts +0 -88
  244. package/tests/integration/provider-registry.test.ts +0 -107
  245. package/tests/integration/recall-golden.test.ts +0 -59
  246. package/tests/integration/recall-sqlite.test.ts +0 -169
  247. package/tests/integration/scheduler.test.ts +0 -391
  248. package/tests/integration/session-end-hook.test.ts +0 -48
  249. package/tests/integration/session-start-hook.test.ts +0 -126
  250. package/tests/integration/source-registry.test.ts +0 -120
  251. package/tests/integration/sqlite-fact-store.test.ts +0 -346
  252. package/tests/integration/stop-hook.test.ts +0 -560
  253. package/tests/integration/wal-checkpoint.test.ts +0 -49
  254. package/tests/unit/cli/launchctl-helpers.test.ts +0 -60
  255. package/tests/unit/core/adapters/aider.test.ts +0 -230
  256. package/tests/unit/core/adapters/claude-code.test.ts +0 -118
  257. package/tests/unit/core/adapters/hermes-agent.test.ts +0 -329
  258. package/tests/unit/core/adapters/hermes.test.ts +0 -81
  259. package/tests/unit/core/adapters/jsonl-generic.test.ts +0 -142
  260. package/tests/unit/core/adapters/opencode.test.ts +0 -354
  261. package/tests/unit/core/adapters/pi.test.ts +0 -110
  262. package/tests/unit/core/classifier/prompt.test.ts +0 -126
  263. package/tests/unit/core/embedding/chunk-body.test.ts +0 -100
  264. package/tests/unit/core/facts/extract-facts.test.ts +0 -117
  265. package/tests/unit/core/filter.test.ts +0 -40
  266. package/tests/unit/core/hook/citation-detect-cite-session.test.ts +0 -96
  267. package/tests/unit/core/hook/citation-detect.test.ts +0 -124
  268. package/tests/unit/core/hook/gate.test.ts +0 -29
  269. package/tests/unit/core/hook/pointer-block.test.ts +0 -22
  270. package/tests/unit/core/hook/select.test.ts +0 -66
  271. package/tests/unit/core/match-fields.test.ts +0 -39
  272. package/tests/unit/core/mcp-cite-session.test.ts +0 -51
  273. package/tests/unit/core/providers/provider-models.test.ts +0 -101
  274. package/tests/unit/core/query-shape.test.ts +0 -92
  275. package/tests/unit/core/recall-facts/fact-recall-service.test.ts +0 -258
  276. package/tests/unit/core/recall-service.test.ts +0 -200
  277. package/tests/unit/core/storage/live-status.test.ts +0 -54
  278. package/tests/unit/core/tokenize.test.ts +0 -32
  279. package/tests/unit/core/useful-scan.test.ts +0 -537
  280. package/tests/unit/llm/embed.test.ts +0 -93
  281. package/tests/unit/llm/ollama-client.test.ts +0 -124
  282. package/tests/unit/scripts/longmemeval-scorer.test.ts +0 -114
  283. package/tsconfig.json +0 -31
  284. package/tsconfig.test.json +0 -11
  285. package/vitest.config.ts +0 -22
@@ -1,31 +0,0 @@
1
- -- Migration 005: sources registry.
2
- --
3
- -- Each row represents one transcript source the daemon should scan. The
4
- -- three hardcoded adapters (claude-code / hermes / pi) become seeded rows
5
- -- pointing at the same parse logic; future custom JSONL or webhook sources
6
- -- live alongside them.
7
- --
8
- -- Parse config is a JSON blob whose shape depends on `kind`:
9
- -- - "claude-code" / "hermes" / "pi": preset adapters. parse_config is
10
- -- reserved but unused — paths come from path_or_url.
11
- -- - "jsonl-generic": { sessionIdField, textField, startedAtField,
12
- -- roleField, runtimeLabel, ... }
13
- -- - "webhook": parse_config is empty; ingest is push-based.
14
- --
15
- -- See docs/plans/desktop-product.md (Phase 0).
16
-
17
- CREATE TABLE IF NOT EXISTS sources (
18
- id INTEGER PRIMARY KEY AUTOINCREMENT,
19
- kind TEXT NOT NULL CHECK (kind IN ('claude-code', 'hermes', 'pi', 'jsonl-generic', 'webhook')),
20
- name TEXT NOT NULL UNIQUE,
21
- path_or_url TEXT,
22
- runtime_label TEXT NOT NULL,
23
- parse_config TEXT NOT NULL DEFAULT '{}',
24
- enabled INTEGER NOT NULL DEFAULT 1,
25
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
26
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
27
- );
28
-
29
- CREATE INDEX IF NOT EXISTS idx_sources_enabled ON sources(enabled) WHERE enabled = 1;
30
-
31
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (5, '005_sources');
@@ -1,33 +0,0 @@
1
- -- Migration 006: providers registry.
2
- --
3
- -- Each row represents one LLM endpoint configured for classification or
4
- -- (future) embedding. The classifier reads this table to choose a
5
- -- provider/model at boot; the UI mutates it through /api/providers.
6
- --
7
- -- API key storage. v0 stores keys in the api_key column. This is fine
8
- -- because the SQLite file already contains the user's transcripts —
9
- -- anyone with disk access already has everything sensitive. Phase 2
10
- -- (Tauri shell) migrates keys to the OS keychain and replaces the column
11
- -- with a keychain reference. The API shape stays the same.
12
- --
13
- -- `kind` is the structural family (openai-compatible, ollama,
14
- -- anthropic-native, deepseek). The classifier+model UI uses it to decide
15
- -- which client class to instantiate.
16
- --
17
- -- See docs/plans/desktop-product.md (Phase 0 task 3).
18
-
19
- CREATE TABLE IF NOT EXISTS providers (
20
- id INTEGER PRIMARY KEY AUTOINCREMENT,
21
- kind TEXT NOT NULL CHECK (kind IN ('deepseek', 'ollama', 'openai', 'anthropic', 'openrouter', 'openai-compatible')),
22
- name TEXT NOT NULL UNIQUE,
23
- base_url TEXT,
24
- api_key TEXT,
25
- default_model TEXT,
26
- enabled INTEGER NOT NULL DEFAULT 1,
27
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
28
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
29
- );
30
-
31
- CREATE INDEX IF NOT EXISTS idx_providers_enabled ON providers(enabled) WHERE enabled = 1;
32
-
33
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (6, '006_providers');
@@ -1,17 +0,0 @@
1
- -- Migration 007: webhook token on sources.
2
- --
3
- -- Webhook sources need a bearer token so external tools (anything that
4
- -- pushes sessions via POST /api/ingest) can authenticate. The token is
5
- -- stored alongside the source row so one webhook = one token = one
6
- -- provenance label.
7
- --
8
- -- Storage policy mirrors providers.api_key: column in the canonical
9
- -- SQLite for v0 (the DB file already holds the user's transcripts,
10
- -- adding a token doesn't change the threat model). Phase 2 migrates to
11
- -- OS keychain without changing the API shape.
12
- --
13
- -- See docs/plans/desktop-product.md (Phase 0 task 5).
14
-
15
- ALTER TABLE sources ADD COLUMN token TEXT;
16
-
17
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (7, '007_source_tokens');
@@ -1,9 +0,0 @@
1
- -- One-time safety rebuild of the sessions_fts external-content FTS5 index.
2
- -- The virtual table and its sync triggers (sessions_ai / sessions_au /
3
- -- sessions_ad) were declared in migration 000 and have fired on every write
4
- -- since, so the index is normally already in sync. This rebuild guarantees
5
- -- the index matches every existing sessions row before the recall path
6
- -- starts depending on FTS5 for keyword search. Safe and idempotent.
7
- INSERT INTO sessions_fts(sessions_fts) VALUES('rebuild');
8
-
9
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (8, 'fts_rebuild');
@@ -1,46 +0,0 @@
1
- -- Migration 009: chunk + max-pool semantic index.
2
- --
3
- -- Replaces session_embeddings (one vector per session, truncated at
4
- -- MAX_EMBED_CHARS=8000) with per-chunk vectors. Recall-time score is
5
- -- max cosine across chunks per session.
6
- --
7
- -- The 2026-05-25 LongMemEval-S baseline showed 98% of gold sessions
8
- -- exceed 8000 chars and were silently truncated. Raising the per-call
9
- -- cap (#172) hit Ollama 500s on >50% of long inputs. Chunking sidesteps
10
- -- both: each chunk is well under the Ollama failure cliff, and the full
11
- -- body becomes searchable. Expected lift: semantic R@5 87.2 → >92,
12
- -- hybrid R@5 94.6 → >96.
13
- --
14
- -- Schema choices:
15
- -- * Auxiliary columns (+session_id, +chunk_idx) so KNN queries return
16
- -- session attribution without a join.
17
- -- * Separate session_chunk_map keyed on session_id supports
18
- -- `DELETE FROM session_embedding_chunks WHERE chunk_id IN
19
- -- (SELECT chunk_id FROM session_chunk_map WHERE session_id = ?)`
20
- -- since vec0 has no documented filtering on aux columns.
21
- --
22
- -- session_embeddings (single-vector) is intentionally left in place:
23
- -- * keeps rollback trivial (revert recall code, old vectors still there)
24
- -- * avoids forcing a multi-hour re-embed at deploy time; backfill
25
- -- populates chunks asynchronously
26
- -- * a future cleanup migration drops it once chunks are validated
27
-
28
- CREATE VIRTUAL TABLE IF NOT EXISTS session_embedding_chunks USING vec0(
29
- chunk_id INTEGER PRIMARY KEY,
30
- embedding float[768],
31
- +session_id TEXT,
32
- +chunk_idx INTEGER
33
- );
34
-
35
- CREATE TABLE IF NOT EXISTS session_chunk_map (
36
- chunk_id INTEGER PRIMARY KEY,
37
- session_id TEXT NOT NULL,
38
- chunk_idx INTEGER NOT NULL,
39
- FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
40
- );
41
-
42
- CREATE INDEX IF NOT EXISTS idx_session_chunk_map_session
43
- ON session_chunk_map(session_id);
44
-
45
- INSERT OR IGNORE INTO schema_migrations (version, name)
46
- VALUES (9, '009_session_embedding_chunks');
@@ -1,30 +0,0 @@
1
- -- Migration 010: add 'opencode' to the sources.kind CHECK constraint.
2
- --
3
- -- SQLite does not support ALTER COLUMN to modify CHECK constraints in place.
4
- -- Standard approach: rename → recreate → copy → drop old.
5
-
6
- PRAGMA foreign_keys = OFF;
7
-
8
- CREATE TABLE sources_new (
9
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10
- kind TEXT NOT NULL CHECK (kind IN ('claude-code', 'hermes', 'opencode', 'pi', 'jsonl-generic', 'webhook')),
11
- name TEXT NOT NULL UNIQUE,
12
- path_or_url TEXT,
13
- runtime_label TEXT NOT NULL,
14
- parse_config TEXT NOT NULL DEFAULT '{}',
15
- enabled INTEGER NOT NULL DEFAULT 1,
16
- token TEXT,
17
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
18
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
19
- );
20
-
21
- INSERT INTO sources_new SELECT id, kind, name, path_or_url, runtime_label, parse_config, enabled, token, created_at, updated_at FROM sources;
22
-
23
- DROP TABLE sources;
24
- ALTER TABLE sources_new RENAME TO sources;
25
-
26
- CREATE INDEX IF NOT EXISTS idx_sources_enabled ON sources(enabled) WHERE enabled = 1;
27
-
28
- PRAGMA foreign_keys = ON;
29
-
30
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (10, '010_sources_opencode');
@@ -1,30 +0,0 @@
1
- -- Migration 011: add 'hermes-agent' to the sources.kind CHECK constraint.
2
- --
3
- -- SQLite does not support ALTER COLUMN to modify CHECK constraints in place.
4
- -- Standard approach: rename → recreate → copy → drop old.
5
-
6
- PRAGMA foreign_keys = OFF;
7
-
8
- CREATE TABLE sources_new (
9
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10
- kind TEXT NOT NULL CHECK (kind IN ('claude-code', 'hermes', 'hermes-agent', 'opencode', 'pi', 'jsonl-generic', 'webhook')),
11
- name TEXT NOT NULL UNIQUE,
12
- path_or_url TEXT,
13
- runtime_label TEXT NOT NULL,
14
- parse_config TEXT NOT NULL DEFAULT '{}',
15
- enabled INTEGER NOT NULL DEFAULT 1,
16
- token TEXT,
17
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
18
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
19
- );
20
-
21
- INSERT INTO sources_new SELECT id, kind, name, path_or_url, runtime_label, parse_config, enabled, token, created_at, updated_at FROM sources;
22
-
23
- DROP TABLE sources;
24
- ALTER TABLE sources_new RENAME TO sources;
25
-
26
- CREATE INDEX IF NOT EXISTS idx_sources_enabled ON sources(enabled) WHERE enabled = 1;
27
-
28
- PRAGMA foreign_keys = ON;
29
-
30
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (11, '011_sources_hermes_agent');
@@ -1,30 +0,0 @@
1
- -- Migration 012: add 'aider' to the sources.kind CHECK constraint.
2
- --
3
- -- SQLite does not support ALTER COLUMN to modify CHECK constraints in place.
4
- -- Standard approach: rename → recreate → copy → drop old.
5
-
6
- PRAGMA foreign_keys = OFF;
7
-
8
- CREATE TABLE sources_new (
9
- id INTEGER PRIMARY KEY AUTOINCREMENT,
10
- kind TEXT NOT NULL CHECK (kind IN ('claude-code', 'hermes', 'hermes-agent', 'aider', 'opencode', 'pi', 'jsonl-generic', 'webhook')),
11
- name TEXT NOT NULL UNIQUE,
12
- path_or_url TEXT,
13
- runtime_label TEXT NOT NULL,
14
- parse_config TEXT NOT NULL DEFAULT '{}',
15
- enabled INTEGER NOT NULL DEFAULT 1,
16
- token TEXT,
17
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
18
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
19
- );
20
-
21
- INSERT INTO sources_new SELECT id, kind, name, path_or_url, runtime_label, parse_config, enabled, token, created_at, updated_at FROM sources;
22
-
23
- DROP TABLE sources;
24
- ALTER TABLE sources_new RENAME TO sources;
25
-
26
- CREATE INDEX IF NOT EXISTS idx_sources_enabled ON sources(enabled) WHERE enabled = 1;
27
-
28
- PRAGMA foreign_keys = ON;
29
-
30
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (12, '012_sources_aider');
@@ -1,12 +0,0 @@
1
- -- Migration 013: add failure_count to adapter_state.
2
- --
3
- -- Tracks consecutive classify/storage failures per source file. The scheduler
4
- -- skips files whose failure_count has reached the backoff ceiling (default 3)
5
- -- and whose file_size has not changed since the last attempt. When the file
6
- -- grows (new content appended), failure_count resets to 0 and the file is
7
- -- retried. This prevents a single permanently-broken or over-large transcript
8
- -- from flooding daemon-err.log on every 30-minute tick.
9
-
10
- ALTER TABLE adapter_state ADD COLUMN failure_count INTEGER NOT NULL DEFAULT 0;
11
-
12
- INSERT OR IGNORE INTO schema_migrations (version, name) VALUES (13, '013_adapter_state_failure_count');
@@ -1,49 +0,0 @@
1
- # nlm-memory Hermes Agent plugin
2
-
3
- This directory is the [NousResearch Hermes Agent](https://github.com/NousResearch/hermes-agent) plugin distribution surface for nlm-memory.
4
-
5
- ## Install
6
-
7
- Prerequisite: `npm install -g nlm-memory` (puts `nlm` on PATH; the MCP server spawns `nlm mcp`).
8
-
9
- ```bash
10
- # Recommended — uses nlm connect to copy the plugin and enable it
11
- nlm connect hermes-agent
12
-
13
- # Or manually
14
- cp -r plugin-hermes-agent ~/.hermes/plugins/nlm-memory
15
- hermes plugins enable nlm-memory
16
- nlm connect hermes # also writes the MCP server entry to ~/.hermes/config.yaml
17
- ```
18
-
19
- ## What this ships
20
-
21
- - **`plugin.yaml`** — Hermes plugin manifest (name: `nlm-memory`, kind: `memory`)
22
- - **`__init__.py`** — Python shim that registers 6 lifecycle hooks; all delegate to the local nlm daemon at `http://localhost:3940`
23
-
24
- ## How it works
25
-
26
- | Hermes hook | Action |
27
- |---|---|
28
- | `pre_llm_call` | Keyword-recalls up to 3 relevant prior sessions; injects them as context above the user message |
29
- | `post_llm_call` | Scans the assistant response for cited session IDs and logs prose citation events |
30
- | `on_session_start` | No-op (memo is created lazily on first recall hit) |
31
- | `on_session_end` | Clears the per-session surfaced-ID memo |
32
- | `on_session_finalize` | Clears the per-session surfaced-ID memo |
33
- | `on_session_reset` | Clears the per-session surfaced-ID memo |
34
-
35
- ## Configuration
36
-
37
- | Env var | Default | Purpose |
38
- |---|---|---|
39
- | `NLM_DAEMON_PORT` | `3940` | Port the nlm daemon listens on |
40
-
41
- ## Daemon required
42
-
43
- The plugin delegates entirely to the nlm daemon. Start it with `nlm start` or `nlm install` (macOS LaunchAgent). If the daemon is unreachable, every hook silently returns `None` — the agent loop is never blocked.
44
-
45
- ## Uninstall
46
-
47
- ```bash
48
- nlm disconnect hermes-agent
49
- ```
@@ -1,75 +0,0 @@
1
- """
2
- nlm-memory Hermes Agent plugin.
3
-
4
- Wires 6 Hermes lifecycle hooks to the local nlm-memory daemon
5
- (http://localhost:3940 by default). All calls are fire-and-forget
6
- except pre_llm_call, which returns a context string for injection.
7
-
8
- The daemon must be running for hooks to have effect. If it is unreachable,
9
- every hook silently returns None so the agent loop is never blocked.
10
- """
11
-
12
- import json
13
- import os
14
- import urllib.error
15
- import urllib.request
16
- from typing import Any
17
-
18
-
19
- def _base_url() -> str:
20
- port = os.environ.get("NLM_DAEMON_PORT", "3940")
21
- return f"http://localhost:{port}"
22
-
23
-
24
- def _post(path: str, payload: dict[str, Any]) -> dict[str, Any]:
25
- url = f"{_base_url()}/api/{path}"
26
- data = json.dumps(payload).encode()
27
- req = urllib.request.Request(
28
- url,
29
- data=data,
30
- headers={"Content-Type": "application/json"},
31
- method="POST",
32
- )
33
- try:
34
- with urllib.request.urlopen(req, timeout=5) as resp:
35
- return json.loads(resp.read())
36
- except (urllib.error.URLError, OSError, json.JSONDecodeError):
37
- return {}
38
-
39
-
40
- def register(ctx: Any) -> None:
41
- def pre_llm_call(
42
- session_id: str,
43
- user_message: str,
44
- is_first_turn: bool = False,
45
- **kwargs: Any,
46
- ) -> str | None:
47
- result = _post(
48
- "hook/hermes-agent/pre-turn",
49
- {"session_id": session_id, "user_message": user_message, "is_first_turn": is_first_turn},
50
- )
51
- context = result.get("context")
52
- return context if isinstance(context, str) and context else None
53
-
54
- def post_llm_call(
55
- session_id: str,
56
- assistant_response: str,
57
- **kwargs: Any,
58
- ) -> None:
59
- _post(
60
- "hook/hermes-agent/post-turn",
61
- {"session_id": session_id, "assistant_response": assistant_response},
62
- )
63
-
64
- def _session_event(event: str, session_id: str | None, **kwargs: Any) -> None:
65
- _post(
66
- "hook/hermes-agent/session-lifecycle",
67
- {"event": event, "session_id": session_id},
68
- )
69
-
70
- ctx.register_hook("pre_llm_call", pre_llm_call)
71
- ctx.register_hook("post_llm_call", post_llm_call)
72
- ctx.register_hook("on_session_start", lambda session_id, **kw: _session_event("start", session_id, **kw))
73
- ctx.register_hook("on_session_end", lambda session_id, **kw: _session_event("end", session_id, **kw))
74
- ctx.register_hook("on_session_finalize", lambda session_id=None, **kw: _session_event("finalize", session_id, **kw))
75
- ctx.register_hook("on_session_reset", lambda session_id, **kw: _session_event("reset", session_id, **kw))
@@ -1,15 +0,0 @@
1
- name: nlm-memory
2
- version: 0.1.0
3
- description: Non-linear memory for Hermes — surfaces relevant prior sessions at each turn via keyword recall
4
- kind: memory
5
- author: pbmagnet4
6
- provides_hooks:
7
- - pre_llm_call
8
- - post_llm_call
9
- - on_session_start
10
- - on_session_end
11
- - on_session_finalize
12
- - on_session_reset
13
- requires_env:
14
- - name: NLM_DAEMON_PORT
15
- description: "Port the nlm-memory daemon listens on (default: 3940)"
Binary file
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Bundles each hook entry under src/hook/ as a single-file .mjs into
4
- * plugin/scripts/, ready for Codex to invoke via ${CLAUDE_PLUGIN_ROOT}.
5
- *
6
- * Why single-file bundles: Codex plugins are content-addressed by file hash
7
- * for the trust system. Shipping a tree of imports would force the user to
8
- * re-trust every release across dozens of files. One file per hook keeps
9
- * the trust footprint minimal.
10
- *
11
- * Why esbuild not tsc: tsc emits a tree of files with relative imports; the
12
- * plugin would have to ship `dist/` plus its dependency tree. esbuild flattens
13
- * to one .mjs per entry with all internal deps inlined. node builtins are
14
- * left as-is (node:*) and zero runtime deps are bundled because the hook
15
- * scripts only talk to the daemon over HTTP — no native bindings required.
16
- */
17
-
18
- import { build } from "esbuild";
19
- import { resolve, dirname } from "node:path";
20
- import { fileURLToPath } from "node:url";
21
- import { mkdirSync } from "node:fs";
22
-
23
- const __dirname = dirname(fileURLToPath(import.meta.url));
24
- const REPO_ROOT = resolve(__dirname, "..");
25
- const OUT_DIR = resolve(REPO_ROOT, "plugin/scripts");
26
-
27
- const ENTRIES = [
28
- "src/hook/prompt-recall-hook.ts",
29
- "src/hook/stop-hook.ts",
30
- ];
31
-
32
- mkdirSync(OUT_DIR, { recursive: true });
33
-
34
- const results = await Promise.all(
35
- ENTRIES.map(async (entry) => {
36
- const entryPath = resolve(REPO_ROOT, entry);
37
- const outName = entry.split("/").pop().replace(/\.ts$/, ".mjs");
38
- const outFile = resolve(OUT_DIR, outName);
39
- const result = await build({
40
- entryPoints: [entryPath],
41
- outfile: outFile,
42
- bundle: true,
43
- platform: "node",
44
- format: "esm",
45
- target: "node20",
46
- minify: false,
47
- sourcemap: false,
48
- legalComments: "none",
49
- logLevel: "warning",
50
- banner: { js: "#!/usr/bin/env node" },
51
- tsconfig: resolve(REPO_ROOT, "tsconfig.json"),
52
- });
53
- return { entry, outFile, warnings: result.warnings.length };
54
- }),
55
- );
56
-
57
- for (const r of results) {
58
- process.stdout.write(`bundled ${r.entry} -> ${r.outFile.replace(REPO_ROOT + "/", "")}`);
59
- if (r.warnings > 0) process.stdout.write(` (${r.warnings} warnings)`);
60
- process.stdout.write("\n");
61
- }
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env node
2
- // Direct DeepSeek probe — calls the API with the same prompt + a known-failing
3
- // session body and dumps everything: raw choice content, finish_reason, usage.
4
-
5
- import { readFileSync } from "node:fs";
6
- import Database from "better-sqlite3";
7
-
8
- const envContent = readFileSync(`${process.env.HOME}/.nlm/.env`, "utf8");
9
- const apiKey = envContent.split("\n").find((l) => l.startsWith("DEEPSEEK_API_KEY="))?.split("=")[1]?.trim();
10
- if (!apiKey) {
11
- console.error("no DEEPSEEK_API_KEY in ~/.nlm/.env");
12
- process.exit(1);
13
- }
14
-
15
- const db = new Database(`${process.env.HOME}/.nlm/canonical.sqlite`, { readonly: true });
16
- const targets = process.argv.slice(2);
17
- if (targets.length === 0) {
18
- console.error("usage: deepseek-probe.mjs <session-id> [more ids...]");
19
- process.exit(1);
20
- }
21
-
22
- // Pull the actual classifier prompt from the build
23
- const promptModule = await import("/Users/echalupa/Documents/Coding Projects/nlm-memory-ts/src/core/classifier/prompt.ts");
24
-
25
- for (const sid of targets) {
26
- const row = db.prepare("SELECT body FROM sessions WHERE id = ?").get(sid);
27
- if (!row?.body) {
28
- console.log(`\n=== ${sid}: no body ===`);
29
- continue;
30
- }
31
- const userPrompt = promptModule.buildUserPrompt(row.body, "");
32
- console.log(`\n=== ${sid} (body ${row.body.length} chars, prompt ${userPrompt.length} chars) ===`);
33
-
34
- const res = await fetch("https://api.deepseek.com/v1/chat/completions", {
35
- method: "POST",
36
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
37
- body: JSON.stringify({
38
- model: "deepseek-v4-flash",
39
- messages: [
40
- { role: "system", content: promptModule.CLASSIFIER_SYSTEM_PROMPT },
41
- { role: "user", content: userPrompt },
42
- ],
43
- response_format: { type: "json_object" },
44
- temperature: 0.1,
45
- max_tokens: 8192,
46
- stream: false,
47
- }),
48
- });
49
- if (!res.ok) {
50
- console.log(`HTTP ${res.status}: ${(await res.text()).slice(0, 500)}`);
51
- continue;
52
- }
53
- const data = await res.json();
54
- const choice = data.choices?.[0];
55
- console.log(`finish_reason: ${choice?.finish_reason}`);
56
- console.log(`usage: ${JSON.stringify(data.usage)}`);
57
- const content = choice?.message?.content ?? "";
58
- console.log(`content (${content.length} chars):`);
59
- console.log(content);
60
- console.log("--- end content ---");
61
- try {
62
- JSON.parse(content.replace(/^```(?:json)?\s*|\s*```$/gm, "").trim());
63
- console.log("JSON.parse: OK");
64
- } catch (e) {
65
- console.log(`JSON.parse: FAILED — ${e.message}`);
66
- }
67
- }