claude-memory-layer 1.0.27 → 1.0.29

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 (331) hide show
  1. package/.env.example +7 -0
  2. package/AGENTS.md +11 -0
  3. package/README.md +374 -49
  4. package/benchmarks/replay/anonymized-real-sessions.json +48 -0
  5. package/dist/cli/index.js +10097 -6003
  6. package/dist/cli/index.js.map +4 -4
  7. package/dist/core/index.js +9745 -5587
  8. package/dist/core/index.js.map +4 -4
  9. package/dist/hooks/post-tool-use.js +6545 -5270
  10. package/dist/hooks/post-tool-use.js.map +4 -4
  11. package/dist/hooks/semantic-daemon.js +6646 -5354
  12. package/dist/hooks/semantic-daemon.js.map +4 -4
  13. package/dist/hooks/session-end.js +6618 -5347
  14. package/dist/hooks/session-end.js.map +4 -4
  15. package/dist/hooks/session-start.js +6619 -5354
  16. package/dist/hooks/session-start.js.map +4 -4
  17. package/dist/hooks/stop.js +6614 -5325
  18. package/dist/hooks/stop.js.map +4 -4
  19. package/dist/hooks/user-prompt-submit.js +6702 -5356
  20. package/dist/hooks/user-prompt-submit.js.map +4 -4
  21. package/dist/index.js +13537 -0
  22. package/dist/index.js.map +7 -0
  23. package/dist/mcp/index.js +20770 -0
  24. package/dist/mcp/index.js.map +7 -0
  25. package/dist/server/api/index.js +6632 -5319
  26. package/dist/server/api/index.js.map +4 -4
  27. package/dist/server/index.js +6667 -5340
  28. package/dist/server/index.js.map +4 -4
  29. package/dist/services/memory-service.js +6568 -5350
  30. package/dist/services/memory-service.js.map +4 -4
  31. package/dist/ui/assets/js/bootstrap.js +244 -0
  32. package/dist/ui/assets/js/chat.js +373 -0
  33. package/dist/ui/assets/js/disclosure.js +232 -0
  34. package/dist/ui/assets/js/modals.js +298 -0
  35. package/dist/ui/assets/js/overview.js +655 -0
  36. package/dist/ui/assets/js/state.js +72 -0
  37. package/dist/ui/assets/js/views.js +468 -0
  38. package/dist/ui/index.html +43 -1
  39. package/dist/ui/index.ts +3 -0
  40. package/dist/ui/style.css +222 -0
  41. package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +627 -0
  42. package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +440 -0
  43. package/docs/MEMORY_USEFULNESS_AUDIT.md +371 -0
  44. package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +80 -0
  45. package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +333 -0
  46. package/docs/PRODUCT_VALIDATION_MATRIX.md +82 -0
  47. package/docs/PROJECT_STRUCTURE_ANALYSIS.md +421 -0
  48. package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +501 -0
  49. package/docs/REFACTORING_PLAN_THIN_CORE.md +414 -0
  50. package/docs/REFERENCE_PROJECT_ANALYSES.md +25 -0
  51. package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +452 -0
  52. package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +446 -0
  53. package/docs/architecture/comparison-index.md +47 -0
  54. package/docs/reports/codex-real-data-validation-20260505T040447Z.md +46 -0
  55. package/package.json +12 -5
  56. package/scripts/build.ts +25 -8
  57. package/scripts/generate-session-qrels.ts +126 -0
  58. package/scripts/postinstall-embedding-backend.cjs +142 -0
  59. package/scripts/replay-retrieval-benchmark.ts +69 -0
  60. package/specs/thin-core-refactor/context.md +275 -0
  61. package/specs/thin-core-refactor/plan.md +536 -0
  62. package/specs/thin-core-refactor/spec.md +465 -0
  63. package/src/adapters/claude/capture/index.ts +3 -0
  64. package/src/adapters/claude/context/index.ts +3 -0
  65. package/src/adapters/claude/hooks/index.ts +21 -0
  66. package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
  67. package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
  68. package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
  69. package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
  70. package/src/adapters/claude/hooks/session-end.ts +59 -0
  71. package/src/adapters/claude/hooks/session-start.ts +73 -0
  72. package/src/adapters/claude/hooks/stop.ts +128 -0
  73. package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
  74. package/src/adapters/claude/index.ts +4 -0
  75. package/src/adapters/claude/transcript/index.ts +4 -0
  76. package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
  77. package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
  78. package/src/apps/cli/claude-settings-hooks.ts +138 -0
  79. package/src/apps/cli/codex-import-runner.ts +125 -0
  80. package/src/apps/cli/codex-validation-output.ts +95 -0
  81. package/src/apps/cli/hermes-import-runner.ts +130 -0
  82. package/src/apps/cli/hermes-validation-output.ts +91 -0
  83. package/src/apps/cli/index.ts +1731 -0
  84. package/src/apps/cli/mcp-install.ts +106 -0
  85. package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
  86. package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
  87. package/src/apps/dashboard/assets/js/chat.js +373 -0
  88. package/src/apps/dashboard/assets/js/disclosure.js +232 -0
  89. package/src/apps/dashboard/assets/js/modals.js +298 -0
  90. package/src/apps/dashboard/assets/js/overview.js +655 -0
  91. package/src/apps/dashboard/assets/js/state.js +72 -0
  92. package/src/apps/dashboard/assets/js/views.js +468 -0
  93. package/src/{ui → apps/dashboard}/index.html +43 -1
  94. package/src/apps/dashboard/index.ts +3 -0
  95. package/src/{ui → apps/dashboard}/style.css +222 -0
  96. package/src/apps/index.ts +5 -0
  97. package/src/apps/server/api/chat.ts +244 -0
  98. package/src/apps/server/api/citations.ts +105 -0
  99. package/src/apps/server/api/events.ts +137 -0
  100. package/src/apps/server/api/health.ts +53 -0
  101. package/src/apps/server/api/index.ts +26 -0
  102. package/src/apps/server/api/projects.ts +74 -0
  103. package/src/apps/server/api/search.ts +184 -0
  104. package/src/apps/server/api/sessions.ts +115 -0
  105. package/src/apps/server/api/stats.ts +723 -0
  106. package/src/apps/server/api/turns.ts +143 -0
  107. package/src/apps/server/api/utils.ts +65 -0
  108. package/src/apps/server/index.ts +111 -0
  109. package/src/cli/index.ts +2 -1311
  110. package/src/cli/retrieval-disclosure-output.ts +2 -0
  111. package/src/compat/index.ts +5 -0
  112. package/src/core/derive/fact-deriver.ts +170 -0
  113. package/src/core/derive/index.ts +2 -0
  114. package/src/core/derive/summary-deriver.ts +76 -0
  115. package/src/core/embedder.ts +4 -152
  116. package/src/core/engine/embedding-maintenance-service.ts +187 -0
  117. package/src/core/engine/endless-memory-services.ts +4 -0
  118. package/src/core/engine/index.ts +19 -0
  119. package/src/core/engine/memory-engine-services.ts +170 -0
  120. package/src/core/engine/memory-ingest-service.ts +317 -0
  121. package/src/core/engine/memory-query-service.ts +173 -0
  122. package/src/core/engine/memory-runtime-service.ts +162 -0
  123. package/src/core/engine/memory-service-composition.ts +231 -0
  124. package/src/core/engine/retrieval-analytics-service.ts +181 -0
  125. package/src/core/engine/retrieval-disclosure-service.ts +420 -0
  126. package/src/core/engine/retrieval-orchestrator.ts +377 -0
  127. package/src/core/engine/retrieval-services.ts +176 -0
  128. package/src/core/engine/shared-memory-services.ts +4 -0
  129. package/src/core/entity-repo.ts +1 -3
  130. package/src/core/event-store.ts +3 -3
  131. package/src/core/evidence-aligner.ts +2 -2
  132. package/src/core/external-market-context.ts +582 -0
  133. package/src/core/graduation.ts +2 -3
  134. package/src/core/index.ts +21 -0
  135. package/src/core/matcher.ts +2 -4
  136. package/src/core/model/memory-fact.ts +30 -0
  137. package/src/core/model/memory-rule.ts +14 -0
  138. package/src/core/model/memory-summary.ts +21 -0
  139. package/src/core/model/raw-event.ts +28 -0
  140. package/src/core/model/retrieval-result.ts +35 -0
  141. package/src/core/privacy/filter.ts +21 -10
  142. package/src/core/product-validation-matrix.ts +314 -0
  143. package/src/core/progressive-retriever.ts +1 -2
  144. package/src/core/registry/project-path.ts +54 -0
  145. package/src/core/registry/session-registry.ts +69 -0
  146. package/src/core/replay-evaluator.ts +625 -0
  147. package/src/core/retrieval-benchmark.ts +117 -0
  148. package/src/core/retrieval-quality.ts +109 -0
  149. package/src/core/retriever.ts +53 -15
  150. package/src/core/session-qrels.ts +360 -0
  151. package/src/core/shared-event-store.ts +1 -1
  152. package/src/core/sqlite-event-store.ts +35 -11
  153. package/src/core/task/blocker-resolver.ts +2 -2
  154. package/src/core/task/task-resolver.ts +0 -1
  155. package/src/core/vector-outbox.ts +1 -10
  156. package/src/core/vector-worker.ts +1 -1
  157. package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
  158. package/src/extensions/endless-memory/index.ts +1 -0
  159. package/src/extensions/index.ts +5 -0
  160. package/src/extensions/mcp/handlers.ts +960 -0
  161. package/src/extensions/mcp/index.ts +48 -0
  162. package/src/extensions/mcp/tools.ts +252 -0
  163. package/src/extensions/shared-memory/index.ts +1 -0
  164. package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
  165. package/src/extensions/vector/embedder.ts +197 -0
  166. package/src/extensions/vector/index.ts +1 -0
  167. package/src/hooks/post-tool-use.ts +3 -236
  168. package/src/hooks/semantic-daemon-client.ts +1 -208
  169. package/src/hooks/semantic-daemon.ts +6 -271
  170. package/src/hooks/session-end.ts +4 -79
  171. package/src/hooks/session-start.ts +4 -73
  172. package/src/hooks/stop.ts +3 -173
  173. package/src/hooks/user-prompt-submit.ts +3 -338
  174. package/src/index.ts +13 -0
  175. package/src/mcp/handlers.ts +2 -212
  176. package/src/mcp/index.ts +3 -46
  177. package/src/mcp/tools.ts +2 -78
  178. package/src/server/api/chat.ts +2 -244
  179. package/src/server/api/citations.ts +2 -105
  180. package/src/server/api/events.ts +2 -137
  181. package/src/server/api/health.ts +2 -53
  182. package/src/server/api/index.ts +2 -26
  183. package/src/server/api/projects.ts +2 -74
  184. package/src/server/api/search.ts +2 -102
  185. package/src/server/api/sessions.ts +2 -115
  186. package/src/server/api/stats.ts +2 -724
  187. package/src/server/api/turns.ts +2 -143
  188. package/src/server/api/utils.ts +2 -46
  189. package/src/server/index.ts +2 -100
  190. package/src/services/bootstrap-organizer.ts +46 -26
  191. package/src/services/codex-session-history-importer.ts +521 -29
  192. package/src/services/hermes-session-history-importer.ts +733 -0
  193. package/src/services/memory-service-config.ts +36 -0
  194. package/src/services/memory-service-registry.ts +150 -0
  195. package/src/services/memory-service.ts +211 -1325
  196. package/src/services/session-history-importer.ts +58 -14
  197. package/tests/README.md +23 -0
  198. package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
  199. package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
  200. package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
  201. package/tests/apps/app-layer-boundary.test.ts +48 -0
  202. package/tests/apps/claude-settings-hooks.test.ts +107 -0
  203. package/tests/apps/cli-disclosure-output.test.ts +212 -0
  204. package/tests/apps/codex-import-runner.test.ts +99 -0
  205. package/tests/apps/codex-validation-output.test.ts +100 -0
  206. package/tests/apps/hermes-import-runner.test.ts +99 -0
  207. package/tests/apps/mcp-install-command.test.ts +59 -0
  208. package/tests/apps/package-build-entrypoints.test.ts +30 -0
  209. package/tests/apps/postinstall-embedding-backend.test.ts +167 -0
  210. package/tests/apps/search-api-disclosure.test.ts +162 -0
  211. package/tests/apps/stats-api-lightweight.test.ts +67 -0
  212. package/tests/apps/ui-disclosure-output.test.ts +140 -0
  213. package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
  214. package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
  215. package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
  216. package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
  217. package/tests/core/embedding-maintenance-service.test.ts +282 -0
  218. package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
  219. package/tests/core/external-market-context.test.ts +209 -0
  220. package/tests/core/fact-deriver.test.ts +79 -0
  221. package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
  222. package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
  223. package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
  224. package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
  225. package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
  226. package/tests/core/memory-engine-services.test.ts +240 -0
  227. package/tests/core/memory-ingest-service.test.ts +296 -0
  228. package/tests/core/memory-query-service.test.ts +129 -0
  229. package/tests/core/memory-runtime-service.test.ts +201 -0
  230. package/tests/core/memory-service-composition.test.ts +192 -0
  231. package/tests/core/memory-service-config.test.ts +41 -0
  232. package/tests/core/memory-service-facade.test.ts +30 -0
  233. package/tests/core/memory-service-registry.test.ts +206 -0
  234. package/tests/core/product-validation-matrix.test.ts +61 -0
  235. package/tests/core/project-registry.test.ts +78 -0
  236. package/tests/core/replay-evaluator.test.ts +181 -0
  237. package/tests/core/retrieval-analytics-service.test.ts +210 -0
  238. package/tests/core/retrieval-benchmark.test.ts +93 -0
  239. package/tests/core/retrieval-disclosure-service.test.ts +264 -0
  240. package/tests/core/retrieval-orchestrator.test.ts +403 -0
  241. package/tests/core/retrieval-quality.test.ts +31 -0
  242. package/tests/core/retrieval-services.test.ts +185 -0
  243. package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
  244. package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
  245. package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
  246. package/tests/core/session-history-importer-filter.test.ts +78 -0
  247. package/tests/core/session-qrels.test.ts +250 -0
  248. package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
  249. package/tests/core/summary-deriver.test.ts +66 -0
  250. package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
  251. package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
  252. package/tests/extensions/endless-memory-services.test.ts +325 -0
  253. package/tests/extensions/mcp-context-tools.test.ts +905 -0
  254. package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
  255. package/tests/extensions/mcp-package-build.test.ts +22 -0
  256. package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
  257. package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
  258. package/tests/extensions/shared-memory-services.test.ts +309 -0
  259. package/tests/extensions/vector-extension-boundary.test.ts +21 -0
  260. package/.claude/settings.local.json +0 -25
  261. package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
  262. package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
  263. package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
  264. package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
  265. package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
  266. package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
  267. package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
  268. package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
  269. package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
  270. package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
  271. package/.npm-cache/_update-notifier-last-checked +0 -0
  272. package/bootstrap-kb/decisions/decisions.md +0 -244
  273. package/bootstrap-kb/glossary/glossary.md +0 -46
  274. package/bootstrap-kb/modules/.claude-plugin.md +0 -22
  275. package/bootstrap-kb/modules/agents.md.md +0 -15
  276. package/bootstrap-kb/modules/claude.md.md +0 -15
  277. package/bootstrap-kb/modules/context.md.md +0 -15
  278. package/bootstrap-kb/modules/docs.md +0 -18
  279. package/bootstrap-kb/modules/handoff.md.md +0 -15
  280. package/bootstrap-kb/modules/package-lock.json.md +0 -15
  281. package/bootstrap-kb/modules/package.json.md +0 -15
  282. package/bootstrap-kb/modules/plan.md.md +0 -15
  283. package/bootstrap-kb/modules/readme.md.md +0 -15
  284. package/bootstrap-kb/modules/scripts.md +0 -26
  285. package/bootstrap-kb/modules/spec.md.md +0 -15
  286. package/bootstrap-kb/modules/specs.md +0 -20
  287. package/bootstrap-kb/modules/src.md +0 -51
  288. package/bootstrap-kb/modules/tests.md +0 -42
  289. package/bootstrap-kb/modules/tsconfig.json.md +0 -15
  290. package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
  291. package/bootstrap-kb/overview/overview.md +0 -40
  292. package/bootstrap-kb/sources/manifest.json +0 -950
  293. package/bootstrap-kb/sources/manifest.md +0 -227
  294. package/bootstrap-kb/timeline/timeline.md +0 -57
  295. package/claude-memory-layer-1.0.14.tgz +0 -0
  296. package/d.sh +0 -3
  297. package/deploy.sh +0 -3
  298. package/dist/ui/app.js +0 -2101
  299. package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
  300. package/memory/_index.md +0 -419
  301. package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
  302. package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
  303. package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
  304. package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
  305. package/memory/default/uncategorized/2026-02-25.md +0 -4839
  306. package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
  307. package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
  308. package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
  309. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
  310. package/memory/specs/citations-system/2026-02-25.md +0 -1121
  311. package/memory/specs/endless-mode/2026-02-25.md +0 -1392
  312. package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
  313. package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
  314. package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
  315. package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
  316. package/memory/specs/private-tags/2026-02-25.md +0 -1057
  317. package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
  318. package/memory/specs/task-entity-system/2026-02-25.md +0 -924
  319. package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
  320. package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
  321. package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
  322. package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
  323. package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
  324. package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
  325. package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
  326. package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
  327. package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
  328. package/specs/optional-duckdb/context.md +0 -77
  329. package/specs/optional-duckdb/plan.md +0 -142
  330. package/specs/optional-duckdb/spec.md +0 -35
  331. package/src/ui/app.js +0 -2101
@@ -0,0 +1,625 @@
1
+ import {
2
+ computePrecisionRecallAtK,
3
+ summarizeReplayMetrics,
4
+ type ReplayMetricsSummary,
5
+ type ReplayQueryMetrics
6
+ } from './retrieval-benchmark.js';
7
+ import { createRetrievalServices, type RetrieveMemoriesOptions } from './engine/retrieval-services.js';
8
+ import { Matcher } from './matcher.js';
9
+ import type { Embedder } from './embedder.js';
10
+ import type { MatchConfidence, MemoryEvent } from './types.js';
11
+ import type { SearchResult, VectorStore } from './vector-store.js';
12
+
13
+ export type ReplayExpectation = 'match' | 'no_match';
14
+
15
+ export interface ReplayEvaluationQuery {
16
+ queryId: string;
17
+ query: string;
18
+ expectedIds: string[];
19
+ expectedRelevance?: Record<string, number>;
20
+ expectation?: ReplayExpectation;
21
+ forbiddenIds?: string[];
22
+ knownAnswer?: string;
23
+ }
24
+
25
+ export interface ReplayEvaluationMemory {
26
+ id: string;
27
+ content: string;
28
+ sourceSessionId?: string;
29
+ sourceTurnIndex?: number;
30
+ timestamp?: string;
31
+ eventType?: MemoryEvent['eventType'];
32
+ canonicalKey?: string;
33
+ metadata?: Record<string, unknown>;
34
+ }
35
+
36
+ export interface ReplayEvaluationFixtureMetadata {
37
+ sourceFileCount?: number;
38
+ rawContentIncluded?: boolean;
39
+ generatedAt?: string;
40
+ }
41
+
42
+ export interface ReplayEvaluationFixture {
43
+ name: string;
44
+ description?: string;
45
+ ks: number[];
46
+ queries: ReplayEvaluationQuery[];
47
+ memories: ReplayEvaluationMemory[];
48
+ metadata?: ReplayEvaluationFixtureMetadata;
49
+ }
50
+
51
+ export interface ReplayRetrievalRunInput {
52
+ fixture: ReplayEvaluationFixture;
53
+ query: ReplayEvaluationQuery;
54
+ topK: number;
55
+ retrievalOptions: Partial<RetrieveMemoriesOptions>;
56
+ }
57
+
58
+ export interface ReplayRetrievalRunResult {
59
+ retrievedIds: string[];
60
+ candidateIds?: string[];
61
+ confidence?: MatchConfidence;
62
+ fallbackTrace?: string[];
63
+ }
64
+
65
+ export type ReplayRetrievalRunner = (
66
+ query: string,
67
+ input: ReplayRetrievalRunInput
68
+ ) => Promise<ReplayRetrievalRunResult>;
69
+
70
+ export interface ReplayEvaluationOptions {
71
+ generatedAt?: string;
72
+ includePerQuery?: boolean;
73
+ evaluator?: string;
74
+ topK?: number;
75
+ retrievalOptions?: Partial<RetrieveMemoriesOptions>;
76
+ retrievalRunner?: ReplayRetrievalRunner;
77
+ }
78
+
79
+ export interface ReplayFixtureStats {
80
+ queryCount: number;
81
+ memoryCount: number;
82
+ ks: number[];
83
+ sourceFileCount?: number;
84
+ rawContentIncluded?: boolean;
85
+ }
86
+
87
+ export interface ReplayFailedQuery {
88
+ queryId: string;
89
+ expectedIds: string[];
90
+ retrievedIds: string[];
91
+ expectation?: ReplayExpectation;
92
+ reason?: 'missing_expected' | 'unexpected_match';
93
+ }
94
+
95
+ export interface ReplayEvaluationSummary extends ReplayMetricsSummary {
96
+ positiveQueryCount: number;
97
+ noMatchQueryCount: number;
98
+ noMatchCorrect: number;
99
+ noMatchAccuracy: number;
100
+ forbiddenHitCount: number;
101
+ hitAtK: Record<number, number>;
102
+ mrr: number;
103
+ failedQueryCount: number;
104
+ failedQueries: ReplayFailedQuery[];
105
+ }
106
+
107
+ export interface ReplayEvaluationQueryMetrics extends ReplayQueryMetrics {
108
+ retrievedIds: string[];
109
+ candidateIds: string[];
110
+ confidence: MatchConfidence;
111
+ fallbackTrace: string[];
112
+ reciprocalRank: number;
113
+ expectation?: ReplayExpectation;
114
+ forbiddenHitIds?: string[];
115
+ noMatchSatisfied?: boolean;
116
+ }
117
+
118
+ export interface ReplayEvaluationReport {
119
+ name: string;
120
+ description?: string;
121
+ evaluator: string;
122
+ generatedAt: string;
123
+ fixtureStats: ReplayFixtureStats;
124
+ summary: ReplayEvaluationSummary;
125
+ perQuery: ReplayEvaluationQueryMetrics[];
126
+ }
127
+
128
+ export interface ReplayEvaluationMarkdownOptions {
129
+ qrelsPath?: string;
130
+ }
131
+
132
+ export async function evaluateReplayFixture(
133
+ fixture: ReplayEvaluationFixture,
134
+ options: ReplayEvaluationOptions = {}
135
+ ): Promise<ReplayEvaluationReport> {
136
+ const topK = determineTopK(fixture, options.topK);
137
+ const retrievalOptions: Partial<RetrieveMemoriesOptions> = {
138
+ strategy: 'auto',
139
+ minScore: 0.1,
140
+ includeShared: false,
141
+ adaptiveRerank: false,
142
+ ...options.retrievalOptions,
143
+ topK
144
+ };
145
+ const runner = options.retrievalRunner ?? createReplayRetrievalRunner(fixture);
146
+
147
+ const runs = await Promise.all(
148
+ fixture.queries.map(async (query) => {
149
+ const run = await runner(query.query, {
150
+ fixture,
151
+ query,
152
+ topK,
153
+ retrievalOptions
154
+ });
155
+ return {
156
+ query,
157
+ retrievedIds: uniqueIds(run.retrievedIds).slice(0, topK),
158
+ candidateIds: uniqueIds(run.candidateIds ?? run.retrievedIds),
159
+ confidence: run.confidence ?? 'none',
160
+ fallbackTrace: run.fallbackTrace ?? []
161
+ };
162
+ })
163
+ );
164
+
165
+ const baseMetrics = computePrecisionRecallAtK(
166
+ runs.map((run) => ({
167
+ queryId: run.query.queryId,
168
+ expectedIds: run.query.expectedIds,
169
+ expectedRelevance: run.query.expectedRelevance,
170
+ retrievedIds: run.retrievedIds
171
+ })),
172
+ fixture.ks
173
+ );
174
+
175
+ const perQuery: ReplayEvaluationQueryMetrics[] = baseMetrics.map((metric, index) => {
176
+ const run = runs[index];
177
+ const expectation = getReplayExpectation(run.query);
178
+ const base: ReplayEvaluationQueryMetrics = {
179
+ ...metric,
180
+ retrievedIds: run.retrievedIds,
181
+ candidateIds: run.candidateIds,
182
+ confidence: run.confidence,
183
+ fallbackTrace: run.fallbackTrace,
184
+ reciprocalRank: expectation === 'match' ? reciprocalRank(run.retrievedIds, run.query.expectedIds) : 0
185
+ };
186
+
187
+ if (expectation === 'no_match') {
188
+ const forbiddenHitIds = findForbiddenHitIds(run.retrievedIds, run.query.forbiddenIds ?? []);
189
+ base.expectation = 'no_match';
190
+ base.forbiddenHitIds = forbiddenHitIds;
191
+ base.noMatchSatisfied = forbiddenHitIds.length === 0 && run.confidence === 'none';
192
+ }
193
+
194
+ return base;
195
+ });
196
+
197
+ const fixtureStats: ReplayFixtureStats = {
198
+ queryCount: fixture.queries.length,
199
+ memoryCount: fixture.memories.length,
200
+ ks: fixture.ks
201
+ };
202
+ if (fixture.metadata?.sourceFileCount !== undefined) {
203
+ fixtureStats.sourceFileCount = fixture.metadata.sourceFileCount;
204
+ }
205
+ if (fixture.metadata?.rawContentIncluded !== undefined) {
206
+ fixtureStats.rawContentIncluded = fixture.metadata.rawContentIncluded;
207
+ }
208
+
209
+ const report: ReplayEvaluationReport = {
210
+ name: fixture.name,
211
+ evaluator: options.evaluator ?? 'retriever-pipeline-v1',
212
+ generatedAt: options.generatedAt ?? new Date().toISOString(),
213
+ fixtureStats,
214
+ summary: summarizeEvaluationMetrics(perQuery, fixture.queries, fixture.ks),
215
+ perQuery: options.includePerQuery === false ? [] : perQuery
216
+ };
217
+
218
+ if (fixture.description !== undefined) {
219
+ report.description = fixture.description;
220
+ }
221
+
222
+ return report;
223
+ }
224
+
225
+ export function createReplayRetrievalRunner(
226
+ fixture: ReplayEvaluationFixture
227
+ ): ReplayRetrievalRunner {
228
+ const eventStore = new ReplayEventStore(fixture.memories);
229
+ const vectorStore = new ReplayVectorStore(eventStore.events);
230
+ const embedder = new ReplayEmbedder();
231
+ const services = createRetrievalServices({
232
+ initialize: async () => undefined,
233
+ eventStore: eventStore as unknown as Parameters<typeof createRetrievalServices>[0]['eventStore'],
234
+ vectorStore: vectorStore as unknown as VectorStore,
235
+ embedder: embedder as unknown as Embedder,
236
+ matcher: new Matcher(),
237
+ getProjectHash: () => null,
238
+ hasSharedStore: () => false
239
+ });
240
+
241
+ return async (query, input) => {
242
+ const result = await services.retrievalOrchestrator.retrieveMemories(query, {
243
+ ...input.retrievalOptions,
244
+ topK: input.topK,
245
+ includeShared: false
246
+ });
247
+
248
+ return {
249
+ retrievedIds: result.memories.map((memory) => memory.event.id),
250
+ candidateIds: (result.candidateDebug ?? result.selectedDebug ?? [])
251
+ .map((detail) => detail.eventId),
252
+ confidence: result.matchResult.confidence,
253
+ fallbackTrace: result.fallbackTrace ?? []
254
+ };
255
+ };
256
+ }
257
+
258
+ export function formatReplayEvaluationMarkdown(
259
+ report: ReplayEvaluationReport,
260
+ options: ReplayEvaluationMarkdownOptions = {}
261
+ ): string {
262
+ const lines: string[] = [];
263
+ lines.push('# Retrieval Replay Benchmark Report');
264
+ lines.push('');
265
+ lines.push(`- Fixture: ${escapeMarkdownCell(report.name)}`);
266
+ if (report.description) lines.push(`- Description: ${escapeMarkdownCell(report.description)}`);
267
+ if (options.qrelsPath) lines.push(`- Qrels: \`${options.qrelsPath}\``);
268
+ lines.push(`- Evaluator: \`${report.evaluator}\``);
269
+ lines.push(`- Generated at: ${report.generatedAt}`);
270
+ lines.push(`- Queries: ${report.fixtureStats.queryCount}`);
271
+ lines.push(`- Memories: ${report.fixtureStats.memoryCount}`);
272
+ if (report.fixtureStats.sourceFileCount !== undefined) {
273
+ lines.push(`- Source files: ${report.fixtureStats.sourceFileCount}`);
274
+ }
275
+ if (report.fixtureStats.rawContentIncluded !== undefined) {
276
+ lines.push(`- Raw content in evaluated fixture: ${report.fixtureStats.rawContentIncluded ? 'yes' : 'no'}`);
277
+ }
278
+ lines.push('');
279
+ lines.push('## Summary');
280
+ lines.push('');
281
+ lines.push('| k | Precision@k | Recall@k | nDCG@k | Hit@k |');
282
+ lines.push('|---:|---:|---:|---:|---:|');
283
+
284
+ for (const k of sortedKValues(report.summary)) {
285
+ lines.push(
286
+ `| ${k} | ${formatMetric(report.summary.precisionAtK[k] ?? 0)} | ${formatMetric(report.summary.recallAtK[k] ?? 0)} | ${formatMetric(report.summary.ndcgAtK[k] ?? 0)} | ${formatMetric(report.summary.hitAtK[k] ?? 0)} |`
287
+ );
288
+ }
289
+
290
+ lines.push('');
291
+ lines.push('## Key metrics');
292
+ lines.push('');
293
+ lines.push('| Metric | Value |');
294
+ lines.push('|---|---:|');
295
+ lines.push(`| Positive queries | ${report.summary.positiveQueryCount} |`);
296
+ lines.push(`| No-match queries | ${report.summary.noMatchQueryCount} |`);
297
+ lines.push(`| No-match accuracy | ${formatMetric(report.summary.noMatchAccuracy)} |`);
298
+ lines.push(`| Forbidden hits | ${report.summary.forbiddenHitCount} |`);
299
+ lines.push(`| MRR | ${formatMetric(report.summary.mrr)} |`);
300
+ lines.push(`| Failed queries | ${report.summary.failedQueryCount} |`);
301
+ for (const k of sortedKValues(report.summary)) {
302
+ lines.push(`| Precision@${k} | ${formatMetric(report.summary.precisionAtK[k] ?? 0)} |`);
303
+ lines.push(`| Recall@${k} | ${formatMetric(report.summary.recallAtK[k] ?? 0)} |`);
304
+ lines.push(`| nDCG@${k} | ${formatMetric(report.summary.ndcgAtK[k] ?? 0)} |`);
305
+ lines.push(`| Hit@${k} | ${formatMetric(report.summary.hitAtK[k] ?? 0)} |`);
306
+ }
307
+
308
+ if (report.summary.failedQueries.length > 0) {
309
+ lines.push('');
310
+ lines.push('## Failed queries');
311
+ lines.push('');
312
+ lines.push('| queryId | expectedIds | retrievedIds |');
313
+ lines.push('|---|---|---|');
314
+ for (const failed of report.summary.failedQueries) {
315
+ lines.push(
316
+ `| ${escapeMarkdownCell(failed.queryId)} | ${escapeMarkdownCell(failed.expectedIds.join(', '))} | ${escapeMarkdownCell(failed.retrievedIds.join(', '))} |`
317
+ );
318
+ }
319
+ }
320
+
321
+ if (report.perQuery.length > 0) {
322
+ lines.push('');
323
+ lines.push('## Per-query metrics');
324
+ lines.push('');
325
+ lines.push('| queryId | k | hits | Precision@k | Recall@k | nDCG@k | RR | confidence |');
326
+ lines.push('|---|---:|---:|---:|---:|---:|---:|---|');
327
+
328
+ for (const query of report.perQuery) {
329
+ const ks = Object.keys(query.at).map(Number).sort((a, b) => a - b);
330
+ for (const k of ks) {
331
+ const metric = query.at[k];
332
+ if (!metric) continue;
333
+ lines.push(
334
+ `| ${escapeMarkdownCell(query.queryId)} | ${k} | ${metric.hits} | ${formatMetric(metric.precision)} | ${formatMetric(metric.recall)} | ${formatMetric(metric.ndcg)} | ${formatMetric(query.reciprocalRank)} | ${query.confidence} |`
335
+ );
336
+ }
337
+ }
338
+ }
339
+
340
+ lines.push('');
341
+ lines.push('> Report intentionally omits raw query and memory text.');
342
+ lines.push('');
343
+ return lines.join('\n');
344
+ }
345
+
346
+ function summarizeEvaluationMetrics(
347
+ perQuery: ReplayEvaluationQueryMetrics[],
348
+ queries: ReplayEvaluationQuery[],
349
+ ks: number[]
350
+ ): ReplayEvaluationSummary {
351
+ const pairs = perQuery.map((metric, index) => ({
352
+ metric,
353
+ query: queries[index],
354
+ expectation: getReplayExpectation(queries[index])
355
+ }));
356
+ const positivePairs = pairs.filter((pair) => pair.expectation === 'match');
357
+ const noMatchPairs = pairs.filter((pair) => pair.expectation === 'no_match');
358
+ const positiveMetrics = positivePairs.map((pair) => pair.metric);
359
+ const base = summarizeReplayMetrics(positiveMetrics, ks);
360
+ const normalizedKs = normalizeKs(ks);
361
+ const hitAtK: Record<number, number> = {};
362
+ for (const k of normalizedKs) {
363
+ hitAtK[k] = average(positiveMetrics.map((metric) => (metric.at[k]?.hits ?? 0) > 0 ? 1 : 0));
364
+ }
365
+
366
+ const positiveFailures: ReplayFailedQuery[] = positivePairs
367
+ .filter(({ metric, query }) => query.expectedIds.length > 0 && metric.reciprocalRank === 0)
368
+ .map(({ metric, query }) => ({
369
+ queryId: query.queryId,
370
+ expectedIds: [...query.expectedIds],
371
+ retrievedIds: [...metric.retrievedIds],
372
+ expectation: 'match',
373
+ reason: 'missing_expected'
374
+ }));
375
+
376
+ const noMatchFailures: ReplayFailedQuery[] = noMatchPairs
377
+ .filter(({ metric }) => metric.noMatchSatisfied !== true)
378
+ .map(({ metric, query }) => ({
379
+ queryId: query.queryId,
380
+ expectedIds: [],
381
+ retrievedIds: [...metric.retrievedIds],
382
+ expectation: 'no_match',
383
+ reason: 'unexpected_match'
384
+ }));
385
+
386
+ const noMatchCorrect = noMatchPairs.filter(({ metric }) => metric.noMatchSatisfied === true).length;
387
+ const forbiddenHitCount = noMatchPairs.reduce(
388
+ (sum, { metric }) => sum + (metric.forbiddenHitIds?.length ?? 0),
389
+ 0
390
+ );
391
+ const failedQueries = [...positiveFailures, ...noMatchFailures];
392
+
393
+ return {
394
+ ...base,
395
+ queryCount: perQuery.length,
396
+ positiveQueryCount: positivePairs.length,
397
+ noMatchQueryCount: noMatchPairs.length,
398
+ noMatchCorrect,
399
+ noMatchAccuracy: noMatchPairs.length === 0 ? 0 : noMatchCorrect / noMatchPairs.length,
400
+ forbiddenHitCount,
401
+ hitAtK,
402
+ mrr: average(positiveMetrics.map((metric) => metric.reciprocalRank)),
403
+ failedQueryCount: failedQueries.length,
404
+ failedQueries
405
+ };
406
+ }
407
+
408
+ function determineTopK(fixture: ReplayEvaluationFixture, optionTopK?: number): number {
409
+ return Math.max(1, optionTopK ?? 0, ...fixture.ks.map((k) => Math.floor(k)).filter((k) => k > 0));
410
+ }
411
+
412
+ function sortedKValues(summary: ReplayMetricsSummary): number[] {
413
+ return Object.keys(summary.precisionAtK).map(Number).sort((a, b) => a - b);
414
+ }
415
+
416
+ function normalizeKs(ks: number[]): number[] {
417
+ const seen = new Set<number>();
418
+ const normalized: number[] = [];
419
+ for (const rawK of ks) {
420
+ const k = Math.max(0, Math.floor(rawK));
421
+ if (seen.has(k)) continue;
422
+ seen.add(k);
423
+ normalized.push(k);
424
+ }
425
+ return normalized.sort((a, b) => a - b);
426
+ }
427
+
428
+ function uniqueIds(ids: string[]): string[] {
429
+ const seen = new Set<string>();
430
+ const unique: string[] = [];
431
+ for (const id of ids) {
432
+ if (seen.has(id)) continue;
433
+ seen.add(id);
434
+ unique.push(id);
435
+ }
436
+ return unique;
437
+ }
438
+
439
+ function reciprocalRank(retrievedIds: string[], expectedIds: string[]): number {
440
+ const expected = new Set(expectedIds);
441
+ if (expected.size === 0) return 0;
442
+ const index = retrievedIds.findIndex((id) => expected.has(id));
443
+ return index === -1 ? 0 : 1 / (index + 1);
444
+ }
445
+
446
+ function getReplayExpectation(query: ReplayEvaluationQuery | undefined): ReplayExpectation {
447
+ if (!query) return 'match';
448
+ return query.expectation ?? (query.expectedIds.length === 0 ? 'no_match' : 'match');
449
+ }
450
+
451
+ function findForbiddenHitIds(retrievedIds: string[], forbiddenIds: string[]): string[] {
452
+ if (forbiddenIds.length === 0) return [];
453
+ const forbidden = new Set(forbiddenIds);
454
+ return uniqueIds(retrievedIds.filter((id) => forbidden.has(id)));
455
+ }
456
+
457
+ function tokenize(text: string): string[] {
458
+ return text
459
+ .toLowerCase()
460
+ .replace(/[^\p{L}\p{N}\s_.:-]/gu, ' ')
461
+ .split(/\s+/)
462
+ .flatMap((token) => token.split(/(?=[._:-])|(?<=[._:-])/g))
463
+ .map((token) => token.replace(/^[._:-]+|[._:-]+$/g, ''))
464
+ .filter((token) => token.length >= 2)
465
+ .slice(0, 128);
466
+ }
467
+
468
+ function keywordScore(queryTokens: string[], content: string): number {
469
+ if (queryTokens.length === 0) return 0;
470
+ const contentTokens = new Set(tokenize(content));
471
+ const hits = queryTokens.filter((token) => contentTokens.has(token)).length;
472
+ return hits / queryTokens.length;
473
+ }
474
+
475
+ function vectorize(text: string, dimensions = 64): number[] {
476
+ const vector = new Array<number>(dimensions).fill(0);
477
+ for (const token of tokenize(text)) {
478
+ let hash = 2166136261;
479
+ for (let i = 0; i < token.length; i += 1) {
480
+ hash ^= token.charCodeAt(i);
481
+ hash = Math.imul(hash, 16777619);
482
+ }
483
+ vector[Math.abs(hash) % dimensions] += 1;
484
+ }
485
+ const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0)) || 1;
486
+ return vector.map((value) => value / norm);
487
+ }
488
+
489
+ function cosine(a: number[], b: number[]): number {
490
+ const length = Math.min(a.length, b.length);
491
+ let dot = 0;
492
+ for (let i = 0; i < length; i += 1) dot += a[i] * b[i];
493
+ return Math.max(0, Math.min(1, dot));
494
+ }
495
+
496
+ function formatMetric(value: number): string {
497
+ return value.toFixed(4).replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1');
498
+ }
499
+
500
+ function escapeMarkdownCell(value: string): string {
501
+ return value.replace(/\|/g, '\\|').replace(/\n/g, ' ');
502
+ }
503
+
504
+ function average(values: number[]): number {
505
+ if (values.length === 0) return 0;
506
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
507
+ }
508
+
509
+ class ReplayEventStore {
510
+ readonly events: MemoryEvent[];
511
+ private readonly byId: Map<string, MemoryEvent>;
512
+
513
+ constructor(memories: ReplayEvaluationMemory[]) {
514
+ this.events = memories.map((memory, index) => replayMemoryToEvent(memory, index));
515
+ this.byId = new Map(this.events.map((event) => [event.id, event]));
516
+ }
517
+
518
+ async keywordSearch(query: string, limit = 10): Promise<Array<{ event: MemoryEvent; rank: number }>> {
519
+ const queryTokens = tokenize(query);
520
+ return this.events
521
+ .map((event) => ({ event, score: keywordScore(queryTokens, event.content) }))
522
+ .filter((row) => row.score > 0)
523
+ .sort((a, b) => b.score - a.score || a.event.id.localeCompare(b.event.id))
524
+ .slice(0, limit)
525
+ .map((row, index) => ({ event: row.event, rank: -row.score - index / 1000 }));
526
+ }
527
+
528
+ async getRecentEvents(limit = 100): Promise<MemoryEvent[]> {
529
+ return [...this.events]
530
+ .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime() || a.id.localeCompare(b.id))
531
+ .slice(0, limit);
532
+ }
533
+
534
+ async getEvent(id: string): Promise<MemoryEvent | null> {
535
+ return this.byId.get(id) ?? null;
536
+ }
537
+
538
+ async getSessionEvents(sessionId: string): Promise<MemoryEvent[]> {
539
+ return this.events
540
+ .filter((event) => event.sessionId === sessionId)
541
+ .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime() || a.id.localeCompare(b.id));
542
+ }
543
+
544
+ async getHelpfulnessStats(): Promise<{ avgScore: number; totalEvaluated: number; totalRetrievals: number; helpful: number; neutral: number; unhelpful: number }> {
545
+ return { avgScore: 0, totalEvaluated: 0, totalRetrievals: 0, helpful: 0, neutral: 0, unhelpful: 0 };
546
+ }
547
+
548
+ async recordRetrievalTrace(): Promise<void> {
549
+ return undefined;
550
+ }
551
+
552
+ async incrementAccessCount(): Promise<void> {
553
+ return undefined;
554
+ }
555
+
556
+ async recordRetrieval(): Promise<void> {
557
+ return undefined;
558
+ }
559
+ }
560
+
561
+ class ReplayVectorStore {
562
+ private readonly rows: Array<SearchResult & { vector: number[] }>;
563
+
564
+ constructor(events: MemoryEvent[]) {
565
+ this.rows = events.map((event) => ({
566
+ id: `replay-vector-${event.id}`,
567
+ eventId: event.id,
568
+ content: event.content,
569
+ score: 0,
570
+ sessionId: event.sessionId,
571
+ eventType: event.eventType,
572
+ timestamp: event.timestamp.toISOString(),
573
+ vector: vectorize(event.content)
574
+ }));
575
+ }
576
+
577
+ async search(queryVector: number[], options: { limit?: number; minScore?: number; sessionId?: string } = {}): Promise<SearchResult[]> {
578
+ const limit = options.limit ?? 5;
579
+ const minScore = options.minScore ?? 0;
580
+ return this.rows
581
+ .filter((row) => !options.sessionId || row.sessionId === options.sessionId)
582
+ .map((row) => ({ ...row, score: cosine(queryVector, row.vector) }))
583
+ .filter((row) => row.score >= minScore)
584
+ .sort((a, b) => b.score - a.score || a.eventId.localeCompare(b.eventId))
585
+ .slice(0, limit)
586
+ .map((row) => ({
587
+ id: row.id,
588
+ eventId: row.eventId,
589
+ content: row.content,
590
+ score: row.score,
591
+ sessionId: row.sessionId,
592
+ eventType: row.eventType,
593
+ timestamp: row.timestamp
594
+ }));
595
+ }
596
+
597
+ async count(): Promise<number> {
598
+ return this.rows.length;
599
+ }
600
+ }
601
+
602
+ class ReplayEmbedder {
603
+ async embed(text: string): Promise<{ vector: number[]; model: string; dimensions: number }> {
604
+ const vector = vectorize(text);
605
+ return { vector, model: 'deterministic-replay-hash', dimensions: vector.length };
606
+ }
607
+ }
608
+
609
+ function replayMemoryToEvent(memory: ReplayEvaluationMemory, index: number): MemoryEvent {
610
+ const sessionId = memory.sourceSessionId ?? 'replay-fixture';
611
+ const timestamp = memory.timestamp
612
+ ? new Date(memory.timestamp)
613
+ : new Date(Date.UTC(2026, 0, 1, 0, 0, index));
614
+
615
+ return {
616
+ id: memory.id,
617
+ sessionId,
618
+ eventType: memory.eventType ?? 'agent_response',
619
+ content: memory.content,
620
+ canonicalKey: memory.canonicalKey ?? `replay/${memory.id}`,
621
+ dedupeKey: `replay:${sessionId}:${memory.id}`,
622
+ timestamp,
623
+ metadata: memory.metadata ?? {}
624
+ };
625
+ }