@remnic/core 9.3.612 → 9.3.614

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 (374) hide show
  1. package/dist/access-cli.js +58 -57
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +22 -22
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +19 -19
  7. package/dist/access-schema.d.ts +12 -12
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-DGG_2xPK.d.ts} +1 -1
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +16 -16
  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-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  48. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  49. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  50. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  51. package/dist/chunk-6L46YAEZ.js.map +1 -0
  52. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  53. package/dist/{chunk-IOTENEVL.js → chunk-7YQFWOF7.js} +57 -50
  54. package/dist/chunk-7YQFWOF7.js.map +1 -0
  55. package/dist/{chunk-2QANQKSQ.js → chunk-ADNZVFXG.js} +15 -15
  56. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  57. package/dist/chunk-AL4RAJL5.js.map +1 -0
  58. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  59. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  60. package/dist/{chunk-TH67Q46T.js → chunk-B6FDZPCF.js} +17 -9
  61. package/dist/chunk-B6FDZPCF.js.map +1 -0
  62. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  63. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  64. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  65. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  66. package/dist/chunk-C6C7XVKG.js.map +1 -0
  67. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  68. package/dist/chunk-CI7RKSRE.js.map +1 -0
  69. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  70. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  71. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  72. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  73. package/dist/chunk-DCGT4FPP.js.map +1 -0
  74. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  75. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  76. package/dist/chunk-DEVUWMME.js.map +1 -0
  77. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  78. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  79. package/dist/chunk-FAV25DUZ.js +12 -0
  80. package/dist/chunk-FAV25DUZ.js.map +1 -0
  81. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  82. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  83. package/dist/chunk-GDB4J2H3.js.map +1 -0
  84. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  85. package/dist/chunk-GLPBYIXN.js.map +1 -0
  86. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  87. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  88. package/dist/chunk-IBTZEBUD.js.map +1 -0
  89. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  90. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  91. package/dist/chunk-JOASJWQR.js.map +1 -0
  92. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  93. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  94. package/dist/chunk-KBL3JJR6.js.map +1 -0
  95. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  96. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  97. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  98. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  99. package/dist/chunk-MHQC2WU2.js.map +1 -0
  100. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  101. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  102. package/dist/chunk-ODPLEWB6.js.map +1 -0
  103. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  104. package/dist/chunk-OIF36KGD.js.map +1 -0
  105. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  106. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  107. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  108. package/dist/chunk-PYIFUBRK.js.map +1 -0
  109. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  110. package/dist/{chunk-PPPZY2EU.js → chunk-QEMCQFDW.js} +2 -2
  111. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  112. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  113. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  114. package/dist/chunk-RG3LBSGH.js.map +1 -0
  115. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  116. package/dist/chunk-S53OYO3F.js.map +1 -0
  117. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  118. package/dist/chunk-SCPFRKIT.js.map +1 -0
  119. package/dist/{chunk-HJNQQICM.js → chunk-T5XWMMU2.js} +107 -50
  120. package/dist/chunk-T5XWMMU2.js.map +1 -0
  121. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  122. package/dist/chunk-T7N6KQGS.js.map +1 -0
  123. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  124. package/dist/chunk-TNOWU6RP.js.map +1 -0
  125. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  126. package/dist/chunk-TZVQQTG4.js.map +1 -0
  127. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  128. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  129. package/dist/{chunk-5RPTH6AU.js → chunk-VPGUMLBA.js} +8 -7
  130. package/dist/chunk-VPGUMLBA.js.map +1 -0
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  134. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  135. package/dist/chunk-X6IRLNOO.js.map +1 -0
  136. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  137. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  138. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  139. package/dist/chunk-YRMKDTKF.js.map +1 -0
  140. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  141. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  142. package/dist/{cli-OrfKXNU4.d.ts → cli-DWeu7eTY.d.ts} +6 -2
  143. package/dist/cli.d.ts +3 -3
  144. package/dist/cli.js +60 -59
  145. package/dist/compounding/engine.js +3 -3
  146. package/dist/compounding/preference-consolidator.js +39 -11
  147. package/dist/compounding/preference-consolidator.js.map +1 -1
  148. package/dist/config.js +1 -1
  149. package/dist/connectors/codex-materialize-runner.js +3 -3
  150. package/dist/connectors/index.js +3 -3
  151. package/dist/consolidation-provenance-check.js +1 -1
  152. package/dist/contradiction/index.js +4 -4
  153. package/dist/conversation-index/backend.js +2 -2
  154. package/dist/conversation-index/indexer.js +1 -1
  155. package/dist/cross-namespace-budget.js +1 -1
  156. package/dist/enrichment/index.js +1 -1
  157. package/dist/entity-retrieval.js +3 -3
  158. package/dist/evals.js +1 -1
  159. package/dist/explicit-capture.d.ts +1 -1
  160. package/dist/extraction-judge.js +8 -1
  161. package/dist/extraction.js +2 -2
  162. package/dist/fallback-llm.d.ts +23 -6
  163. package/dist/fallback-llm.js +5 -3
  164. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  165. package/dist/index.d.ts +3 -3
  166. package/dist/index.js +94 -93
  167. package/dist/index.js.map +1 -1
  168. package/dist/lcm/archive.js +2 -2
  169. package/dist/lcm/engine.js +5 -5
  170. package/dist/lcm/index.js +7 -7
  171. package/dist/lcm/summarizer.js +3 -3
  172. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  173. package/dist/maintenance/memory-governance-cron.js +1 -1
  174. package/dist/maintenance/memory-governance.js +3 -3
  175. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  176. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  177. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  178. package/dist/mcp-memory-inspector-app.js +1 -1
  179. package/dist/migrate/from-engram.js +1 -1
  180. package/dist/namespaces/migrate.js +16 -15
  181. package/dist/namespaces/search.js +12 -11
  182. package/dist/namespaces/storage.js +3 -3
  183. package/dist/network/webdav.d.ts +2 -0
  184. package/dist/network/webdav.js +1 -1
  185. package/dist/objective-state-writers.js +2 -2
  186. package/dist/operator-toolkit.d.ts +3 -1
  187. package/dist/operator-toolkit.js +21 -20
  188. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  189. package/dist/orchestrator.d.ts +1 -1
  190. package/dist/orchestrator.js +47 -44
  191. package/dist/patterns-cli.js +1 -1
  192. package/dist/qmd-recall-cache.d.ts +2 -0
  193. package/dist/qmd-recall-cache.js +1 -1
  194. package/dist/qmd.d.ts +37 -2
  195. package/dist/qmd.js +4 -1
  196. package/dist/recall-explain-renderer.js +3 -3
  197. package/dist/recall-planner-llm.d.ts +57 -0
  198. package/dist/recall-planner-llm.js +167 -0
  199. package/dist/recall-planner-llm.js.map +1 -0
  200. package/dist/recall-xray-cli.js +4 -4
  201. package/dist/recall-xray-renderer.js +3 -3
  202. package/dist/recall-xray.js +2 -2
  203. package/dist/resume-bundles.js +2 -2
  204. package/dist/retrieval-agents.js +2 -2
  205. package/dist/routing/store.js +1 -1
  206. package/dist/search/factory.js +11 -10
  207. package/dist/search/index.js +11 -10
  208. package/dist/search/lancedb-backend.d.ts +1 -1
  209. package/dist/search/lancedb-backend.js +3 -2
  210. package/dist/search/meilisearch-backend.d.ts +1 -1
  211. package/dist/search/meilisearch-backend.js +3 -2
  212. package/dist/search/noop-backend.d.ts +1 -1
  213. package/dist/search/noop-backend.js +1 -1
  214. package/dist/search/orama-backend.d.ts +1 -1
  215. package/dist/search/orama-backend.js +3 -2
  216. package/dist/search/port.d.ts +6 -1
  217. package/dist/search/port.js +7 -0
  218. package/dist/search/remote-backend.d.ts +1 -1
  219. package/dist/search/remote-backend.js +1 -1
  220. package/dist/semantic-consolidation.js +4 -4
  221. package/dist/semantic-rule-promotion.js +3 -3
  222. package/dist/semantic-rule-verifier.js +3 -3
  223. package/dist/session-observer-state.js +1 -1
  224. package/dist/storage.js +2 -2
  225. package/dist/summarizer.js +2 -2
  226. package/dist/temporal-index.js +1 -1
  227. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  228. package/dist/transfer/backup.js +2 -2
  229. package/dist/transfer/capsule-export.js +2 -2
  230. package/dist/transfer/capsule-import.js +2 -2
  231. package/dist/transfer/export-sqlite.js +1 -1
  232. package/dist/types.d.ts +32 -0
  233. package/dist/types.js +1 -1
  234. package/dist/utility-learner.js +1 -1
  235. package/dist/utility-runtime.js +2 -2
  236. package/dist/verified-recall.js +3 -3
  237. package/dist/work/board.js +2 -2
  238. package/dist/work/storage.d.ts +2 -0
  239. package/dist/work/storage.js +1 -1
  240. package/package.json +1 -1
  241. package/src/access-http.ts +3 -0
  242. package/src/access-mcp.test.ts +51 -0
  243. package/src/access-mcp.ts +26 -5
  244. package/src/active-recall.test.ts +40 -0
  245. package/src/active-recall.ts +19 -2
  246. package/src/behavior-learner.ts +5 -3
  247. package/src/buffer-session.test.ts +58 -0
  248. package/src/buffer-surprise-trigger.test.ts +4 -18
  249. package/src/buffer.ts +39 -11
  250. package/src/calibration.ts +10 -4
  251. package/src/causal-consolidation.test.ts +47 -2
  252. package/src/causal-consolidation.ts +13 -9
  253. package/src/cli.ts +19 -4
  254. package/src/compounding/engine.ts +2 -0
  255. package/src/compounding/preference-consolidator.test.ts +292 -0
  256. package/src/compounding/preference-consolidator.ts +55 -19
  257. package/src/config.test.ts +213 -0
  258. package/src/config.ts +175 -4
  259. package/src/connectors/codex-materialize-runner.ts +7 -4
  260. package/src/consolidation-provenance-check.ts +24 -5
  261. package/src/conversation-index/indexer.test.ts +22 -0
  262. package/src/conversation-index/indexer.ts +7 -3
  263. package/src/cross-namespace-budget.test.ts +44 -21
  264. package/src/cross-namespace-budget.ts +2 -2
  265. package/src/enrichment/pipeline.ts +11 -16
  266. package/src/evals.ts +1 -1
  267. package/src/extraction-judge-chain.test.ts +55 -0
  268. package/src/extraction-judge.ts +7 -9
  269. package/src/extraction.ts +16 -5
  270. package/src/fallback-llm.test.ts +600 -1
  271. package/src/fallback-llm.ts +91 -22
  272. package/src/maintenance/memory-governance-cron.ts +39 -29
  273. package/src/mcp-memory-inspector-app.ts +54 -12
  274. package/src/message-parts/index.ts +6 -0
  275. package/src/message-parts/message-parts.test.ts +30 -0
  276. package/src/migrate/from-engram.ts +19 -5
  277. package/src/namespaces/search.test.ts +15 -2
  278. package/src/namespaces/search.ts +1 -1
  279. package/src/network/webdav.ts +61 -21
  280. package/src/operator-toolkit.ts +6 -2
  281. package/src/orchestrator.ts +173 -20
  282. package/src/qmd-client.test.ts +85 -0
  283. package/src/qmd-recall-cache.test.ts +16 -0
  284. package/src/qmd-recall-cache.ts +7 -0
  285. package/src/qmd.test.ts +54 -0
  286. package/src/qmd.ts +119 -19
  287. package/src/recall-planner-llm.test.ts +224 -0
  288. package/src/recall-planner-llm.ts +289 -0
  289. package/src/routing/store.ts +4 -8
  290. package/src/search/factory.ts +3 -0
  291. package/src/search/lancedb-backend.ts +15 -3
  292. package/src/search/meilisearch-backend.ts +70 -7
  293. package/src/search/noop-backend.ts +5 -1
  294. package/src/search/orama-backend.ts +15 -3
  295. package/src/search/port.ts +15 -0
  296. package/src/search/remote-backend.ts +5 -1
  297. package/src/session-observer-state.ts +1 -1
  298. package/src/summarizer.ts +3 -3
  299. package/src/temporal-index.test.ts +18 -0
  300. package/src/temporal-index.ts +45 -0
  301. package/src/training-export/cli-date-validation.test.ts +36 -0
  302. package/src/training-export/date-parse.ts +21 -2
  303. package/src/transfer/export-sqlite.ts +3 -0
  304. package/src/types.ts +35 -0
  305. package/src/utility-learner.ts +1 -0
  306. package/src/work/storage.ts +23 -0
  307. package/dist/chunk-5RPTH6AU.js.map +0 -1
  308. package/dist/chunk-AJA46VX5.js.map +0 -1
  309. package/dist/chunk-C4SQJZAF.js.map +0 -1
  310. package/dist/chunk-CHCA44C3.js.map +0 -1
  311. package/dist/chunk-CSKLPDN6.js.map +0 -1
  312. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  313. package/dist/chunk-EAZGEEG2.js.map +0 -1
  314. package/dist/chunk-EVZFIAPG.js.map +0 -1
  315. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  316. package/dist/chunk-GMAG2HS4.js.map +0 -1
  317. package/dist/chunk-HENLZHIT.js.map +0 -1
  318. package/dist/chunk-HINSGUA7.js.map +0 -1
  319. package/dist/chunk-HJNQQICM.js.map +0 -1
  320. package/dist/chunk-HPWVAEET.js.map +0 -1
  321. package/dist/chunk-IOTENEVL.js.map +0 -1
  322. package/dist/chunk-IP73YCZP.js.map +0 -1
  323. package/dist/chunk-JHMFYY7L.js.map +0 -1
  324. package/dist/chunk-JNANKJLN.js.map +0 -1
  325. package/dist/chunk-KGK2QKWL.js.map +0 -1
  326. package/dist/chunk-KM2A35EO.js.map +0 -1
  327. package/dist/chunk-KVEVLBKC.js.map +0 -1
  328. package/dist/chunk-L227SKTB.js.map +0 -1
  329. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  330. package/dist/chunk-NOMEVTUD.js.map +0 -1
  331. package/dist/chunk-NZPF2SYV.js.map +0 -1
  332. package/dist/chunk-PCI747N2.js.map +0 -1
  333. package/dist/chunk-TH67Q46T.js.map +0 -1
  334. package/dist/chunk-UWY7GIVS.js.map +0 -1
  335. package/dist/chunk-VJXSUAO7.js.map +0 -1
  336. package/dist/chunk-XKIQZXUB.js.map +0 -1
  337. package/dist/chunk-XPSVGJYA.js.map +0 -1
  338. package/dist/chunk-XSWKORGM.js.map +0 -1
  339. package/dist/chunk-YCN4BVDK.js.map +0 -1
  340. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  341. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  342. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  343. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  344. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  345. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  346. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  347. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  348. /package/dist/{chunk-2QANQKSQ.js.map → chunk-ADNZVFXG.js.map} +0 -0
  349. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  350. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  351. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  352. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  353. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  354. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  355. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  356. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  357. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  358. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  359. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  360. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  361. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  362. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  363. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  364. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  365. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  366. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  367. /package/dist/{chunk-PPPZY2EU.js.map → chunk-QEMCQFDW.js.map} +0 -0
  368. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  369. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  370. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  371. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  372. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  373. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  374. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -721,3 +721,54 @@ test("MCP profiling report rejects invalid argument types before dispatch", asyn
721
721
  assert.equal((badFormat as Record<string, unknown> & { result?: { isError?: boolean } }).result?.isError, true);
722
722
  assert.equal((badLimit as Record<string, unknown> & { result?: { isError?: boolean } }).result?.isError, true);
723
723
  });
724
+
725
+ // ──────────────────────────────────────────────────────────────────────────
726
+ // Issue #1427: opt-out of legacy engram.* tool aliases on tools/list
727
+ // ──────────────────────────────────────────────────────────────────────────
728
+
729
+ function listToolNames(response: unknown): string[] {
730
+ const tools = (response as { result?: { tools?: Array<{ name: string }> } }).result?.tools ?? [];
731
+ return tools.map((t) => t.name);
732
+ }
733
+
734
+ const TOOLS_LIST_REQUEST = { jsonrpc: "2.0", id: 1, method: "tools/list", params: {} };
735
+
736
+ test("tools/list advertises both remnic.* and engram.* by default (back-compat)", async () => {
737
+ const server = new EngramMcpServer(makeMockService());
738
+ const names = listToolNames(await server.handleRequest(TOOLS_LIST_REQUEST));
739
+ assert.ok(names.includes("remnic.recall"), "canonical name present");
740
+ assert.ok(names.includes("engram.recall"), "legacy alias present by default");
741
+ const legacyCount = names.filter((n) => n.startsWith("engram.")).length;
742
+ assert.ok(legacyCount > 0, "legacy aliases advertised by default");
743
+ });
744
+
745
+ test("tools/list omits engram.* aliases when emitLegacyTools is false", async () => {
746
+ const server = new EngramMcpServer(makeMockService(), { emitLegacyTools: false });
747
+ const names = listToolNames(await server.handleRequest(TOOLS_LIST_REQUEST));
748
+ assert.ok(names.includes("remnic.recall"), "canonical name still present");
749
+ assert.equal(
750
+ names.filter((n) => n.startsWith("engram.")).length,
751
+ 0,
752
+ "no engram.* aliases advertised when opted out",
753
+ );
754
+ // Every advertised tool uses the canonical prefix; the surface is halved.
755
+ assert.ok(names.every((n) => n.startsWith("remnic.")), "all advertised tools are canonical");
756
+ });
757
+
758
+ test("emitLegacyTools=false still allows calling tools under BOTH names (advertising-only opt-out)", async () => {
759
+ const server = new EngramMcpServer(makeMockService(), { emitLegacyTools: false });
760
+ // Canonical call works.
761
+ const canonical = await server.handleRequest(makeToolRequest("remnic.recall", { query: "hello" }));
762
+ assert.notEqual(
763
+ (canonical as { result?: { isError?: boolean } }).result?.isError,
764
+ true,
765
+ "canonical remnic.recall call succeeds",
766
+ );
767
+ // Legacy call still dispatches even though it is no longer advertised.
768
+ const legacy = await server.handleRequest(makeToolRequest("engram.recall", { query: "hello" }));
769
+ assert.notEqual(
770
+ (legacy as { result?: { isError?: boolean } }).result?.isError,
771
+ true,
772
+ "legacy engram.recall call still works (callability preserved)",
773
+ );
774
+ });
package/src/access-mcp.ts CHANGED
@@ -81,11 +81,15 @@ function toLegacyToolName(name: string): string {
81
81
  : name;
82
82
  }
83
83
 
84
- function withToolAliases(tool: McpTool): McpTool[] {
84
+ function withToolAliases(tool: McpTool, emitLegacyTools = true): McpTool[] {
85
85
  const canonicalName = toCanonicalToolName(tool.name);
86
86
  const canonicalTool = canonicalName === tool.name ? tool : { ...tool, name: canonicalName };
87
87
  if (canonicalName === tool.name) return [canonicalTool];
88
- return [canonicalTool, tool];
88
+ // Issue #1427: when legacy aliases are opted out, advertise only the
89
+ // canonical `remnic.*` name and drop the `engram.*` duplicate. Tool *calls*
90
+ // still accept both names (the dispatch canonicalizes), so suppressing the
91
+ // alias only trims `tools/list`, not callability.
92
+ return emitLegacyTools ? [canonicalTool, tool] : [canonicalTool];
89
93
  }
90
94
 
91
95
  function resolveChatGptInspectorRecallSessionKey(
@@ -212,13 +216,25 @@ export class EngramMcpServer {
212
216
  private readonly citationsEnabled: boolean;
213
217
  /** Whether to auto-enable citations for Codex adapter connections. */
214
218
  private readonly citationsAutoDetect: boolean;
219
+ /**
220
+ * Whether to advertise legacy `engram.*` tool aliases alongside the canonical
221
+ * `remnic.*` names (issue #1427). Defaults to true for backward compatibility;
222
+ * set false to halve the advertised `tools/list` surface.
223
+ */
224
+ private readonly emitLegacyTools: boolean;
215
225
 
216
226
  constructor(
217
227
  private readonly service: EngramAccessService,
218
- options: { principal?: string; citationsEnabled?: boolean; citationsAutoDetect?: boolean } = {},
228
+ options: {
229
+ principal?: string;
230
+ citationsEnabled?: boolean;
231
+ citationsAutoDetect?: boolean;
232
+ emitLegacyTools?: boolean;
233
+ } = {},
219
234
  ) {
220
235
  this.citationsEnabled = options.citationsEnabled === true;
221
236
  this.citationsAutoDetect = options.citationsAutoDetect !== false;
237
+ this.emitLegacyTools = options.emitLegacyTools !== false;
222
238
  this.authenticatedPrincipal =
223
239
  options.principal?.trim() ||
224
240
  readEnvVar("OPENCLAW_ENGRAM_ACCESS_PRINCIPAL")?.trim() ||
@@ -1703,7 +1719,7 @@ export class EngramMcpServer {
1703
1719
  additionalProperties: false,
1704
1720
  },
1705
1721
  },
1706
- ].flatMap((tool) => withToolAliases(tool));
1722
+ ].flatMap((tool) => withToolAliases(tool, this.emitLegacyTools));
1707
1723
  }
1708
1724
 
1709
1725
  /** Get clientInfo for a specific MCP session. Returns undefined for non-MCP requests. */
@@ -1985,7 +2001,12 @@ export class EngramMcpServer {
1985
2001
  }
1986
2002
 
1987
2003
  private toolAcceptsArgument(name: string, key: string): boolean {
1988
- const tool = this.tools.find((entry) => entry.name === name);
2004
+ // Match by canonical name so argument validation resolves whether the
2005
+ // caller used the `engram.*` or `remnic.*` name and regardless of whether
2006
+ // legacy aliases are advertised (issue #1427) — a tool stays callable under
2007
+ // both names even when only the canonical alias appears in `tools/list`.
2008
+ const target = toCanonicalToolName(name);
2009
+ const tool = this.tools.find((entry) => toCanonicalToolName(entry.name) === target);
1989
2010
  const inputSchema = getObjectProperties(tool?.inputSchema);
1990
2011
  const properties = getObjectProperties(inputSchema?.properties);
1991
2012
  if (properties && Object.prototype.hasOwnProperty.call(properties, key)) {
@@ -198,6 +198,46 @@ test("active recall cache hits report cache-hit latency instead of reusing gener
198
198
  assert.equal(second.latencyMs, 1);
199
199
  });
200
200
 
201
+ test("active recall cache hits append a fresh transcript entry", async () => {
202
+ const transcriptDir = await mkdtemp(path.join(os.tmpdir(), "active-recall-cache-transcript-"));
203
+ let generateCalls = 0;
204
+ const engine = createActiveRecallEngine(
205
+ {
206
+ async recall() {
207
+ return "CI worker drain after Redis reconnect storm.";
208
+ },
209
+ async generateSummary() {
210
+ generateCalls += 1;
211
+ return { text: "Redis reconnect storm caused the worker drain." };
212
+ },
213
+ now: (() => {
214
+ let tick = 40_000;
215
+ return () => tick++;
216
+ })(),
217
+ },
218
+ baseConfig({
219
+ cacheTtlMs: 5000,
220
+ persistTranscripts: true,
221
+ transcriptDir,
222
+ }),
223
+ );
224
+
225
+ const first = await engine.run(baseInput());
226
+ const second = await engine.run(baseInput());
227
+
228
+ assert.equal(generateCalls, 1);
229
+ assert.equal(first.cacheHit, false);
230
+ assert.equal(second.cacheHit, true);
231
+ assert.ok(first.transcriptPath, "expected first transcript path");
232
+ assert.ok(second.transcriptPath, "expected cache-hit transcript path");
233
+
234
+ const raw = await readFile(second.transcriptPath ?? "", "utf8");
235
+ const entries = raw.trim().split("\n").map((line) => JSON.parse(line) as { cacheHit: boolean });
236
+ assert.equal(entries.length, 2);
237
+ assert.equal(entries[0].cacheHit, false);
238
+ assert.equal(entries[1].cacheHit, true);
239
+ });
240
+
201
241
  test("active recall cache stores an isolated snapshot instead of a mutable caller reference", async () => {
202
242
  let generateCalls = 0;
203
243
  const engine = createActiveRecallEngine(
@@ -361,11 +361,26 @@ export function createActiveRecallEngine(
361
361
  }
362
362
  const cached = cache.get(cacheKey);
363
363
  if (cacheEnabled && cached) {
364
- return {
364
+ const result: ActiveRecallResult = {
365
365
  ...cloneRecallResult(cached.value),
366
366
  latencyMs: Math.max(0, now() - currentTime),
367
367
  cacheHit: true,
368
368
  };
369
+ result.transcriptPath = null;
370
+ if (config.persistTranscripts) {
371
+ try {
372
+ result.transcriptPath = await appendActiveRecallTranscript(
373
+ config.transcriptDir,
374
+ input,
375
+ config,
376
+ result,
377
+ queryBundle,
378
+ );
379
+ } catch {
380
+ result.transcriptPath = null;
381
+ }
382
+ }
383
+ return result;
369
384
  }
370
385
 
371
386
  const start = currentTime;
@@ -451,9 +466,11 @@ export function createActiveRecallEngine(
451
466
 
452
467
  if (cacheEnabled) {
453
468
  const completedAt = now();
469
+ const cachedValue = cloneRecallResult(result);
470
+ cachedValue.transcriptPath = null;
454
471
  cache.set(cacheKey, {
455
472
  expiresAt: completedAt + config.cacheTtlMs,
456
- value: cloneRecallResult(result),
473
+ value: cachedValue,
457
474
  });
458
475
  enforceCacheLimit(cache);
459
476
  }
@@ -68,13 +68,15 @@ function aggregateSignalPressure(signals: BehaviorSignalEvent[]): number {
68
68
  }
69
69
 
70
70
  function selectRecentSignals(input: BehaviorLearnerInput): BehaviorSignalEvent[] {
71
- if (input.learningWindowDays <= 0) return [...input.signals];
72
71
  const nowMs = (input.now ?? new Date()).getTime();
73
- const minTs = nowMs - input.learningWindowDays * 86_400_000;
72
+ const minTs =
73
+ input.learningWindowDays <= 0
74
+ ? Number.NEGATIVE_INFINITY
75
+ : nowMs - input.learningWindowDays * 86_400_000;
74
76
  return input.signals.filter((signal) => {
75
77
  const ts = Date.parse(signal.timestamp);
76
78
  if (!Number.isFinite(ts)) return false;
77
- return ts >= minTs;
79
+ return ts >= minTs && ts <= nowMs;
78
80
  });
79
81
  }
80
82
 
@@ -2,6 +2,7 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { SmartBuffer } from "./buffer.js";
4
4
  import { parseConfig } from "./config.js";
5
+ import type { BufferSurpriseProbe } from "./buffer.js";
5
6
  import type { BufferState, BufferTurn } from "./types.js";
6
7
 
7
8
  class FakeStorage {
@@ -111,6 +112,63 @@ test("SmartBuffer serializes concurrent addTurn mutations", async () => {
111
112
  );
112
113
  });
113
114
 
115
+ test("SmartBuffer ignores stale surprise promotion after newer turns arrive", async () => {
116
+ const storage = new FakeStorage({
117
+ turns: [],
118
+ lastExtractionAt: null,
119
+ extractionCount: 0,
120
+ });
121
+ let resolveFirstProbe = (_score: number): void => {
122
+ throw new Error("first surprise probe did not start");
123
+ };
124
+ let markFirstProbeStarted = (): void => {
125
+ throw new Error("probe start marker was not initialized");
126
+ };
127
+ const firstProbeStarted = new Promise<void>((resolve) => {
128
+ markFirstProbeStarted = resolve;
129
+ });
130
+ const probe: BufferSurpriseProbe = {
131
+ async scoreTurn(_bufferKey, turn) {
132
+ if (turn.content !== "turn A") return null;
133
+ markFirstProbeStarted();
134
+ return new Promise<number>((probeResolve) => {
135
+ resolveFirstProbe = probeResolve;
136
+ });
137
+ },
138
+ };
139
+ const buffer = new SmartBuffer(
140
+ parseConfig({
141
+ bufferSurpriseTriggerEnabled: true,
142
+ bufferSurpriseThreshold: 0.35,
143
+ bufferSurpriseProbeTimeoutMs: 10_000,
144
+ triggerMode: "smart",
145
+ }),
146
+ storage as any,
147
+ probe,
148
+ );
149
+
150
+ const firstOutcomePromise = buffer.addTurnWithOutcome(
151
+ "thread-a",
152
+ makeTurn("thread-a", "turn A"),
153
+ );
154
+ await firstProbeStarted;
155
+
156
+ const secondOutcome = await buffer.addTurnWithOutcome(
157
+ "thread-a",
158
+ makeTurn("thread-a", "turn B"),
159
+ );
160
+ assert.deepEqual(secondOutcome, { decision: "keep_buffering" });
161
+
162
+ resolveFirstProbe(1);
163
+ const firstOutcome = await firstOutcomePromise;
164
+
165
+ assert.deepEqual(firstOutcome, { decision: "keep_buffering" });
166
+ assert.deepEqual(
167
+ buffer.getTurns("thread-a").map((turn) => turn.content),
168
+ ["turn A", "turn B"],
169
+ );
170
+ });
171
+
114
172
  test("SmartBuffer clearAfterExtraction only clears the targeted logical session", async () => {
115
173
  const storage = new FakeStorage({
116
174
  turns: [],
@@ -643,7 +643,7 @@ test("flag on: slow-probe promotion survives prefix clears before the scored tur
643
643
  );
644
644
  });
645
645
 
646
- test("flag on: slow-probe promotion survives later appends to same buffer", async () => {
646
+ test("flag on: slow-probe promotion is ignored after later appends to same buffer", async () => {
647
647
  const storage = new FakeStorage(emptyBuffer());
648
648
  let releaseOldProbe: (() => void) | null = null;
649
649
  let oldProbeStarted: (() => void) | null = null;
@@ -684,8 +684,8 @@ test("flag on: slow-probe promotion survives later appends to same buffer", asyn
684
684
  (releaseOldProbe as () => void)();
685
685
  assert.equal(
686
686
  await oldDecision,
687
- "extract_now",
688
- "a later append must not suppress a valid surprise flush for the older turn",
687
+ "keep_buffering",
688
+ "a later append must suppress stale surprise promotion for the older turn",
689
689
  );
690
690
  const turns = buffer.getTurns("sess-1");
691
691
  assert.equal(turns.length, 2);
@@ -695,18 +695,9 @@ test("flag on: slow-probe promotion survives later appends to same buffer", asyn
695
695
 
696
696
  test("flag on: extraction clear preserves turns appended after the queued snapshot", async () => {
697
697
  const storage = new FakeStorage(emptyBuffer());
698
- let releaseOldProbe: (() => void) | null = null;
699
- let oldProbeStarted: (() => void) | null = null;
700
- const oldProbeStartedPromise = new Promise<void>((resolve) => {
701
- oldProbeStarted = resolve;
702
- });
703
698
  const probe: BufferSurpriseProbe = {
704
699
  async scoreTurn(_key, turn) {
705
- if (turn.content !== "old surprising turn") return null;
706
- oldProbeStarted?.();
707
- return new Promise<number | null>((resolve) => {
708
- releaseOldProbe = () => resolve(0.99);
709
- });
700
+ return turn.content === "old surprising turn" ? 0.99 : null;
710
701
  },
711
702
  };
712
703
  const config = parseConfig({
@@ -722,11 +713,6 @@ test("flag on: extraction clear preserves turns appended after the queued snapsh
722
713
  "sess-1",
723
714
  makeTurn("sess-1", "old surprising turn"),
724
715
  );
725
- await oldProbeStartedPromise;
726
-
727
- await buffer.addTurn("sess-1", makeTurn("sess-1", "newer ordinary turn"));
728
- assert.ok(releaseOldProbe);
729
- (releaseOldProbe as () => void)();
730
716
  assert.equal(await oldDecision, "extract_now");
731
717
 
732
718
  const queuedSnapshot = buffer.getTurns("sess-1");
package/src/buffer.ts CHANGED
@@ -85,7 +85,8 @@ interface AddTurnMutationResult {
85
85
  decision: TriggerDecision;
86
86
  signalLevel: SignalLevel;
87
87
  priorTurns: BufferTurn[];
88
- turnSnapshot: BufferTurn;
88
+ activeTurnsSnapshot: BufferTurn[];
89
+ retainedTurnsSnapshot: BufferTurn[];
89
90
  turnCountInWindow: number;
90
91
  }
91
92
 
@@ -298,9 +299,10 @@ export class SmartBuffer {
298
299
  const shouldPromote = surprise > this.config.bufferSurpriseThreshold;
299
300
  let triggered = false;
300
301
  if (shouldPromote) {
301
- const currentTurns = await this.getExtractionTurnsIfTurnSnapshotStillCurrent(
302
+ const currentTurns = await this.getExtractionTurnsIfBufferSnapshotStillCurrent(
302
303
  bufferKey,
303
- mutation.turnSnapshot,
304
+ mutation.activeTurnsSnapshot,
305
+ mutation.retainedTurnsSnapshot,
304
306
  );
305
307
  if (currentTurns) {
306
308
  log.debug(
@@ -361,7 +363,8 @@ export class SmartBuffer {
361
363
  const entry = this.entryFor(bufferKey);
362
364
  const priorTurns = entry.turns.slice();
363
365
  entry.turns.push(turn);
364
- const turnSnapshot = copyBufferTurn(turn);
366
+ const activeTurnsSnapshot = entry.turns.map(copyBufferTurn);
367
+ const retainedTurnsSnapshot = (entry.retainedTurns ?? []).map(copyBufferTurn);
365
368
  if (bufferKey === "default") {
366
369
  this.state.turns = entry.turns;
367
370
  }
@@ -376,24 +379,26 @@ export class SmartBuffer {
376
379
  decision,
377
380
  signalLevel: signal.level,
378
381
  priorTurns,
379
- turnSnapshot,
382
+ activeTurnsSnapshot,
383
+ retainedTurnsSnapshot,
380
384
  turnCountInWindow,
381
385
  };
382
386
  }
383
387
 
384
- private async getExtractionTurnsIfTurnSnapshotStillCurrent(
388
+ private async getExtractionTurnsIfBufferSnapshotStillCurrent(
385
389
  bufferKey: string,
386
- turnSnapshot: BufferTurn,
390
+ activeTurnsSnapshot: readonly BufferTurn[],
391
+ retainedTurnsSnapshot: readonly BufferTurn[],
387
392
  ): Promise<BufferTurn[] | null> {
388
393
  return this.enqueueMutation(async () => {
389
394
  await this.loadUnlocked();
390
395
  const entry = this.peekEntry(bufferKey);
391
396
  if (!entry) return null;
392
- const stillCurrent = entry.turns.some((turn) =>
393
- bufferTurnsEqual(turn, turnSnapshot),
394
- );
395
- if (!stillCurrent) return null;
396
397
  const retained = entry.retainedTurns ?? [];
398
+ const stillCurrent =
399
+ bufferTurnArrayIsSuffixOfSnapshot(entry.turns, activeTurnsSnapshot) &&
400
+ bufferTurnArraysEqual(retained, retainedTurnsSnapshot);
401
+ if (!stillCurrent) return null;
397
402
  return [...retained, ...entry.turns];
398
403
  });
399
404
  }
@@ -802,6 +807,29 @@ function bufferTurnsEqual(left: BufferTurn | undefined, right: BufferTurn): bool
802
807
  );
803
808
  }
804
809
 
810
+ function bufferTurnArraysEqual(
811
+ left: readonly BufferTurn[],
812
+ right: readonly BufferTurn[],
813
+ ): boolean {
814
+ return (
815
+ left.length === right.length &&
816
+ left.every((turn, index) => bufferTurnsEqual(turn, right[index]))
817
+ );
818
+ }
819
+
820
+ function bufferTurnArrayIsSuffixOfSnapshot(
821
+ liveTurns: readonly BufferTurn[],
822
+ snapshot: readonly BufferTurn[],
823
+ ): boolean {
824
+ if (liveTurns.length === 0 || liveTurns.length > snapshot.length) {
825
+ return false;
826
+ }
827
+ const offset = snapshot.length - liveTurns.length;
828
+ return liveTurns.every((turn, index) =>
829
+ bufferTurnsEqual(turn, snapshot[offset + index]),
830
+ );
831
+ }
832
+
805
833
  function liveTurnsFromExtractionSnapshot(
806
834
  entry: BufferEntryState,
807
835
  extractedTurns: readonly BufferTurn[],
@@ -16,7 +16,7 @@ import { createHash } from "node:crypto";
16
16
  import path from "node:path";
17
17
  import { mkdir, readFile, writeFile } from "node:fs/promises";
18
18
  import { FallbackLlmClient } from "./fallback-llm.js";
19
- import type { GatewayConfig, MemoryFile } from "./types.js";
19
+ import type { AgentPersonaModelConfig, GatewayConfig, MemoryFile } from "./types.js";
20
20
  import { listJsonFiles, readJsonFile } from "./json-store.js";
21
21
  import { isRecord } from "./store-contract.js";
22
22
  import { log } from "./logger.js";
@@ -226,6 +226,7 @@ export async function synthesizeCalibrationRules(
226
226
  llm: FallbackLlmClient,
227
227
  existingRules: CalibrationRule[],
228
228
  agentId?: string,
229
+ modelChain?: AgentPersonaModelConfig,
229
230
  ): Promise<CalibrationRule[]> {
230
231
  if (corrections.length < 2) return [];
231
232
 
@@ -244,7 +245,7 @@ export async function synthesizeCalibrationRules(
244
245
  { role: "system", content: CLUSTER_PROMPT },
245
246
  { role: "user", content: `Here are ${corrections.length} corrections from this user:\n\n${correctionText}${existingRulesText}` },
246
247
  ],
247
- { temperature: 0.3, maxTokens: 3000, agentId },
248
+ { temperature: 0.3, maxTokens: 3000, agentId, modelChain },
248
249
  );
249
250
 
250
251
  if (!response?.content) return [];
@@ -349,13 +350,14 @@ export async function runCalibrationConsolidation(options: {
349
350
  memoryDir: string;
350
351
  gatewayConfig?: GatewayConfig;
351
352
  gatewayAgentId?: string;
353
+ modelChain?: AgentPersonaModelConfig;
352
354
  workspaceDir?: string;
353
355
  }): Promise<CalibrationRule[]> {
354
356
  try {
355
357
  const llm = new FallbackLlmClient(options.gatewayConfig, {
356
358
  workspaceDir: options.workspaceDir,
357
359
  });
358
- if (!llm.isAvailable(options.gatewayAgentId)) {
360
+ if (!llm.isAvailable({ agentId: options.gatewayAgentId, modelChain: options.modelChain })) {
359
361
  log.debug("[calibration] no LLM available — skipping consolidation");
360
362
  return [];
361
363
  }
@@ -368,7 +370,7 @@ export async function runCalibrationConsolidation(options: {
368
370
 
369
371
  const existingIndex = await readCalibrationIndex(options.memoryDir);
370
372
 
371
- const newRules = await synthesizeCalibrationRules(corrections, llm, existingIndex.rules, options.gatewayAgentId);
373
+ const newRules = await synthesizeCalibrationRules(corrections, llm, existingIndex.rules, options.gatewayAgentId, options.modelChain);
372
374
  if (newRules.length === 0) {
373
375
  log.debug("[calibration] no new calibration rules synthesized");
374
376
  return existingIndex.rules;
@@ -414,6 +416,8 @@ export async function runCalibrationIfEnabled(options: {
414
416
  memoryDir: string;
415
417
  calibrationEnabled: boolean;
416
418
  gatewayConfig?: GatewayConfig;
419
+ gatewayAgentId?: string;
420
+ modelChain?: AgentPersonaModelConfig;
417
421
  workspaceDir?: string;
418
422
  }): Promise<CalibrationRule[]> {
419
423
  if (!options.calibrationEnabled) {
@@ -422,6 +426,8 @@ export async function runCalibrationIfEnabled(options: {
422
426
  return runCalibrationConsolidation({
423
427
  memoryDir: options.memoryDir,
424
428
  gatewayConfig: options.gatewayConfig,
429
+ gatewayAgentId: options.gatewayAgentId,
430
+ modelChain: options.modelChain,
425
431
  workspaceDir: options.workspaceDir,
426
432
  });
427
433
  }
@@ -49,14 +49,24 @@ async function withTrajectoryStore<T>(
49
49
 
50
50
  function llmStub() {
51
51
  let calls = 0;
52
+ let availableOptions: unknown;
53
+ let completionOptions: unknown;
52
54
  return {
53
55
  get calls() {
54
56
  return calls;
55
57
  },
56
- isAvailable() {
58
+ get availableOptions() {
59
+ return availableOptions;
60
+ },
61
+ get completionOptions() {
62
+ return completionOptions;
63
+ },
64
+ isAvailable(options?: unknown) {
65
+ availableOptions = options;
57
66
  return true;
58
67
  },
59
- async chatCompletion() {
68
+ async chatCompletion(_messages?: unknown, options?: unknown) {
69
+ completionOptions = options;
60
70
  calls += 1;
61
71
  return {
62
72
  content: JSON.stringify({
@@ -154,6 +164,41 @@ test("deriveCausalPromotionCandidates calls LLM when recurrence session and succ
154
164
  );
155
165
  });
156
166
 
167
+ test("deriveCausalPromotionCandidates forwards task model chain to availability and chat completion", async () => {
168
+ const llm = llmStub();
169
+ const modelChain = {
170
+ primary: "openai/task-primary",
171
+ fallbacks: ["openai/task-fallback"],
172
+ };
173
+ await withTrajectoryStore(
174
+ [
175
+ trajectory("t1", "a", "success"),
176
+ trajectory("t2", "b", "success"),
177
+ trajectory("t3", "c", "partial"),
178
+ ],
179
+ async ({ memoryDir, storeDir }) => {
180
+ const candidates = await deriveCausalPromotionCandidates({
181
+ memoryDir,
182
+ causalTrajectoryStoreDir: storeDir,
183
+ config: { minRecurrence: 3, minSessions: 2, successThreshold: 0.7 },
184
+ gatewayAgentId: "expensive-agent",
185
+ modelChain,
186
+ llmClient: llm,
187
+ });
188
+
189
+ assert.equal(llm.calls, 1);
190
+ assert.equal(candidates.length, 1);
191
+ assert.deepEqual(llm.availableOptions, { agentId: "expensive-agent", modelChain });
192
+ assert.deepEqual(llm.completionOptions, {
193
+ temperature: 0.2,
194
+ maxTokens: 2000,
195
+ agentId: "expensive-agent",
196
+ modelChain,
197
+ });
198
+ },
199
+ );
200
+ });
201
+
157
202
  test("deriveCausalPromotionCandidates validates LLM-derived rule fields", async () => {
158
203
  const llm = llmWithContent(JSON.stringify({
159
204
  rules: [
@@ -18,7 +18,7 @@ import { readChainIndex, resolveChainsDir, type CausalChainIndex, type CausalEdg
18
18
  import { listJsonFiles, readJsonFile } from "./json-store.js";
19
19
  import { isRecord } from "./store-contract.js";
20
20
  import { FallbackLlmClient, fallbackLlmRuntimeContextFromConfig } from "./fallback-llm.js";
21
- import type { GatewayConfig, MemoryFile, PluginConfig } from "./types.js";
21
+ import type { AgentPersonaModelConfig, GatewayConfig, MemoryFile, PluginConfig } from "./types.js";
22
22
  import path from "node:path";
23
23
  import { log } from "./logger.js";
24
24
  import { runPostConsolidationMaterialize } from "./connectors/codex-materialize-runner.js";
@@ -64,10 +64,10 @@ export interface LlmConsolidationResult {
64
64
  const CAUSAL_RULE_CATEGORIES = new Set(["rule", "principle", "preference"]);
65
65
 
66
66
  interface ConsolidationLlmClient {
67
- isAvailable(agentId?: string): boolean;
67
+ isAvailable(options?: { agentId?: string; modelChain?: AgentPersonaModelConfig }): boolean;
68
68
  chatCompletion(
69
69
  messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
70
- options?: { temperature?: number; maxTokens?: number; agentId?: string },
70
+ options?: { temperature?: number; maxTokens?: number; agentId?: string; modelChain?: AgentPersonaModelConfig },
71
71
  ): Promise<{ content: string } | null>;
72
72
  }
73
73
 
@@ -188,14 +188,14 @@ If no clear patterns exist, return {"rules": [], "preferences": []}.`;
188
188
  async function consolidateWithLlm(
189
189
  context: string,
190
190
  llm: ConsolidationLlmClient,
191
- agentId?: string,
191
+ options: { agentId?: string; modelChain?: AgentPersonaModelConfig } = {},
192
192
  ): Promise<LlmConsolidationResult> {
193
193
  const response = await llm.chatCompletion(
194
194
  [
195
195
  { role: "system", content: CONSOLIDATION_PROMPT },
196
196
  { role: "user", content: context },
197
197
  ],
198
- { temperature: 0.2, maxTokens: 2000, agentId },
198
+ { temperature: 0.2, maxTokens: 2000, agentId: options.agentId, modelChain: options.modelChain },
199
199
  );
200
200
 
201
201
  if (!response?.content) {
@@ -369,6 +369,7 @@ export async function deriveCausalPromotionCandidates(options: {
369
369
  config: ConsolidationConfig;
370
370
  gatewayConfig?: GatewayConfig;
371
371
  gatewayAgentId?: string;
372
+ modelChain?: AgentPersonaModelConfig;
372
373
  workspaceDir?: string;
373
374
  pluginConfig?: PluginConfig;
374
375
  llmClient?: ConsolidationLlmClient;
@@ -400,13 +401,14 @@ export async function deriveCausalPromotionCandidates(options: {
400
401
  })
401
402
  : { workspaceDir: options.workspaceDir },
402
403
  );
403
- if (!llm.isAvailable(options.gatewayAgentId)) {
404
+ const llmOptions = { agentId: options.gatewayAgentId, modelChain: options.modelChain };
405
+ if (!llm.isAvailable(llmOptions)) {
404
406
  log.debug("[cmc] no LLM available for consolidation — skipping");
405
407
  return [];
406
408
  }
407
409
 
408
410
  // Call LLM for pattern analysis
409
- const result = await consolidateWithLlm(context, llm, options.gatewayAgentId);
411
+ const result = await consolidateWithLlm(context, llm, llmOptions);
410
412
  const candidates = llmResultToCandidates(result);
411
413
 
412
414
  log.debug(`[cmc] LLM consolidation produced ${candidates.length} rule(s) and ${result.preferences.length} preference(s)`);
@@ -426,6 +428,7 @@ export async function synthesizeCausalPreferencesViaLlm(options: {
426
428
  causalTrajectoryStoreDir?: string;
427
429
  gatewayConfig?: GatewayConfig;
428
430
  gatewayAgentId?: string;
431
+ modelChain?: AgentPersonaModelConfig;
429
432
  workspaceDir?: string;
430
433
  minTrajectories?: number;
431
434
  }): Promise<string | null> {
@@ -440,9 +443,10 @@ export async function synthesizeCausalPreferencesViaLlm(options: {
440
443
  const llm = new FallbackLlmClient(options.gatewayConfig, {
441
444
  workspaceDir: options.workspaceDir,
442
445
  });
443
- if (!llm.isAvailable(options.gatewayAgentId)) return null;
446
+ const llmOptions = { agentId: options.gatewayAgentId, modelChain: options.modelChain };
447
+ if (!llm.isAvailable(llmOptions)) return null;
444
448
 
445
- const result = await consolidateWithLlm(context, llm, options.gatewayAgentId);
449
+ const result = await consolidateWithLlm(context, llm, llmOptions);
446
450
  if (result.preferences.length === 0 && result.rules.length === 0) return null;
447
451
 
448
452
  const lines: string[] = ["## Behavioral Insights (from Causal Chain Analysis)", ""];