@remnic/core 9.3.613 → 9.3.615

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 (386) hide show
  1. package/dist/access-cli.js +59 -58
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +23 -23
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +20 -20
  7. package/dist/access-schema.d.ts +26 -14
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-CBNEKjzN.d.ts} +71 -6
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +17 -17
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-TH67Q46T.js → chunk-5OHHEORR.js} +64 -21
  48. package/dist/chunk-5OHHEORR.js.map +1 -0
  49. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  50. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  51. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  52. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  53. package/dist/chunk-6L46YAEZ.js.map +1 -0
  54. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  55. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  56. package/dist/chunk-AL4RAJL5.js.map +1 -0
  57. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  58. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  59. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  60. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  61. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  62. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  63. package/dist/chunk-C6C7XVKG.js.map +1 -0
  64. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  65. package/dist/chunk-CI7RKSRE.js.map +1 -0
  66. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  67. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  68. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  69. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  70. package/dist/chunk-DCGT4FPP.js.map +1 -0
  71. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  72. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  73. package/dist/chunk-DEVUWMME.js.map +1 -0
  74. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  75. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  76. package/dist/{chunk-HJNQQICM.js → chunk-EXUAP5LH.js} +108 -51
  77. package/dist/chunk-EXUAP5LH.js.map +1 -0
  78. package/dist/chunk-FAV25DUZ.js +12 -0
  79. package/dist/chunk-FAV25DUZ.js.map +1 -0
  80. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  81. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  82. package/dist/chunk-GDB4J2H3.js.map +1 -0
  83. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  84. package/dist/chunk-GLPBYIXN.js.map +1 -0
  85. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  86. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  87. package/dist/chunk-IBTZEBUD.js.map +1 -0
  88. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  89. package/dist/{chunk-EUML3N6B.js → chunk-IMA6GU4Y.js} +3 -3
  90. package/dist/chunk-IMA6GU4Y.js.map +1 -0
  91. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  92. package/dist/chunk-JOASJWQR.js.map +1 -0
  93. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  94. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  95. package/dist/chunk-KBL3JJR6.js.map +1 -0
  96. package/dist/{chunk-IOTENEVL.js → chunk-KGLPJROV.js} +57 -50
  97. package/dist/chunk-KGLPJROV.js.map +1 -0
  98. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  99. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  100. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  101. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  102. package/dist/chunk-MHQC2WU2.js.map +1 -0
  103. package/dist/{chunk-5RPTH6AU.js → chunk-NM5NQYJE.js} +20 -19
  104. package/dist/chunk-NM5NQYJE.js.map +1 -0
  105. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  106. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  107. package/dist/chunk-ODPLEWB6.js.map +1 -0
  108. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  109. package/dist/chunk-OIF36KGD.js.map +1 -0
  110. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  111. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  112. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  113. package/dist/chunk-PYIFUBRK.js.map +1 -0
  114. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  115. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  116. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  117. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  118. package/dist/chunk-RG3LBSGH.js.map +1 -0
  119. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  120. package/dist/chunk-S53OYO3F.js.map +1 -0
  121. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  122. package/dist/chunk-SCPFRKIT.js.map +1 -0
  123. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  124. package/dist/chunk-T7N6KQGS.js.map +1 -0
  125. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  126. package/dist/chunk-TNOWU6RP.js.map +1 -0
  127. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  128. package/dist/chunk-TZVQQTG4.js.map +1 -0
  129. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  130. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-PPPZY2EU.js → chunk-WD2W4234.js} +9 -3
  134. package/dist/chunk-WD2W4234.js.map +1 -0
  135. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  136. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  137. package/dist/chunk-X6IRLNOO.js.map +1 -0
  138. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  139. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  140. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  141. package/dist/chunk-YRMKDTKF.js.map +1 -0
  142. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  143. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  144. package/dist/{chunk-2QANQKSQ.js → chunk-ZK32E74R.js} +156 -45
  145. package/dist/chunk-ZK32E74R.js.map +1 -0
  146. package/dist/{cli-OrfKXNU4.d.ts → cli-Cw729yLf.d.ts} +6 -2
  147. package/dist/cli.d.ts +3 -3
  148. package/dist/cli.js +61 -60
  149. package/dist/compounding/engine.js +3 -3
  150. package/dist/compounding/preference-consolidator.js +39 -11
  151. package/dist/compounding/preference-consolidator.js.map +1 -1
  152. package/dist/config.js +1 -1
  153. package/dist/connectors/codex-materialize-runner.js +3 -3
  154. package/dist/connectors/index.js +3 -3
  155. package/dist/consolidation-provenance-check.js +1 -1
  156. package/dist/contradiction/index.js +4 -4
  157. package/dist/conversation-index/backend.js +2 -2
  158. package/dist/conversation-index/indexer.js +1 -1
  159. package/dist/cross-namespace-budget.js +1 -1
  160. package/dist/enrichment/index.js +1 -1
  161. package/dist/entity-retrieval.js +3 -3
  162. package/dist/evals.js +1 -1
  163. package/dist/explicit-capture.d.ts +11 -1
  164. package/dist/explicit-capture.js +1 -1
  165. package/dist/extraction-judge.js +8 -1
  166. package/dist/extraction.js +2 -2
  167. package/dist/fallback-llm.d.ts +23 -6
  168. package/dist/fallback-llm.js +5 -3
  169. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  170. package/dist/index.d.ts +3 -3
  171. package/dist/index.js +95 -94
  172. package/dist/index.js.map +1 -1
  173. package/dist/lcm/archive.js +2 -2
  174. package/dist/lcm/engine.js +5 -5
  175. package/dist/lcm/index.js +7 -7
  176. package/dist/lcm/summarizer.js +3 -3
  177. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  178. package/dist/maintenance/memory-governance-cron.js +1 -1
  179. package/dist/maintenance/memory-governance.js +3 -3
  180. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  181. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  182. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  183. package/dist/mcp-memory-inspector-app.js +1 -1
  184. package/dist/migrate/from-engram.js +1 -1
  185. package/dist/namespaces/migrate.js +16 -15
  186. package/dist/namespaces/search.js +12 -11
  187. package/dist/namespaces/storage.js +3 -3
  188. package/dist/network/webdav.d.ts +2 -0
  189. package/dist/network/webdav.js +1 -1
  190. package/dist/objective-state-writers.js +2 -2
  191. package/dist/operator-toolkit.d.ts +3 -1
  192. package/dist/operator-toolkit.js +21 -20
  193. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  194. package/dist/orchestrator.d.ts +1 -1
  195. package/dist/orchestrator.js +48 -45
  196. package/dist/patterns-cli.js +1 -1
  197. package/dist/qmd-recall-cache.d.ts +2 -0
  198. package/dist/qmd-recall-cache.js +1 -1
  199. package/dist/qmd.d.ts +37 -2
  200. package/dist/qmd.js +4 -1
  201. package/dist/recall-explain-renderer.js +3 -3
  202. package/dist/recall-planner-llm.d.ts +57 -0
  203. package/dist/recall-planner-llm.js +167 -0
  204. package/dist/recall-planner-llm.js.map +1 -0
  205. package/dist/recall-xray-cli.js +4 -4
  206. package/dist/recall-xray-renderer.js +3 -3
  207. package/dist/recall-xray.js +2 -2
  208. package/dist/resume-bundles.js +2 -2
  209. package/dist/retrieval-agents.js +2 -2
  210. package/dist/routing/store.js +1 -1
  211. package/dist/schemas.d.ts +22 -22
  212. package/dist/search/factory.js +11 -10
  213. package/dist/search/index.js +11 -10
  214. package/dist/search/lancedb-backend.d.ts +1 -1
  215. package/dist/search/lancedb-backend.js +3 -2
  216. package/dist/search/meilisearch-backend.d.ts +1 -1
  217. package/dist/search/meilisearch-backend.js +3 -2
  218. package/dist/search/noop-backend.d.ts +1 -1
  219. package/dist/search/noop-backend.js +1 -1
  220. package/dist/search/orama-backend.d.ts +1 -1
  221. package/dist/search/orama-backend.js +3 -2
  222. package/dist/search/port.d.ts +6 -1
  223. package/dist/search/port.js +7 -0
  224. package/dist/search/remote-backend.d.ts +1 -1
  225. package/dist/search/remote-backend.js +1 -1
  226. package/dist/semantic-consolidation.js +4 -4
  227. package/dist/semantic-rule-promotion.js +3 -3
  228. package/dist/semantic-rule-verifier.js +3 -3
  229. package/dist/session-observer-state.js +1 -1
  230. package/dist/storage.js +2 -2
  231. package/dist/summarizer.js +2 -2
  232. package/dist/temporal-index.js +1 -1
  233. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  234. package/dist/transfer/backup.js +2 -2
  235. package/dist/transfer/capsule-export.js +2 -2
  236. package/dist/transfer/capsule-import.js +2 -2
  237. package/dist/transfer/export-sqlite.js +1 -1
  238. package/dist/transfer/types.d.ts +12 -12
  239. package/dist/types.d.ts +32 -0
  240. package/dist/types.js +1 -1
  241. package/dist/utility-learner.js +1 -1
  242. package/dist/utility-runtime.js +2 -2
  243. package/dist/verified-recall.js +3 -3
  244. package/dist/work/board.js +2 -2
  245. package/dist/work/storage.d.ts +2 -0
  246. package/dist/work/storage.js +1 -1
  247. package/package.json +1 -1
  248. package/src/access-http.ts +24 -10
  249. package/src/access-mcp.test.ts +160 -0
  250. package/src/access-mcp.ts +72 -7
  251. package/src/access-schema.ts +11 -0
  252. package/src/access-service-coding-write.test.ts +478 -0
  253. package/src/access-service.ts +237 -32
  254. package/src/active-recall.test.ts +40 -0
  255. package/src/active-recall.ts +19 -2
  256. package/src/behavior-learner.ts +5 -3
  257. package/src/buffer-session.test.ts +58 -0
  258. package/src/buffer-surprise-trigger.test.ts +4 -18
  259. package/src/buffer.ts +39 -11
  260. package/src/calibration.ts +10 -4
  261. package/src/causal-consolidation.test.ts +47 -2
  262. package/src/causal-consolidation.ts +13 -9
  263. package/src/cli.ts +19 -4
  264. package/src/compounding/engine.ts +2 -0
  265. package/src/compounding/preference-consolidator.test.ts +292 -0
  266. package/src/compounding/preference-consolidator.ts +55 -19
  267. package/src/config.test.ts +213 -0
  268. package/src/config.ts +175 -4
  269. package/src/connectors/codex-materialize-runner.ts +7 -4
  270. package/src/consolidation-provenance-check.ts +24 -5
  271. package/src/conversation-index/indexer.test.ts +22 -0
  272. package/src/conversation-index/indexer.ts +7 -3
  273. package/src/cross-namespace-budget.test.ts +44 -21
  274. package/src/cross-namespace-budget.ts +2 -2
  275. package/src/enrichment/pipeline.ts +11 -16
  276. package/src/evals.ts +1 -1
  277. package/src/explicit-capture.ts +19 -2
  278. package/src/extraction-judge-chain.test.ts +55 -0
  279. package/src/extraction-judge.ts +7 -9
  280. package/src/extraction.ts +16 -5
  281. package/src/fallback-llm.test.ts +600 -1
  282. package/src/fallback-llm.ts +91 -22
  283. package/src/maintenance/memory-governance-cron.ts +39 -29
  284. package/src/mcp-memory-inspector-app.ts +54 -12
  285. package/src/message-parts/index.ts +6 -0
  286. package/src/message-parts/message-parts.test.ts +30 -0
  287. package/src/migrate/from-engram.ts +19 -5
  288. package/src/namespaces/search.test.ts +15 -2
  289. package/src/namespaces/search.ts +1 -1
  290. package/src/network/webdav.ts +61 -21
  291. package/src/operator-toolkit.ts +6 -2
  292. package/src/orchestrator.ts +173 -20
  293. package/src/qmd-client.test.ts +85 -0
  294. package/src/qmd-recall-cache.test.ts +16 -0
  295. package/src/qmd-recall-cache.ts +7 -0
  296. package/src/qmd.test.ts +54 -0
  297. package/src/qmd.ts +119 -19
  298. package/src/recall-planner-llm.test.ts +224 -0
  299. package/src/recall-planner-llm.ts +289 -0
  300. package/src/routing/store.ts +4 -8
  301. package/src/search/factory.ts +3 -0
  302. package/src/search/lancedb-backend.ts +15 -3
  303. package/src/search/meilisearch-backend.ts +70 -7
  304. package/src/search/noop-backend.ts +5 -1
  305. package/src/search/orama-backend.ts +15 -3
  306. package/src/search/port.ts +15 -0
  307. package/src/search/remote-backend.ts +5 -1
  308. package/src/session-observer-state.ts +1 -1
  309. package/src/summarizer.ts +3 -3
  310. package/src/temporal-index.test.ts +18 -0
  311. package/src/temporal-index.ts +45 -0
  312. package/src/training-export/cli-date-validation.test.ts +36 -0
  313. package/src/training-export/date-parse.ts +21 -2
  314. package/src/transfer/export-sqlite.ts +3 -0
  315. package/src/types.ts +35 -0
  316. package/src/utility-learner.ts +1 -0
  317. package/src/work/storage.ts +23 -0
  318. package/dist/chunk-2QANQKSQ.js.map +0 -1
  319. package/dist/chunk-5RPTH6AU.js.map +0 -1
  320. package/dist/chunk-AJA46VX5.js.map +0 -1
  321. package/dist/chunk-C4SQJZAF.js.map +0 -1
  322. package/dist/chunk-CHCA44C3.js.map +0 -1
  323. package/dist/chunk-CSKLPDN6.js.map +0 -1
  324. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  325. package/dist/chunk-EAZGEEG2.js.map +0 -1
  326. package/dist/chunk-EUML3N6B.js.map +0 -1
  327. package/dist/chunk-EVZFIAPG.js.map +0 -1
  328. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  329. package/dist/chunk-GMAG2HS4.js.map +0 -1
  330. package/dist/chunk-HENLZHIT.js.map +0 -1
  331. package/dist/chunk-HINSGUA7.js.map +0 -1
  332. package/dist/chunk-HJNQQICM.js.map +0 -1
  333. package/dist/chunk-HPWVAEET.js.map +0 -1
  334. package/dist/chunk-IOTENEVL.js.map +0 -1
  335. package/dist/chunk-IP73YCZP.js.map +0 -1
  336. package/dist/chunk-JHMFYY7L.js.map +0 -1
  337. package/dist/chunk-JNANKJLN.js.map +0 -1
  338. package/dist/chunk-KGK2QKWL.js.map +0 -1
  339. package/dist/chunk-KM2A35EO.js.map +0 -1
  340. package/dist/chunk-KVEVLBKC.js.map +0 -1
  341. package/dist/chunk-L227SKTB.js.map +0 -1
  342. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  343. package/dist/chunk-NOMEVTUD.js.map +0 -1
  344. package/dist/chunk-NZPF2SYV.js.map +0 -1
  345. package/dist/chunk-PCI747N2.js.map +0 -1
  346. package/dist/chunk-PPPZY2EU.js.map +0 -1
  347. package/dist/chunk-TH67Q46T.js.map +0 -1
  348. package/dist/chunk-UWY7GIVS.js.map +0 -1
  349. package/dist/chunk-VJXSUAO7.js.map +0 -1
  350. package/dist/chunk-XKIQZXUB.js.map +0 -1
  351. package/dist/chunk-XPSVGJYA.js.map +0 -1
  352. package/dist/chunk-XSWKORGM.js.map +0 -1
  353. package/dist/chunk-YCN4BVDK.js.map +0 -1
  354. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  355. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  356. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  357. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  358. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  359. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  360. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  361. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  362. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  363. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  364. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  365. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  366. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  367. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  368. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  369. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  370. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  371. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  372. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  373. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  374. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  375. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  376. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  377. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  378. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  379. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  380. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  381. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  382. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  383. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  384. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  385. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  386. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -89,6 +89,7 @@ interface QmdRuntimeLike {
89
89
  isAvailable(): boolean;
90
90
  ensureCollection(
91
91
  memoryDir: string,
92
+ collectionOrExecution?: string | { signal?: AbortSignal },
92
93
  execution?: { signal?: AbortSignal },
93
94
  ): Promise<"present" | "missing" | "unknown" | "skipped">;
94
95
  debugStatus(): string;
@@ -730,7 +731,10 @@ export async function runOperatorSetup(options: OperatorSetupOptions): Promise<O
730
731
 
731
732
  const qmdAvailable = await options.orchestrator.qmd.probe();
732
733
  const collectionState = options.orchestrator.config.qmdEnabled
733
- ? await options.orchestrator.qmd.ensureCollection(options.orchestrator.config.memoryDir)
734
+ ? await options.orchestrator.qmd.ensureCollection(
735
+ options.orchestrator.config.memoryDir,
736
+ options.orchestrator.config.qmdCollection,
737
+ )
734
738
  : "skipped";
735
739
  const nativeKnowledgeStatus = await summarizeNativeKnowledgeStatus(options.orchestrator.config);
736
740
 
@@ -1108,7 +1112,7 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
1108
1112
 
1109
1113
  const qmdAvailable = await options.orchestrator.qmd.probe();
1110
1114
  const collectionState = config.qmdEnabled
1111
- ? await options.orchestrator.qmd.ensureCollection(config.memoryDir)
1115
+ ? await options.orchestrator.qmd.ensureCollection(config.memoryDir, config.qmdCollection)
1112
1116
  : "skipped";
1113
1117
  checks.push({
1114
1118
  key: "qmd",
@@ -263,6 +263,7 @@ import {
263
263
  import { normalizeReplaySessionKey, type ReplayTurn } from "./replay/types.js";
264
264
  import type { ImportTurn } from "./bulk-import/types.js";
265
265
  import {
266
+ type AgentPersonaModelConfig,
266
267
  confidenceTier,
267
268
  type MemoryIntent,
268
269
  type MemorySummary,
@@ -529,6 +530,32 @@ export interface RecallModeDecision {
529
530
  effectiveMode: RecallPlanMode;
530
531
  graphExpandedIntentDetected: boolean;
531
532
  graphReason?: string;
533
+ /**
534
+ * Where `plannedMode` came from (issue #1367 / Option C). `"heuristic"` for
535
+ * the regex planner; `"llm"` when the LLM planner classified it; and
536
+ * `"heuristic-fallback"` when the LLM was enabled but errored/timed out and we
537
+ * fell back. Absent on the synchronous heuristic-only path.
538
+ */
539
+ plannerSource?: "heuristic" | "llm" | "heuristic-fallback";
540
+ /** Short rationale from the planner (for telemetry / x-ray). */
541
+ plannerReason?: string;
542
+ /** Wall-clock spent in the LLM planner call, when one was made. */
543
+ plannerLatencyMs?: number;
544
+ /** True when the LLM planner was enabled but fell back to the heuristic. */
545
+ plannerFallbackUsed?: boolean;
546
+ /** Model that served the LLM classification, when one was used. */
547
+ plannerModelUsed?: string;
548
+ /**
549
+ * The regex-heuristic baseline mode, captured whenever the LLM planner ran
550
+ * (any source). Lets operators compare planned-vs-heuristic during rollout —
551
+ * distinct from `plannedMode`, which on the LLM path is the LLM's choice.
552
+ */
553
+ plannerHeuristicMode?: RecallPlanMode;
554
+ /**
555
+ * In shadow mode, the mode the LLM *would* have chosen (recorded for
556
+ * comparison) while `effectiveMode` stays on the heuristic decision.
557
+ */
558
+ shadowLlmMode?: RecallPlanMode;
532
559
  }
533
560
 
534
561
  /**
@@ -1052,16 +1079,27 @@ export function resolveEffectiveRecallMode(options: {
1052
1079
  return resolveRecallModeDecision(options).effectiveMode;
1053
1080
  }
1054
1081
 
1055
- export function resolveRecallModeDecision(options: {
1082
+ interface RecallModeGraphOptions {
1056
1083
  plannerEnabled: boolean;
1057
1084
  graphRecallEnabled: boolean;
1058
1085
  multiGraphMemoryEnabled: boolean;
1059
1086
  graphExpandedIntentEnabled?: boolean;
1060
1087
  prompt: string;
1061
- }): RecallModeDecision {
1062
- let plannedMode: RecallPlanMode = options.plannerEnabled
1063
- ? planRecallMode(options.prompt)
1064
- : "full";
1088
+ }
1089
+
1090
+ /**
1091
+ * Apply the graph-mode overlay + gating to a planner-produced mode.
1092
+ *
1093
+ * Shared by the heuristic ({@link resolveRecallModeDecision}) and LLM
1094
+ * ({@link resolveRecallModeDecisionAsync}) paths so graph promotion and the
1095
+ * "graph disabled → fall back to full" gating behave identically regardless of
1096
+ * which planner produced `plannedModeRaw` (gotcha #39).
1097
+ */
1098
+ function finalizeRecallModeDecision(
1099
+ plannedModeRaw: RecallPlanMode,
1100
+ options: RecallModeGraphOptions,
1101
+ ): RecallModeDecision {
1102
+ let plannedMode: RecallPlanMode = plannedModeRaw;
1065
1103
  const graphExpandedIntentDetected =
1066
1104
  options.plannerEnabled &&
1067
1105
  options.graphExpandedIntentEnabled === true &&
@@ -1089,6 +1127,74 @@ export function resolveRecallModeDecision(options: {
1089
1127
  };
1090
1128
  }
1091
1129
 
1130
+ export function resolveRecallModeDecision(options: RecallModeGraphOptions): RecallModeDecision {
1131
+ const plannedMode: RecallPlanMode = options.plannerEnabled
1132
+ ? planRecallMode(options.prompt)
1133
+ : "full";
1134
+ return finalizeRecallModeDecision(plannedMode, options);
1135
+ }
1136
+
1137
+ /**
1138
+ * Async recall-mode decision with optional LLM-based planning (issue #1367 /
1139
+ * Option C). Falls back to the heuristic decision when the LLM planner is
1140
+ * disabled, in shadow mode, or unavailable/failed — so this is always safe to
1141
+ * await on the recall hot path. Provider-agnostic: the LLM call routes through
1142
+ * the gateway/fallback chain.
1143
+ *
1144
+ * `recallPlannerEnabled === false` keeps the legacy "always full" behavior and
1145
+ * skips the LLM entirely (the planner as a whole is off).
1146
+ */
1147
+ export async function resolveRecallModeDecisionAsync(
1148
+ options: RecallModeGraphOptions & {
1149
+ config: PluginConfig;
1150
+ hints?: string[];
1151
+ llm?: FallbackLlmClient;
1152
+ signal?: AbortSignal;
1153
+ },
1154
+ ): Promise<RecallModeDecision> {
1155
+ const heuristicDecision = resolveRecallModeDecision(options);
1156
+
1157
+ // Planner globally off, or LLM planning not opted into → heuristic only.
1158
+ if (!options.plannerEnabled || !options.config.recallPlannerLlmEnabled) {
1159
+ return heuristicDecision;
1160
+ }
1161
+
1162
+ const { planRecallModeLLM } = await import("./recall-planner-llm.js");
1163
+ const planned = await planRecallModeLLM(
1164
+ options.prompt,
1165
+ options.hints,
1166
+ options.config,
1167
+ options.llm,
1168
+ options.signal,
1169
+ );
1170
+
1171
+ // Shadow mode: record what the LLM would have chosen but keep the heuristic
1172
+ // effective decision (safe rollout / comparison — gotcha #30).
1173
+ if (options.config.recallPlannerShadowMode) {
1174
+ return {
1175
+ ...heuristicDecision,
1176
+ plannerSource: planned.source,
1177
+ plannerReason: `shadow:${planned.reason}`,
1178
+ plannerLatencyMs: planned.latencyMs,
1179
+ plannerFallbackUsed: planned.fallbackUsed,
1180
+ plannerModelUsed: planned.modelUsed,
1181
+ plannerHeuristicMode: planned.heuristicMode,
1182
+ shadowLlmMode: planned.mode,
1183
+ };
1184
+ }
1185
+
1186
+ const llmDecision = finalizeRecallModeDecision(planned.mode, options);
1187
+ return {
1188
+ ...llmDecision,
1189
+ plannerSource: planned.source,
1190
+ plannerReason: planned.reason,
1191
+ plannerLatencyMs: planned.latencyMs,
1192
+ plannerFallbackUsed: planned.fallbackUsed,
1193
+ plannerModelUsed: planned.modelUsed,
1194
+ plannerHeuristicMode: planned.heuristicMode,
1195
+ };
1196
+ }
1197
+
1092
1198
  export function hasIdentityRecoveryIntent(prompt: string): boolean {
1093
1199
  const text = typeof prompt === "string" ? prompt.toLowerCase() : "";
1094
1200
  if (!text) return false;
@@ -2512,9 +2618,11 @@ export class Orchestrator {
2512
2618
  namespace,
2513
2619
  { signal: collectionCheckAbort.signal },
2514
2620
  )
2515
- : this.qmd.ensureCollection(this.config.memoryDir, {
2516
- signal: collectionCheckAbort.signal,
2517
- }),
2621
+ : this.qmd.ensureCollection(
2622
+ this.config.memoryDir,
2623
+ this.config.qmdCollection,
2624
+ { signal: collectionCheckAbort.signal },
2625
+ ),
2518
2626
  collectionCheckAbort,
2519
2627
  namespace,
2520
2628
  );
@@ -2860,7 +2968,7 @@ export class Orchestrator {
2860
2968
  namespace,
2861
2969
  state: this.config.namespacesEnabled
2862
2970
  ? await this.namespaceSearchRouter.ensureNamespaceCollection(namespace, { signal })
2863
- : await this.qmd.ensureCollection(this.config.memoryDir, { signal }),
2971
+ : await this.qmd.ensureCollection(this.config.memoryDir, this.config.qmdCollection, { signal }),
2864
2972
  })),
2865
2973
  );
2866
2974
 
@@ -3390,22 +3498,31 @@ export class Orchestrator {
3390
3498
 
3391
3499
  // Use FallbackLlmClient for LLM calls (same pattern as causal-consolidation.ts)
3392
3500
  // Honor semanticConsolidationModel: "auto" = primary, "fast" = local fast, or specific model
3393
- const { FallbackLlmClient } = await import("./fallback-llm.js");
3501
+ const { FallbackLlmClient, gatewayTaskChainOptions } = await import("./fallback-llm.js");
3394
3502
  const useGateway = this.config.modelSource === "gateway";
3395
3503
  const modelSetting = this.config.semanticConsolidationModel;
3396
3504
  if (modelSetting === "fast" && this.fastLlm && !useGateway) {
3397
3505
  log.info("[semantic-consolidation] using fast local LLM for synthesis");
3398
3506
  }
3399
- const gatewayAgentId = useGateway
3400
- ? (modelSetting === "fast" && this.config.fastGatewayAgentId
3401
- ? this.config.fastGatewayAgentId
3402
- : this.config.gatewayAgentId || undefined)
3403
- : undefined;
3507
+ // Gateway routing: an explicit "fast" setting keeps the fast persona chain
3508
+ // (the operator's deliberate fast-tier choice). Otherwise route through the
3509
+ // shared task-chain resolution so taskModelChain applies to semantic
3510
+ // consolidation like every other background task (gotcha #22). Issue #1365.
3511
+ const gatewayChainOptions: { modelChain?: AgentPersonaModelConfig; agentId?: string } =
3512
+ !useGateway
3513
+ ? {}
3514
+ : modelSetting === "fast"
3515
+ ? (this.config.fastGatewayAgentId
3516
+ ? { agentId: this.config.fastGatewayAgentId }
3517
+ : this.config.gatewayAgentId
3518
+ ? { agentId: this.config.gatewayAgentId }
3519
+ : {})
3520
+ : gatewayTaskChainOptions(this.config);
3404
3521
  const llm = new FallbackLlmClient(
3405
3522
  this.config.gatewayConfig,
3406
3523
  fallbackLlmRuntimeContextFromConfig(this.config),
3407
3524
  );
3408
- if (!llm.isAvailable(gatewayAgentId) && !(modelSetting === "fast" && this.fastLlm && !useGateway)) {
3525
+ if (!llm.isAvailable(gatewayChainOptions) && !(modelSetting === "fast" && this.fastLlm && !useGateway)) {
3409
3526
  log.warn(
3410
3527
  "[semantic-consolidation] no LLM available — skipping synthesis",
3411
3528
  );
@@ -3454,7 +3571,7 @@ export class Orchestrator {
3454
3571
  let response: { content: string } | null = null;
3455
3572
  if (useGateway) {
3456
3573
  // Gateway model source — use the appropriate agent chain
3457
- response = await llm.chatCompletion(messages, { ...llmOpts, agentId: gatewayAgentId });
3574
+ response = await llm.chatCompletion(messages, { ...llmOpts, ...gatewayChainOptions });
3458
3575
  } else if (modelSetting === "fast" && this.fastLlm) {
3459
3576
  const fastResult = await this.fastLlm.chatCompletion(messages, {
3460
3577
  operation: "semantic-consolidation",
@@ -5628,6 +5745,12 @@ export class Orchestrator {
5628
5745
  ) {
5629
5746
  if (debugSearchOptions?.intent) {
5630
5747
  lastHybridTopUpSkippedReason = "intent_hint_active";
5748
+ } else if (this.config.qmdSearchStrategy === "lex") {
5749
+ // BM25-only strategy: a hybrid top-up runs vectorSearch (see
5750
+ // QmdClient.hybridSearch), which would reintroduce the vector path the
5751
+ // operator opted out of. Keep "lex" BM25-only end-to-end so the gate is
5752
+ // uniform across primary + top-up (gotcha #39). Issue #1335 (codex review #1422).
5753
+ lastHybridTopUpSkippedReason = "lex_strategy";
5631
5754
  } else {
5632
5755
  const hybridResults = options.collection
5633
5756
  ? await this.qmd.hybridSearch(
@@ -6382,16 +6505,44 @@ export class Orchestrator {
6382
6505
  let identityInjectedChars = 0;
6383
6506
  let identityInjectionTruncated = false;
6384
6507
  timings.queryPolicy = `${queryPolicy.promptShape}/${queryPolicy.retrievalBudgetMode}${queryPolicy.skipConversationRecall ? "/skip-conv" : ""}`;
6385
- const recallDecision = resolveRecallModeDecision({
6508
+ const recallModeDecisionOptions = {
6386
6509
  plannerEnabled: this.config.recallPlannerEnabled,
6387
6510
  graphRecallEnabled: this.config.graphRecallEnabled,
6388
6511
  multiGraphMemoryEnabled: this.config.multiGraphMemoryEnabled,
6389
6512
  graphExpandedIntentEnabled:
6390
6513
  this.config.graphExpandedIntentEnabled === true,
6391
6514
  prompt,
6392
- });
6393
- this.profiler.endSpan("planning", profileTraceId);
6515
+ };
6394
6516
  const requestedMode = options.mode;
6517
+ // When the caller forces a mode, skip the (async, possibly LLM-backed)
6518
+ // planner entirely — the decision is overridden anyway. Otherwise consult
6519
+ // the LLM planner when opted in (issue #1367 / Option C); it falls back to
6520
+ // the heuristic on disable / shadow / timeout / error.
6521
+ const recallDecision =
6522
+ requestedMode !== undefined
6523
+ ? resolveRecallModeDecision(recallModeDecisionOptions)
6524
+ : await resolveRecallModeDecisionAsync({
6525
+ ...recallModeDecisionOptions,
6526
+ config: this.config,
6527
+ signal: options.abortSignal,
6528
+ });
6529
+ if (
6530
+ this.config.recallPlannerTelemetryEnabled &&
6531
+ recallDecision.plannerSource &&
6532
+ recallDecision.plannerSource !== "heuristic"
6533
+ ) {
6534
+ log.debug(
6535
+ `[recall-planner] mode=${recallDecision.shadowLlmMode ?? recallDecision.effectiveMode} ` +
6536
+ `source=${recallDecision.plannerSource} ` +
6537
+ `planned=${recallDecision.plannedMode} ` +
6538
+ `heuristic=${recallDecision.plannerHeuristicMode ?? recallDecision.plannedMode} ` +
6539
+ `model=${recallDecision.plannerModelUsed ?? "n/a"} ` +
6540
+ `latencyMs=${recallDecision.plannerLatencyMs ?? 0} ` +
6541
+ `fallback=${recallDecision.plannerFallbackUsed ?? false}` +
6542
+ (recallDecision.shadowLlmMode ? " (shadow)" : ""),
6543
+ );
6544
+ }
6545
+ this.profiler.endSpan("planning", profileTraceId);
6395
6546
  const recallMode: RecallPlanMode =
6396
6547
  requestedMode ?? recallDecision.effectiveMode;
6397
6548
  const queryIntent = inferIntentFromText(retrievalQuery);
@@ -7780,6 +7931,8 @@ export class Orchestrator {
7780
7931
  maxResults: qmdFetchLimit,
7781
7932
  memoryDir: this.config.memoryDir,
7782
7933
  searchOptions: qmdSearchOptions,
7934
+ searchStrategy: this.config.qmdSearchStrategy,
7935
+ subprocessStrategy: this.config.qmdSubprocessStrategy,
7783
7936
  });
7784
7937
  const cachedQmd = getCachedQmdRecall<Exclude<QmdPhaseResult, null>>(
7785
7938
  qmdCacheKey,
@@ -43,3 +43,88 @@ test("QmdClient rechecks daemon availability before returning unavailable", asyn
43
43
  assert.equal(results.length, 1);
44
44
  assert.equal(results[0]?.transport, "daemon");
45
45
  });
46
+
47
+ type SubprocessInternals = {
48
+ available: boolean;
49
+ runQmdCommand: (args: string[]) => Promise<{ stdout: string; stderr: string }>;
50
+ searchViaSubprocess: (
51
+ query: string,
52
+ collection: string,
53
+ maxResults: number,
54
+ ) => Promise<QmdSearchResult[]>;
55
+ searchGlobalViaSubprocess: (query: string, maxResults: number) => Promise<QmdSearchResult[]>;
56
+ };
57
+
58
+ function captureSubprocessArgs(client: QmdClient): string[][] {
59
+ const calls: string[][] = [];
60
+ const internals = client as unknown as SubprocessInternals;
61
+ internals.available = true;
62
+ internals.runQmdCommand = async (args: string[]) => {
63
+ calls.push(args);
64
+ return { stdout: "[]", stderr: "" };
65
+ };
66
+ return calls;
67
+ }
68
+
69
+ test("subprocess fallback defaults to `qmd query` for scoped and global recall", async () => {
70
+ const client = new QmdClient("memories", 3, {});
71
+ const calls = captureSubprocessArgs(client);
72
+ const internals = client as unknown as SubprocessInternals;
73
+
74
+ await internals.searchViaSubprocess("hermes deployment", "memories", 3);
75
+ await internals.searchGlobalViaSubprocess("hermes deployment", 3);
76
+
77
+ assert.equal(calls[0]?.[0], "query", "scoped fallback must default to `qmd query`");
78
+ assert.equal(calls[1]?.[0], "query", "global fallback must default to `qmd query`");
79
+ });
80
+
81
+ test("qmdSubprocessStrategy 'search' applies BM25 to scoped AND global recall (gotcha #39)", async () => {
82
+ // Cursor #1422 review: the gate must be uniform across every subprocess path,
83
+ // not just the scoped one.
84
+ const client = new QmdClient("memories", 3, { qmdSubprocessStrategy: "search" });
85
+ const calls = captureSubprocessArgs(client);
86
+ const internals = client as unknown as SubprocessInternals;
87
+
88
+ await internals.searchViaSubprocess("hermes deployment", "memories", 3);
89
+ await internals.searchGlobalViaSubprocess("hermes deployment", 3);
90
+
91
+ assert.equal(calls[0]?.[0], "search", "scoped fallback must honor BM25 opt-in");
92
+ assert.equal(calls[1]?.[0], "search", "global fallback must honor BM25 opt-in");
93
+ // Global BM25 must NOT pass a collection flag.
94
+ assert.ok(!calls[1]?.includes("-c"), "global BM25 search must not include -c");
95
+ });
96
+
97
+ test("QMD search cache key isolates results by strategy (codex review on #1422)", async () => {
98
+ // Two clients with different strategies must not serve each other's cached
99
+ // results for the same query/collection within the global cache TTL.
100
+ function makeClient(opts: Record<string, unknown>): {
101
+ client: QmdClient;
102
+ calls: string[][];
103
+ } {
104
+ const client = new QmdClient("memories", 3, opts);
105
+ const internals = client as unknown as SubprocessInternals & {
106
+ daemonAvailable: boolean;
107
+ };
108
+ internals.available = true;
109
+ internals.daemonAvailable = false;
110
+ const calls: string[][] = [];
111
+ internals.runQmdCommand = async (args: string[]) => {
112
+ calls.push(args);
113
+ return { stdout: "[]", stderr: "" };
114
+ };
115
+ return { client, calls };
116
+ }
117
+
118
+ // Unique query avoids colliding with cache entries from other tests.
119
+ const query = "strategy-cache-isolation-probe-xyz";
120
+ const a = makeClient({ qmdSearchStrategy: "hybrid" });
121
+ const b = makeClient({ qmdSearchStrategy: "lex" });
122
+
123
+ await a.client.search(query, "memories", 3);
124
+ await b.client.search(query, "memories", 3);
125
+
126
+ // If the cache key ignored strategy, b would hit a's cached entry and never
127
+ // invoke the subprocess. Both must register their own subprocess call.
128
+ assert.equal(a.calls.length, 1, "first strategy populates its own cache entry");
129
+ assert.equal(b.calls.length, 1, "second strategy must NOT reuse the first's cached result");
130
+ });
@@ -97,6 +97,22 @@ test("qmd recall cache key reflects all defined search options", () => {
97
97
  assert.notEqual(left, right);
98
98
  });
99
99
 
100
+ test("qmd recall cache key reflects search and subprocess strategy (codex review #1422)", () => {
101
+ const base = {
102
+ query: "api rate limit",
103
+ namespaces: ["a", "b"],
104
+ recallMode: "minimal" as const,
105
+ maxResults: 4,
106
+ memoryDir: "/tmp/engram-a",
107
+ };
108
+ const hybrid = buildQmdRecallCacheKey({ ...base, searchStrategy: "hybrid", subprocessStrategy: "query" });
109
+ const lex = buildQmdRecallCacheKey({ ...base, searchStrategy: "lex", subprocessStrategy: "query" });
110
+ const bm25Fallback = buildQmdRecallCacheKey({ ...base, searchStrategy: "hybrid", subprocessStrategy: "search" });
111
+
112
+ assert.notEqual(hybrid, lex, "different search strategies must not share a recall cache entry");
113
+ assert.notEqual(hybrid, bm25Fallback, "different subprocess strategies must not share a recall cache entry");
114
+ });
115
+
100
116
  test("qmd recall cache returns cloned values so callers cannot mutate cached entries", () => {
101
117
  clearQmdRecallCache();
102
118
  const key = buildQmdRecallCacheKey({
@@ -22,6 +22,11 @@ export interface QmdRecallCacheKeyOptions {
22
22
  memoryDir?: string;
23
23
  collection?: string;
24
24
  searchOptions?: SearchQueryOptions;
25
+ // QMD search/subprocess strategies change the recalled results, so they must
26
+ // participate in the cache key — otherwise a different strategy's cached QMD
27
+ // phase is served within the TTL (gotcha #37). Issue #1335 (codex review #1422).
28
+ searchStrategy?: string;
29
+ subprocessStrategy?: string;
25
30
  }
26
31
 
27
32
  const qmdRecallCache = new Map<string, QmdRecallCacheEntry>();
@@ -61,6 +66,8 @@ export function buildQmdRecallCacheKey(
61
66
  memoryDir: normalizePathScope(options.memoryDir),
62
67
  collection: options.collection ?? "",
63
68
  searchOptions: normalizeSearchOptions(options.searchOptions),
69
+ searchStrategy: options.searchStrategy ?? "",
70
+ subprocessStrategy: options.subprocessStrategy ?? "",
64
71
  });
65
72
  }
66
73
 
package/src/qmd.test.ts CHANGED
@@ -3,6 +3,7 @@ import test from "node:test";
3
3
 
4
4
  import { parseConfig } from "./config.js";
5
5
  import {
6
+ buildDefaultStructuredSearches,
6
7
  getQmdCommandName,
7
8
  getQmdPostInstallProbeTargets,
8
9
  parseQmdVersionOutput,
@@ -67,6 +68,59 @@ test("resolveQmdCapabilities gates qmd 2.5 features by installed version", () =>
67
68
  assert.equal(v201.legacySkillInstall, true);
68
69
  });
69
70
 
71
+ test("buildDefaultStructuredSearches defaults to the full hybrid lex+vec+hyde plan", () => {
72
+ // Default (no strategy argument) must preserve historical behavior so an
73
+ // upgrade never silently drops vector/HyDE recall. Issue #1335.
74
+ const searches = buildDefaultStructuredSearches("hermes deployment");
75
+ assert.deepEqual(
76
+ searches.map((s) => s.type),
77
+ ["lex", "vec", "hyde"],
78
+ );
79
+ });
80
+
81
+ test("buildDefaultStructuredSearches honors the hybrid strategy explicitly", () => {
82
+ const searches = buildDefaultStructuredSearches("hermes deployment", undefined, "hybrid");
83
+ assert.deepEqual(
84
+ searches.map((s) => s.type),
85
+ ["lex", "vec", "hyde"],
86
+ );
87
+ });
88
+
89
+ test("buildDefaultStructuredSearches lex-vec strategy drops the expensive HyDE leg", () => {
90
+ const searches = buildDefaultStructuredSearches("hermes deployment", undefined, "lex-vec");
91
+ assert.deepEqual(
92
+ searches.map((s) => s.type),
93
+ ["lex", "vec"],
94
+ );
95
+ });
96
+
97
+ test("buildDefaultStructuredSearches lex strategy is BM25-only", () => {
98
+ const searches = buildDefaultStructuredSearches("hermes deployment", undefined, "lex");
99
+ assert.deepEqual(
100
+ searches.map((s) => s.type),
101
+ ["lex"],
102
+ );
103
+ });
104
+
105
+ test("buildDefaultStructuredSearches lets an explicit per-call override win over the strategy", () => {
106
+ // Per-call structuredSearches override must still take precedence regardless
107
+ // of the configured default strategy.
108
+ const searches = buildDefaultStructuredSearches(
109
+ "hermes deployment",
110
+ { structuredSearches: [{ type: "vec", query: "hermes deployment" }] },
111
+ "lex",
112
+ );
113
+ assert.deepEqual(
114
+ searches.map((s) => s.type),
115
+ ["vec"],
116
+ );
117
+ });
118
+
119
+ test("buildDefaultStructuredSearches returns empty for blank queries under any strategy", () => {
120
+ assert.deepEqual(buildDefaultStructuredSearches(" ", undefined, "hybrid"), []);
121
+ assert.deepEqual(buildDefaultStructuredSearches(" ", undefined, "lex"), []);
122
+ });
123
+
70
124
  test("shouldAutoUpgradeQmd only upgrades below Remnic supported version", () => {
71
125
  assert.equal(shouldAutoUpgradeQmd("qmd 2.1.0", "2.5.3"), true);
72
126
  assert.equal(shouldAutoUpgradeQmd("qmd 2.5.3", "2.5.3"), false);