@remnic/core 1.1.12 → 1.1.13

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 (1324) hide show
  1. package/dist/access-cli.d.ts +2 -1
  2. package/dist/access-cli.js +263 -82
  3. package/dist/access-cli.js.map +1 -1
  4. package/dist/access-http.d.ts +26 -60
  5. package/dist/access-http.js +43 -29
  6. package/dist/access-mcp.d.ts +24 -6
  7. package/dist/access-mcp.js +35 -28
  8. package/dist/access-schema.d.ts +9 -6
  9. package/dist/access-schema.js +7 -5
  10. package/dist/access-service-DcCDmNYC.d.ts +1542 -0
  11. package/dist/access-service.d.ts +25 -7
  12. package/dist/access-service.js +33 -26
  13. package/dist/active-memory-bridge.js +2 -2
  14. package/dist/active-recall.js +11 -3
  15. package/dist/active-recall.js.map +1 -1
  16. package/dist/adapters/claude-code.d.ts +24 -0
  17. package/dist/adapters/claude-code.js +9 -0
  18. package/dist/adapters/codex.d.ts +25 -0
  19. package/dist/adapters/codex.js +9 -0
  20. package/dist/adapters/hermes.d.ts +35 -0
  21. package/dist/adapters/hermes.js +9 -0
  22. package/dist/adapters/index.d.ts +6 -0
  23. package/dist/adapters/index.js +26 -0
  24. package/dist/adapters/registry.d.ts +20 -0
  25. package/dist/adapters/registry.js +13 -0
  26. package/dist/adapters/replit.d.ts +28 -0
  27. package/dist/adapters/replit.js +9 -0
  28. package/dist/adapters/types.d.ts +43 -0
  29. package/dist/adapters/types.js +8 -0
  30. package/dist/bootstrap.d.ts +20 -5
  31. package/dist/boxes.d.ts +7 -0
  32. package/dist/boxes.js +1 -1
  33. package/dist/briefing.d.ts +5 -3
  34. package/dist/briefing.js +9 -6
  35. package/dist/buffer-surprise-report.js +1 -1
  36. package/dist/buffer.d.ts +18 -4
  37. package/dist/buffer.js +1 -1
  38. package/dist/calibration.js +4 -4
  39. package/dist/capsule-cli.d.ts +4 -4
  40. package/dist/capsule-cli.js +1 -1
  41. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  42. package/dist/capsule-merge-4MGKE7C5.js +189 -0
  43. package/dist/causal-behavior.d.ts +8 -28
  44. package/dist/causal-behavior.js +6 -3
  45. package/dist/causal-behavior.js.map +1 -1
  46. package/dist/causal-chain.js +3 -2
  47. package/dist/causal-consolidation.d.ts +1 -1
  48. package/dist/causal-consolidation.js +24 -13
  49. package/dist/causal-consolidation.js.map +1 -1
  50. package/dist/causal-retrieval.js +3 -3
  51. package/dist/causal-trajectory.js +1 -1
  52. package/dist/chunk-25MQ7IHJ.js +427 -0
  53. package/dist/chunk-25MQ7IHJ.js.map +1 -0
  54. package/dist/chunk-2F2W355T.js +256 -0
  55. package/dist/chunk-2F2W355T.js.map +1 -0
  56. package/dist/chunk-2KI4QFHU.js +228 -0
  57. package/dist/chunk-2KI4QFHU.js.map +1 -0
  58. package/dist/chunk-2PRQG7PV.js +86 -0
  59. package/dist/chunk-2PRQG7PV.js.map +1 -0
  60. package/dist/chunk-2QR3XXIC.js +2272 -0
  61. package/dist/chunk-2QR3XXIC.js.map +1 -0
  62. package/dist/chunk-2WWLHTZY.js +121 -0
  63. package/dist/chunk-326G7DJK.js +2185 -0
  64. package/dist/chunk-326G7DJK.js.map +1 -0
  65. package/dist/chunk-34DQE4KF.js +174 -0
  66. package/dist/chunk-34DQE4KF.js.map +1 -0
  67. package/dist/chunk-3APJ5EVB.js +601 -0
  68. package/dist/chunk-3APJ5EVB.js.map +1 -0
  69. package/dist/chunk-3HPAPHUK.js +51 -0
  70. package/dist/chunk-3HPAPHUK.js.map +1 -0
  71. package/dist/chunk-3JXBXXM2.js +69 -0
  72. package/dist/chunk-3JXBXXM2.js.map +1 -0
  73. package/dist/chunk-3KW65B36.js +681 -0
  74. package/dist/chunk-3KW65B36.js.map +1 -0
  75. package/dist/chunk-3UXOZBHV.js +20 -0
  76. package/dist/chunk-3UXOZBHV.js.map +1 -0
  77. package/dist/chunk-3VAL7ZL2.js +266 -0
  78. package/dist/chunk-3VAL7ZL2.js.map +1 -0
  79. package/dist/chunk-3Y4P7RXM.js +31 -0
  80. package/dist/chunk-3Y4P7RXM.js.map +1 -0
  81. package/dist/chunk-47VWKCAF.js +273 -0
  82. package/dist/chunk-47VWKCAF.js.map +1 -0
  83. package/dist/chunk-4CRG46BG.js +271 -0
  84. package/dist/chunk-5375UYTQ.js +914 -0
  85. package/dist/chunk-5375UYTQ.js.map +1 -0
  86. package/dist/chunk-56K5QLHX.js +506 -0
  87. package/dist/chunk-56K5QLHX.js.map +1 -0
  88. package/dist/chunk-5RGLBDQF.js +596 -0
  89. package/dist/chunk-5RGLBDQF.js.map +1 -0
  90. package/dist/chunk-5UZXUTVO.js +9 -0
  91. package/dist/chunk-5UZXUTVO.js.map +1 -0
  92. package/dist/chunk-65PG43EQ.js +105 -0
  93. package/dist/chunk-65PG43EQ.js.map +1 -0
  94. package/dist/chunk-66DHUKLO.js +57 -0
  95. package/dist/chunk-66DHUKLO.js.map +1 -0
  96. package/dist/chunk-6FC5EGNV.js +46 -0
  97. package/dist/chunk-6FC5EGNV.js.map +1 -0
  98. package/dist/chunk-6H2TESSP.js +62 -0
  99. package/dist/chunk-6H2TESSP.js.map +1 -0
  100. package/dist/chunk-6LVVDPJ4.js +32 -0
  101. package/dist/chunk-6LVVDPJ4.js.map +1 -0
  102. package/dist/chunk-6RVI47ZR.js +159 -0
  103. package/dist/chunk-6RVI47ZR.js.map +1 -0
  104. package/dist/chunk-7AAT6G4Q.js +5117 -0
  105. package/dist/chunk-7AAT6G4Q.js.map +1 -0
  106. package/dist/chunk-7DTASS5T.js +29 -0
  107. package/dist/chunk-7DTASS5T.js.map +1 -0
  108. package/dist/chunk-7IASACLB.js +596 -0
  109. package/dist/chunk-7MNMYOFP.js +32 -0
  110. package/dist/chunk-7MNMYOFP.js.map +1 -0
  111. package/dist/chunk-7N4KAIGN.js +133 -0
  112. package/dist/chunk-7N4KAIGN.js.map +1 -0
  113. package/dist/chunk-7OZ53EXP.js +101 -0
  114. package/dist/chunk-7OZ53EXP.js.map +1 -0
  115. package/dist/chunk-7XYTQGCC.js +134 -0
  116. package/dist/chunk-7XYTQGCC.js.map +1 -0
  117. package/dist/chunk-A2XUIMJ3.js +341 -0
  118. package/dist/chunk-A2XUIMJ3.js.map +1 -0
  119. package/dist/chunk-AGZQD76C.js +201 -0
  120. package/dist/chunk-AGZQD76C.js.map +1 -0
  121. package/dist/chunk-APO3DCMU.js +361 -0
  122. package/dist/chunk-APO3DCMU.js.map +1 -0
  123. package/dist/chunk-BFBF3XEF.js +283 -0
  124. package/dist/chunk-BFBF3XEF.js.map +1 -0
  125. package/dist/chunk-BJ3KMYTB.js +1974 -0
  126. package/dist/chunk-BJ3KMYTB.js.map +1 -0
  127. package/dist/chunk-CHEL3SKB.js +6758 -0
  128. package/dist/chunk-CHEL3SKB.js.map +1 -0
  129. package/dist/chunk-CQZRLNMV.js +1491 -0
  130. package/dist/chunk-CQZRLNMV.js.map +1 -0
  131. package/dist/chunk-D46YSIYX.js +892 -0
  132. package/dist/chunk-D46YSIYX.js.map +1 -0
  133. package/dist/chunk-DINWEURR.js +648 -0
  134. package/dist/chunk-DINWEURR.js.map +1 -0
  135. package/dist/chunk-DK5LDEQM.js +530 -0
  136. package/dist/chunk-DK5LDEQM.js.map +1 -0
  137. package/dist/chunk-DOM4GKSW.js +34 -0
  138. package/dist/chunk-DOM4GKSW.js.map +1 -0
  139. package/dist/chunk-EDTHC6UD.js +1075 -0
  140. package/dist/chunk-EFJ3MQ4V.js +721 -0
  141. package/dist/chunk-EHRTFRWW.js +89 -0
  142. package/dist/chunk-EHRTFRWW.js.map +1 -0
  143. package/dist/chunk-FAJ7FZYM.js +11 -0
  144. package/dist/chunk-FAJ7FZYM.js.map +1 -0
  145. package/dist/chunk-FBYESMQ2.js +570 -0
  146. package/dist/chunk-FDU6HUUL.js +147 -0
  147. package/dist/chunk-FF4KLI5W.js +99 -0
  148. package/dist/chunk-FF4KLI5W.js.map +1 -0
  149. package/dist/chunk-FIT6DMX6.js +310 -0
  150. package/dist/chunk-FIT6DMX6.js.map +1 -0
  151. package/dist/chunk-FJ43PRLT.js +272 -0
  152. package/dist/chunk-FJ43PRLT.js.map +1 -0
  153. package/dist/chunk-FKFMOY3N.js +32 -0
  154. package/dist/chunk-FKFMOY3N.js.map +1 -0
  155. package/dist/chunk-FLTNHQK6.js +262 -0
  156. package/dist/chunk-FLTNHQK6.js.map +1 -0
  157. package/dist/chunk-GA454ALV.js +12436 -0
  158. package/dist/chunk-GA454ALV.js.map +1 -0
  159. package/dist/chunk-GGKRUQOO.js +228 -0
  160. package/dist/chunk-GIF42EW3.js +63 -0
  161. package/dist/chunk-GIF42EW3.js.map +1 -0
  162. package/dist/chunk-GL6I6MEQ.js +647 -0
  163. package/dist/chunk-H3ME6L6D.js +709 -0
  164. package/dist/chunk-H3ME6L6D.js.map +1 -0
  165. package/dist/chunk-HHLLAQGZ.js +1 -0
  166. package/dist/chunk-HXXBL2KD.js +2040 -0
  167. package/dist/chunk-I5V2VDIW.js +219 -0
  168. package/dist/chunk-I5V2VDIW.js.map +1 -0
  169. package/dist/chunk-I6K5FBRQ.js +35 -0
  170. package/dist/chunk-I6K5FBRQ.js.map +1 -0
  171. package/dist/chunk-ICRIXAP2.js +121 -0
  172. package/dist/chunk-ICRIXAP2.js.map +1 -0
  173. package/dist/chunk-J4EB7DNW.js +11 -0
  174. package/dist/chunk-J4EB7DNW.js.map +1 -0
  175. package/dist/chunk-JLFA7DQG.js +62 -0
  176. package/dist/chunk-JLFA7DQG.js.map +1 -0
  177. package/dist/chunk-KJTKLXTH.js +9 -0
  178. package/dist/chunk-KJTKLXTH.js.map +1 -0
  179. package/dist/chunk-KLAO5DGL.js +917 -0
  180. package/dist/chunk-KLAO5DGL.js.map +1 -0
  181. package/dist/chunk-KNKUID7G.js +183 -0
  182. package/dist/chunk-KOSORCJG.js +624 -0
  183. package/dist/chunk-KOSORCJG.js.map +1 -0
  184. package/dist/chunk-KUJVMMZQ.js +1262 -0
  185. package/dist/chunk-KUJVMMZQ.js.map +1 -0
  186. package/dist/chunk-LCR46JY5.js +123 -0
  187. package/dist/chunk-LCR46JY5.js.map +1 -0
  188. package/dist/chunk-LLQ2LLWF.js +148 -0
  189. package/dist/chunk-LLQ2LLWF.js.map +1 -0
  190. package/dist/chunk-LPMVBPA3.js +236 -0
  191. package/dist/chunk-LT3NLYSI.js +50 -0
  192. package/dist/chunk-LT3NLYSI.js.map +1 -0
  193. package/dist/chunk-LUDTDZLK.js +287 -0
  194. package/dist/chunk-LUDTDZLK.js.map +1 -0
  195. package/dist/chunk-M23FSH32.js +3963 -0
  196. package/dist/chunk-M23FSH32.js.map +1 -0
  197. package/dist/chunk-MC26UJIM.js +118 -0
  198. package/dist/chunk-ME6ESPZU.js +119 -0
  199. package/dist/chunk-ME6ESPZU.js.map +1 -0
  200. package/dist/chunk-MGKYQQYF.js +272 -0
  201. package/dist/chunk-MJFNCJXV.js +66 -0
  202. package/dist/chunk-MJFNCJXV.js.map +1 -0
  203. package/dist/chunk-MSWG7JI6.js +237 -0
  204. package/dist/chunk-MSWG7JI6.js.map +1 -0
  205. package/dist/chunk-MT25YHYH.js +141 -0
  206. package/dist/chunk-MT25YHYH.js.map +1 -0
  207. package/dist/chunk-MT4HVDUZ.js +53 -0
  208. package/dist/chunk-MY6TPVXW.js +219 -0
  209. package/dist/chunk-N2D6GXBM.js +267 -0
  210. package/dist/chunk-N2D6GXBM.js.map +1 -0
  211. package/dist/chunk-NJ3MJQZX.js +46 -0
  212. package/dist/chunk-NJ3MJQZX.js.map +1 -0
  213. package/dist/chunk-NMZY542O.js +335 -0
  214. package/dist/chunk-NMZY542O.js.map +1 -0
  215. package/dist/chunk-NNVTUXEB.js +23 -0
  216. package/dist/chunk-NZL6GGQE.js +375 -0
  217. package/dist/chunk-NZL6GGQE.js.map +1 -0
  218. package/dist/chunk-P4NEIHUT.js +108 -0
  219. package/dist/chunk-P7FMDTKL.js +103 -0
  220. package/dist/chunk-P7FMDTKL.js.map +1 -0
  221. package/dist/chunk-PHK3HARR.js +32 -0
  222. package/dist/chunk-PHK3HARR.js.map +1 -0
  223. package/dist/chunk-PIRJPV5T.js +98 -0
  224. package/dist/chunk-PIRJPV5T.js.map +1 -0
  225. package/dist/chunk-PK7H5L6Y.js +159 -0
  226. package/dist/chunk-PK7H5L6Y.js.map +1 -0
  227. package/dist/chunk-PR5FBTFU.js +233 -0
  228. package/dist/chunk-PR5FBTFU.js.map +1 -0
  229. package/dist/chunk-PU63GXWS.js +174 -0
  230. package/dist/chunk-PU63GXWS.js.map +1 -0
  231. package/dist/chunk-PZIAX57I.js +124 -0
  232. package/dist/chunk-PZIAX57I.js.map +1 -0
  233. package/dist/chunk-Q7P4WJDP.js +26 -0
  234. package/dist/chunk-Q7P4WJDP.js.map +1 -0
  235. package/dist/chunk-QQUAB63I.js +63 -0
  236. package/dist/chunk-QQUAB63I.js.map +1 -0
  237. package/dist/chunk-QRNI5JBH.js +18 -0
  238. package/dist/chunk-RHY3HH7P.js +601 -0
  239. package/dist/chunk-RHY3HH7P.js.map +1 -0
  240. package/dist/chunk-RRF5UOBJ.js +91 -0
  241. package/dist/chunk-RXDLTSWT.js +124 -0
  242. package/dist/chunk-RXDLTSWT.js.map +1 -0
  243. package/dist/chunk-RYED3SPJ.js +42 -0
  244. package/dist/chunk-RYED3SPJ.js.map +1 -0
  245. package/dist/chunk-S7KDBTWT.js +106 -0
  246. package/dist/chunk-S7KDBTWT.js.map +1 -0
  247. package/dist/chunk-SEDEKFYQ.js +1 -0
  248. package/dist/chunk-TECVW3JP.js +36 -0
  249. package/dist/chunk-TECVW3JP.js.map +1 -0
  250. package/dist/chunk-TFO23QT4.js +88 -0
  251. package/dist/chunk-TFO23QT4.js.map +1 -0
  252. package/dist/chunk-TK4UEOSK.js +76 -0
  253. package/dist/chunk-TK4UEOSK.js.map +1 -0
  254. package/dist/chunk-TKWGAOLV.js +122 -0
  255. package/dist/chunk-TKWGAOLV.js.map +1 -0
  256. package/dist/chunk-TMM4S4IJ.js +597 -0
  257. package/dist/chunk-TMM4S4IJ.js.map +1 -0
  258. package/dist/chunk-TMQLARTH.js +188 -0
  259. package/dist/chunk-TMQLARTH.js.map +1 -0
  260. package/dist/chunk-TPDBFYEG.js +130 -0
  261. package/dist/chunk-TPDBFYEG.js.map +1 -0
  262. package/dist/chunk-TPMQ3G6Z.js +145 -0
  263. package/dist/chunk-TPMQ3G6Z.js.map +1 -0
  264. package/dist/chunk-TZOLIGIG.js +61 -0
  265. package/dist/chunk-TZOLIGIG.js.map +1 -0
  266. package/dist/chunk-U3PN77QT.js +113 -0
  267. package/dist/chunk-U3WSW6PZ.js +277 -0
  268. package/dist/chunk-U4SCL7B7.js +640 -0
  269. package/dist/chunk-U4SCL7B7.js.map +1 -0
  270. package/dist/chunk-UWK5OXUJ.js +156 -0
  271. package/dist/chunk-UWK5OXUJ.js.map +1 -0
  272. package/dist/chunk-UWVJF25J.js +74 -0
  273. package/dist/chunk-UXHQAFNA.js +1317 -0
  274. package/dist/chunk-UXHQAFNA.js.map +1 -0
  275. package/dist/chunk-V5OCT34X.js +1 -0
  276. package/dist/chunk-VLXA6PI2.js +304 -0
  277. package/dist/chunk-VLXA6PI2.js.map +1 -0
  278. package/dist/chunk-VNO6ZJ35.js +500 -0
  279. package/dist/chunk-VNO6ZJ35.js.map +1 -0
  280. package/dist/chunk-VW676BEI.js +827 -0
  281. package/dist/chunk-VW676BEI.js.map +1 -0
  282. package/dist/chunk-W3LR522O.js +2296 -0
  283. package/dist/chunk-W4L6CZKA.js +96 -0
  284. package/dist/chunk-W4L6CZKA.js.map +1 -0
  285. package/dist/chunk-W4RVMTHR.js +372 -0
  286. package/dist/chunk-W4RVMTHR.js.map +1 -0
  287. package/dist/chunk-WEHSQBFR.js +188 -0
  288. package/dist/chunk-WEHSQBFR.js.map +1 -0
  289. package/dist/chunk-WELDCG6C.js +380 -0
  290. package/dist/chunk-WELDCG6C.js.map +1 -0
  291. package/dist/chunk-WZYKANL3.js +2800 -0
  292. package/dist/chunk-WZYKANL3.js.map +1 -0
  293. package/dist/chunk-XIG5PDM7.js +48 -0
  294. package/dist/chunk-XJNBEDFE.js +193 -0
  295. package/dist/chunk-XJNBEDFE.js.map +1 -0
  296. package/dist/chunk-XVVIG67A.js +291 -0
  297. package/dist/chunk-XVVIG67A.js.map +1 -0
  298. package/dist/chunk-XVZ7B3HG.js +135 -0
  299. package/dist/chunk-YBPYIAA5.js +73 -0
  300. package/dist/chunk-YBPYIAA5.js.map +1 -0
  301. package/dist/chunk-Z734BLO3.js +21 -0
  302. package/dist/chunk-Z734BLO3.js.map +1 -0
  303. package/dist/chunk-ZKSK55RC.js +269 -0
  304. package/dist/chunk-ZKSK55RC.js.map +1 -0
  305. package/dist/chunk-ZTFCYYEZ.js +69 -0
  306. package/dist/chunk-ZTFCYYEZ.js.map +1 -0
  307. package/dist/chunk-ZY2MNJR6.js +329 -0
  308. package/dist/chunk-ZY2MNJR6.js.map +1 -0
  309. package/dist/cli-D3VpkVwB.d.ts +1136 -0
  310. package/dist/cli.d.ts +39 -10
  311. package/dist/cli.js +108 -49
  312. package/dist/commitment-ledger.js +1 -1
  313. package/dist/compat/checks.d.ts +5 -0
  314. package/dist/compat/checks.js +11 -0
  315. package/dist/compat/checks.js.map +1 -0
  316. package/dist/compat/types.d.ts +30 -0
  317. package/dist/compat/types.js +1 -0
  318. package/dist/compat/types.js.map +1 -0
  319. package/dist/compounding/engine.d.ts +221 -0
  320. package/dist/compounding/engine.js +32 -0
  321. package/dist/compounding/engine.js.map +1 -0
  322. package/dist/compounding/preference-consolidator.d.ts +92 -0
  323. package/dist/compounding/preference-consolidator.js +553 -0
  324. package/dist/compounding/preference-consolidator.js.map +1 -0
  325. package/dist/config.d.ts +4 -2
  326. package/dist/config.js +9 -4
  327. package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
  328. package/dist/connectors/codex-materialize-runner.d.ts +64 -0
  329. package/dist/connectors/codex-materialize-runner.js +33 -0
  330. package/dist/connectors/codex-materialize-runner.js.map +1 -0
  331. package/dist/connectors/codex-materialize.d.ts +195 -0
  332. package/dist/connectors/codex-materialize.js +38 -0
  333. package/dist/connectors/codex-materialize.js.map +1 -0
  334. package/dist/connectors/index.d.ts +444 -0
  335. package/dist/connectors/index.js +115 -0
  336. package/dist/connectors/index.js.map +1 -0
  337. package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
  338. package/dist/connectors-cli.d.ts +1 -1
  339. package/dist/consolidation-provenance-check.d.ts +3 -1
  340. package/dist/consolidation-undo.d.ts +3 -1
  341. package/dist/contradiction/index.d.ts +258 -0
  342. package/dist/contradiction/index.js +43 -0
  343. package/dist/contradiction/index.js.map +1 -0
  344. package/dist/contradiction-review-ATP4S6IC.js +30 -0
  345. package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
  346. package/dist/contradiction-scan-5A4IDZV5.js +13 -0
  347. package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
  348. package/dist/conversation-index/backend.d.ts +97 -0
  349. package/dist/conversation-index/backend.js +13 -0
  350. package/dist/conversation-index/backend.js.map +1 -0
  351. package/dist/conversation-index/chunker.d.ts +16 -0
  352. package/dist/conversation-index/chunker.js +8 -0
  353. package/dist/conversation-index/chunker.js.map +1 -0
  354. package/dist/conversation-index/cleanup.d.ts +11 -0
  355. package/dist/conversation-index/cleanup.js +9 -0
  356. package/dist/conversation-index/cleanup.js.map +1 -0
  357. package/dist/conversation-index/faiss-adapter.d.ts +6 -0
  358. package/dist/conversation-index/faiss-adapter.js +16 -0
  359. package/dist/conversation-index/faiss-adapter.js.map +1 -0
  360. package/dist/conversation-index/indexer.d.ts +23 -0
  361. package/dist/conversation-index/indexer.js +15 -0
  362. package/dist/conversation-index/indexer.js.map +1 -0
  363. package/dist/conversation-index/search.d.ts +6 -0
  364. package/dist/conversation-index/search.js +11 -0
  365. package/dist/conversation-index/search.js.map +1 -0
  366. package/dist/embedding-fallback.js +2 -2
  367. package/dist/enrichment/index.d.ts +163 -0
  368. package/dist/enrichment/index.js +18 -0
  369. package/dist/enrichment/index.js.map +1 -0
  370. package/dist/entity-retrieval.d.ts +4 -2
  371. package/dist/entity-retrieval.js +8 -5
  372. package/dist/evals.js +1 -1
  373. package/dist/explicit-capture.d.ts +20 -5
  374. package/dist/explicit-capture.js +2 -2
  375. package/dist/extraction-judge-training.js +1 -1
  376. package/dist/extraction.js +8 -8
  377. package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
  378. package/dist/fallback-llm.d.ts +2 -0
  379. package/dist/fallback-llm.js +4 -4
  380. package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
  381. package/dist/index.d.ts +66 -711
  382. package/dist/index.js +556 -2680
  383. package/dist/index.js.map +1 -1
  384. package/dist/lcm/archive.d.ts +89 -0
  385. package/dist/lcm/archive.js +12 -0
  386. package/dist/lcm/archive.js.map +1 -0
  387. package/dist/lcm/dag.d.ts +48 -0
  388. package/dist/lcm/dag.js +8 -0
  389. package/dist/lcm/dag.js.map +1 -0
  390. package/dist/lcm/engine.d.ts +116 -0
  391. package/dist/lcm/engine.js +20 -0
  392. package/dist/lcm/engine.js.map +1 -0
  393. package/dist/lcm/index.d.ts +12 -0
  394. package/dist/lcm/index.js +44 -0
  395. package/dist/lcm/index.js.map +1 -0
  396. package/dist/lcm/queue.d.ts +62 -0
  397. package/dist/lcm/queue.js +8 -0
  398. package/dist/lcm/queue.js.map +1 -0
  399. package/dist/lcm/recall.d.ts +20 -0
  400. package/dist/lcm/recall.js +8 -0
  401. package/dist/lcm/recall.js.map +1 -0
  402. package/dist/lcm/schema.d.ts +16 -0
  403. package/dist/lcm/schema.js +14 -0
  404. package/dist/lcm/schema.js.map +1 -0
  405. package/dist/lcm/summarizer.d.ts +38 -0
  406. package/dist/lcm/summarizer.js +12 -0
  407. package/dist/lcm/summarizer.js.map +1 -0
  408. package/dist/lcm/tools.d.ts +29 -0
  409. package/dist/lcm/tools.js +8 -0
  410. package/dist/lcm/tools.js.map +1 -0
  411. package/dist/live-connectors-runner.js +5 -5
  412. package/dist/local-llm.js +3 -3
  413. package/dist/maintenance/archive-observations.d.ts +18 -0
  414. package/dist/maintenance/archive-observations.js +8 -0
  415. package/dist/maintenance/archive-observations.js.map +1 -0
  416. package/dist/maintenance/backup-stamp.d.ts +3 -0
  417. package/dist/maintenance/backup-stamp.js +8 -0
  418. package/dist/maintenance/backup-stamp.js.map +1 -0
  419. package/dist/maintenance/memory-governance-cron.d.ts +85 -0
  420. package/dist/maintenance/memory-governance-cron.js +22 -0
  421. package/dist/maintenance/memory-governance-cron.js.map +1 -0
  422. package/dist/maintenance/memory-governance.d.ts +137 -0
  423. package/dist/maintenance/memory-governance.js +40 -0
  424. package/dist/maintenance/memory-governance.js.map +1 -0
  425. package/dist/maintenance/migrate-observations.d.ts +18 -0
  426. package/dist/maintenance/migrate-observations.js +9 -0
  427. package/dist/maintenance/migrate-observations.js.map +1 -0
  428. package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
  429. package/dist/maintenance/observation-ledger-utils.js +10 -0
  430. package/dist/maintenance/observation-ledger-utils.js.map +1 -0
  431. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
  432. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
  433. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
  434. package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
  435. package/dist/maintenance/rebuild-memory-projection.js +35 -0
  436. package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
  437. package/dist/maintenance/rebuild-observations.d.ts +17 -0
  438. package/dist/maintenance/rebuild-observations.js +9 -0
  439. package/dist/maintenance/rebuild-observations.js.map +1 -0
  440. package/dist/mcp-memory-inspector-app.d.ts +24 -6
  441. package/dist/memory-projection-store.d.ts +108 -3
  442. package/dist/memory-projection-store.js +2 -1
  443. package/dist/memory-worth-outcomes.d.ts +4 -2
  444. package/dist/migrate/from-engram.d.ts +24 -0
  445. package/dist/migrate/from-engram.js +12 -0
  446. package/dist/migrate/from-engram.js.map +1 -0
  447. package/dist/namespaces/migrate.d.ts +50 -0
  448. package/dist/namespaces/migrate.js +50 -0
  449. package/dist/namespaces/migrate.js.map +1 -0
  450. package/dist/namespaces/principal.d.ts +17 -0
  451. package/dist/namespaces/principal.js +16 -0
  452. package/dist/namespaces/principal.js.map +1 -0
  453. package/dist/namespaces/search.d.ts +46 -0
  454. package/dist/namespaces/search.js +28 -0
  455. package/dist/namespaces/search.js.map +1 -0
  456. package/dist/namespaces/storage.d.ts +32 -0
  457. package/dist/namespaces/storage.js +28 -0
  458. package/dist/namespaces/storage.js.map +1 -0
  459. package/dist/network/tailscale.d.ts +41 -0
  460. package/dist/network/tailscale.js +9 -0
  461. package/dist/network/tailscale.js.map +1 -0
  462. package/dist/network/webdav.d.ts +39 -0
  463. package/dist/network/webdav.js +10 -0
  464. package/dist/network/webdav.js.map +1 -0
  465. package/dist/objective-state-writers.js +2 -2
  466. package/dist/operator-toolkit.d.ts +4 -2
  467. package/dist/operator-toolkit.js +32 -14
  468. package/dist/opik-exporter.js +2 -2
  469. package/dist/opik-exporter.js.map +1 -1
  470. package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
  471. package/dist/orchestrator.d.ts +22 -7
  472. package/dist/orchestrator.js +79 -44
  473. package/dist/path-MR5JPYOP.js +9 -0
  474. package/dist/path-MR5JPYOP.js.map +1 -0
  475. package/dist/qmd-recall-cache.d.ts +1 -1
  476. package/dist/qmd.d.ts +102 -3
  477. package/dist/qmd.js +23 -5
  478. package/dist/recall-explain-renderer.js +3 -3
  479. package/dist/recall-xray-cli.js +4 -4
  480. package/dist/recall-xray-renderer.js +3 -3
  481. package/dist/recall-xray.js +2 -2
  482. package/dist/replay/normalizers/chatgpt.d.ts +6 -0
  483. package/dist/replay/normalizers/chatgpt.js +11 -0
  484. package/dist/replay/normalizers/chatgpt.js.map +1 -0
  485. package/dist/replay/normalizers/claude.d.ts +6 -0
  486. package/dist/replay/normalizers/claude.js +11 -0
  487. package/dist/replay/normalizers/claude.js.map +1 -0
  488. package/dist/replay/normalizers/openclaw.d.ts +6 -0
  489. package/dist/replay/normalizers/openclaw.js +11 -0
  490. package/dist/replay/normalizers/openclaw.js.map +1 -0
  491. package/dist/replay/normalizers/shared.d.ts +16 -0
  492. package/dist/replay/normalizers/shared.js +14 -0
  493. package/dist/replay/normalizers/shared.js.map +1 -0
  494. package/dist/replay/runner.d.ts +35 -0
  495. package/dist/replay/runner.js +16 -0
  496. package/dist/replay/runner.js.map +1 -0
  497. package/dist/replay/types.d.ts +57 -0
  498. package/dist/replay/types.js +19 -0
  499. package/dist/replay/types.js.map +1 -0
  500. package/dist/resolution-B7FNQSSP.js +12 -0
  501. package/dist/resolution-B7FNQSSP.js.map +1 -0
  502. package/dist/resolve-provider-secret.js +2 -2
  503. package/dist/resume-bundles.js +8 -6
  504. package/dist/retrieval-agents.d.ts +1 -1
  505. package/dist/routing/engine.d.ts +35 -0
  506. package/dist/routing/engine.js +16 -0
  507. package/dist/routing/engine.js.map +1 -0
  508. package/dist/routing/store.d.ts +27 -0
  509. package/dist/routing/store.js +10 -0
  510. package/dist/routing/store.js.map +1 -0
  511. package/dist/runtime/better-sqlite.d.ts +8 -0
  512. package/dist/runtime/better-sqlite.js +10 -0
  513. package/dist/runtime/better-sqlite.js.map +1 -0
  514. package/dist/runtime/child-process.d.ts +32 -0
  515. package/dist/runtime/child-process.js +10 -0
  516. package/dist/runtime/child-process.js.map +1 -0
  517. package/dist/runtime/env.d.ts +5 -0
  518. package/dist/runtime/env.js +12 -0
  519. package/dist/runtime/env.js.map +1 -0
  520. package/dist/schemas.d.ts +22 -22
  521. package/dist/sdk-compat.js +1 -1
  522. package/dist/search/document-scanner.d.ts +22 -0
  523. package/dist/search/document-scanner.js +8 -0
  524. package/dist/search/document-scanner.js.map +1 -0
  525. package/dist/search/embed-helper.d.ts +35 -0
  526. package/dist/search/embed-helper.js +9 -0
  527. package/dist/search/embed-helper.js.map +1 -0
  528. package/dist/search/factory.d.ts +32 -0
  529. package/dist/search/factory.js +29 -0
  530. package/dist/search/factory.js.map +1 -0
  531. package/dist/search/index.d.ts +15 -0
  532. package/dist/search/index.js +50 -0
  533. package/dist/search/index.js.map +1 -0
  534. package/dist/search/lancedb-backend.d.ts +51 -0
  535. package/dist/search/lancedb-backend.js +10 -0
  536. package/dist/search/lancedb-backend.js.map +1 -0
  537. package/dist/search/meilisearch-backend.d.ts +48 -0
  538. package/dist/search/meilisearch-backend.js +10 -0
  539. package/dist/search/meilisearch-backend.js.map +1 -0
  540. package/dist/search/noop-backend.d.ts +26 -0
  541. package/dist/search/noop-backend.js +8 -0
  542. package/dist/search/noop-backend.js.map +1 -0
  543. package/dist/search/orama-backend.d.ts +53 -0
  544. package/dist/search/orama-backend.js +10 -0
  545. package/dist/search/orama-backend.js.map +1 -0
  546. package/dist/search/port.d.ts +61 -0
  547. package/dist/search/port.js +1 -0
  548. package/dist/search/port.js.map +1 -0
  549. package/dist/search/remote-backend.d.ts +39 -0
  550. package/dist/search/remote-backend.js +9 -0
  551. package/dist/search/remote-backend.js.map +1 -0
  552. package/dist/secure-store/index.d.ts +890 -0
  553. package/dist/secure-store/index.js +156 -0
  554. package/dist/secure-store/index.js.map +1 -0
  555. package/dist/semantic-VwGI14Ok.d.ts +69 -0
  556. package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
  557. package/dist/semantic-consolidation.d.ts +2 -2
  558. package/dist/semantic-consolidation.js +13 -6
  559. package/dist/semantic-rule-promotion.js +8 -5
  560. package/dist/semantic-rule-verifier.js +8 -5
  561. package/dist/shared-context/manager.d.ts +131 -0
  562. package/dist/shared-context/manager.js +15 -0
  563. package/dist/shared-context/manager.js.map +1 -0
  564. package/dist/skills-registry.js +13 -1
  565. package/dist/skills-registry.js.map +1 -1
  566. package/dist/state-store-VZU2IA53.js +16 -0
  567. package/dist/state-store-VZU2IA53.js.map +1 -0
  568. package/dist/storage-paths.d.ts +9 -0
  569. package/dist/storage-paths.js +20 -0
  570. package/dist/storage-paths.js.map +1 -0
  571. package/dist/storage.d.ts +3 -1
  572. package/dist/storage.js +7 -4
  573. package/dist/summarizer.d.ts +5 -0
  574. package/dist/summarizer.js +9 -8
  575. package/dist/summary-snapshot.js +2 -1
  576. package/dist/surfaces/dreams.d.ts +16 -0
  577. package/dist/surfaces/dreams.js +282 -0
  578. package/dist/surfaces/dreams.js.map +1 -0
  579. package/dist/surfaces/heartbeat.d.ts +17 -0
  580. package/dist/surfaces/heartbeat.js +265 -0
  581. package/dist/surfaces/heartbeat.js.map +1 -0
  582. package/dist/temporal-supersession.d.ts +3 -1
  583. package/dist/threading.d.ts +5 -0
  584. package/dist/threading.js +2 -1
  585. package/dist/tier-migration.d.ts +4 -2
  586. package/dist/tokens.js +2 -2
  587. package/dist/transcript.d.ts +15 -1
  588. package/dist/transcript.js +2 -1
  589. package/dist/transfer/autodetect.d.ts +4 -0
  590. package/dist/transfer/autodetect.js +15 -0
  591. package/dist/transfer/autodetect.js.map +1 -0
  592. package/dist/transfer/backup.d.ts +21 -0
  593. package/dist/transfer/backup.js +17 -0
  594. package/dist/transfer/backup.js.map +1 -0
  595. package/dist/transfer/capsule-export.d.ts +113 -0
  596. package/dist/transfer/capsule-export.js +19 -0
  597. package/dist/transfer/capsule-export.js.map +1 -0
  598. package/dist/transfer/capsule-import.d.ts +124 -0
  599. package/dist/transfer/capsule-import.js +16 -0
  600. package/dist/transfer/capsule-import.js.map +1 -0
  601. package/dist/transfer/constants.d.ts +13 -0
  602. package/dist/transfer/constants.js +12 -0
  603. package/dist/transfer/constants.js.map +1 -0
  604. package/dist/transfer/export-json.d.ts +11 -0
  605. package/dist/transfer/export-json.js +11 -0
  606. package/dist/transfer/export-json.js.map +1 -0
  607. package/dist/transfer/export-md.d.ts +10 -0
  608. package/dist/transfer/export-md.js +13 -0
  609. package/dist/transfer/export-md.js.map +1 -0
  610. package/dist/transfer/export-sqlite.d.ts +9 -0
  611. package/dist/transfer/export-sqlite.js +12 -0
  612. package/dist/transfer/export-sqlite.js.map +1 -0
  613. package/dist/transfer/fs-utils.d.ts +61 -0
  614. package/dist/transfer/fs-utils.js +40 -0
  615. package/dist/transfer/fs-utils.js.map +1 -0
  616. package/dist/transfer/import-json.d.ts +16 -0
  617. package/dist/transfer/import-json.js +13 -0
  618. package/dist/transfer/import-json.js.map +1 -0
  619. package/dist/transfer/import-md.d.ts +14 -0
  620. package/dist/transfer/import-md.js +11 -0
  621. package/dist/transfer/import-md.js.map +1 -0
  622. package/dist/transfer/import-sqlite.d.ts +14 -0
  623. package/dist/transfer/import-sqlite.js +12 -0
  624. package/dist/transfer/import-sqlite.js.map +1 -0
  625. package/dist/transfer/sqlite-schema.d.ts +4 -0
  626. package/dist/transfer/sqlite-schema.js +10 -0
  627. package/dist/transfer/sqlite-schema.js.map +1 -0
  628. package/dist/transfer/types.d.ts +916 -0
  629. package/dist/transfer/types.js +30 -0
  630. package/dist/transfer/types.js.map +1 -0
  631. package/dist/types.d.ts +28 -1
  632. package/dist/types.js +1 -1
  633. package/dist/verified-recall.js +9 -6
  634. package/dist/work/board.d.ts +43 -0
  635. package/dist/work/board.js +14 -0
  636. package/dist/work/board.js.map +1 -0
  637. package/dist/work/boundary.d.ts +8 -0
  638. package/dist/work/boundary.js +14 -0
  639. package/dist/work/boundary.js.map +1 -0
  640. package/dist/work/storage.d.ts +39 -0
  641. package/dist/work/storage.js +11 -0
  642. package/dist/work/storage.js.map +1 -0
  643. package/dist/work/types.d.ts +75 -0
  644. package/dist/work/types.js +1 -0
  645. package/dist/work/types.js.map +1 -0
  646. package/package.json +2767 -6
  647. package/scripts/faiss_index.py +816 -0
  648. package/scripts/faiss_requirements.txt +3 -0
  649. package/skills/remnic-entities/SKILL.md +51 -0
  650. package/skills/remnic-memory-workflow/SKILL.md +61 -0
  651. package/skills/remnic-recall/SKILL.md +51 -0
  652. package/skills/remnic-remember/SKILL.md +56 -0
  653. package/skills/remnic-search/SKILL.md +51 -0
  654. package/skills/remnic-status/SKILL.md +51 -0
  655. package/src/abort-error.test.ts +49 -0
  656. package/src/abort-error.ts +46 -0
  657. package/src/abstraction-nodes.ts +162 -0
  658. package/src/access-audit.test.ts +178 -0
  659. package/src/access-audit.ts +125 -0
  660. package/src/access-cli.test.ts +439 -0
  661. package/src/access-cli.ts +438 -0
  662. package/src/access-http.test.ts +225 -0
  663. package/src/access-http.ts +1899 -0
  664. package/src/access-idempotency.ts +232 -0
  665. package/src/access-mcp.test.ts +568 -0
  666. package/src/access-mcp.ts +3056 -0
  667. package/src/access-schema-pi.test.ts +60 -0
  668. package/src/access-schema.ts +522 -0
  669. package/src/access-service-namespace.test.ts +123 -0
  670. package/src/access-service.ts +5629 -0
  671. package/src/action-confidence.test.ts +206 -0
  672. package/src/action-confidence.ts +466 -0
  673. package/src/active-memory-bridge.test.ts +285 -0
  674. package/src/active-memory-bridge.ts +217 -0
  675. package/src/active-recall.test.ts +484 -0
  676. package/src/active-recall.ts +459 -0
  677. package/src/adapters/claude-code.ts +56 -0
  678. package/src/adapters/codex.ts +57 -0
  679. package/src/adapters/hermes.ts +64 -0
  680. package/src/adapters/index.ts +6 -0
  681. package/src/adapters/registry.ts +41 -0
  682. package/src/adapters/replit.ts +55 -0
  683. package/src/adapters/types.ts +51 -0
  684. package/src/behavior-learner.ts +144 -0
  685. package/src/behavior-signals.ts +73 -0
  686. package/src/binary-lifecycle/backend.ts +117 -0
  687. package/src/binary-lifecycle/index.ts +35 -0
  688. package/src/binary-lifecycle/manifest.ts +79 -0
  689. package/src/binary-lifecycle/pipeline.ts +352 -0
  690. package/src/binary-lifecycle/scanner.ts +89 -0
  691. package/src/binary-lifecycle/types.ts +89 -0
  692. package/src/bootstrap.ts +178 -0
  693. package/src/boxes.ts +521 -0
  694. package/src/briefing.test.ts +1535 -0
  695. package/src/briefing.ts +1382 -0
  696. package/src/buffer-session.test.ts +443 -0
  697. package/src/buffer-surprise-report.ts +176 -0
  698. package/src/buffer-surprise-telemetry.test.ts +606 -0
  699. package/src/buffer-surprise-trigger.test.ts +766 -0
  700. package/src/buffer-surprise.test.ts +339 -0
  701. package/src/buffer-surprise.ts +203 -0
  702. package/src/buffer.ts +900 -0
  703. package/src/bulk-import/cli-command.test.ts +204 -0
  704. package/src/bulk-import/index.ts +34 -0
  705. package/src/bulk-import/pipeline.test.ts +445 -0
  706. package/src/bulk-import/pipeline.ts +178 -0
  707. package/src/bulk-import/registry.test.ts +151 -0
  708. package/src/bulk-import/registry.ts +72 -0
  709. package/src/bulk-import/types.test.ts +272 -0
  710. package/src/bulk-import/types.ts +145 -0
  711. package/src/calibration.ts +394 -0
  712. package/src/capsule-cli.test.ts +398 -0
  713. package/src/capsule-cli.ts +565 -0
  714. package/src/causal-behavior.ts +308 -0
  715. package/src/causal-chain.ts +419 -0
  716. package/src/causal-consolidation.ts +370 -0
  717. package/src/causal-retrieval.ts +286 -0
  718. package/src/causal-trajectory-graph.ts +60 -0
  719. package/src/causal-trajectory.ts +303 -0
  720. package/src/chunking.ts +220 -0
  721. package/src/citations.ts +232 -0
  722. package/src/cli.ts +9403 -0
  723. package/src/codex-cli-fallback.ts +162 -0
  724. package/src/codex-thread-key.ts +1 -0
  725. package/src/coding/access-coding-context.test.ts +197 -0
  726. package/src/coding/coding-branch-scope.test.ts +281 -0
  727. package/src/coding/coding-namespace.test.ts +360 -0
  728. package/src/coding/coding-namespace.ts +412 -0
  729. package/src/coding/coding-orchestrator.test.ts +249 -0
  730. package/src/coding/git-context.test.ts +507 -0
  731. package/src/coding/git-context.ts +336 -0
  732. package/src/coding/mcp-set-coding-context.test.ts +174 -0
  733. package/src/coding/review-context.test.ts +316 -0
  734. package/src/coding/review-context.ts +349 -0
  735. package/src/coding/wire-coding-context.test.ts +468 -0
  736. package/src/commitment-ledger.test.ts +78 -0
  737. package/src/commitment-ledger.ts +337 -0
  738. package/src/compat/checks.test.ts +206 -0
  739. package/src/compat/checks.ts +716 -0
  740. package/src/compat/types.ts +33 -0
  741. package/src/compounding/engine.ts +1686 -0
  742. package/src/compounding/preference-consolidator.ts +778 -0
  743. package/src/compression-optimizer.ts +312 -0
  744. package/src/config.test.ts +930 -0
  745. package/src/config.ts +3807 -0
  746. package/src/connectors/codex/instructions.md +160 -0
  747. package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  748. package/src/connectors/codex-marketplace.ts +500 -0
  749. package/src/connectors/codex-materialize-runner.ts +212 -0
  750. package/src/connectors/codex-materialize.ts +983 -0
  751. package/src/connectors/coerce.ts +62 -0
  752. package/src/connectors/index.test.ts +1570 -0
  753. package/src/connectors/index.ts +3222 -0
  754. package/src/connectors/live/framework.ts +164 -0
  755. package/src/connectors/live/github.test.ts +1218 -0
  756. package/src/connectors/live/github.ts +1068 -0
  757. package/src/connectors/live/gmail.test.ts +1706 -0
  758. package/src/connectors/live/gmail.ts +1293 -0
  759. package/src/connectors/live/google-drive.test.ts +696 -0
  760. package/src/connectors/live/google-drive.ts +724 -0
  761. package/src/connectors/live/index.ts +101 -0
  762. package/src/connectors/live/live-connectors.test.ts +689 -0
  763. package/src/connectors/live/notion.test.ts +1109 -0
  764. package/src/connectors/live/notion.ts +978 -0
  765. package/src/connectors/live/registry.ts +103 -0
  766. package/src/connectors/live/state-store.ts +399 -0
  767. package/src/connectors/live/transient-errors.ts +150 -0
  768. package/src/connectors/weclone-installer.test.ts +850 -0
  769. package/src/connectors-cli.ts +513 -0
  770. package/src/console/state.test.ts +224 -0
  771. package/src/console/state.ts +514 -0
  772. package/src/console/trace.test.ts +813 -0
  773. package/src/console/trace.ts +603 -0
  774. package/src/console/tui.test.ts +582 -0
  775. package/src/console/tui.ts +508 -0
  776. package/src/consolidation-operator.ts +182 -0
  777. package/src/consolidation-provenance-check.ts +551 -0
  778. package/src/consolidation-undo.ts +718 -0
  779. package/src/contradiction/contradiction-judge.test.ts +189 -0
  780. package/src/contradiction/contradiction-judge.ts +333 -0
  781. package/src/contradiction/contradiction-review.ts +574 -0
  782. package/src/contradiction/contradiction-scan.ts +504 -0
  783. package/src/contradiction/contradiction.test.ts +2230 -0
  784. package/src/contradiction/index.ts +37 -0
  785. package/src/contradiction/resolution.ts +383 -0
  786. package/src/conversation-index/backend.ts +323 -0
  787. package/src/conversation-index/chunker.ts +47 -0
  788. package/src/conversation-index/cleanup.ts +53 -0
  789. package/src/conversation-index/faiss-adapter.ts +384 -0
  790. package/src/conversation-index/indexer.test.ts +164 -0
  791. package/src/conversation-index/indexer.ts +192 -0
  792. package/src/conversation-index/search.ts +37 -0
  793. package/src/cross-namespace-budget.test.ts +275 -0
  794. package/src/cross-namespace-budget.ts +365 -0
  795. package/src/cue-anchors.ts +163 -0
  796. package/src/curation/index.ts +544 -0
  797. package/src/dashboard-runtime.ts +337 -0
  798. package/src/day-summary.ts +122 -0
  799. package/src/dedup/index.ts +330 -0
  800. package/src/dedup/semantic.test.ts +1577 -0
  801. package/src/dedup/semantic.ts +148 -0
  802. package/src/delinearize.ts +193 -0
  803. package/src/direct-answer-wiring.test.ts +473 -0
  804. package/src/direct-answer-wiring.ts +180 -0
  805. package/src/direct-answer.test.ts +484 -0
  806. package/src/direct-answer.ts +273 -0
  807. package/src/embedding-fallback.ts +565 -0
  808. package/src/enrichment/audit.ts +89 -0
  809. package/src/enrichment/index.ts +27 -0
  810. package/src/enrichment/pipeline.ts +197 -0
  811. package/src/enrichment/provider-registry.ts +85 -0
  812. package/src/enrichment/types.ts +100 -0
  813. package/src/enrichment/web-search-provider.ts +63 -0
  814. package/src/entity-retrieval.ts +774 -0
  815. package/src/entity-schema.ts +239 -0
  816. package/src/evals.ts +1312 -0
  817. package/src/event-order-recall.test.ts +4164 -0
  818. package/src/event-order-recall.ts +2802 -0
  819. package/src/evidence-pack.test.ts +89 -0
  820. package/src/evidence-pack.ts +388 -0
  821. package/src/explicit-capture.ts +530 -0
  822. package/src/explicit-cue-recall.test.ts +3019 -0
  823. package/src/explicit-cue-recall.ts +5545 -0
  824. package/src/extraction-judge-telemetry.ts +234 -0
  825. package/src/extraction-judge-training.ts +221 -0
  826. package/src/extraction-judge.ts +846 -0
  827. package/src/extraction-timeout.test.ts +265 -0
  828. package/src/extraction.ts +2719 -0
  829. package/src/fallback-llm.test.ts +1060 -0
  830. package/src/fallback-llm.ts +918 -0
  831. package/src/focused-list-recall.test.ts +734 -0
  832. package/src/focused-list-recall.ts +1160 -0
  833. package/src/graph-dashboard-diff.ts +35 -0
  834. package/src/graph-dashboard-key.ts +5 -0
  835. package/src/graph-dashboard-parser.ts +104 -0
  836. package/src/graph-edge-reinforcement.ts +192 -0
  837. package/src/graph-events.ts +151 -0
  838. package/src/graph-recall.test.ts +164 -0
  839. package/src/graph-recall.ts +189 -0
  840. package/src/graph-retrieval.test.ts +809 -0
  841. package/src/graph-retrieval.ts +823 -0
  842. package/src/graph-snapshot.ts +329 -0
  843. package/src/graph.ts +813 -0
  844. package/src/harmonic-retrieval.ts +223 -0
  845. package/src/himem.ts +154 -0
  846. package/src/hygiene.ts +87 -0
  847. package/src/identity-continuity.ts +333 -0
  848. package/src/importance.ts +328 -0
  849. package/src/importers/base.test.ts +294 -0
  850. package/src/importers/base.ts +436 -0
  851. package/src/importers/index.ts +21 -0
  852. package/src/index.ts +1204 -0
  853. package/src/intent.ts +154 -0
  854. package/src/json-extract.ts +85 -0
  855. package/src/json-store.ts +42 -0
  856. package/src/lcm/archive.ts +617 -0
  857. package/src/lcm/dag.ts +199 -0
  858. package/src/lcm/engine.ts +645 -0
  859. package/src/lcm/index.ts +7 -0
  860. package/src/lcm/queue.test.ts +178 -0
  861. package/src/lcm/queue.ts +200 -0
  862. package/src/lcm/recall.ts +117 -0
  863. package/src/lcm/schema.ts +154 -0
  864. package/src/lcm/summarizer.ts +235 -0
  865. package/src/lcm/tools.ts +191 -0
  866. package/src/lcm-engine.test.ts +660 -0
  867. package/src/legacy-hook-compat.test.ts +20 -0
  868. package/src/legacy-hook-compat.ts +45 -0
  869. package/src/lifecycle.ts +289 -0
  870. package/src/live-connectors-runner.ts +385 -0
  871. package/src/local-llm-qos.test.ts +303 -0
  872. package/src/local-llm-thinking.test.ts +292 -0
  873. package/src/local-llm.ts +1464 -0
  874. package/src/logger.ts +49 -0
  875. package/src/maintenance/archive-observations.ts +147 -0
  876. package/src/maintenance/backup-stamp.ts +3 -0
  877. package/src/maintenance/dreams-ledger.ts +516 -0
  878. package/src/maintenance/first-start-migration.ts +362 -0
  879. package/src/maintenance/forget.test.ts +206 -0
  880. package/src/maintenance/forget.ts +126 -0
  881. package/src/maintenance/graph-edge-decay.test.ts +409 -0
  882. package/src/maintenance/graph-edge-decay.ts +394 -0
  883. package/src/maintenance/memory-governance-cron.ts +447 -0
  884. package/src/maintenance/memory-governance.ts +1039 -0
  885. package/src/maintenance/migrate-observations.ts +216 -0
  886. package/src/maintenance/observation-ledger-utils.ts +54 -0
  887. package/src/maintenance/pattern-reinforcement.test.ts +875 -0
  888. package/src/maintenance/pattern-reinforcement.ts +369 -0
  889. package/src/maintenance/purge.ts +334 -0
  890. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
  891. package/src/maintenance/rebuild-memory-projection.ts +1234 -0
  892. package/src/maintenance/rebuild-observations.ts +178 -0
  893. package/src/maintenance/tier-stats.test.ts +378 -0
  894. package/src/maintenance/tier-stats.ts +222 -0
  895. package/src/mcp-memory-inspector-app.ts +421 -0
  896. package/src/memory-action-policy.ts +80 -0
  897. package/src/memory-cache.ts +208 -0
  898. package/src/memory-extension/claude-code-publisher.ts +51 -0
  899. package/src/memory-extension/codex-publisher.ts +149 -0
  900. package/src/memory-extension/hermes-publisher.ts +51 -0
  901. package/src/memory-extension/index.ts +100 -0
  902. package/src/memory-extension/shared-instructions.ts +133 -0
  903. package/src/memory-extension/types.ts +86 -0
  904. package/src/memory-extension-host/host-discovery.ts +276 -0
  905. package/src/memory-extension-host/index.ts +14 -0
  906. package/src/memory-extension-host/render-extensions-block.ts +73 -0
  907. package/src/memory-extension-host/types.ts +21 -0
  908. package/src/memory-lifecycle-ledger-utils.ts +116 -0
  909. package/src/memory-projection-format.ts +11 -0
  910. package/src/memory-projection-store.ts +951 -0
  911. package/src/memory-provenance.test.ts +196 -0
  912. package/src/memory-provenance.ts +484 -0
  913. package/src/memory-worth-bench.test.ts +71 -0
  914. package/src/memory-worth-bench.ts +265 -0
  915. package/src/memory-worth-filter.test.ts +209 -0
  916. package/src/memory-worth-filter.ts +204 -0
  917. package/src/memory-worth-frontmatter.test.ts +311 -0
  918. package/src/memory-worth-outcomes.test.ts +316 -0
  919. package/src/memory-worth-outcomes.ts +286 -0
  920. package/src/memory-worth.test.ts +317 -0
  921. package/src/memory-worth.ts +215 -0
  922. package/src/message-parts/index.ts +806 -0
  923. package/src/message-parts/message-parts.test.ts +421 -0
  924. package/src/migrate/from-engram.ts +789 -0
  925. package/src/model-registry.ts +313 -0
  926. package/src/models-json.ts +76 -0
  927. package/src/namespaces/migrate.ts +187 -0
  928. package/src/namespaces/path.ts +25 -0
  929. package/src/namespaces/principal.test.ts +195 -0
  930. package/src/namespaces/principal.ts +86 -0
  931. package/src/namespaces/search.test.ts +105 -0
  932. package/src/namespaces/search.ts +233 -0
  933. package/src/namespaces/storage.ts +74 -0
  934. package/src/native-knowledge.ts +1823 -0
  935. package/src/negative.ts +72 -0
  936. package/src/network/tailscale.ts +179 -0
  937. package/src/network/webdav.ts +385 -0
  938. package/src/objective-state-writers.ts +951 -0
  939. package/src/objective-state.ts +320 -0
  940. package/src/onboarding/index.ts +529 -0
  941. package/src/openai-chat-compat.ts +56 -0
  942. package/src/operator-toolkit.ts +2132 -0
  943. package/src/opik-exporter.test.ts +72 -0
  944. package/src/opik-exporter.ts +587 -0
  945. package/src/orchestrator-extraction-queue.test.ts +197 -0
  946. package/src/orchestrator-flush.test.ts +1171 -0
  947. package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
  948. package/src/orchestrator-source-attribution.test.ts +701 -0
  949. package/src/orchestrator.ts +16368 -0
  950. package/src/page-versioning.ts +450 -0
  951. package/src/patterns-cli.ts +574 -0
  952. package/src/peers/index.ts +54 -0
  953. package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
  954. package/src/peers/migrate-from-identity-anchor.ts +350 -0
  955. package/src/peers/peers.test.ts +419 -0
  956. package/src/peers/profile-reasoner.ts +694 -0
  957. package/src/peers/storage.ts +1350 -0
  958. package/src/peers/types.ts +138 -0
  959. package/src/plugin-id.ts +84 -0
  960. package/src/policy-runtime.ts +209 -0
  961. package/src/procedural/procedure-miner.ts +150 -0
  962. package/src/procedural/procedure-recall.ts +93 -0
  963. package/src/procedural/procedure-stats.ts +213 -0
  964. package/src/procedural/procedure-types.ts +132 -0
  965. package/src/procedural/reinforcement-core.test.ts +132 -0
  966. package/src/procedural/reinforcement-core.ts +73 -0
  967. package/src/profiling.test.ts +263 -0
  968. package/src/profiling.ts +435 -0
  969. package/src/projection/index.ts +398 -0
  970. package/src/qmd-recall-cache.test.ts +138 -0
  971. package/src/qmd-recall-cache.ts +111 -0
  972. package/src/qmd.test.ts +257 -0
  973. package/src/qmd.ts +2614 -0
  974. package/src/reasoning-trace-recall.ts +201 -0
  975. package/src/reasoning-trace-types.ts +235 -0
  976. package/src/recall-audit-anomaly.test.ts +246 -0
  977. package/src/recall-audit-anomaly.ts +297 -0
  978. package/src/recall-audit.test.ts +51 -0
  979. package/src/recall-audit.ts +72 -0
  980. package/src/recall-budget-config.test.ts +87 -0
  981. package/src/recall-disclosure-escalation.test.ts +196 -0
  982. package/src/recall-disclosure-escalation.ts +158 -0
  983. package/src/recall-disclosure-shaping.test.ts +146 -0
  984. package/src/recall-disclosure.test.ts +214 -0
  985. package/src/recall-explain-renderer.test.ts +140 -0
  986. package/src/recall-explain-renderer.ts +356 -0
  987. package/src/recall-mmr.test.ts +808 -0
  988. package/src/recall-mmr.ts +607 -0
  989. package/src/recall-qos.test.ts +85 -0
  990. package/src/recall-qos.ts +82 -0
  991. package/src/recall-query-policy.ts +221 -0
  992. package/src/recall-state.test.ts +233 -0
  993. package/src/recall-state.ts +456 -0
  994. package/src/recall-tag-filter.ts +143 -0
  995. package/src/recall-tokenization.ts +35 -0
  996. package/src/recall-xray-cli.test.ts +118 -0
  997. package/src/recall-xray-cli.ts +100 -0
  998. package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
  999. package/src/recall-xray-renderer.test.ts +539 -0
  1000. package/src/recall-xray-renderer.ts +487 -0
  1001. package/src/recall-xray.test.ts +503 -0
  1002. package/src/recall-xray.ts +621 -0
  1003. package/src/reconstruct.ts +41 -0
  1004. package/src/release-changelog.ts +35 -0
  1005. package/src/relevance.ts +67 -0
  1006. package/src/replay/normalizers/chatgpt.ts +133 -0
  1007. package/src/replay/normalizers/claude.ts +102 -0
  1008. package/src/replay/normalizers/openclaw.ts +119 -0
  1009. package/src/replay/normalizers/shared.ts +69 -0
  1010. package/src/replay/runner.ts +197 -0
  1011. package/src/replay/types.ts +143 -0
  1012. package/src/rerank.test.ts +48 -0
  1013. package/src/rerank.ts +176 -0
  1014. package/src/resolve-auth-token.test.ts +226 -0
  1015. package/src/resolve-auth-token.ts +151 -0
  1016. package/src/resolve-provider-secret.test.ts +187 -0
  1017. package/src/resolve-provider-secret.ts +410 -0
  1018. package/src/response-guidance-recall.test.ts +3952 -0
  1019. package/src/response-guidance-recall.ts +4431 -0
  1020. package/src/resume-bundles.ts +415 -0
  1021. package/src/retrieval-agents.ts +623 -0
  1022. package/src/retrieval-tiers.ts +25 -0
  1023. package/src/retrieval.ts +104 -0
  1024. package/src/review/index.test.ts +201 -0
  1025. package/src/review/index.ts +536 -0
  1026. package/src/routing/engine.ts +162 -0
  1027. package/src/routing/store.ts +321 -0
  1028. package/src/runtime/better-sqlite.test.ts +32 -0
  1029. package/src/runtime/better-sqlite.ts +76 -0
  1030. package/src/runtime/child-process.ts +67 -0
  1031. package/src/runtime/env.ts +48 -0
  1032. package/src/sanitize.ts +58 -0
  1033. package/src/schemas.ts +449 -0
  1034. package/src/sdk-compat.ts +87 -0
  1035. package/src/search/document-scanner.ts +96 -0
  1036. package/src/search/embed-helper.ts +142 -0
  1037. package/src/search/factory.ts +189 -0
  1038. package/src/search/index.ts +10 -0
  1039. package/src/search/lancedb-backend.ts +342 -0
  1040. package/src/search/meilisearch-backend.ts +232 -0
  1041. package/src/search/noop-backend.ts +57 -0
  1042. package/src/search/orama-backend.ts +358 -0
  1043. package/src/search/port.ts +86 -0
  1044. package/src/search/remote-backend.ts +124 -0
  1045. package/src/secure-store/cipher.ts +271 -0
  1046. package/src/secure-store/cli-handlers.ts +355 -0
  1047. package/src/secure-store/cli-renderer.ts +131 -0
  1048. package/src/secure-store/header.ts +373 -0
  1049. package/src/secure-store/index.ts +137 -0
  1050. package/src/secure-store/kdf.ts +263 -0
  1051. package/src/secure-store/keyring.ts +106 -0
  1052. package/src/secure-store/metadata.ts +394 -0
  1053. package/src/secure-store/passphrase-reader.ts +252 -0
  1054. package/src/secure-store/secure-fs.ts +571 -0
  1055. package/src/secure-store/secure-store.test.ts +755 -0
  1056. package/src/semantic-chunking.ts +545 -0
  1057. package/src/semantic-consolidation.test.ts +182 -0
  1058. package/src/semantic-consolidation.ts +432 -0
  1059. package/src/semantic-rule-promotion.ts +183 -0
  1060. package/src/semantic-rule-verifier.ts +160 -0
  1061. package/src/session-integrity.ts +569 -0
  1062. package/src/session-observer-bands.ts +11 -0
  1063. package/src/session-observer-state.ts +346 -0
  1064. package/src/session-toggles.test.ts +96 -0
  1065. package/src/session-toggles.ts +159 -0
  1066. package/src/shared-context/manager.ts +810 -0
  1067. package/src/signal.ts +84 -0
  1068. package/src/skills-registry.test.ts +277 -0
  1069. package/src/skills-registry.ts +120 -0
  1070. package/src/source-attribution-roundtrip.test.ts +215 -0
  1071. package/src/source-attribution.test.ts +1425 -0
  1072. package/src/source-attribution.ts +639 -0
  1073. package/src/spaces/index.ts +627 -0
  1074. package/src/storage-paths.ts +117 -0
  1075. package/src/storage.ts +6657 -0
  1076. package/src/store-contract.ts +55 -0
  1077. package/src/summarizer.ts +844 -0
  1078. package/src/summary-snapshot.test.ts +681 -0
  1079. package/src/summary-snapshot.ts +238 -0
  1080. package/src/surfaces/dreams.test.ts +394 -0
  1081. package/src/surfaces/dreams.ts +346 -0
  1082. package/src/surfaces/heartbeat.test.ts +415 -0
  1083. package/src/surfaces/heartbeat.ts +325 -0
  1084. package/src/sync/index.ts +308 -0
  1085. package/src/targeted-fact-recall.test.ts +1694 -0
  1086. package/src/targeted-fact-recall.ts +2905 -0
  1087. package/src/taxonomy/default-taxonomy.ts +87 -0
  1088. package/src/taxonomy/index.ts +26 -0
  1089. package/src/taxonomy/resolver-doc-generator.ts +57 -0
  1090. package/src/taxonomy/resolver.ts +184 -0
  1091. package/src/taxonomy/taxonomy-loader.ts +186 -0
  1092. package/src/taxonomy/types.ts +48 -0
  1093. package/src/telemetry-transcript.ts +70 -0
  1094. package/src/temporal-index.ts +890 -0
  1095. package/src/temporal-supersession.test.ts +2703 -0
  1096. package/src/temporal-supersession.ts +493 -0
  1097. package/src/temporal-validity.test.ts +448 -0
  1098. package/src/temporal-validity.ts +123 -0
  1099. package/src/threading.ts +395 -0
  1100. package/src/tier-migration.ts +124 -0
  1101. package/src/tier-routing.ts +102 -0
  1102. package/src/tmt.ts +462 -0
  1103. package/src/tokens.test.ts +178 -0
  1104. package/src/tokens.ts +279 -0
  1105. package/src/topics.ts +147 -0
  1106. package/src/training-export/cli-date-validation.test.ts +258 -0
  1107. package/src/training-export/converter.test.ts +452 -0
  1108. package/src/training-export/converter.ts +319 -0
  1109. package/src/training-export/date-parse.ts +117 -0
  1110. package/src/training-export/index.ts +26 -0
  1111. package/src/training-export/registry.test.ts +85 -0
  1112. package/src/training-export/registry.ts +57 -0
  1113. package/src/training-export/types.ts +31 -0
  1114. package/src/transcript.ts +1179 -0
  1115. package/src/transfer/autodetect.ts +30 -0
  1116. package/src/transfer/backup.ts +138 -0
  1117. package/src/transfer/capsule-crypto.ts +485 -0
  1118. package/src/transfer/capsule-encrypt.test.ts +690 -0
  1119. package/src/transfer/capsule-export.ts +543 -0
  1120. package/src/transfer/capsule-fork.ts +375 -0
  1121. package/src/transfer/capsule-import.ts +564 -0
  1122. package/src/transfer/capsule-merge.ts +433 -0
  1123. package/src/transfer/conflict-policy.ts +16 -0
  1124. package/src/transfer/constants.ts +13 -0
  1125. package/src/transfer/exclusions.ts +37 -0
  1126. package/src/transfer/export-json.ts +65 -0
  1127. package/src/transfer/export-md.ts +59 -0
  1128. package/src/transfer/export-sqlite.ts +52 -0
  1129. package/src/transfer/fs-utils.ts +269 -0
  1130. package/src/transfer/import-json.ts +108 -0
  1131. package/src/transfer/import-md.ts +84 -0
  1132. package/src/transfer/import-sqlite.ts +100 -0
  1133. package/src/transfer/integrity.ts +71 -0
  1134. package/src/transfer/sqlite-schema.ts +16 -0
  1135. package/src/transfer/types.ts +297 -0
  1136. package/src/trust-zones.ts +1186 -0
  1137. package/src/types.ts +3074 -0
  1138. package/src/user-model.test.ts +124 -0
  1139. package/src/user-model.ts +162 -0
  1140. package/src/utility-learner.ts +353 -0
  1141. package/src/utility-runtime.ts +88 -0
  1142. package/src/utility-telemetry.ts +215 -0
  1143. package/src/utils/category-dir.ts +44 -0
  1144. package/src/utils/errno.ts +6 -0
  1145. package/src/utils/iso-timestamp.test.ts +37 -0
  1146. package/src/utils/iso-timestamp.ts +164 -0
  1147. package/src/utils/path.ts +26 -0
  1148. package/src/verified-recall.ts +138 -0
  1149. package/src/version-utils.test.ts +10 -0
  1150. package/src/version-utils.ts +9 -0
  1151. package/src/whitespace.ts +10 -0
  1152. package/src/work/board.ts +359 -0
  1153. package/src/work/boundary.ts +107 -0
  1154. package/src/work/storage.ts +436 -0
  1155. package/src/work/types.ts +82 -0
  1156. package/src/work-product-ledger.ts +265 -0
  1157. package/dist/access-service-DDjzFALq.d.ts +0 -2088
  1158. package/dist/capsule-crypto-SJS5VVAP.js +0 -18
  1159. package/dist/capsule-export-7QNCBZOQ.js +0 -17
  1160. package/dist/capsule-import-EPBHD2EN.js +0 -16
  1161. package/dist/capsule-merge-DI7PNQ2H.js +0 -189
  1162. package/dist/chunk-23ZZK64Y.js +0 -26
  1163. package/dist/chunk-23ZZK64Y.js.map +0 -1
  1164. package/dist/chunk-242S3I2A.js +0 -647
  1165. package/dist/chunk-2LGMW3DJ.js +0 -111
  1166. package/dist/chunk-3B6KIRBH.js +0 -5213
  1167. package/dist/chunk-3B6KIRBH.js.map +0 -1
  1168. package/dist/chunk-457A4P3L.js +0 -119
  1169. package/dist/chunk-457A4P3L.js.map +0 -1
  1170. package/dist/chunk-4IS4SXIQ.js +0 -2040
  1171. package/dist/chunk-4YM32CRU.js +0 -721
  1172. package/dist/chunk-6TBWYBJ3.js +0 -236
  1173. package/dist/chunk-74EMIVE4.js +0 -329
  1174. package/dist/chunk-74EMIVE4.js.map +0 -1
  1175. package/dist/chunk-767ODGE6.js +0 -183
  1176. package/dist/chunk-7V22HTMD.js +0 -623
  1177. package/dist/chunk-7V22HTMD.js.map +0 -1
  1178. package/dist/chunk-7ZM3BFKK.js +0 -9705
  1179. package/dist/chunk-7ZM3BFKK.js.map +0 -1
  1180. package/dist/chunk-AQJNPMOA.js +0 -643
  1181. package/dist/chunk-AQJNPMOA.js.map +0 -1
  1182. package/dist/chunk-ASAITVLA.js +0 -64
  1183. package/dist/chunk-ASAITVLA.js.map +0 -1
  1184. package/dist/chunk-BBE34QBJ.js +0 -275
  1185. package/dist/chunk-BBE34QBJ.js.map +0 -1
  1186. package/dist/chunk-BZSQEPRW.js +0 -14710
  1187. package/dist/chunk-BZSQEPRW.js.map +0 -1
  1188. package/dist/chunk-CPKTBRS2.js +0 -891
  1189. package/dist/chunk-CPKTBRS2.js.map +0 -1
  1190. package/dist/chunk-D4GAOFF6.js +0 -562
  1191. package/dist/chunk-D4GAOFF6.js.map +0 -1
  1192. package/dist/chunk-D54LZC5L.js +0 -147
  1193. package/dist/chunk-DF3RVK3X.js +0 -119
  1194. package/dist/chunk-DF3RVK3X.js.map +0 -1
  1195. package/dist/chunk-DZZPC36E.js +0 -1451
  1196. package/dist/chunk-DZZPC36E.js.map +0 -1
  1197. package/dist/chunk-E2UCDP5S.js +0 -570
  1198. package/dist/chunk-E6K4NIEU.js +0 -747
  1199. package/dist/chunk-E6K4NIEU.js.map +0 -1
  1200. package/dist/chunk-EEQLFRUM.js +0 -89
  1201. package/dist/chunk-ETOW6ACV.js +0 -158
  1202. package/dist/chunk-ETOW6ACV.js.map +0 -1
  1203. package/dist/chunk-FMEBPEAO.js +0 -347
  1204. package/dist/chunk-FMEBPEAO.js.map +0 -1
  1205. package/dist/chunk-FQDPCE3I.js +0 -1837
  1206. package/dist/chunk-FQDPCE3I.js.map +0 -1
  1207. package/dist/chunk-FYIYMQ5N.js +0 -221
  1208. package/dist/chunk-FYIYMQ5N.js.map +0 -1
  1209. package/dist/chunk-G2WADRQ3.js +0 -219
  1210. package/dist/chunk-G4SK7DSQ.js +0 -121
  1211. package/dist/chunk-GVPWB7EY.js +0 -390
  1212. package/dist/chunk-GVPWB7EY.js.map +0 -1
  1213. package/dist/chunk-HELQZFZO.js +0 -1075
  1214. package/dist/chunk-HL5LRPNA.js +0 -1914
  1215. package/dist/chunk-HL5LRPNA.js.map +0 -1
  1216. package/dist/chunk-HQZVVSVB.js +0 -147
  1217. package/dist/chunk-HQZVVSVB.js.map +0 -1
  1218. package/dist/chunk-HY3L4WKC.js +0 -2195
  1219. package/dist/chunk-HY3L4WKC.js.map +0 -1
  1220. package/dist/chunk-IB3BFHGN.js +0 -228
  1221. package/dist/chunk-IXEJRKCZ.js +0 -18
  1222. package/dist/chunk-JBMSGZEQ.js +0 -441
  1223. package/dist/chunk-JBMSGZEQ.js.map +0 -1
  1224. package/dist/chunk-JESOB2HO.js +0 -108
  1225. package/dist/chunk-JKDVIE52.js +0 -272
  1226. package/dist/chunk-JRNQ3RNA.js +0 -284
  1227. package/dist/chunk-JRNQ3RNA.js.map +0 -1
  1228. package/dist/chunk-K6WK37A6.js +0 -865
  1229. package/dist/chunk-K6WK37A6.js.map +0 -1
  1230. package/dist/chunk-MARWOCVP.js +0 -48
  1231. package/dist/chunk-MNU6ZBWT.js +0 -4454
  1232. package/dist/chunk-MNU6ZBWT.js.map +0 -1
  1233. package/dist/chunk-N5AKDXAI.js +0 -74
  1234. package/dist/chunk-OA3L7BFR.js +0 -183
  1235. package/dist/chunk-OA3L7BFR.js.map +0 -1
  1236. package/dist/chunk-OR64ZGRZ.js +0 -23
  1237. package/dist/chunk-P77UEOU2.js +0 -1521
  1238. package/dist/chunk-P77UEOU2.js.map +0 -1
  1239. package/dist/chunk-PH4C2U43.js +0 -239
  1240. package/dist/chunk-PH4C2U43.js.map +0 -1
  1241. package/dist/chunk-RVPLBATS.js +0 -1586
  1242. package/dist/chunk-RVPLBATS.js.map +0 -1
  1243. package/dist/chunk-U5JMRGKX.js +0 -340
  1244. package/dist/chunk-U5JMRGKX.js.map +0 -1
  1245. package/dist/chunk-URB2WSKZ.js +0 -350
  1246. package/dist/chunk-URB2WSKZ.js.map +0 -1
  1247. package/dist/chunk-UVMUAWVT.js +0 -596
  1248. package/dist/chunk-WEJG4TB5.js +0 -118
  1249. package/dist/chunk-X7HPGUVG.js +0 -271
  1250. package/dist/chunk-XAMBKFQS.js +0 -2777
  1251. package/dist/chunk-XAMBKFQS.js.map +0 -1
  1252. package/dist/chunk-XJKFSSDW.js +0 -726
  1253. package/dist/chunk-XJKFSSDW.js.map +0 -1
  1254. package/dist/chunk-XMHBH5H6.js +0 -283
  1255. package/dist/chunk-XMHBH5H6.js.map +0 -1
  1256. package/dist/chunk-XMVFHBHT.js +0 -277
  1257. package/dist/chunk-Y3VMVTYX.js +0 -53
  1258. package/dist/chunk-YNB73F22.js +0 -137
  1259. package/dist/chunk-YNB73F22.js.map +0 -1
  1260. package/dist/chunk-Z2E7VW55.js +0 -335
  1261. package/dist/chunk-Z2E7VW55.js.map +0 -1
  1262. package/dist/chunk-ZG7PTKBK.js +0 -2296
  1263. package/dist/chunk-ZNQN6ZTA.js +0 -135
  1264. package/dist/chunk-ZVTKDVVM.js +0 -827
  1265. package/dist/chunk-ZVTKDVVM.js.map +0 -1
  1266. package/dist/cli-BR8KpIU0.d.ts +0 -1259
  1267. package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
  1268. package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
  1269. package/dist/contradiction-review-5LTTVDQV.js +0 -22
  1270. package/dist/contradiction-scan-QTXAMBUA.js +0 -414
  1271. package/dist/contradiction-scan-QTXAMBUA.js.map +0 -1
  1272. package/dist/engine-35M5BKQ7.js +0 -28
  1273. package/dist/fs-utils-IRVUFB6G.js +0 -30
  1274. package/dist/graph-edge-decay-PWB63GRE.js +0 -207
  1275. package/dist/memory-governance-IMPQZXFC.js +0 -37
  1276. package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
  1277. package/dist/orchestrator-DDMPqU6R.d.ts +0 -1792
  1278. package/dist/path-RMTY5Y5A.js +0 -9
  1279. package/dist/port-B6VEDIkC.d.ts +0 -53
  1280. package/dist/resolution-YGIBORXI.js +0 -101
  1281. package/dist/resolution-YGIBORXI.js.map +0 -1
  1282. package/dist/secure-store-4R2GSO7S.js +0 -156
  1283. package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
  1284. package/dist/state-store-3EH7HYIN.js +0 -16
  1285. package/dist/types-V3FJ26TF.js +0 -30
  1286. /package/dist/{capsule-crypto-SJS5VVAP.js.map → adapters/claude-code.js.map} +0 -0
  1287. /package/dist/{capsule-export-7QNCBZOQ.js.map → adapters/codex.js.map} +0 -0
  1288. /package/dist/{capsule-import-EPBHD2EN.js.map → adapters/hermes.js.map} +0 -0
  1289. /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/index.js.map} +0 -0
  1290. /package/dist/{engine-35M5BKQ7.js.map → adapters/registry.js.map} +0 -0
  1291. /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/replit.js.map} +0 -0
  1292. /package/dist/{memory-governance-IMPQZXFC.js.map → adapters/types.js.map} +0 -0
  1293. /package/dist/{path-RMTY5Y5A.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  1294. /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
  1295. /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
  1296. /package/dist/{chunk-X7HPGUVG.js.map → chunk-4CRG46BG.js.map} +0 -0
  1297. /package/dist/{chunk-UVMUAWVT.js.map → chunk-7IASACLB.js.map} +0 -0
  1298. /package/dist/{chunk-HELQZFZO.js.map → chunk-EDTHC6UD.js.map} +0 -0
  1299. /package/dist/{chunk-4YM32CRU.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
  1300. /package/dist/{chunk-E2UCDP5S.js.map → chunk-FBYESMQ2.js.map} +0 -0
  1301. /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
  1302. /package/dist/{chunk-IB3BFHGN.js.map → chunk-GGKRUQOO.js.map} +0 -0
  1303. /package/dist/{chunk-242S3I2A.js.map → chunk-GL6I6MEQ.js.map} +0 -0
  1304. /package/dist/{secure-store-4R2GSO7S.js.map → chunk-HHLLAQGZ.js.map} +0 -0
  1305. /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
  1306. /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
  1307. /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
  1308. /package/dist/{chunk-WEJG4TB5.js.map → chunk-MC26UJIM.js.map} +0 -0
  1309. /package/dist/{chunk-JKDVIE52.js.map → chunk-MGKYQQYF.js.map} +0 -0
  1310. /package/dist/{chunk-Y3VMVTYX.js.map → chunk-MT4HVDUZ.js.map} +0 -0
  1311. /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
  1312. /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
  1313. /package/dist/{chunk-JESOB2HO.js.map → chunk-P4NEIHUT.js.map} +0 -0
  1314. /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
  1315. /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
  1316. /package/dist/{state-store-3EH7HYIN.js.map → chunk-SEDEKFYQ.js.map} +0 -0
  1317. /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
  1318. /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
  1319. /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
  1320. /package/dist/{types-V3FJ26TF.js.map → chunk-V5OCT34X.js.map} +0 -0
  1321. /package/dist/{chunk-ZG7PTKBK.js.map → chunk-W3LR522O.js.map} +0 -0
  1322. /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
  1323. /package/dist/{chunk-ZNQN6ZTA.js.map → chunk-XVZ7B3HG.js.map} +0 -0
  1324. /package/dist/{graph-edge-decay-PWB63GRE.js.map → graph-edge-decay-5DI5GUNL.js.map} +0 -0
@@ -0,0 +1,2719 @@
1
+ import OpenAI from "openai";
2
+ import { log } from "./logger.js";
3
+ import { delinearize } from "./delinearize.js";
4
+ import { LocalLlmClient } from "./local-llm.js";
5
+ import { FallbackLlmClient } from "./fallback-llm.js";
6
+ import {
7
+ ExtractionResultSchema,
8
+ ConsolidationResultSchema,
9
+ IdentityConsolidationResultSchema,
10
+ buildProfileConsolidationResultSchema,
11
+ ProactiveExtractionResultSchema,
12
+ ProactiveQuestionsResultSchema,
13
+ type ContradictionVerificationResult,
14
+ type SuggestedLinks,
15
+ type MemorySummaryResult,
16
+ type ProactiveQuestionsResultParsed,
17
+ DaySummaryResultSchema,
18
+ } from "./schemas.js";
19
+ import type {
20
+ BufferTurn,
21
+ ExtractionResult,
22
+ ConsolidationResult,
23
+ MemoryFile,
24
+ PluginConfig,
25
+ LlmTraceEvent,
26
+ GatewayConfig,
27
+ MemoryCategory,
28
+ DaySummaryResult as DaySummaryResultShape,
29
+ } from "./types.js";
30
+ import { ModelRegistry } from "./model-registry.js";
31
+ import { extractJsonCandidates } from "./json-extract.js";
32
+ import { sanitizeMemoryContent } from "./sanitize.js";
33
+ import { applyWorkExtractionBoundary } from "./work/boundary.js";
34
+ import { buildChatCompletionTokenLimit, shouldAssumeOpenAiChatCompletions } from "./openai-chat-compat.js";
35
+ import { formatDaySummaryMemories, loadDaySummaryPrompt, buildExtensionsFooterForSummary } from "./day-summary.js";
36
+ import { ProfilingCollector } from "./profiling.js";
37
+ import { normalizeProcedureSteps } from "./procedural/procedure-types.js";
38
+ import { normalizeReasoningTrace } from "./reasoning-trace-types.js";
39
+ import { looksLikeMechanicalTelemetryTranscript } from "./telemetry-transcript.js";
40
+
41
+ type ExtractionQuestion = ExtractionResult["questions"][number];
42
+ type ExtractedFactResult = ExtractionResult["facts"][number];
43
+ type ExtractedEntityResult = ExtractionResult["entities"][number];
44
+ type ExtractedRelationshipResult = NonNullable<ExtractionResult["relationships"]>[number];
45
+ const PROACTIVE_MIN_CONFIDENCE = 0.8;
46
+ const CONSOLIDATION_RESPONSE_SCHEMA = `{
47
+ "items": [
48
+ {
49
+ "existingId": "id",
50
+ "action": "ADD",
51
+ "mergeWith": "optional-existing-id",
52
+ "updatedContent": "optional replacement content",
53
+ "reason": "brief reason for this action"
54
+ }
55
+ ],
56
+ "profileUpdates": ["optional profile update"],
57
+ "entityUpdates": [{"name": "person-jane-doe", "type": "person", "facts": ["Now leads the backend team", "Recently migrated the user service to TypeScript"]}]
58
+ }`;
59
+
60
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
61
+ return typeof value === "object" && value !== null && !Array.isArray(value);
62
+ }
63
+
64
+ function normalizeQuestion(question: ExtractionQuestion): ExtractionQuestion {
65
+ const priority = Number.isFinite(question.priority)
66
+ ? Math.max(0, Math.min(1, question.priority))
67
+ : 0.5;
68
+ return {
69
+ question: typeof question.question === "string" ? question.question.trim() : "",
70
+ context: typeof question.context === "string" ? question.context.trim() : "",
71
+ priority,
72
+ };
73
+ }
74
+
75
+ function normalizeFactKey(fact: Pick<ExtractedFactResult, "category" | "content">): string {
76
+ return `${fact.category}:${fact.content.trim().toLowerCase()}`;
77
+ }
78
+
79
+ function normalizeEntityKey(entity: Pick<ExtractedEntityResult, "name" | "type">): string {
80
+ return `${entity.type}:${entity.name.trim().toLowerCase()}`;
81
+ }
82
+
83
+ function normalizeRelationshipKey(
84
+ relationship: Pick<ExtractedRelationshipResult, "source" | "target" | "label">,
85
+ ): string {
86
+ return `${relationship.source.trim().toLowerCase()}=>${relationship.target.trim().toLowerCase()}:${relationship.label.trim().toLowerCase()}`;
87
+ }
88
+
89
+ function normalizeProfileUpdateKey(update: string): string {
90
+ return update.trim().toLowerCase();
91
+ }
92
+
93
+ export class ExtractionEngine {
94
+ private client: OpenAI | null;
95
+ private localLlm: LocalLlmClient;
96
+ private fallbackLlm: FallbackLlmClient;
97
+ private modelRegistry: ModelRegistry;
98
+ private profiler: ProfilingCollector;
99
+
100
+ constructor(
101
+ private readonly config: PluginConfig,
102
+ profilerArg?: ProfilingCollector,
103
+ localLlm?: LocalLlmClient,
104
+ gatewayConfig?: GatewayConfig,
105
+ modelRegistry?: ModelRegistry,
106
+ ) {
107
+ this.profiler = profilerArg ?? new ProfilingCollector({ enabled: false, storageDir: "/tmp/engram-profiler-disabled", maxTraces: 0 });
108
+ if (config.openaiApiKey) {
109
+ this.client = new OpenAI({
110
+ apiKey: config.openaiApiKey,
111
+ ...(config.openaiBaseUrl ? { baseURL: config.openaiBaseUrl } : {}),
112
+ });
113
+ } else {
114
+ this.client = null;
115
+ log.warn("no OpenAI API key — direct OpenAI client disabled; local and gateway fallback paths remain available");
116
+ }
117
+ this.localLlm = localLlm ?? new LocalLlmClient(config, modelRegistry);
118
+ this.fallbackLlm = new FallbackLlmClient(gatewayConfig, {
119
+ workspaceDir: config.workspaceDir,
120
+ });
121
+ this.modelRegistry = modelRegistry ?? new ModelRegistry(config.memoryDir);
122
+ if (config.modelSource === "gateway") {
123
+ log.debug(
124
+ `extraction engine: gateway model source active; extraction uses the gateway chain as its primary path` +
125
+ (config.gatewayAgentId ? ` (agent: ${config.gatewayAgentId})` : " (defaults)"),
126
+ );
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Whether LLM calls should be routed through the gateway model chain
132
+ * instead of the plugin's own local/OpenAI clients.
133
+ */
134
+ private get useGatewayModelSource(): boolean {
135
+ return this.config.modelSource === "gateway";
136
+ }
137
+
138
+ /**
139
+ * Whether the local LLM path should be attempted.
140
+ * Disabled when gateway model source is active (gateway chain replaces local).
141
+ */
142
+ private get shouldUseLocalLlm(): boolean {
143
+ return this.config.localLlmEnabled && !this.useGatewayModelSource;
144
+ }
145
+
146
+ /**
147
+ * Whether the direct OpenAI client should be used.
148
+ * Disabled when gateway model source is active.
149
+ */
150
+ private get shouldUseDirectClient(): boolean {
151
+ return !this.useGatewayModelSource && this.client !== null;
152
+ }
153
+
154
+ /**
155
+ * Build FallbackLlmOptions with the configured gateway agent ID injected.
156
+ */
157
+ private withGatewayAgent(options: import("./fallback-llm.js").FallbackLlmOptions): import("./fallback-llm.js").FallbackLlmOptions {
158
+ if (!this.useGatewayModelSource) return options;
159
+ const agentId = this.config.gatewayAgentId || undefined;
160
+ return agentId ? { ...options, agentId } : options;
161
+ }
162
+
163
+ private emit(event: LlmTraceEvent): void {
164
+ try {
165
+ const cb = (globalThis as any).__openclawEngramTrace;
166
+ if (typeof cb === "function") cb(event);
167
+ } catch {
168
+ // Never throw — broken subscriber must not crash extraction
169
+ }
170
+ }
171
+
172
+ private directClientUsesOpenAiTokenSemantics(): boolean {
173
+ return shouldAssumeOpenAiChatCompletions(this.config.openaiBaseUrl);
174
+ }
175
+
176
+ private sanitizeExtractionResult(result: ExtractionResult, messageTimestamp?: Date): ExtractionResult {
177
+ const proceduralOn = this.config.procedural?.enabled === true;
178
+ const ts = messageTimestamp ?? new Date();
179
+ const facts = result.facts
180
+ .filter((fact) => proceduralOn || fact.category !== "procedure")
181
+ .map((fact) => {
182
+ const sanitized = sanitizeMemoryContent(fact.content);
183
+ if (!sanitized.clean) {
184
+ log.warn(`extraction fact sanitized; violations=${sanitized.violations.join(", ")}`);
185
+ }
186
+ let content = sanitized.text;
187
+ // De-linearize: resolve coreferences + anchor temporal expressions
188
+ if (this.config.delinearizeEnabled) {
189
+ content = delinearize(content, result.entities, ts);
190
+ }
191
+ return { ...fact, content };
192
+ });
193
+ return { ...result, facts };
194
+ }
195
+
196
+ private hasExtractionOutputs(result: ExtractionResult): boolean {
197
+ return result.facts.length > 0
198
+ || result.entities.length > 0
199
+ || result.questions.length > 0
200
+ || result.profileUpdates.length > 0
201
+ || (result.relationships?.length ?? 0) > 0;
202
+ }
203
+
204
+ private normalizeExtractionResultPayload(parsed: any): ExtractionResult {
205
+ const entities = Array.isArray(parsed?.entities)
206
+ ? parsed.entities
207
+ .map((e: any) => this.normalizeEntityUpdate(e))
208
+ .filter((e: any) => e.name.length > 0)
209
+ : [];
210
+
211
+ const facts = Array.isArray(parsed?.facts)
212
+ ? parsed.facts
213
+ .map((f: any) => ({
214
+ category: typeof f?.category === "string" ? f.category : "fact",
215
+ content: typeof f?.content === "string" ? f.content : typeof f?.text === "string" ? f.text : "",
216
+ confidence: typeof f?.confidence === "number" ? f.confidence : 0.7,
217
+ tags: Array.isArray(f?.tags) ? f.tags.filter((t: any) => typeof t === "string") : [],
218
+ entityRef: typeof f?.entityRef === "string" ? f.entityRef : undefined,
219
+ promptedByQuestion:
220
+ typeof f?.promptedByQuestion === "string" ? f.promptedByQuestion : undefined,
221
+ scope:
222
+ f?.scope === "global" || f?.scope === "project" ? f.scope : undefined,
223
+ structuredAttributes:
224
+ f?.structuredAttributes && typeof f.structuredAttributes === "object" && !Array.isArray(f.structuredAttributes)
225
+ ? Object.fromEntries(
226
+ Object.entries(f.structuredAttributes)
227
+ .filter(([k, v]) => typeof k === "string" && typeof v === "string")
228
+ ) as Record<string, string>
229
+ : undefined,
230
+ procedureSteps: Array.isArray(f?.procedureSteps)
231
+ ? normalizeProcedureSteps(f.procedureSteps)
232
+ : undefined,
233
+ reasoningTrace: (() => {
234
+ // Accept both camelCase and snake_case payload keys. The
235
+ // category itself is snake_case and we already tolerate
236
+ // snake_case nested fields in normalizeReasoningTrace, so a
237
+ // loose local/direct LLM that outputs `reasoning_trace` on the
238
+ // fact should not silently drop the structured chain.
239
+ const candidate =
240
+ f?.reasoningTrace && typeof f.reasoningTrace === "object" && !Array.isArray(f.reasoningTrace)
241
+ ? f.reasoningTrace
242
+ : f?.reasoning_trace && typeof f.reasoning_trace === "object" && !Array.isArray(f.reasoning_trace)
243
+ ? f.reasoning_trace
244
+ : null;
245
+ return candidate ? normalizeReasoningTrace(candidate) ?? undefined : undefined;
246
+ })(),
247
+ }))
248
+ .filter((f: any) => f.content.length > 0)
249
+ : [];
250
+
251
+ const questions = Array.isArray(parsed?.questions)
252
+ ? parsed.questions
253
+ .map((q: any) => {
254
+ if (typeof q === "string") return { question: q, context: "", priority: 0.5 };
255
+ return {
256
+ question: typeof q?.question === "string" ? q.question : typeof q?.text === "string" ? q.text : "",
257
+ context: typeof q?.context === "string" ? q.context : "",
258
+ priority: typeof q?.priority === "number" ? q.priority : 0.5,
259
+ };
260
+ })
261
+ .filter((q: any) => q.question.length > 0)
262
+ : [];
263
+
264
+ return {
265
+ facts,
266
+ entities,
267
+ profileUpdates: Array.isArray(parsed?.profileUpdates)
268
+ ? parsed.profileUpdates.filter((u: any) => typeof u === "string" && u.trim().length > 0)
269
+ : [],
270
+ questions,
271
+ identityReflection: parsed?.identityReflection ?? undefined,
272
+ relationships: Array.isArray(parsed?.relationships)
273
+ ? parsed.relationships.filter(
274
+ (r: any) =>
275
+ typeof r?.source === "string" &&
276
+ typeof r?.target === "string" &&
277
+ typeof r?.label === "string",
278
+ )
279
+ .map((r: any) => ({
280
+ source: r.source,
281
+ target: r.target,
282
+ label: r.label,
283
+ promptedByQuestion:
284
+ typeof r?.promptedByQuestion === "string" ? r.promptedByQuestion : undefined,
285
+ }))
286
+ : undefined,
287
+ };
288
+ }
289
+
290
+ private normalizeEntityUpdate(entity: any): ExtractedEntityResult {
291
+ const rawUpdates = isPlainRecord(entity?.updates) ? entity.updates : null;
292
+ const directFacts = Array.isArray(entity?.facts)
293
+ ? entity.facts
294
+ .filter((fact: any) => typeof fact === "string")
295
+ .map((fact: string) => fact.trim())
296
+ .filter((fact: string) => fact.length > 0)
297
+ : [];
298
+ const updateFacts = rawUpdates && Array.isArray(rawUpdates.facts)
299
+ ? rawUpdates.facts
300
+ .filter((fact: unknown) => typeof fact === "string")
301
+ .map((fact: string) => fact.trim())
302
+ .filter((fact: string) => fact.length > 0)
303
+ : [];
304
+ const scalarUpdateFacts = rawUpdates
305
+ ? Object.keys(rawUpdates)
306
+ .sort((a, b) => a.localeCompare(b))
307
+ .filter((key) => !["facts", "name", "promptedByQuestion", "structuredSections", "type"].includes(key))
308
+ .flatMap((key) => {
309
+ const value = rawUpdates[key];
310
+ if (typeof value === "string" && value.trim().length > 0) {
311
+ return [`${key}: ${value.trim()}`];
312
+ }
313
+ if (typeof value === "number" || typeof value === "boolean") {
314
+ return [`${key}: ${String(value)}`];
315
+ }
316
+ return [];
317
+ })
318
+ : [];
319
+ const structuredSectionsSource = Array.isArray(entity?.structuredSections)
320
+ ? entity.structuredSections
321
+ : Array.isArray(rawUpdates?.structuredSections)
322
+ ? rawUpdates.structuredSections
323
+ : [];
324
+ const name =
325
+ typeof entity?.name === "string"
326
+ ? entity.name.trim()
327
+ : typeof entity?.entityId === "string"
328
+ ? entity.entityId.trim()
329
+ : typeof rawUpdates?.name === "string"
330
+ ? rawUpdates.name.trim()
331
+ : "";
332
+ const type =
333
+ typeof entity?.type === "string" && entity.type.trim().length > 0
334
+ ? entity.type.trim()
335
+ : typeof rawUpdates?.type === "string" && rawUpdates.type.trim().length > 0
336
+ ? rawUpdates.type.trim()
337
+ : "other";
338
+
339
+ return {
340
+ name,
341
+ type,
342
+ facts: [...directFacts, ...updateFacts, ...scalarUpdateFacts],
343
+ structuredSections: structuredSectionsSource.length > 0
344
+ ? structuredSectionsSource
345
+ .map((section: any) => ({
346
+ key: typeof section?.key === "string" ? section.key.trim() : "",
347
+ title: typeof section?.title === "string" ? section.title.trim() : "",
348
+ facts: Array.isArray(section?.facts)
349
+ ? section.facts.filter((fact: any) => typeof fact === "string")
350
+ .map((fact: string) => fact.trim())
351
+ .filter((fact: string) => fact.length > 0)
352
+ : [],
353
+ }))
354
+ .filter((section: any) => (
355
+ section.key.length > 0 &&
356
+ section.title.length > 0 &&
357
+ section.facts.length > 0
358
+ ))
359
+ : undefined,
360
+ promptedByQuestion:
361
+ typeof entity?.promptedByQuestion === "string"
362
+ ? entity.promptedByQuestion
363
+ : typeof rawUpdates?.promptedByQuestion === "string"
364
+ ? rawUpdates.promptedByQuestion
365
+ : undefined,
366
+ };
367
+ }
368
+
369
+ private parseJsonObject(content?: string | null): any | null {
370
+ const trimmed = content?.trim();
371
+ if (!trimmed) return null;
372
+
373
+ for (const candidate of extractJsonCandidates(trimmed)) {
374
+ try {
375
+ return JSON.parse(candidate);
376
+ } catch {
377
+ // keep trying candidates
378
+ }
379
+ }
380
+
381
+ return null;
382
+ }
383
+
384
+ private normalizeContradictionVerificationResult(parsed: any): ContradictionVerificationResult | null {
385
+ if (!parsed || typeof parsed.isContradiction !== "boolean") return null;
386
+
387
+ const rawWhich = parsed.whichIsNewer ?? parsed.winner;
388
+ const normalizedWhich =
389
+ rawWhich === "first" || rawWhich === "existing"
390
+ ? "first"
391
+ : rawWhich === "second" || rawWhich === "new"
392
+ ? "second"
393
+ : "unclear";
394
+
395
+ return {
396
+ isContradiction: Boolean(parsed.isContradiction),
397
+ confidence: typeof parsed.confidence === "number" ? parsed.confidence : 0.5,
398
+ reasoning:
399
+ typeof parsed.reasoning === "string"
400
+ ? parsed.reasoning
401
+ : typeof parsed.explanation === "string"
402
+ ? parsed.explanation
403
+ : "",
404
+ whichIsNewer: normalizedWhich,
405
+ };
406
+ }
407
+
408
+ private normalizeSuggestedLinksResult(parsed: any): SuggestedLinks | null {
409
+ if (!parsed || !Array.isArray(parsed.links)) {
410
+ return null;
411
+ }
412
+
413
+ const normalizedLinks = parsed.links
414
+ .map((link: any) => {
415
+ const rawLinkType = link?.linkType ?? link?.type;
416
+ return {
417
+ targetId: typeof link?.targetId === "string" ? link.targetId : "",
418
+ linkType:
419
+ rawLinkType === "follows" ||
420
+ rawLinkType === "references" ||
421
+ rawLinkType === "contradicts" ||
422
+ rawLinkType === "supports" ||
423
+ rawLinkType === "related"
424
+ ? rawLinkType
425
+ : "related",
426
+ strength: typeof link?.strength === "number" ? Math.max(0, Math.min(1, link.strength)) : 0.5,
427
+ reason: typeof link?.reason === "string" ? link.reason : undefined,
428
+ };
429
+ })
430
+ .filter((link: any) => link.targetId.length > 0);
431
+
432
+ return { links: normalizedLinks };
433
+ }
434
+
435
+ private normalizeMemorySummaryResult(parsed: any): MemorySummaryResult | null {
436
+ if (!parsed) return null;
437
+
438
+ const normalized: MemorySummaryResult = {
439
+ summaryText:
440
+ typeof parsed.summaryText === "string"
441
+ ? parsed.summaryText
442
+ : typeof parsed.summary === "string"
443
+ ? parsed.summary
444
+ : "",
445
+ keyFacts: Array.isArray(parsed.keyFacts) ? parsed.keyFacts.filter((f: unknown) => typeof f === "string") : [],
446
+ keyEntities: Array.isArray(parsed.keyEntities)
447
+ ? parsed.keyEntities.filter((e: unknown) => typeof e === "string")
448
+ : Array.isArray(parsed.entities)
449
+ ? parsed.entities.filter((e: unknown) => typeof e === "string")
450
+ : [],
451
+ };
452
+
453
+ return normalized.summaryText.length > 0 ? normalized : null;
454
+ }
455
+
456
+ private normalizeDaySummaryResult(parsed: any): DaySummaryResultShape | null {
457
+ if (!parsed) return null;
458
+
459
+ const normalized: DaySummaryResultShape = {
460
+ summary: typeof parsed.summary === "string" ? parsed.summary.trim() : "",
461
+ bullets: Array.isArray(parsed.bullets)
462
+ ? parsed.bullets.filter((item: unknown) => typeof item === "string").map((item: string) => item.trim()).filter(Boolean)
463
+ : [],
464
+ next_actions: Array.isArray(parsed.next_actions)
465
+ ? parsed.next_actions.filter((item: unknown) => typeof item === "string").map((item: string) => item.trim()).filter(Boolean)
466
+ : [],
467
+ risks_or_open_loops: Array.isArray(parsed.risks_or_open_loops)
468
+ ? parsed.risks_or_open_loops.filter((item: unknown) => typeof item === "string").map((item: string) => item.trim()).filter(Boolean)
469
+ : [],
470
+ };
471
+
472
+ return normalized.summary.length > 0 ? normalized : null;
473
+ }
474
+
475
+ private sanitizeConsolidationResult(result: {
476
+ items?: unknown[];
477
+ profileUpdates?: unknown[];
478
+ entityUpdates?: unknown[];
479
+ }): ConsolidationResult {
480
+ const items: ConsolidationResult["items"] = [];
481
+ for (const item of Array.isArray(result.items) ? result.items : []) {
482
+ const rawAction = typeof (item as any)?.action === "string" ? (item as any).action.toUpperCase() : "SKIP";
483
+ const action =
484
+ rawAction === "ADD" ||
485
+ rawAction === "MERGE" ||
486
+ rawAction === "UPDATE" ||
487
+ rawAction === "INVALIDATE" ||
488
+ rawAction === "SKIP"
489
+ ? rawAction
490
+ : "SKIP";
491
+ const existingId =
492
+ typeof (item as any)?.existingId === "string"
493
+ ? (item as any).existingId.trim()
494
+ : typeof (item as any)?.newMemoryId === "string"
495
+ ? (item as any).newMemoryId.trim()
496
+ : typeof (item as any)?.memoryId === "string"
497
+ ? (item as any).memoryId.trim()
498
+ : "";
499
+ if (!existingId) continue;
500
+ const mergeWith = typeof (item as any)?.mergeWith === "string" ? (item as any).mergeWith : undefined;
501
+ const reason = typeof (item as any)?.reason === "string" ? (item as any).reason : "";
502
+ const rawUpdatedContent = typeof (item as any)?.updatedContent === "string" ? (item as any).updatedContent : undefined;
503
+ if (!rawUpdatedContent) {
504
+ items.push({ existingId, action, mergeWith, updatedContent: undefined, reason });
505
+ continue;
506
+ }
507
+ const sanitized = sanitizeMemoryContent(rawUpdatedContent);
508
+ if (!sanitized.clean) {
509
+ log.warn(`consolidation item sanitized (${existingId}); violations=${sanitized.violations.join(", ")}`);
510
+ }
511
+ items.push({
512
+ existingId,
513
+ action,
514
+ mergeWith,
515
+ updatedContent: sanitized.text,
516
+ reason,
517
+ });
518
+ }
519
+ const profileUpdates = (Array.isArray(result.profileUpdates) ? result.profileUpdates : [])
520
+ .map((update: any) =>
521
+ typeof update === "string"
522
+ ? update.trim()
523
+ : typeof update?.content === "string"
524
+ ? update.content.trim()
525
+ : "",
526
+ )
527
+ .filter((update) => update.length > 0);
528
+ const entityUpdates = (Array.isArray(result.entityUpdates) ? result.entityUpdates : [])
529
+ .map((entity: any) => this.normalizeEntityUpdate(entity))
530
+ .filter((entity: ExtractedEntityResult) => entity.name.length > 0);
531
+ return { items, profileUpdates, entityUpdates };
532
+ }
533
+
534
+ private async applyProactiveQuestionPass(
535
+ conversation: string,
536
+ base: ExtractionResult,
537
+ ): Promise<ExtractionResult> {
538
+ if (!this.config.proactiveExtractionEnabled) return base;
539
+ const maxAdditional = Math.max(0, Math.floor(this.config.maxProactiveQuestionsPerExtraction));
540
+ if (maxAdditional === 0) return base;
541
+ if (this.config.proactiveExtractionTimeoutMs === 0) return base;
542
+ if (this.config.proactiveExtractionMaxTokens === 0) return base;
543
+
544
+ try {
545
+ const proactive = await this.generateProactiveQuestions(conversation, base, maxAdditional);
546
+ if (proactive.length === 0) return base;
547
+ const proactiveAdditions = await this.answerProactiveQuestions(
548
+ conversation,
549
+ base,
550
+ proactive,
551
+ maxAdditional,
552
+ );
553
+ if (!this.hasExtractionOutputs(proactiveAdditions)) return base;
554
+ return this.mergeProactiveExtractionPass(base, proactiveAdditions, maxAdditional);
555
+ } catch (err) {
556
+ log.debug(`proactive extraction question pass failed (ignored): ${err}`);
557
+ return base;
558
+ }
559
+ }
560
+
561
+ private parseProactiveQuestionsFromText(
562
+ content: string,
563
+ existingQuestionKeys: Set<string>,
564
+ ): ExtractionQuestion[] {
565
+ for (const candidate of extractJsonCandidates(content)) {
566
+ try {
567
+ const parsed = JSON.parse(candidate) as Partial<ProactiveQuestionsResultParsed>;
568
+ if (!Array.isArray(parsed.questions)) continue;
569
+ return parsed.questions
570
+ .map((q) => normalizeQuestion(q as ExtractionQuestion))
571
+ .filter((q) => q.question.length > 0)
572
+ .filter((q) => !existingQuestionKeys.has(q.question.toLowerCase()));
573
+ } catch {
574
+ // Continue to next candidate.
575
+ }
576
+ }
577
+ return [];
578
+ }
579
+
580
+ private parseProactiveExtractionResultFromText(content: string): ExtractionResult | null {
581
+ for (const candidate of extractJsonCandidates(content)) {
582
+ try {
583
+ const parsed = ProactiveExtractionResultSchema.parse(JSON.parse(candidate));
584
+ return this.normalizeExtractionResultPayload({
585
+ ...parsed,
586
+ questions: [],
587
+ });
588
+ } catch {
589
+ // Continue to next candidate.
590
+ }
591
+ }
592
+ return null;
593
+ }
594
+
595
+ private async generateProactiveQuestions(
596
+ conversation: string,
597
+ base: ExtractionResult,
598
+ maxAdditional: number,
599
+ ): Promise<ExtractionQuestion[]> {
600
+ const existingQuestionKeys = new Set(
601
+ (base.questions ?? [])
602
+ .map((q) => q.question.trim().toLowerCase())
603
+ .filter((q) => q.length > 0),
604
+ );
605
+ const factsPreview = base.facts
606
+ .slice(0, 8)
607
+ .map((f) => `- (${f.category}) ${f.content}`)
608
+ .join("\n");
609
+ const existingQuestionsPreview = (base.questions ?? [])
610
+ .slice(0, 8)
611
+ .map((q) => `- ${q.question}`)
612
+ .join("\n");
613
+
614
+ const prompt = [
615
+ "You are doing a proactive second-pass memory extraction.",
616
+ `Generate up to ${maxAdditional} additional high-value follow-up questions not already covered.`,
617
+ "Return only valid JSON with this shape:",
618
+ '{"questions":[{"question":"...","context":"...","priority":0.0}]}',
619
+ "",
620
+ "Current extracted facts:",
621
+ factsPreview || "(none)",
622
+ "",
623
+ "Questions already extracted (do not repeat):",
624
+ existingQuestionsPreview || "(none)",
625
+ "",
626
+ "Conversation:",
627
+ conversation,
628
+ ].join("\n");
629
+
630
+ if (this.shouldUseLocalLlm) {
631
+ try {
632
+ const localResponse = await this.localLlm.chatCompletion(
633
+ [
634
+ {
635
+ role: "system",
636
+ content: "You are a proactive memory extraction assistant. Output valid JSON only.",
637
+ },
638
+ { role: "user", content: prompt },
639
+ ],
640
+ {
641
+ temperature: 0.2,
642
+ maxTokens: this.config.proactiveExtractionMaxTokens,
643
+ timeoutMs: this.config.proactiveExtractionTimeoutMs,
644
+ operation: "proactive_extraction",
645
+ priority: "background",
646
+ },
647
+ );
648
+ if (localResponse?.content) {
649
+ const localParsed = this.parseProactiveQuestionsFromText(
650
+ localResponse.content.trim(),
651
+ existingQuestionKeys,
652
+ );
653
+ if (localParsed.length > 0) {
654
+ return localParsed.slice(0, maxAdditional);
655
+ }
656
+ }
657
+ if (!this.config.localLlmFallback) {
658
+ return [];
659
+ }
660
+ } catch (err) {
661
+ if (!this.config.localLlmFallback) {
662
+ throw err;
663
+ }
664
+ }
665
+ }
666
+
667
+ const fallbackResult = await this.fallbackLlm.parseWithSchema(
668
+ [
669
+ {
670
+ role: "system",
671
+ content: "Generate additional proactive memory follow-up questions. Return valid JSON only.",
672
+ },
673
+ { role: "user", content: prompt },
674
+ ],
675
+ ProactiveQuestionsResultSchema,
676
+ this.withGatewayAgent({
677
+ temperature: 0.2,
678
+ maxTokens: this.config.proactiveExtractionMaxTokens,
679
+ timeoutMs: this.config.proactiveExtractionTimeoutMs,
680
+ }),
681
+ );
682
+ if (!fallbackResult?.questions) return [];
683
+ return fallbackResult.questions
684
+ .map((q) => normalizeQuestion(q as ExtractionQuestion))
685
+ .filter((q) => q.question.length > 0)
686
+ .filter((q) => !existingQuestionKeys.has(q.question.toLowerCase()))
687
+ .slice(0, maxAdditional);
688
+ }
689
+
690
+ private async answerProactiveQuestions(
691
+ conversation: string,
692
+ base: ExtractionResult,
693
+ proactiveQuestions: ExtractionQuestion[],
694
+ maxAdditional: number,
695
+ ): Promise<ExtractionResult> {
696
+ const factsPreview = base.facts
697
+ .slice(0, 8)
698
+ .map((f) => `- (${f.category}) ${f.content}`)
699
+ .join("\n");
700
+ const entitiesPreview = base.entities
701
+ .slice(0, 8)
702
+ .map((entity) => `- (${entity.type}) ${entity.name}: ${entity.facts.join("; ") || "(no facts)"}`)
703
+ .join("\n");
704
+ const proactivePreview = proactiveQuestions
705
+ .slice(0, maxAdditional)
706
+ .map((question, index) => `${index + 1}. ${question.question}${question.context ? `\n context: ${question.context}` : ""}`)
707
+ .join("\n");
708
+
709
+ const prompt = [
710
+ "You are answering proactive memory follow-up questions using only the provided buffered conversation.",
711
+ `Return at most ${maxAdditional} additional high-confidence memory candidates that were omitted from the base extraction.`,
712
+ "Only include information directly supported by the conversation. Do not speculate. Do not repeat the base extraction.",
713
+ "Return only valid JSON with this shape:",
714
+ '{"facts":[{"category":"fact","content":"...","confidence":0.0,"tags":["..."],"entityRef":"optional","promptedByQuestion":"optional"}],"profileUpdates":["..."],"entities":[{"name":"...","type":"person","facts":["..."],"structuredSections":[{"key":"beliefs","title":"Beliefs","facts":["..."]}],"promptedByQuestion":"optional"}],"relationships":[{"source":"...","target":"...","label":"...","promptedByQuestion":"optional"}]}',
715
+ "",
716
+ "Base extracted facts (do not repeat):",
717
+ factsPreview || "(none)",
718
+ "",
719
+ "Base extracted entities (do not repeat):",
720
+ entitiesPreview || "(none)",
721
+ "",
722
+ "Answer these follow-up questions from the same conversation only:",
723
+ proactivePreview || "(none)",
724
+ "",
725
+ "Conversation:",
726
+ conversation,
727
+ ].join("\n");
728
+
729
+ if (this.shouldUseLocalLlm) {
730
+ try {
731
+ const localResponse = await this.localLlm.chatCompletion(
732
+ [
733
+ {
734
+ role: "system",
735
+ content: "You are a proactive memory extraction assistant. Output valid JSON only.",
736
+ },
737
+ { role: "user", content: prompt },
738
+ ],
739
+ {
740
+ temperature: 0.2,
741
+ maxTokens: this.config.proactiveExtractionMaxTokens,
742
+ timeoutMs: this.config.proactiveExtractionTimeoutMs,
743
+ operation: "proactive_extraction",
744
+ priority: "background",
745
+ },
746
+ );
747
+ if (localResponse?.content) {
748
+ const parsed = this.parseProactiveExtractionResultFromText(localResponse.content.trim());
749
+ if (parsed) {
750
+ return this.sanitizeExtractionResult(parsed);
751
+ }
752
+ }
753
+ if (!this.config.localLlmFallback) {
754
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
755
+ }
756
+ } catch (err) {
757
+ if (!this.config.localLlmFallback) {
758
+ throw err;
759
+ }
760
+ }
761
+ }
762
+
763
+ const fallbackResult = await this.fallbackLlm.parseWithSchema(
764
+ [
765
+ {
766
+ role: "system",
767
+ content: "Answer proactive memory follow-up questions from the provided conversation only. Return valid JSON only.",
768
+ },
769
+ { role: "user", content: prompt },
770
+ ],
771
+ ProactiveExtractionResultSchema,
772
+ this.withGatewayAgent({
773
+ temperature: 0.2,
774
+ maxTokens: this.config.proactiveExtractionMaxTokens,
775
+ timeoutMs: this.config.proactiveExtractionTimeoutMs,
776
+ }),
777
+ );
778
+ if (!fallbackResult) {
779
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
780
+ }
781
+ return this.sanitizeExtractionResult(
782
+ this.normalizeExtractionResultPayload({
783
+ ...fallbackResult,
784
+ questions: [],
785
+ }),
786
+ );
787
+ }
788
+
789
+ private mergeProactiveExtractionPass(
790
+ base: ExtractionResult,
791
+ proactive: ExtractionResult,
792
+ maxAdditional: number,
793
+ ): ExtractionResult {
794
+ const allowlist = this.config.proactiveExtractionCategoryAllowlist;
795
+ let remainingBudget = Math.max(0, Math.floor(maxAdditional));
796
+ const mergedFacts = [...base.facts];
797
+ const seenFacts = new Set(base.facts.map((fact) => normalizeFactKey(fact)));
798
+ for (const fact of proactive.facts) {
799
+ if (remainingBudget <= 0) break;
800
+ if (fact.confidence < PROACTIVE_MIN_CONFIDENCE) continue;
801
+ if (allowlist && !allowlist.includes(fact.category as MemoryCategory)) continue;
802
+ const key = normalizeFactKey(fact);
803
+ if (seenFacts.has(key)) continue;
804
+ seenFacts.add(key);
805
+ mergedFacts.push({ ...fact, source: "proactive" });
806
+ remainingBudget -= 1;
807
+ }
808
+
809
+ const mergedEntities = base.entities.map((entity) => ({
810
+ ...entity,
811
+ facts: [...entity.facts],
812
+ structuredSections: entity.structuredSections
813
+ ? entity.structuredSections.map((section) => ({
814
+ ...section,
815
+ facts: [...section.facts],
816
+ }))
817
+ : undefined,
818
+ }));
819
+ const entityIndex = new Map(mergedEntities.map((entity, index) => [normalizeEntityKey(entity), index]));
820
+ for (const entity of proactive.entities) {
821
+ if (remainingBudget <= 0) break;
822
+ const key = normalizeEntityKey(entity);
823
+ const existingIndex = entityIndex.get(key);
824
+ if (typeof existingIndex === "number") {
825
+ const existing = mergedEntities[existingIndex]!;
826
+ const nextFacts = new Set(existing.facts.map((fact) => fact.trim()));
827
+ const nextSections = new Map(
828
+ (existing.structuredSections ?? []).map((section) => [section.key, {
829
+ ...section,
830
+ facts: [...section.facts],
831
+ }]),
832
+ );
833
+ let changed = false;
834
+ for (const fact of entity.facts) {
835
+ const trimmed = fact.trim();
836
+ if (!trimmed || nextFacts.has(trimmed)) continue;
837
+ nextFacts.add(trimmed);
838
+ changed = true;
839
+ }
840
+ for (const section of entity.structuredSections ?? []) {
841
+ const existingSection = nextSections.get(section.key);
842
+ if (!existingSection) {
843
+ nextSections.set(section.key, {
844
+ key: section.key,
845
+ title: section.title,
846
+ facts: [...section.facts],
847
+ });
848
+ changed = true;
849
+ continue;
850
+ }
851
+ const nextSectionFacts = new Set(existingSection.facts.map((fact) => fact.trim()));
852
+ for (const fact of section.facts) {
853
+ const trimmed = fact.trim();
854
+ if (!trimmed || nextSectionFacts.has(trimmed)) continue;
855
+ nextSectionFacts.add(trimmed);
856
+ changed = true;
857
+ }
858
+ existingSection.facts = Array.from(nextSectionFacts);
859
+ }
860
+ if (changed) {
861
+ mergedEntities[existingIndex] = {
862
+ ...existing,
863
+ facts: Array.from(nextFacts),
864
+ structuredSections: Array.from(nextSections.values()),
865
+ source: "proactive",
866
+ promptedByQuestion: existing.promptedByQuestion ?? entity.promptedByQuestion,
867
+ };
868
+ remainingBudget -= 1;
869
+ }
870
+ continue;
871
+ }
872
+ mergedEntities.push({
873
+ ...entity,
874
+ source: "proactive",
875
+ structuredSections: entity.structuredSections
876
+ ? entity.structuredSections.map((section) => ({
877
+ ...section,
878
+ facts: [...section.facts],
879
+ }))
880
+ : undefined,
881
+ });
882
+ entityIndex.set(key, mergedEntities.length - 1);
883
+ remainingBudget -= 1;
884
+ }
885
+
886
+ const mergedProfileUpdates = [...base.profileUpdates];
887
+ const seenProfileUpdates = new Set(base.profileUpdates.map((update) => normalizeProfileUpdateKey(update)));
888
+ for (const update of proactive.profileUpdates) {
889
+ if (remainingBudget <= 0) break;
890
+ const key = normalizeProfileUpdateKey(update);
891
+ if (!key || seenProfileUpdates.has(key)) continue;
892
+ seenProfileUpdates.add(key);
893
+ mergedProfileUpdates.push(update.trim());
894
+ remainingBudget -= 1;
895
+ }
896
+
897
+ const mergedRelationships = [...(base.relationships ?? [])];
898
+ const seenRelationships = new Set(mergedRelationships.map((relationship) => normalizeRelationshipKey(relationship)));
899
+ for (const relationship of proactive.relationships ?? []) {
900
+ if (remainingBudget <= 0) break;
901
+ const key = normalizeRelationshipKey(relationship);
902
+ if (seenRelationships.has(key)) continue;
903
+ seenRelationships.add(key);
904
+ mergedRelationships.push({ ...relationship, extractionSource: "proactive" });
905
+ remainingBudget -= 1;
906
+ }
907
+
908
+ return {
909
+ ...base,
910
+ facts: mergedFacts,
911
+ entities: mergedEntities,
912
+ profileUpdates: mergedProfileUpdates,
913
+ relationships: mergedRelationships,
914
+ };
915
+ }
916
+
917
+ private async parseWithGatewayFallback<T>(
918
+ traceId: string,
919
+ operation: LlmTraceEvent["operation"],
920
+ startedAtMs: number,
921
+ schema: { parse: (data: unknown) => T },
922
+ messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
923
+ options: { temperature?: number; maxTokens?: number } = {},
924
+ ): Promise<T | null> {
925
+ const detailed = await this.fallbackLlm.parseWithSchemaDetailed(messages, schema, this.withGatewayAgent(options));
926
+ if (detailed?.result) {
927
+ const durationMs = Date.now() - startedAtMs;
928
+ this.emit({
929
+ kind: "llm_end",
930
+ traceId,
931
+ model: detailed.modelUsed,
932
+ operation,
933
+ durationMs,
934
+ output: JSON.stringify(detailed.result).slice(0, 2000),
935
+ });
936
+ return detailed.result;
937
+ }
938
+ return null;
939
+ }
940
+
941
+ async extract(turns: BufferTurn[], existingEntities?: string[]): Promise<ExtractionResult> {
942
+
943
+ // Guard: skip if buffer is empty or all turns are whitespace-only
944
+ const substantiveTurns = turns.filter((t) => t.content.trim().length > 0);
945
+ if (substantiveTurns.length === 0) {
946
+ log.debug("extraction skipped — no substantive turns in buffer");
947
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
948
+ }
949
+
950
+ const boundedTurns = substantiveTurns
951
+ .map((turn) => ({
952
+ ...turn,
953
+ content: turn.role === "assistant"
954
+ ? applyWorkExtractionBoundary(turn.content)
955
+ : turn.content,
956
+ }))
957
+ .filter((turn) => turn.content.trim().length > 0);
958
+ const conversation = boundedTurns
959
+ .map((t) => {
960
+ const roleLabel =
961
+ t.extractionContextOnly === true ? `context ${t.role}` : t.role;
962
+ return `[${roleLabel}] ${t.content}`;
963
+ })
964
+ .join("\n\n");
965
+ if (conversation.trim().length === 0) {
966
+ log.debug("extraction skipped — conversation only contained non-memory work-layer context");
967
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
968
+ }
969
+ if (
970
+ this.config.extractionTelemetryPrefilterEnabled &&
971
+ looksLikeMechanicalTelemetryTranscript(conversation)
972
+ ) {
973
+ log.debug("extraction skipped — mechanical action/state telemetry without durable-memory cues");
974
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
975
+ }
976
+
977
+ // Use the last turn's timestamp for temporal anchoring (more accurate than wall-clock)
978
+ const lastTurnTs = boundedTurns.length > 0 ? new Date(boundedTurns[boundedTurns.length - 1].timestamp) : undefined;
979
+ const messageTimestamp = lastTurnTs && !isNaN(lastTurnTs.getTime()) ? lastTurnTs : undefined;
980
+
981
+ const traceId = crypto.randomUUID();
982
+ // Only emit llm_start for the direct path when a client or local LLM is configured.
983
+ // Fallback-only deployments skip this to avoid fake spans in Opik.
984
+ const emittedDirectStart = !!(this.shouldUseDirectClient || this.shouldUseLocalLlm);
985
+ if (emittedDirectStart) {
986
+ this.emit({ kind: "llm_start", traceId, model: this.config.model, operation: "extraction", input: conversation });
987
+ }
988
+ let closedDirectTrace = false;
989
+ const startTime = Date.now();
990
+
991
+ // --- profiling instrumentation ---
992
+ const extractionTraceId = this.profiler.startTrace("extraction", undefined, {
993
+ model: this.config.model,
994
+ localLlm: this.config.localLlmEnabled,
995
+ });
996
+ this.profiler.startSpan("total", extractionTraceId);
997
+
998
+ try {
999
+ // Try local LLM first if enabled
1000
+ if (this.shouldUseLocalLlm) {
1001
+ this.profiler.startSpan("local-llm", extractionTraceId);
1002
+ try {
1003
+ const localResult = await this.extractWithLocalLlm(conversation, existingEntities);
1004
+ if (localResult) {
1005
+ const durationMs = Date.now() - startTime;
1006
+ this.profiler.endSpan("local-llm", extractionTraceId);
1007
+ this.emit({ kind: "llm_end", traceId, model: this.config.localLlmModel, operation: "extraction", durationMs });
1008
+ log.debug(`extraction: used local LLM — ${localResult.facts.length} facts, ${localResult.entities.length} entities`);
1009
+ const sanitized = this.sanitizeExtractionResult(localResult, messageTimestamp);
1010
+ return await this.applyProactiveQuestionPass(conversation, sanitized);
1011
+ }
1012
+ // Local failed, fall back if allowed
1013
+ if (!this.config.localLlmFallback) {
1014
+ log.warn("extraction: local LLM failed and fallback disabled");
1015
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
1016
+ }
1017
+ log.info("extraction: local LLM unavailable, falling back to gateway default AI");
1018
+ } catch (err) {
1019
+ if (!this.config.localLlmFallback) {
1020
+ log.warn("extraction: local LLM error and fallback disabled:", err);
1021
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
1022
+ }
1023
+ log.info("extraction: local LLM error, falling back to gateway default AI:", err);
1024
+ } finally {
1025
+ // End local-llm span if it wasn't ended on the success path
1026
+ try { this.profiler.endSpan("local-llm", extractionTraceId); } catch { /* span may already be closed */ }
1027
+ }
1028
+ }
1029
+
1030
+ // Try direct OpenAI-compatible client (Scryr, OpenRouter, etc.)
1031
+ if (this.shouldUseDirectClient) {
1032
+ this.profiler.startSpan("direct-client", extractionTraceId);
1033
+ try {
1034
+ const directResult = await this.extractWithDirectClient(conversation, existingEntities);
1035
+ if (directResult) {
1036
+ const durationMs = Date.now() - startTime;
1037
+ this.profiler.endSpan("direct-client", extractionTraceId);
1038
+ this.emit({ kind: "llm_end", traceId, model: this.config.model, operation: "extraction", durationMs });
1039
+ log.debug(`extraction: used direct client (${this.config.model}) — ${directResult.facts.length} facts, ${directResult.entities.length} entities`);
1040
+ const sanitized = this.sanitizeExtractionResult(directResult, messageTimestamp);
1041
+ return await this.applyProactiveQuestionPass(conversation, sanitized);
1042
+ }
1043
+ // Emit error event so Opik sees the direct client failure before fallback.
1044
+ // Wrapped in try/catch so a subscriber error doesn't break the fallback path.
1045
+ try {
1046
+ this.emit({
1047
+ kind: "llm_error", traceId, model: this.config.model, operation: "extraction",
1048
+ durationMs: Date.now() - startTime, error: "direct client returned no result",
1049
+ });
1050
+ } catch { /* trace emit must not block fallback */ }
1051
+ closedDirectTrace = true;
1052
+ log.info("extraction: direct client returned no result, falling back to gateway AI");
1053
+ } catch (err) {
1054
+ try {
1055
+ this.emit({
1056
+ kind: "llm_error", traceId, model: this.config.model, operation: "extraction",
1057
+ durationMs: Date.now() - startTime, error: String(err),
1058
+ });
1059
+ } catch { /* trace emit must not block fallback */ }
1060
+ closedDirectTrace = true;
1061
+ log.info("extraction: direct client failed, falling back to gateway AI:", err);
1062
+ } finally {
1063
+ try { this.profiler.endSpan("direct-client", extractionTraceId); } catch { /* span may already be closed */ }
1064
+ }
1065
+ }
1066
+
1067
+ // Close any orphaned direct-path llm_start (e.g., local LLM failed, no direct client)
1068
+ if (emittedDirectStart && !closedDirectTrace) {
1069
+ try {
1070
+ this.emit({
1071
+ kind: "llm_error", traceId, model: this.config.model, operation: "extraction",
1072
+ durationMs: Date.now() - startTime, error: "local LLM failed, handing off to gateway fallback",
1073
+ });
1074
+ } catch { /* trace emit must not block fallback */ }
1075
+ }
1076
+
1077
+ // In gateway mode this is the primary extraction path. In plugin mode it is the
1078
+ // final fallback after local/direct attempts fail. Emit a fresh llm_start so the
1079
+ // gateway-backed call gets its own trace rather than being orphaned under the
1080
+ // direct-client traceId.
1081
+ const fallbackTraceId = crypto.randomUUID();
1082
+ const fallbackStartTime = Date.now();
1083
+ if (this.useGatewayModelSource) {
1084
+ log.debug(
1085
+ `extraction: using gateway model chain as primary path` +
1086
+ (this.config.gatewayAgentId ? ` (agent: ${this.config.gatewayAgentId})` : " (defaults)"),
1087
+ );
1088
+ } else {
1089
+ log.info("extraction: falling back to gateway default AI");
1090
+ }
1091
+
1092
+ this.profiler.startSpan("gateway-fallback", extractionTraceId);
1093
+ try {
1094
+ const messages = [
1095
+ { role: "system" as const, content: this.buildExtractionInstructions(existingEntities) },
1096
+ { role: "user" as const, content: conversation },
1097
+ ];
1098
+
1099
+ this.emit({ kind: "llm_start", traceId: fallbackTraceId, model: "fallback", operation: "extraction", input: conversation });
1100
+
1101
+ const detailed = await this.fallbackLlm.parseWithSchemaDetailed(
1102
+ messages,
1103
+ ExtractionResultSchema,
1104
+ this.withGatewayAgent({
1105
+ temperature: 0.3,
1106
+ maxTokens: this.config.extractionMaxOutputTokens,
1107
+ timeoutMs: this.config.localLlmTimeoutMs,
1108
+ }),
1109
+ );
1110
+
1111
+ const fallbackDurationMs = Date.now() - fallbackStartTime;
1112
+
1113
+ if (detailed?.result && Array.isArray(detailed.result.facts)) {
1114
+ const result = detailed.result;
1115
+ this.emit({
1116
+ kind: "llm_end", traceId: fallbackTraceId, model: detailed.modelUsed, operation: "extraction",
1117
+ durationMs: fallbackDurationMs, output: JSON.stringify(result).slice(0, 2000),
1118
+ });
1119
+ log.debug(
1120
+ `extracted ${result.facts.length} facts, ${result.entities.length} entities, ${(result.questions ?? []).length} questions via fallback (${detailed.modelUsed})`,
1121
+ );
1122
+ // Zod schema accepts snake_case aliases (final_answer / observed_outcome)
1123
+ // alongside camelCase for gateway-tolerance, but the downstream
1124
+ // ExtractedFact contract only exposes camelCase. Collapse each fact's
1125
+ // reasoningTrace through normalizeReasoningTrace before passing it on so
1126
+ // gateway output matches the shape local/direct-client paths produce.
1127
+ const normalizedFacts = result.facts.map((f: any) => {
1128
+ if (!f?.reasoningTrace) return f;
1129
+ return {
1130
+ ...f,
1131
+ reasoningTrace: normalizeReasoningTrace(f.reasoningTrace) ?? undefined,
1132
+ };
1133
+ });
1134
+ const sanitized = this.sanitizeExtractionResult({
1135
+ ...result,
1136
+ facts: normalizedFacts,
1137
+ questions: result.questions ?? [],
1138
+ identityReflection: result.identityReflection ?? undefined,
1139
+ } as ExtractionResult, messageTimestamp);
1140
+ return await this.applyProactiveQuestionPass(conversation, sanitized);
1141
+ }
1142
+
1143
+ this.emit({
1144
+ kind: "llm_error", traceId: fallbackTraceId, model: "fallback", operation: "extraction",
1145
+ durationMs: fallbackDurationMs, error: "fallback returned no parsed output",
1146
+ });
1147
+ log.warn("extraction fallback returned no parsed output");
1148
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
1149
+ } catch (err) {
1150
+ this.emit({
1151
+ kind: "llm_error", traceId: fallbackTraceId, model: "fallback", operation: "extraction",
1152
+ durationMs: Date.now() - fallbackStartTime, error: String(err),
1153
+ });
1154
+ log.error("extraction fallback failed", err);
1155
+ return { facts: [], profileUpdates: [], entities: [], questions: [] };
1156
+ } finally {
1157
+ try { this.profiler.endSpan("gateway-fallback", extractionTraceId); } catch { /* span may already be closed */ }
1158
+ }
1159
+
1160
+ } finally {
1161
+ // --- profiling: close the total span and trace ---
1162
+ this.profiler.endSpan("total", extractionTraceId);
1163
+ this.profiler.endTrace(extractionTraceId); // persists to JSONL file
1164
+ }
1165
+ }
1166
+
1167
+ /**
1168
+ * Extract memories using local LLM with JSON mode.
1169
+ * Uses a minimal prompt to fit within local model context limits (typically 4k-8k).
1170
+ */
1171
+ private async extractWithLocalLlm(conversation: string, existingEntities?: string[]): Promise<ExtractionResult | null> {
1172
+ log.debug(
1173
+ `extractWithLocalLlm: starting extraction, localLlmEnabled=${this.shouldUseLocalLlm}, model=${this.config.localLlmModel}`,
1174
+ );
1175
+
1176
+ // Get dynamic context sizes based on model capabilities (with optional user override)
1177
+ const contextSizes = this.modelRegistry.calculateContextSizes(
1178
+ this.config.localLlmModel,
1179
+ this.config.localLlmMaxContext
1180
+ );
1181
+ log.debug(`Model context: ${contextSizes.description}`);
1182
+
1183
+ const maxConversationChars = contextSizes.maxInputChars;
1184
+ const truncatedConversation = conversation.length > maxConversationChars
1185
+ ? conversation.slice(0, maxConversationChars) + "\n\n[truncated]"
1186
+ : conversation;
1187
+
1188
+ const localPrompt = `You are a memory extraction system. Extract durable, reusable memories from this conversation.
1189
+
1190
+ Memory categories — use the MOST SPECIFIC category that fits:
1191
+ - fact: Objective information about the world
1192
+ - preference: User likes, dislikes, or stylistic choices
1193
+ - correction: User correcting a mistake (highest priority)
1194
+ - entity: People, projects, tools, companies (use canonical hyphenated names like "my-project")
1195
+ - decision: Choices made with rationale
1196
+ - relationship: How entities relate (e.g., "Alice manages Bob")
1197
+ - principle: Durable rules or operating beliefs (e.g., "never use X API")
1198
+ - commitment: Promises, obligations, deadlines
1199
+ - moment: Emotionally significant events
1200
+ - skill: Demonstrated capabilities
1201
+ - rule: Explicit operational rules or constraints
1202
+ - procedure: Repeatable workflows — use when the user describes a multi-step play (≥2 ordered steps). Put the human-readable trigger/context in "content" (e.g. "When you deploy…") and list steps in "procedureSteps" as [{"order":1,"intent":"…"}, …] mirroring the gateway extraction schema.
1203
+ - reasoning_trace: Stored solution chains — use when the user narrates HOW they solved a specific problem step-by-step ("here's how I figured out…", "the debugging went like this…"). Put a short title in "content" (e.g. "How I debugged the staging latency spike") and the chain in "reasoningTrace": {"steps":[{"order":1,"description":"…"}, …], "finalAnswer":"…", "observedOutcome":"…" (optional)}. Require ≥2 ordered steps and a finalAnswer. Do NOT use for ordinary decisions (prefer "decision") or reusable workflows (prefer "procedure").
1204
+
1205
+ IMPORTANT: Do NOT label everything as "fact". Use "decision" for architectural choices, "commitment" for deadlines/promises, "principle" for reusable rules, "correction" for when the user rejects a suggestion, etc.
1206
+
1207
+ === DO NOT EXTRACT (negative examples) ===
1208
+ These are operational noise - skip them:
1209
+ - "The user has a cron job that runs every 30 minutes" (scheduled task descriptions)
1210
+ - "The user encountered error XYZ at 3:45 PM" (temporary error states)
1211
+ - "The file is located at /path/to/project/file" (transient file paths)
1212
+ - "The system is using 4GB of memory" (current resource usage)
1213
+ - "The user ran the 'git status' command" (individual command executions)
1214
+ - "The conversation took place on Tuesday" (session metadata)
1215
+ - "The agent read the file at /path/to/file.txt" (agent's own actions)
1216
+ - "The user's OpenClaw automation posts to #channel on failures" (automation behavior descriptions)
1217
+ - "The user stores state in /path/to/state.json" (implementation details)
1218
+ - "The X-watch automation has been stalled for 58 hours" (system status updates)
1219
+ - "The user processed 5 batch files and extracted insights" (processing summaries)
1220
+ - "The user has a cron job that runs a Checkpoint Loop every 2 hours" (automation schedules)
1221
+ - "The user runs a Morning Surprise cron job daily at 7:30 AM" (automation schedules)
1222
+ - "The user runs an X Bookmarks → Insights pipeline hourly at :13" (automation schedules)
1223
+ - "The user's system mines X/Twitter mentions for ideas every 10a/2p/6p" (automation schedules)
1224
+ - "The user runs a Health Insights cron job weekday mornings" (automation schedules)
1225
+ - "The system monitors the showcase page every 12 hours" (system monitoring configurations)
1226
+
1227
+ === DO EXTRACT (positive examples) ===
1228
+ These are durable insights - capture them:
1229
+ - "The user prefers dark mode interfaces and finds light mode uncomfortable" (preference)
1230
+ - "The user works primarily with TypeScript and avoids Python for frontend code" (long-term fact)
1231
+ - "The user's side project 'alpha-trader' uses a custom algorithm for arbitrage" (entity + detail)
1232
+ - "The user corrected that PostgreSQL 15 is required, not version 14" (correction)
1233
+ - "The user never commits code without running tests first" (principle)
1234
+ - "The user has a meeting with the design team every Friday at 2pm" (commitment)
1235
+
1236
+ === Rules ===
1237
+ - Extract only NEW information worth remembering across sessions
1238
+ - Skip transient details (file paths, current errors, temporary states, agent actions)
1239
+ - Confidence: Explicit (0.95-1.0), Implied (0.70-0.94), Inferred (0.40-0.69), Speculative (0.00-0.39)
1240
+ - Corrections get highest confidence (0.95+)
1241
+ - Each fact should be standalone and self-contained
1242
+ - Lines labelled [context user] or [context assistant] are reference context only. Use them to resolve pronouns and adjacent question/answer pairs, but do not extract a memory stated only in context lines unless a normal [user] or [assistant] line confirms or completes it.
1243
+ - CRITICAL: Use canonical hyphenated entity names (e.g., "jane-doe" not "janedoe")
1244
+ - CRITICAL: NEVER extract the same fact twice - check for duplicates before adding to facts array
1245
+ - CRITICAL: NEVER extract cron job schedules, automation configurations, or system monitoring details (these are operational noise)
1246
+ - If uncertain about relevance, prefer NOT extracting${this.config.extractionScopeClassificationEnabled ? `
1247
+ - For each fact, set "scope" to "global" (cross-project knowledge: framework bugs, library behavior, user preferences, tool configs, general patterns) or "project" (codebase-specific: file paths, env configs, deployment details, project workarounds). When in doubt, prefer "project".` : ""}
1248
+
1249
+ === Structured Attributes ===
1250
+ When a fact contains measurable, categorical, or precisely valued data, add a "structuredAttributes" object with key-value string pairs. This captures exact values for precise retrieval later.
1251
+ Examples of when to add structuredAttributes:
1252
+ - Product details: {"price": "29.99", "brand": "Sony", "color": "black", "rating": "4.5"}
1253
+ - Person details: {"age": "32", "occupation": "engineer", "city": "Austin"}
1254
+ - Events with dates: {"date": "2024-03-15", "location": "San Francisco"}
1255
+ - Decisions: {"chosen": "PostgreSQL", "rejected": "MongoDB", "reason": "ACID compliance"}
1256
+ - Quantities/measurements: {"budget": "50000", "team_size": "5", "deadline": "2024-06-01"}
1257
+ Only add structuredAttributes when there are concrete values. Skip for abstract or narrative facts.
1258
+
1259
+ Also generate:
1260
+ 1. 1-3 genuine questions you're curious about from this conversation
1261
+ 2. Profile updates about user patterns/behaviors (if any)
1262
+ 3. Relationships between entities (max 5). Use normalized names like "person-jane-doe", "company-acme-corp".
1263
+ 4. For entity facts that fit a durable named heading, include entity.structuredSections with {key, title, facts}.
1264
+
1265
+ Output JSON:
1266
+ {
1267
+ "facts": [{"category": "decision", "content": "Chose PostgreSQL over MongoDB for the user service", "importance": 8, "confidence": 0.9, "scope": "project", "structuredAttributes": {"chosen": "PostgreSQL", "rejected": "MongoDB"}}, {"category": "procedure", "content": "When you cut a hotfix release, follow the checklist", "importance": 8, "confidence": 0.9, "scope": "project", "procedureSteps": [{"order": 1, "intent": "Branch from main and cherry-pick the fix"}, {"order": 2, "intent": "Run CI and tag the release"}]}, {"category": "reasoning_trace", "content": "How I debugged the staging latency spike", "importance": 7, "confidence": 0.9, "scope": "project", "reasoningTrace": {"steps": [{"order": 1, "description": "Checked CPU/memory dashboards — both were flat"}, {"order": 2, "description": "Ran a traceroute and saw retries against the cache tier"}, {"order": 3, "description": "Tailed cache-tier logs and spotted eviction storms"}], "finalAnswer": "Root cause was an undersized eviction policy on the session cache", "observedOutcome": "Increased cache size, p95 returned to baseline within 10 minutes"}}, {"category": "commitment", "content": "Must ship v2.0 API by end of March", "importance": 10, "confidence": 1.0, "scope": "project", "structuredAttributes": {"deadline": "end of March", "deliverable": "v2.0 API"}}, {"category": "fact", "content": "The store backend uses Redis for session caching", "importance": 6, "confidence": 0.95, "scope": "project", "entityRef": "project-acme-store"}, {"category": "principle", "content": "Always run migrations in a transaction to avoid partial schema updates", "importance": 8, "confidence": 0.9, "scope": "global"}],
1268
+ "entities": [{"name": "person-jane-doe", "type": "person", "facts": ["Works at Acme Corp", "Prefers Python over JavaScript"], "structuredSections": [{"key": "beliefs", "title": "Beliefs", "facts": ["Python is a better fit than JavaScript for backend work."]}]}, {"name": "project-acme-store", "type": "project", "facts": ["Built with Next.js", "Deployed on Vercel"]}],
1269
+ "profileUpdates": ["User prefers dark mode in all editors"],
1270
+ "questions": [{"question": "Which cloud provider hosts the staging environment?", "context": "Came up during deployment discussion", "priority": 0.5}],
1271
+ "relationships": [{"source": "person-jane-doe", "target": "company-acme-corp", "label": "works at"}]
1272
+ }
1273
+
1274
+ Conversation:
1275
+ ${truncatedConversation}`;
1276
+
1277
+ log.debug(
1278
+ `extractWithLocalLlm: calling localLlm.chatCompletion with prompt length ${localPrompt.length}...`,
1279
+ );
1280
+ const response = await this.localLlm.chatCompletion(
1281
+ [
1282
+ { role: "system", content: "You are a memory extraction system. Output valid JSON only." },
1283
+ { role: "user", content: localPrompt },
1284
+ ],
1285
+ {
1286
+ temperature: 0.1,
1287
+ maxTokens: contextSizes.maxOutputTokens,
1288
+ operation: "extraction",
1289
+ priority: "background",
1290
+ },
1291
+ );
1292
+
1293
+ if (!response?.content) {
1294
+ log.debug("extractWithLocalLlm: chatCompletion returned null or empty content");
1295
+ return null;
1296
+ }
1297
+
1298
+ const content = response.content.trim();
1299
+ // Avoid logging model output content by default (may contain user data).
1300
+ log.debug(`extractWithLocalLlm: got response content, length=${content.length}`);
1301
+
1302
+ try {
1303
+ for (const candidate of extractJsonCandidates(content)) {
1304
+ try {
1305
+ log.debug(`extractWithLocalLlm: attempting JSON parse, candidate length=${candidate.length}`);
1306
+ const parsed = JSON.parse(candidate);
1307
+
1308
+ const result: ExtractionResult = this.normalizeExtractionResultPayload(parsed);
1309
+
1310
+ log.debug(
1311
+ `extractWithLocalLlm: successfully parsed response, facts=${result.facts.length}, entities=${result.entities.length}, profileUpdates=${result.profileUpdates.length}, questions=${result.questions.length}`,
1312
+ );
1313
+ return result;
1314
+ } catch {
1315
+ // keep trying candidates
1316
+ }
1317
+ }
1318
+ return null;
1319
+ } catch (err) {
1320
+ // Try to extract partial facts from truncated JSON
1321
+ log.debug("extractWithLocalLlm: JSON parse failed, attempting partial extraction...");
1322
+ const partial = this.extractPartialFacts(content);
1323
+ if (partial.facts.length > 0 || partial.entities.length > 0) {
1324
+ log.debug(
1325
+ `extractWithLocalLlm: extracted ${partial.facts.length} partial facts from truncated JSON`,
1326
+ );
1327
+ return partial;
1328
+ }
1329
+
1330
+ // Could not extract anything
1331
+ const errMsg = err instanceof Error ? err.message : String(err);
1332
+ log.debug(`extractWithLocalLlm: JSON parse error: ${errMsg}`);
1333
+ return null;
1334
+ }
1335
+ }
1336
+
1337
+ /**
1338
+ * Extract memories using direct OpenAI-compatible client (Chat Completions API).
1339
+ * Works with Scryr, OpenRouter, and other OpenAI-compatible endpoints.
1340
+ */
1341
+ private async extractWithDirectClient(
1342
+ conversation: string,
1343
+ existingEntities?: string[],
1344
+ ): Promise<ExtractionResult | null> {
1345
+ if (!this.client) return null;
1346
+
1347
+ const tokenParams = buildChatCompletionTokenLimit(this.config.model, this.config.extractionMaxOutputTokens, {
1348
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
1349
+ });
1350
+ log.debug(`extractWithDirectClient: calling model=${this.config.model} tokenParams=${JSON.stringify(tokenParams)}`);
1351
+
1352
+ const response = await this.client.chat.completions.create({
1353
+ model: this.config.model,
1354
+ messages: [
1355
+ {
1356
+ role: "system",
1357
+ content:
1358
+ this.buildExtractionInstructions(existingEntities) +
1359
+ `\n\nRespond with valid JSON matching this schema:
1360
+ {
1361
+ "facts": [{"category": "decision", "content": "Chose React over Vue for the dashboard rewrite", "importance": 8, "confidence": 0.9, "tags": ["frontend"], "scope": "project", "structuredAttributes": {"chosen": "React", "rejected": "Vue"}}, {"category": "fact", "content": "The API gateway uses rate limiting at 1000 req/min", "importance": 6, "confidence": 0.95, "tags": ["infra"], "scope": "project", "entityRef": "project-dashboard", "structuredAttributes": {"rate_limit": "1000 req/min"}}, {"category": "reasoning_trace", "content": "How I chose the dashboard rewrite framework", "confidence": 0.9, "tags": ["frontend"], "scope": "project", "reasoningTrace": {"steps": [{"order": 1, "description": "Listed constraints: SSR needed, team mostly JS"}, {"order": 2, "description": "Ran a spike in Vue 3 — worked, but ecosystem felt thin for our needs"}, {"order": 3, "description": "Ran the same spike in React — integrated faster with Next.js"}], "finalAnswer": "Picked React with Next.js for SSR + ecosystem fit"}}],
1362
+ "entities": [{"name": "person-sarah-chen", "type": "person", "facts": ["Leads the backend team", "Joined from Google in 2024"], "structuredSections": [{"key": "beliefs", "title": "Beliefs", "facts": ["Small teams should own whole systems."]}]}, {"name": "project-dashboard", "type": "project", "facts": ["React-based admin panel", "Deployed on AWS ECS"]}],
1363
+ "profileUpdates": ["User prefers TypeScript over plain JavaScript"],
1364
+ "questions": [{"question": "What database does the analytics service use?", "context": "Came up during discussion of migration plan", "priority": 0.5}],
1365
+ "relationships": [{"source": "person-sarah-chen", "target": "project-dashboard", "label": "leads development of"}]
1366
+ }`,
1367
+ },
1368
+ { role: "user", content: conversation },
1369
+ ],
1370
+ ...tokenParams,
1371
+ });
1372
+
1373
+ const content = response.choices?.[0]?.message?.content?.trim();
1374
+ if (!content) {
1375
+ log.info(`extractWithDirectClient: empty response — choices=${JSON.stringify(response.choices?.length ?? 0)} finishReason=${response.choices?.[0]?.finish_reason ?? "n/a"}`);
1376
+ return null;
1377
+ }
1378
+
1379
+ log.info(
1380
+ `extractWithDirectClient: got response, length=${content.length}`,
1381
+ );
1382
+
1383
+ for (const candidate of extractJsonCandidates(content)) {
1384
+ try {
1385
+ const parsed = JSON.parse(candidate);
1386
+
1387
+ return this.normalizeExtractionResultPayload(parsed);
1388
+ } catch {
1389
+ // keep trying candidates
1390
+ }
1391
+ }
1392
+
1393
+ log.info(`extractWithDirectClient: failed to parse JSON from response (first 200 chars: ${content.slice(0, 200)})`);
1394
+ return null;
1395
+ }
1396
+
1397
+ /**
1398
+ * Extract partial facts from truncated JSON responses.
1399
+ * Local LLMs sometimes hit token limits mid-JSON. This tries to salvage valid facts.
1400
+ */
1401
+ private extractPartialFacts(jsonStr: string): ExtractionResult {
1402
+ const allowedCategories = new Set([
1403
+ "fact",
1404
+ "preference",
1405
+ "correction",
1406
+ "entity",
1407
+ "decision",
1408
+ "relationship",
1409
+ "principle",
1410
+ "commitment",
1411
+ "moment",
1412
+ "skill",
1413
+ "rule",
1414
+ "procedure",
1415
+ "reasoning_trace",
1416
+ ]);
1417
+ const allowedEntityTypes = new Set([
1418
+ "person",
1419
+ "project",
1420
+ "tool",
1421
+ "company",
1422
+ "place",
1423
+ "other",
1424
+ ]);
1425
+
1426
+ const facts: ExtractionResult["facts"] = [];
1427
+ const entities: ExtractionResult["entities"] = [];
1428
+
1429
+ try {
1430
+ // Find all complete fact objects (ones with all required fields)
1431
+ const factRegex = /\{\s*"category"\s*:\s*"([^"]+)"\s*,\s*"content"\s*:\s*"([^"]+)"\s*,\s*"confidence"\s*:\s*([0-9.]+)/g;
1432
+ let match;
1433
+ while ((match = factRegex.exec(jsonStr)) !== null) {
1434
+ const rawCat = match[1];
1435
+ const category = allowedCategories.has(rawCat) ? (rawCat as ExtractionResult["facts"][number]["category"]) : "fact";
1436
+ facts.push({
1437
+ category,
1438
+ content: match[2].replace(/\\n/g, '\n').replace(/\\"/g, '"'),
1439
+ confidence: parseFloat(match[3]),
1440
+ tags: [],
1441
+ });
1442
+ }
1443
+
1444
+ // Find all complete entity objects
1445
+ const entityRegex = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"type"\s*:\s*"([^"]+)"/g;
1446
+ while ((match = entityRegex.exec(jsonStr)) !== null) {
1447
+ const rawType = match[2];
1448
+ const type = allowedEntityTypes.has(rawType) ? (rawType as ExtractionResult["entities"][number]["type"]) : "other";
1449
+ entities.push({
1450
+ name: match[1],
1451
+ type,
1452
+ facts: [],
1453
+ });
1454
+ }
1455
+ } catch {
1456
+ // Ignore regex errors
1457
+ }
1458
+
1459
+ return { facts, entities, profileUpdates: [], questions: [] };
1460
+ }
1461
+
1462
+ /**
1463
+ * Build extraction instructions shared between local and cloud LLM.
1464
+ */
1465
+ private buildExtractionInstructions(existingEntities?: string[]): string {
1466
+ return `You are a memory extraction system. Analyze the following conversation and extract durable, reusable memories.
1467
+
1468
+ Memory categories:
1469
+ - fact: Objective information about the world
1470
+ - preference: User likes, dislikes, or stylistic choices
1471
+ - correction: User correcting a mistake or misconception (highest priority)
1472
+ - entity: Information about a specific person, project, tool, or company
1473
+ - decision: A choice that was made with rationale
1474
+ - relationship: How two entities relate to each other (e.g., "Alice is Bob's manager", "Acme Corp uses Shopify")
1475
+ - principle: Durable rules, values, or operating beliefs (e.g., "never use Chat Completions API")
1476
+ - commitment: Promises, obligations, or deadlines (e.g., "deploy by Friday", "call accountant Monday")
1477
+ - moment: Emotionally significant events or milestones (e.g., "first successful deployment of engram")
1478
+ - skill: Capabilities the user or agent has demonstrated (e.g., "user is proficient with Kubernetes")${this.config.causalRuleExtractionEnabled ? `
1479
+ - rule: Causal rules discovered through experience (format: "IF <condition> THEN <action/outcome>", e.g., "IF Shopify API returns 401 THEN the admin token is missing read_products scope")` : ""}
1480
+ - procedure: A reusable workflow the user wants remembered the same way across sessions. Set category to "procedure". Use "content" for a short title that includes explicit trigger phrasing (e.g. "When you deploy to production…", "Whenever you ship a release…"). Add "procedureSteps": an array of at least two objects {"order": number, "intent": "concrete step description"} in execution order. Optional per-step "toolCall": {"kind": "…", "signature": "…"}, "expectedOutcome", "optional": true.
1481
+ - reasoning_trace: A stored solution chain / chain-of-thought the user walked through to solve a problem (e.g. "Here's how I debugged the latency spike: first I checked…, then I…, finally I…"). Set category to "reasoning_trace". Use "content" for a short title summarising the problem (e.g. "How I debugged the staging latency spike"). Add "reasoningTrace": {"steps": [{"order": number, "description": "what happened at this step"}, …], "finalAnswer": "the conclusion or answer", "observedOutcome": "optional confirmation of how it played out"}. Require at least two ordered steps AND a finalAnswer. Use this category only when the user explicitly narrates their reasoning — not for ordinary decisions (use "decision") or reusable workflows (use "procedure").
1482
+
1483
+ Rules:
1484
+ - Only extract genuinely NEW information worth remembering across sessions
1485
+ - Skip transient task details (file paths being edited, current errors, etc.)
1486
+ - Priority: corrections > principles${this.config.causalRuleExtractionEnabled ? " > rules" : ""} > preferences > commitments > decisions > relationships > entities > moments > skills > facts
1487
+ - Corrections (user saying "actually, don't do X" or "I prefer Y") get highest confidence
1488
+ - Each fact should be a standalone, self-contained statement
1489
+ - Lines labelled [context user] or [context assistant] are reference context only. Use them to resolve pronouns and adjacent question/answer pairs, but do not extract a memory stated only in context lines unless a normal [user] or [assistant] line confirms or completes it.
1490
+ - Entity references should use normalized names (lowercase, hyphenated: "jane-doe", "acme-corp")
1491
+ - CRITICAL: Entity names must be CANONICAL. Always use the hyphenated multi-word form: "acme-corp" NOT "acmecorp" or "acme". "jane-doe" NOT "janedoe" or "jane". If unsure, prefer the most specific full name.
1492
+ - Avoid creating entities typed as "other" when a more specific type fits (company, project, tool, person, place)
1493
+ - When entity facts clearly belong under a durable named heading, add them to entity.structuredSections as {key, title, facts}. Example person headings: "Beliefs", "Communication Style", "Building / Working On". Leave structuredSections empty when no stable heading fits.
1494
+ - Tags should be concise and reusable (e.g., "coding-style", "personal", "tools")
1495
+ - When a fact contains measurable, categorical, or precisely valued data, include a "structuredAttributes" field with key-value string pairs (e.g., {"price": "29.99", "brand": "Sony"}, {"date": "2024-03-15", "location": "SF"}, {"chosen": "PostgreSQL", "rejected": "MongoDB"}). Only for concrete values, not narrative content.
1496
+ - Set confidence using these tiers:
1497
+ * Explicit (0.95-1.0): Direct user statements — "I prefer X", "my name is Y"
1498
+ * Implied (0.70-0.94): Strong contextual inference — user consistently does X, clear from conversation flow
1499
+ * Inferred (0.40-0.69): Pattern recognition — reasonable guess from limited evidence
1500
+ * Speculative (0.00-0.39): Tentative hypothesis — weak signal, needs future confirmation. Speculative memories auto-expire after 30 days if not confirmed.
1501
+ - For commitments: include any deadline or timeframe mentioned${this.config.extractionScopeClassificationEnabled ? `
1502
+
1503
+ Scope classification:
1504
+ For each fact, set "scope" to one of:
1505
+ - "global" — knowledge that applies across projects: core framework/library bugs, API behavior patterns, user preferences (editor, language, style), tool configurations, general coding patterns, infrastructure knowledge, technology facts not tied to one codebase
1506
+ - "project" — knowledge specific to one codebase: file paths, environment configs, deployment details, project-specific workarounds, team/stakeholder info tied to one project, repo-specific conventions
1507
+ When in doubt, prefer "project" — it is safer to keep knowledge scoped narrowly.
1508
+ Examples:
1509
+ "Magento 2.4.8 has a race condition in checkout" → "global"
1510
+ "User prefers dark mode in all editors" → "global"
1511
+ "The staging server is at staging.acme.com" → "project"
1512
+ "The deploy script lives at scripts/deploy.sh" → "project"
1513
+ "PostgreSQL 15 requires the uuid-ossp extension for gen_random_uuid()" → "global"
1514
+ "The acme-store repo uses a custom Webpack config for SSR" → "project"` : ""}
1515
+
1516
+ Entity creation rules (STRICT):
1517
+ - Only create entities for DURABLE things: real people, companies, products, tools, ongoing projects
1518
+ - NEVER create entities for transient items: individual PRs, branches, Jira tickets, meetings, agent task IDs, log files, database tables, cron job runs, sessions
1519
+ - When you learn something about a transient item (e.g., PR #58 fixed a bug), store it as a FACT with an entityRef to the parent project — do NOT create an entity for the PR itself
1520
+ - Prefer attaching facts to broad parent entities rather than creating sub-entities. E.g., "acme-store uses Algolia for search" is a fact on entity "acme-store", NOT a new entity "acme-store-algolia-connector"
1521
+ - The entity list should be SHORT — think "things that would have their own Wikipedia page" not "things mentioned in passing"
1522
+
1523
+ ${existingEntities && existingEntities.length > 0 ? `
1524
+ KNOWN ENTITIES (use these exact names when referencing existing things):
1525
+ ${existingEntities.join(", ")}
1526
+
1527
+ When you see something that matches a known entity, use THAT name exactly. Only create a NEW entity if nothing in this list represents it.
1528
+ ` : ""}
1529
+ Also extract relationships between entities mentioned in the conversation.
1530
+ - Format: {source: "entity-name", target: "entity-name", label: "relationship description"}
1531
+ - Max 5 relationships per extraction
1532
+ - Only include clear, durable relationships (e.g., "works at", "created", "manages", "uses")
1533
+ - Use normalized entity names (e.g., "person-jane-doe", "company-acme-corp")
1534
+
1535
+ Also generate 1-3 genuine questions you're curious about based on this conversation. These should be things you'd actually want answers to in future sessions — not prompts, but real curiosity.
1536
+
1537
+ Finally, write a brief identity reflection about the AGENT who had this conversation (not about you, the extraction system). Based on what the agent said and did in the conversation:
1538
+ - What communication patterns did the agent show? (e.g., proactive vs reactive, verbose vs concise)
1539
+ - Did the agent handle the user's needs well or miss something?
1540
+ - What behavioral tendencies are visible? (e.g., cautious, creative, thorough, impatient)
1541
+ - What could the agent improve next time?
1542
+ Do NOT write about the extraction process itself. Do NOT say things like "I extracted durable facts" — that's about YOUR job, not the agent's behavior.`;
1543
+ }
1544
+
1545
+ async consolidate(
1546
+ newMemories: MemoryFile[],
1547
+ existingMemories: MemoryFile[],
1548
+ currentProfile: string,
1549
+ ): Promise<ConsolidationResult> {
1550
+ const newList = newMemories
1551
+ .map(
1552
+ (m) =>
1553
+ `[${m.frontmatter.id}] (${m.frontmatter.category}) ${m.content}`,
1554
+ )
1555
+ .join("\n");
1556
+
1557
+ const existingList = existingMemories
1558
+ .slice(-50) // Only consolidate against recent memories
1559
+ .map(
1560
+ (m) =>
1561
+ `[${m.frontmatter.id}] (${m.frontmatter.category}) ${m.content}`,
1562
+ )
1563
+ .join("\n");
1564
+
1565
+ const cTraceId = crypto.randomUUID();
1566
+ this.emit({ kind: "llm_start", traceId: cTraceId, model: this.config.model, operation: "consolidation", input: newList });
1567
+ const cStartTime = Date.now();
1568
+
1569
+ // Try local LLM first if enabled
1570
+ if (this.shouldUseLocalLlm) {
1571
+ try {
1572
+ const localResult = await this.consolidateWithLocalLlm(newList, existingList, currentProfile);
1573
+ if (localResult) {
1574
+ const durationMs = Date.now() - cStartTime;
1575
+ this.emit({ kind: "llm_end", traceId: cTraceId, model: this.config.localLlmModel, operation: "consolidation", durationMs });
1576
+ log.debug(`consolidation: used local LLM — ${localResult.items.length} decisions`);
1577
+ return this.sanitizeConsolidationResult(localResult);
1578
+ }
1579
+ if (!this.config.localLlmFallback) {
1580
+ log.warn("consolidation: local LLM failed and fallback disabled");
1581
+ return { items: [], profileUpdates: [], entityUpdates: [] };
1582
+ }
1583
+ log.info("consolidation: local LLM unavailable, falling back to gateway AI");
1584
+ } catch (err) {
1585
+ if (!this.config.localLlmFallback) {
1586
+ log.warn("consolidation: local LLM error and fallback disabled:", err);
1587
+ return { items: [], profileUpdates: [], entityUpdates: [] };
1588
+ }
1589
+ log.info("consolidation: local LLM error, falling back to gateway AI:", err);
1590
+ }
1591
+ }
1592
+
1593
+ const fallbackResult = await this.parseWithGatewayFallback(
1594
+ cTraceId,
1595
+ "consolidation",
1596
+ cStartTime,
1597
+ ConsolidationResultSchema,
1598
+ [
1599
+ {
1600
+ role: "system",
1601
+ content: `You are a memory consolidation system. Compare new memories against existing ones and decide what to do with each.
1602
+
1603
+ Actions:
1604
+ - ADD: Keep the new memory as-is (no duplicate exists)
1605
+ - MERGE: Combine with an existing memory (provide mergeWith ID and updated content)
1606
+ - UPDATE: Replace existing memory content (provide updated content)
1607
+ - INVALIDATE: Remove existing memory (it's been superseded or is wrong)
1608
+ - SKIP: This new memory is redundant (exact duplicate or subset of existing)
1609
+
1610
+ Also:
1611
+ - Suggest profile updates based on patterns across memories
1612
+ - Identify entity updates for entity tracking${this.config.causalRuleExtractionEnabled ? `
1613
+ - When merging or updating memories, look for IF→THEN causal patterns. If a memory describes "X failed/succeeded because Y" or "doing X led to Y", rewrite its content to make the causal rule explicit in the form "IF <condition> THEN <action/outcome>".` : ""}`,
1614
+ },
1615
+ {
1616
+ role: "user",
1617
+ content: `Current behavioral profile:
1618
+ ${currentProfile || "(empty)"}
1619
+
1620
+ Existing memories:
1621
+ ${existingList || "(none)"}
1622
+
1623
+ New memories to consolidate:
1624
+ ${newList}
1625
+
1626
+ Consolidate the new memories against existing ones.`,
1627
+ },
1628
+ ],
1629
+ { temperature: 0.3, maxTokens: 4096 },
1630
+ );
1631
+ if (fallbackResult) {
1632
+ log.debug(`consolidation: ${fallbackResult.items.length} decisions via fallback`);
1633
+ return this.sanitizeConsolidationResult({
1634
+ items: fallbackResult.items,
1635
+ profileUpdates: fallbackResult.profileUpdates,
1636
+ entityUpdates: fallbackResult.entityUpdates,
1637
+ });
1638
+ }
1639
+
1640
+ // Fall back to OpenAI API
1641
+ if (!this.client) {
1642
+ log.warn("consolidation skipped — no OpenAI API key and local LLM failed/disabled");
1643
+ return { items: [], profileUpdates: [], entityUpdates: [] };
1644
+ }
1645
+
1646
+ try {
1647
+ const instructionText = `You are a memory consolidation system. Compare new memories against existing ones and decide what to do with each.
1648
+
1649
+ Actions:
1650
+ - ADD: Keep the new memory as-is (no duplicate exists)
1651
+ - MERGE: Combine with an existing memory (provide mergeWith ID and updated content)
1652
+ - UPDATE: Replace existing memory content (provide updated content)
1653
+ - INVALIDATE: Remove existing memory (it's been superseded or is wrong)
1654
+ - SKIP: This new memory is redundant (exact duplicate or subset of existing)
1655
+
1656
+ Also:
1657
+ - Suggest profile updates based on patterns across memories
1658
+ - Identify entity updates for entity tracking${this.config.causalRuleExtractionEnabled ? `
1659
+ - When merging or updating memories, look for IF→THEN causal patterns. If a memory describes "X failed/succeeded because Y" or "doing X led to Y", rewrite its content to make the causal rule explicit in the form "IF <condition> THEN <action/outcome>".` : ""}
1660
+
1661
+ Current behavioral profile:
1662
+ ${currentProfile || "(empty)"}
1663
+
1664
+ Existing memories:
1665
+ ${existingList || "(none)"}
1666
+
1667
+ New memories to consolidate:
1668
+ ${newList}
1669
+
1670
+ Respond with valid JSON only, matching this schema:
1671
+ ${CONSOLIDATION_RESPONSE_SCHEMA}`;
1672
+
1673
+ const response = await this.client.chat.completions.create({
1674
+ model: this.config.model,
1675
+ messages: [
1676
+ { role: "system", content: instructionText },
1677
+ { role: "user", content: "Consolidate the new memories against existing ones." },
1678
+ ],
1679
+ ...(this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {}),
1680
+ ...buildChatCompletionTokenLimit(this.config.model, 4096, {
1681
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
1682
+ }),
1683
+ });
1684
+
1685
+ const rawContent = response.choices?.[0]?.message?.content?.trim();
1686
+ const cDurationMs = Date.now() - cStartTime;
1687
+ const cUsage = (response as any).usage;
1688
+
1689
+ let parsed: any = null;
1690
+ if (rawContent) {
1691
+ for (const candidate of extractJsonCandidates(rawContent)) {
1692
+ try {
1693
+ parsed = JSON.parse(candidate);
1694
+ break;
1695
+ } catch {
1696
+ // keep trying candidates
1697
+ }
1698
+ }
1699
+ }
1700
+
1701
+ this.emit({
1702
+ kind: "llm_end", traceId: cTraceId, model: this.config.model, operation: "consolidation", durationMs: cDurationMs,
1703
+ output: parsed ? JSON.stringify(parsed).slice(0, 2000) : undefined,
1704
+ tokenUsage: cUsage ? { input: cUsage.prompt_tokens, output: cUsage.completion_tokens, total: cUsage.total_tokens } : undefined,
1705
+ });
1706
+
1707
+ if (parsed && Array.isArray(parsed.items)) {
1708
+ log.debug(
1709
+ `consolidation: ${parsed.items.length} decisions`,
1710
+ );
1711
+ return this.sanitizeConsolidationResult({
1712
+ items: parsed.items,
1713
+ profileUpdates: Array.isArray(parsed.profileUpdates) ? parsed.profileUpdates : [],
1714
+ entityUpdates: Array.isArray(parsed.entityUpdates) ? parsed.entityUpdates : [],
1715
+ });
1716
+ }
1717
+
1718
+ log.warn("consolidation returned no parsed output");
1719
+ return { items: [], profileUpdates: [], entityUpdates: [] };
1720
+ } catch (err) {
1721
+ this.emit({
1722
+ kind: "llm_error", traceId: cTraceId, model: this.config.model, operation: "consolidation",
1723
+ durationMs: Date.now() - cStartTime, error: String(err),
1724
+ });
1725
+ log.error("consolidation failed", err);
1726
+ return { items: [], profileUpdates: [], entityUpdates: [] };
1727
+ }
1728
+ }
1729
+
1730
+ /**
1731
+ * Consolidate memories using local LLM.
1732
+ */
1733
+ private async consolidateWithLocalLlm(
1734
+ newList: string,
1735
+ existingList: string,
1736
+ currentProfile: string,
1737
+ ): Promise<ConsolidationResult | null> {
1738
+ // Get dynamic context sizes
1739
+ const contextSizes = this.modelRegistry.calculateContextSizes(
1740
+ this.config.localLlmModel,
1741
+ this.config.localLlmMaxContext
1742
+ );
1743
+ log.debug(`Consolidation model context: ${contextSizes.description}`);
1744
+
1745
+ const prompt = `You are a memory consolidation system. Compare new memories against existing ones and decide what to do with each.
1746
+
1747
+ Actions:
1748
+ - ADD: Keep the new memory as-is (no duplicate exists)
1749
+ - MERGE: Combine with an existing memory (provide mergeWith ID and updated content)
1750
+ - UPDATE: Replace existing memory content (provide updated content)
1751
+ - INVALIDATE: Remove existing memory (it's been superseded or is wrong)
1752
+ - SKIP: This new memory is redundant (exact duplicate or subset of existing)
1753
+
1754
+ Also:
1755
+ - Suggest profile updates based on patterns across memories
1756
+ - Identify entity updates for entity tracking${this.config.causalRuleExtractionEnabled ? `
1757
+ - When merging or updating memories, look for IF→THEN causal patterns. If a memory describes "X failed/succeeded because Y" or "doing X led to Y", rewrite its content to make the causal rule explicit in the form "IF <condition> THEN <action/outcome>".` : ""}
1758
+
1759
+ Current behavioral profile:
1760
+ ${currentProfile || "(empty)"}
1761
+
1762
+ Existing memories:
1763
+ ${existingList || "(none)"}
1764
+
1765
+ New memories to consolidate:
1766
+ ${newList}
1767
+
1768
+ Respond with valid JSON matching this schema:
1769
+ ${CONSOLIDATION_RESPONSE_SCHEMA}`;
1770
+
1771
+ const response = await this.localLlm.chatCompletion(
1772
+ [
1773
+ { role: "system", content: "You are a memory consolidation system. Output valid JSON only." },
1774
+ { role: "user", content: prompt },
1775
+ ],
1776
+ {
1777
+ temperature: 0.3,
1778
+ maxTokens: contextSizes.maxOutputTokens,
1779
+ operation: "consolidation",
1780
+ priority: "background",
1781
+ },
1782
+ );
1783
+
1784
+ if (!response?.content) {
1785
+ return null;
1786
+ }
1787
+
1788
+ try {
1789
+ const content = response.content.trim();
1790
+ for (const candidate of extractJsonCandidates(content)) {
1791
+ try {
1792
+ const parsed = JSON.parse(candidate);
1793
+ return {
1794
+ items: Array.isArray((parsed as any).items) ? (parsed as any).items : [],
1795
+ profileUpdates: Array.isArray((parsed as any).profileUpdates)
1796
+ ? (parsed as any).profileUpdates
1797
+ : [],
1798
+ entityUpdates: Array.isArray((parsed as any).entityUpdates)
1799
+ ? (parsed as any).entityUpdates
1800
+ : [],
1801
+ } as ConsolidationResult;
1802
+ } catch {
1803
+ // keep trying candidates
1804
+ }
1805
+ }
1806
+ return null;
1807
+ } catch (err) {
1808
+ log.warn("local LLM consolidation: failed to parse JSON response:", err);
1809
+ return null;
1810
+ }
1811
+ }
1812
+
1813
+ /**
1814
+ * Consolidate a bloated profile.md into a compact version.
1815
+ * The LLM merges duplicates, removes stale info, and preserves section structure.
1816
+ * Returns the consolidated markdown or null on failure.
1817
+ */
1818
+ async consolidateProfile(
1819
+ fullProfileContent: string,
1820
+ targetLines: number = 50,
1821
+ ): Promise<{ consolidatedProfile: string; removedCount: number; summary: string } | null> {
1822
+ const pTraceId = crypto.randomUUID();
1823
+ this.emit({ kind: "llm_start", traceId: pTraceId, model: this.config.model, operation: "profile_consolidation", input: fullProfileContent.slice(0, 2000) });
1824
+ const pStartTime = Date.now();
1825
+
1826
+ // Try local LLM first if enabled
1827
+ if (this.shouldUseLocalLlm) {
1828
+ try {
1829
+ const localResult = await this.consolidateProfileWithLocalLlm(fullProfileContent, targetLines);
1830
+ if (localResult) {
1831
+ const durationMs = Date.now() - pStartTime;
1832
+ this.emit({ kind: "llm_end", traceId: pTraceId, model: this.config.localLlmModel, operation: "profile_consolidation", durationMs });
1833
+ log.debug(`profile consolidation: used local LLM — removed ${localResult.removedCount} items`);
1834
+ return localResult;
1835
+ }
1836
+ if (!this.config.localLlmFallback) {
1837
+ log.warn("profile consolidation: local LLM failed and fallback disabled");
1838
+ return null;
1839
+ }
1840
+ log.info("profile consolidation: local LLM unavailable, falling back to gateway AI");
1841
+ } catch (err) {
1842
+ if (!this.config.localLlmFallback) {
1843
+ log.warn("profile consolidation: local LLM error and fallback disabled:", err);
1844
+ return null;
1845
+ }
1846
+ log.info("profile consolidation: local LLM error, falling back to gateway AI:", err);
1847
+ }
1848
+ }
1849
+
1850
+ const profileFallback = await this.parseWithGatewayFallback(
1851
+ pTraceId,
1852
+ "profile_consolidation",
1853
+ pStartTime,
1854
+ buildProfileConsolidationResultSchema(targetLines),
1855
+ [
1856
+ {
1857
+ role: "system",
1858
+ content: `You are a profile consolidation system. You are given a behavioral profile (markdown) that has grown too large. Your job is to produce a CONSOLIDATED version that:
1859
+
1860
+ 1. PRESERVES all ## section headers and their structure
1861
+ 2. MERGES duplicate or near-duplicate bullet points into single, clear statements
1862
+ 3. REMOVES stale information that has been superseded by newer bullets
1863
+ 4. REMOVES trivial or overly specific operational details that won't be useful across sessions
1864
+ 5. KEEPS the most important, durable observations about the user's preferences, habits, identity, and working style
1865
+ 6. Target roughly ${targetLines} lines — this is a soft target, prioritize quality over length
1866
+ 7. Write in the same style as the existing profile — concise bullets, no fluff
1867
+
1868
+ The output should be the COMPLETE consolidated profile as valid markdown, starting with "# Behavioral Profile".`,
1869
+ },
1870
+ { role: "user", content: fullProfileContent },
1871
+ ],
1872
+ { temperature: 0.3, maxTokens: 4096 },
1873
+ );
1874
+ if (profileFallback) {
1875
+ log.debug(
1876
+ `profile consolidation: removed ${profileFallback.removedCount} items — ${profileFallback.summary} (fallback)`,
1877
+ );
1878
+ return profileFallback;
1879
+ }
1880
+
1881
+ // Fall back to OpenAI API
1882
+ if (!this.client) {
1883
+ log.warn("profile consolidation skipped — no OpenAI API key and local LLM failed/disabled");
1884
+ return null;
1885
+ }
1886
+
1887
+ try {
1888
+ const instructionText = `You are a profile consolidation system. You are given a behavioral profile (markdown) that has grown too large. Your job is to produce a CONSOLIDATED version that:
1889
+
1890
+ 1. PRESERVES all ## section headers and their structure
1891
+ 2. MERGES duplicate or near-duplicate bullet points into single, clear statements
1892
+ 3. REMOVES stale information that has been superseded by newer bullets
1893
+ 4. REMOVES trivial or overly specific operational details that won't be useful across sessions
1894
+ 5. KEEPS the most important, durable observations about the user's preferences, habits, identity, and working style
1895
+ 6. Target roughly ${targetLines} lines — this is a soft target, prioritize quality over length
1896
+ 7. Write in the same style as the existing profile — concise bullets, no fluff
1897
+
1898
+ The output should be the COMPLETE consolidated profile as valid markdown, starting with "# Behavioral Profile".
1899
+
1900
+ Respond with valid JSON matching this schema:
1901
+ {
1902
+ "consolidatedProfile": "# Behavioral Profile\\n\\n... (complete markdown)",
1903
+ "removedCount": 42,
1904
+ "summary": "brief summary of what was consolidated"
1905
+ }`;
1906
+
1907
+ const response = await this.client.chat.completions.create({
1908
+ model: this.config.model,
1909
+ messages: [
1910
+ { role: "system", content: instructionText },
1911
+ { role: "user", content: fullProfileContent },
1912
+ ],
1913
+ ...(this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {}),
1914
+ ...buildChatCompletionTokenLimit(this.config.model, 4096, {
1915
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
1916
+ }),
1917
+ });
1918
+
1919
+ const rawContent = response.choices?.[0]?.message?.content?.trim();
1920
+ const pDurationMs = Date.now() - pStartTime;
1921
+ const pUsage = (response as any).usage;
1922
+
1923
+ let parsed: any = null;
1924
+ if (rawContent) {
1925
+ for (const candidate of extractJsonCandidates(rawContent)) {
1926
+ try {
1927
+ parsed = JSON.parse(candidate);
1928
+ break;
1929
+ } catch {
1930
+ // keep trying candidates
1931
+ }
1932
+ }
1933
+ }
1934
+
1935
+ this.emit({
1936
+ kind: "llm_end", traceId: pTraceId, model: this.config.model, operation: "profile_consolidation", durationMs: pDurationMs,
1937
+ output: parsed ? parsed.summary : undefined,
1938
+ tokenUsage: pUsage ? { input: pUsage.prompt_tokens, output: pUsage.completion_tokens, total: pUsage.total_tokens } : undefined,
1939
+ });
1940
+
1941
+ if (parsed && typeof parsed.consolidatedProfile === "string") {
1942
+ log.debug(
1943
+ `profile consolidation: removed ${parsed.removedCount ?? 0} items — ${parsed.summary ?? ""}`,
1944
+ );
1945
+ return {
1946
+ consolidatedProfile: parsed.consolidatedProfile,
1947
+ removedCount: Number(parsed.removedCount || 0),
1948
+ summary: String(parsed.summary || ""),
1949
+ };
1950
+ }
1951
+
1952
+ log.warn("profile consolidation returned no parsed output");
1953
+ return null;
1954
+ } catch (err) {
1955
+ this.emit({
1956
+ kind: "llm_error", traceId: pTraceId, model: this.config.model, operation: "profile_consolidation",
1957
+ durationMs: Date.now() - pStartTime, error: String(err),
1958
+ });
1959
+ log.error("profile consolidation failed", err);
1960
+ return null;
1961
+ }
1962
+ }
1963
+
1964
+ /**
1965
+ * Consolidate profile using local LLM.
1966
+ */
1967
+ private async consolidateProfileWithLocalLlm(
1968
+ fullProfileContent: string,
1969
+ targetLines: number = 50,
1970
+ ): Promise<{ consolidatedProfile: string; removedCount: number; summary: string } | null> {
1971
+ // Get dynamic context sizes
1972
+ const contextSizes = this.modelRegistry.calculateContextSizes(
1973
+ this.config.localLlmModel,
1974
+ this.config.localLlmMaxContext
1975
+ );
1976
+ log.debug(`Profile consolidation model context: ${contextSizes.description}`);
1977
+
1978
+ const prompt = `You are a profile consolidation system. You are given a behavioral profile (markdown) that has grown too large. Your job is to produce a CONSOLIDATED version that:
1979
+
1980
+ 1. PRESERVES all ## section headers and their structure
1981
+ 2. MERGES duplicate or near-duplicate bullet points into single, clear statements
1982
+ 3. REMOVES stale information that has been superseded by newer bullets
1983
+ 4. REMOVES trivial or overly specific operational details that won't be useful across sessions
1984
+ 5. KEEPS the most important, durable observations about the user's preferences, habits, identity, and working style
1985
+ 6. Target roughly ${targetLines} lines — this is a soft target, prioritize quality over length
1986
+ 7. Write in the same style as the existing profile — concise bullets, no fluff
1987
+
1988
+ Profile to consolidate:
1989
+ ${fullProfileContent}
1990
+
1991
+ Respond with valid JSON matching this schema:
1992
+ {
1993
+ "consolidatedProfile": "# Behavioral Profile\\n\\n... (complete markdown)",
1994
+ "removedCount": 42,
1995
+ "summary": "brief summary of what was consolidated"
1996
+ }`;
1997
+
1998
+ const response = await this.localLlm.chatCompletion(
1999
+ [
2000
+ { role: "system", content: "You are a profile consolidation system. Output valid JSON only." },
2001
+ { role: "user", content: prompt },
2002
+ ],
2003
+ {
2004
+ temperature: 0.3,
2005
+ maxTokens: contextSizes.maxOutputTokens,
2006
+ operation: "profile_consolidation",
2007
+ priority: "background",
2008
+ },
2009
+ );
2010
+
2011
+ if (!response?.content) {
2012
+ return null;
2013
+ }
2014
+
2015
+ try {
2016
+ const content = response.content.trim();
2017
+ for (const candidate of extractJsonCandidates(content)) {
2018
+ try {
2019
+ const parsed = JSON.parse(candidate);
2020
+ return {
2021
+ consolidatedProfile: String((parsed as any).consolidatedProfile || ""),
2022
+ removedCount: Number((parsed as any).removedCount || 0),
2023
+ summary: String((parsed as any).summary || ""),
2024
+ };
2025
+ } catch {
2026
+ // keep trying candidates
2027
+ }
2028
+ }
2029
+ return null;
2030
+ } catch (err) {
2031
+ log.warn("local LLM profile consolidation: failed to parse JSON response:", err);
2032
+ return null;
2033
+ }
2034
+ }
2035
+
2036
+ /**
2037
+ * Consolidate IDENTITY.md reflections into a concise "Learned Patterns" section.
2038
+ * Returns the new content for the IDENTITY.md file (everything below the static header).
2039
+ */
2040
+ async consolidateIdentity(
2041
+ fullIdentityContent: string,
2042
+ staticHeaderEndMarker: string,
2043
+ ): Promise<{ learnedPatterns: string[]; summary: string } | null> {
2044
+ const iTraceId = crypto.randomUUID();
2045
+ this.emit({ kind: "llm_start", traceId: iTraceId, model: this.config.model, operation: "identity_consolidation", input: fullIdentityContent.slice(0, 2000) });
2046
+ const iStartTime = Date.now();
2047
+
2048
+ // Try local LLM first if enabled
2049
+ if (this.shouldUseLocalLlm) {
2050
+ try {
2051
+ const localResult = await this.consolidateIdentityWithLocalLlm(fullIdentityContent);
2052
+ if (localResult) {
2053
+ const durationMs = Date.now() - iStartTime;
2054
+ this.emit({ kind: "llm_end", traceId: iTraceId, model: this.config.localLlmModel, operation: "identity_consolidation", durationMs });
2055
+ log.debug(`identity consolidation: used local LLM — ${localResult.learnedPatterns.length} patterns`);
2056
+ return localResult;
2057
+ }
2058
+ if (!this.config.localLlmFallback) {
2059
+ log.warn("identity consolidation: local LLM failed and fallback disabled");
2060
+ return null;
2061
+ }
2062
+ log.info("identity consolidation: local LLM unavailable, falling back to gateway AI");
2063
+ } catch (err) {
2064
+ if (!this.config.localLlmFallback) {
2065
+ log.warn("identity consolidation: local LLM error and fallback disabled:", err);
2066
+ return null;
2067
+ }
2068
+ log.info("identity consolidation: local LLM error, falling back to gateway AI:", err);
2069
+ }
2070
+ }
2071
+
2072
+ const identityFallback = await this.parseWithGatewayFallback(
2073
+ iTraceId,
2074
+ "identity_consolidation",
2075
+ iStartTime,
2076
+ IdentityConsolidationResultSchema,
2077
+ [
2078
+ {
2079
+ role: "system",
2080
+ content: `You are an identity consolidation system. You are given the full contents of an IDENTITY.md file that contains many individual reflection entries. Your job is to:
2081
+
2082
+ 1. Read all the reflection entries (sections starting with "## Reflection")
2083
+ 2. Extract the most important, durable behavioral patterns and lessons learned
2084
+ 3. Consolidate them into concise, standalone statements (aim for 10-25 key patterns)
2085
+ 4. Remove redundancy — if multiple reflections say the same thing, merge into one clear statement
2086
+ 5. Prioritize patterns that are actionable and recurring over one-off observations
2087
+ 6. Write a brief summary paragraph
2088
+
2089
+ The goal is to reduce a bloated file to a compact, high-signal set of learned patterns while preserving all genuinely useful self-knowledge.`,
2090
+ },
2091
+ { role: "user", content: fullIdentityContent },
2092
+ ],
2093
+ { temperature: 0.3, maxTokens: 4096 },
2094
+ );
2095
+ if (identityFallback) {
2096
+ log.debug(
2097
+ `identity consolidation: ${identityFallback.learnedPatterns.length} patterns (fallback)`,
2098
+ );
2099
+ return identityFallback;
2100
+ }
2101
+
2102
+ // Fall back to OpenAI API
2103
+ if (!this.client) {
2104
+ log.warn("identity consolidation skipped — no OpenAI API key and local LLM failed/disabled");
2105
+ return null;
2106
+ }
2107
+
2108
+ try {
2109
+ const instructionText = `You are an identity consolidation system. You are given the full contents of an IDENTITY.md file that contains many individual reflection entries. Your job is to:
2110
+
2111
+ 1. Read all the reflection entries (sections starting with "## Reflection")
2112
+ 2. Extract the most important, durable behavioral patterns and lessons learned
2113
+ 3. Consolidate them into concise, standalone statements (aim for 10-25 key patterns)
2114
+ 4. Remove redundancy — if multiple reflections say the same thing, merge into one clear statement
2115
+ 5. Prioritize patterns that are actionable and recurring over one-off observations
2116
+ 6. Write a brief summary paragraph
2117
+
2118
+ The goal is to reduce a bloated file to a compact, high-signal set of learned patterns while preserving all genuinely useful self-knowledge.
2119
+
2120
+ Respond with valid JSON matching this schema:
2121
+ {
2122
+ "learnedPatterns": ["pattern 1", "pattern 2", "pattern 3"],
2123
+ "summary": "brief summary of consolidation"
2124
+ }`;
2125
+
2126
+ const response = await this.client.chat.completions.create({
2127
+ model: this.config.model,
2128
+ messages: [
2129
+ { role: "system", content: instructionText },
2130
+ { role: "user", content: fullIdentityContent },
2131
+ ],
2132
+ ...(this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {}),
2133
+ ...buildChatCompletionTokenLimit(this.config.model, 4096, {
2134
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
2135
+ }),
2136
+ });
2137
+
2138
+ const rawContent = response.choices?.[0]?.message?.content?.trim();
2139
+ const iDurationMs = Date.now() - iStartTime;
2140
+ const iUsage = (response as any).usage;
2141
+
2142
+ let parsed: any = null;
2143
+ if (rawContent) {
2144
+ for (const candidate of extractJsonCandidates(rawContent)) {
2145
+ try {
2146
+ parsed = JSON.parse(candidate);
2147
+ break;
2148
+ } catch {
2149
+ // keep trying candidates
2150
+ }
2151
+ }
2152
+ }
2153
+
2154
+ this.emit({
2155
+ kind: "llm_end", traceId: iTraceId, model: this.config.model, operation: "identity_consolidation", durationMs: iDurationMs,
2156
+ output: parsed ? parsed.summary : undefined,
2157
+ tokenUsage: iUsage ? { input: iUsage.prompt_tokens, output: iUsage.completion_tokens, total: iUsage.total_tokens } : undefined,
2158
+ });
2159
+
2160
+ if (parsed && Array.isArray(parsed.learnedPatterns)) {
2161
+ const learnedPatterns = parsed.learnedPatterns
2162
+ .filter((pattern: unknown) => typeof pattern === "string")
2163
+ .map((pattern: string) => pattern.trim())
2164
+ .filter((pattern: string) => pattern.length > 0);
2165
+ log.debug(
2166
+ `identity consolidation: ${learnedPatterns.length} patterns`,
2167
+ );
2168
+ return {
2169
+ learnedPatterns,
2170
+ summary: String(parsed.summary || ""),
2171
+ };
2172
+ }
2173
+
2174
+ log.warn("identity consolidation returned no parsed output");
2175
+ return null;
2176
+ } catch (err) {
2177
+ this.emit({
2178
+ kind: "llm_error", traceId: iTraceId, model: this.config.model, operation: "identity_consolidation",
2179
+ durationMs: Date.now() - iStartTime, error: String(err),
2180
+ });
2181
+ log.error("identity consolidation failed", err);
2182
+ return null;
2183
+ }
2184
+ }
2185
+
2186
+ /**
2187
+ * Consolidate identity using local LLM.
2188
+ */
2189
+ private async consolidateIdentityWithLocalLlm(
2190
+ fullIdentityContent: string,
2191
+ ): Promise<{ learnedPatterns: string[]; summary: string } | null> {
2192
+ // Get dynamic context sizes
2193
+ const contextSizes = this.modelRegistry.calculateContextSizes(
2194
+ this.config.localLlmModel,
2195
+ this.config.localLlmMaxContext
2196
+ );
2197
+ log.debug(`Identity consolidation model context: ${contextSizes.description}`);
2198
+
2199
+ const prompt = `You are an identity consolidation system. You are given the full contents of an IDENTITY.md file that contains many individual reflection entries. Your job is to:
2200
+
2201
+ 1. Read all the reflection entries (sections starting with "## Reflection")
2202
+ 2. Extract the most important, durable behavioral patterns and lessons learned
2203
+ 3. Consolidate them into concise, standalone statements (aim for 10-25 key patterns)
2204
+ 4. Remove redundancy — if multiple reflections say the same thing, merge into one clear statement
2205
+ 5. Prioritize patterns that are actionable and recurring over one-off observations
2206
+ 6. Write a brief summary paragraph
2207
+
2208
+ The goal is to reduce a bloated file to a compact, high-signal set of learned patterns while preserving all genuinely useful self-knowledge.
2209
+
2210
+ IDENTITY.md content:
2211
+ ${fullIdentityContent}
2212
+
2213
+ Respond with valid JSON matching this schema:
2214
+ {
2215
+ "learnedPatterns": ["pattern 1", "pattern 2", "pattern 3"],
2216
+ "summary": "brief summary of consolidation"
2217
+ }`;
2218
+
2219
+ const response = await this.localLlm.chatCompletion(
2220
+ [
2221
+ { role: "system", content: "You are an identity consolidation system. Output valid JSON only." },
2222
+ { role: "user", content: prompt },
2223
+ ],
2224
+ {
2225
+ temperature: 0.3,
2226
+ maxTokens: contextSizes.maxOutputTokens,
2227
+ operation: "identity_consolidation",
2228
+ priority: "background",
2229
+ },
2230
+ );
2231
+
2232
+ if (!response?.content) {
2233
+ return null;
2234
+ }
2235
+
2236
+ try {
2237
+ const content = response.content.trim();
2238
+ for (const candidate of extractJsonCandidates(content)) {
2239
+ try {
2240
+ const parsed = JSON.parse(candidate);
2241
+ return {
2242
+ learnedPatterns: Array.isArray((parsed as any).learnedPatterns)
2243
+ ? (parsed as any).learnedPatterns
2244
+ : [],
2245
+ summary: String((parsed as any).summary || ""),
2246
+ };
2247
+ } catch {
2248
+ // keep trying candidates
2249
+ }
2250
+ }
2251
+ return null;
2252
+ } catch (err) {
2253
+ log.warn("local LLM identity consolidation: failed to parse JSON response:", err);
2254
+ return null;
2255
+ }
2256
+ }
2257
+
2258
+ /**
2259
+ * Verify if two memories contradict each other using LLM.
2260
+ * Called when QMD finds semantically similar memories (Phase 2B).
2261
+ */
2262
+ async verifyContradiction(
2263
+ newMemory: { content: string; category: string },
2264
+ existingMemory: { id: string; content: string; category: string; created: string },
2265
+ ): Promise<ContradictionVerificationResult | null> {
2266
+ const input = `Memory 1 (existing, created ${existingMemory.created}):
2267
+ Category: ${existingMemory.category}
2268
+ Content: ${existingMemory.content}
2269
+
2270
+ Memory 2 (new):
2271
+ Category: ${newMemory.category}
2272
+ Content: ${newMemory.content}`;
2273
+
2274
+ try {
2275
+ const instructionText = `You are a contradiction detection system. Analyze whether two memories contradict each other.
2276
+
2277
+ IMPORTANT: Not all similar memories are contradictions!
2278
+ - "User likes TypeScript" and "User likes Python" are NOT contradictions (preferences can coexist)
2279
+ - "User prefers dark mode" and "User prefers light mode" ARE contradictions (mutually exclusive)
2280
+ - "User's email is a@b.com" and "User's email is c@d.com" ARE contradictions (only one email)
2281
+ - "User works at Acme" and "User used to work at Acme" might be a contradiction (temporal change)
2282
+
2283
+ Only mark as contradiction if the two statements CANNOT both be true at the same time.
2284
+
2285
+ If they ARE contradictory, determine which represents the more recent/current state based on:
2286
+ - Explicit time references ("now", "currently", "used to", "no longer")
2287
+ - The fact that newer corrections often start with "actually" or "correction"
2288
+ - Context clues about change over time
2289
+
2290
+ Respond with valid JSON matching this schema:
2291
+ {
2292
+ "isContradiction": true,
2293
+ "confidence": 0.95,
2294
+ "reasoning": "why they contradict or don't",
2295
+ "whichIsNewer": "first"
2296
+ }`;
2297
+
2298
+ if (this.shouldUseLocalLlm) {
2299
+ try {
2300
+ const localResponse = await this.localLlm.chatCompletion(
2301
+ [
2302
+ { role: "system", content: instructionText },
2303
+ { role: "user", content: input },
2304
+ ],
2305
+ {
2306
+ temperature: 0.3,
2307
+ maxTokens: 2048,
2308
+ operation: "contradiction_verification",
2309
+ priority: "background",
2310
+ },
2311
+ );
2312
+ const normalized = this.normalizeContradictionVerificationResult(
2313
+ this.parseJsonObject(localResponse?.content),
2314
+ );
2315
+ if (normalized) {
2316
+ log.debug(
2317
+ `contradiction check via local LLM: ${normalized.isContradiction ? "YES" : "NO"} (confidence: ${normalized.confidence})`,
2318
+ );
2319
+ return normalized;
2320
+ }
2321
+ if (!this.config.localLlmFallback) {
2322
+ log.warn("contradiction verification skipped — local LLM returned invalid JSON and cloud fallback is disabled");
2323
+ return null;
2324
+ }
2325
+ } catch (err) {
2326
+ if (!this.config.localLlmFallback) {
2327
+ log.warn(`contradiction verification skipped — local LLM failed and cloud fallback is disabled: ${err}`);
2328
+ return null;
2329
+ }
2330
+ }
2331
+ }
2332
+
2333
+ if (!this.shouldUseDirectClient) {
2334
+ const fallbackResponse = await this.fallbackLlm.chatCompletion(
2335
+ [
2336
+ { role: "system", content: instructionText },
2337
+ { role: "user", content: input },
2338
+ ],
2339
+ this.withGatewayAgent({ temperature: 0.3, maxTokens: 2048 }),
2340
+ );
2341
+ const normalized = this.normalizeContradictionVerificationResult(
2342
+ this.parseJsonObject(fallbackResponse?.content),
2343
+ );
2344
+ if (normalized) {
2345
+ log.debug(
2346
+ `contradiction check via fallback: ${normalized.isContradiction ? "YES" : "NO"} (confidence: ${normalized.confidence})`,
2347
+ );
2348
+ return normalized;
2349
+ }
2350
+ log.warn("contradiction verification skipped — no OpenAI API key and fallback unavailable");
2351
+ return null;
2352
+ }
2353
+
2354
+ const response = await this.client!.chat.completions.create({
2355
+ model: this.config.model,
2356
+ messages: [
2357
+ { role: "system", content: instructionText },
2358
+ { role: "user", content: input },
2359
+ ],
2360
+ ...buildChatCompletionTokenLimit(this.config.model, 2048, {
2361
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
2362
+ }),
2363
+ });
2364
+
2365
+ const normalized = this.normalizeContradictionVerificationResult(
2366
+ this.parseJsonObject(response.choices?.[0]?.message?.content),
2367
+ );
2368
+ if (normalized) {
2369
+ log.debug(
2370
+ `contradiction check: ${normalized.isContradiction ? "YES" : "NO"} (confidence: ${normalized.confidence})`,
2371
+ );
2372
+ return normalized;
2373
+ }
2374
+
2375
+ return null;
2376
+ } catch (err) {
2377
+ log.error("contradiction verification failed", err);
2378
+ return null;
2379
+ }
2380
+ }
2381
+
2382
+ /**
2383
+ * Suggest links between a new memory and existing memories (Phase 3A).
2384
+ * Called during extraction to build the knowledge graph.
2385
+ */
2386
+ async suggestLinks(
2387
+ newMemory: { content: string; category: string },
2388
+ candidateMemories: Array<{ id: string; content: string; category: string }>,
2389
+ ): Promise<SuggestedLinks | null> {
2390
+ if (candidateMemories.length === 0) {
2391
+ return { links: [] };
2392
+ }
2393
+
2394
+ const candidateList = candidateMemories
2395
+ .map((m, i) => `[${i + 1}] ID: ${m.id}\nCategory: ${m.category}\nContent: ${m.content}`)
2396
+ .join("\n\n");
2397
+
2398
+ const input = `New memory:
2399
+ Category: ${newMemory.category}
2400
+ Content: ${newMemory.content}
2401
+
2402
+ Candidate memories to link to:
2403
+ ${candidateList}`;
2404
+
2405
+ try {
2406
+ const instructionText = `You are a memory linking system. Analyze the new memory and suggest relationships to existing memories.
2407
+
2408
+ Link types:
2409
+ - follows: This memory is a continuation or next step (e.g., decision follows discussion)
2410
+ - references: This memory mentions or refers to the other (e.g., fact references entity)
2411
+ - contradicts: This memory conflicts with the other (use sparingly, only for true contradictions)
2412
+ - supports: This memory provides evidence or reinforcement (e.g., example supports principle)
2413
+ - related: General topical relationship
2414
+
2415
+ Rules:
2416
+ - Only suggest links with strength > 0.5
2417
+ - Quality over quantity — 0-3 links is typical
2418
+ - Prefer specific link types over generic "related"
2419
+ - Consider entity references, topics, and causal relationships
2420
+
2421
+ Respond with valid JSON matching this schema:
2422
+ {
2423
+ "links": [{"targetId": "memory-id", "linkType": "follows|references|contradicts|supports|related", "strength": 0.8, "reason": "why"}]
2424
+ }`;
2425
+
2426
+ if (this.shouldUseLocalLlm) {
2427
+ try {
2428
+ const localResponse = await this.localLlm.chatCompletion(
2429
+ [
2430
+ { role: "system", content: instructionText },
2431
+ { role: "user", content: input },
2432
+ ],
2433
+ {
2434
+ temperature: 0.3,
2435
+ maxTokens: 2048,
2436
+ operation: "link_suggestion",
2437
+ priority: "background",
2438
+ },
2439
+ );
2440
+ const normalized = this.normalizeSuggestedLinksResult(this.parseJsonObject(localResponse?.content));
2441
+ if (normalized) {
2442
+ log.debug(`suggested ${normalized.links.length} links via local LLM`);
2443
+ return normalized;
2444
+ }
2445
+ if (!this.config.localLlmFallback) {
2446
+ log.warn("link suggestion skipped — local LLM returned invalid JSON and cloud fallback is disabled");
2447
+ return null;
2448
+ }
2449
+ } catch (err) {
2450
+ if (!this.config.localLlmFallback) {
2451
+ log.warn(`link suggestion skipped — local LLM failed and cloud fallback is disabled: ${err}`);
2452
+ return null;
2453
+ }
2454
+ }
2455
+ }
2456
+
2457
+ if (!this.shouldUseDirectClient) {
2458
+ const fallbackResponse = await this.fallbackLlm.chatCompletion(
2459
+ [
2460
+ { role: "system", content: instructionText },
2461
+ { role: "user", content: input },
2462
+ ],
2463
+ this.withGatewayAgent({ temperature: 0.3, maxTokens: 2048 }),
2464
+ );
2465
+ const normalized = this.normalizeSuggestedLinksResult(this.parseJsonObject(fallbackResponse?.content));
2466
+ if (normalized) {
2467
+ log.debug(`suggested ${normalized.links.length} links via fallback`);
2468
+ return normalized;
2469
+ }
2470
+ log.warn("link suggestion skipped — no OpenAI API key and fallback unavailable");
2471
+ return null;
2472
+ }
2473
+
2474
+ const response = await this.client!.chat.completions.create({
2475
+ model: this.config.model,
2476
+ messages: [
2477
+ { role: "system", content: instructionText },
2478
+ { role: "user", content: input },
2479
+ ],
2480
+ ...buildChatCompletionTokenLimit(this.config.model, 2048, {
2481
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
2482
+ }),
2483
+ });
2484
+
2485
+ const normalized = this.normalizeSuggestedLinksResult(
2486
+ this.parseJsonObject(response.choices?.[0]?.message?.content),
2487
+ );
2488
+ if (normalized) {
2489
+ log.debug(`suggested ${normalized.links.length} links`);
2490
+ return normalized;
2491
+ }
2492
+
2493
+ return null;
2494
+ } catch (err) {
2495
+ log.error("link suggestion failed", err);
2496
+ return null;
2497
+ }
2498
+ }
2499
+
2500
+ async generateDaySummary(memories: string | MemoryFile[]): Promise<DaySummaryResultShape | null> {
2501
+ if (!this.config.daySummaryEnabled) {
2502
+ log.warn("day summary skipped — disabled by config");
2503
+ return null;
2504
+ }
2505
+
2506
+ const memoryContext = formatDaySummaryMemories(memories);
2507
+ if (memoryContext.length === 0) return null;
2508
+
2509
+ const instructionText = await loadDaySummaryPrompt();
2510
+
2511
+ // Append extension footer when extensions are active (#382)
2512
+ let extensionsFooter = "";
2513
+ try {
2514
+ extensionsFooter = await buildExtensionsFooterForSummary(this.config);
2515
+ } catch {
2516
+ // Non-fatal: skip extension footer if discovery fails
2517
+ }
2518
+
2519
+ const userPrompt = `Generate an end-of-day summary from this Remnic memory context:
2520
+
2521
+ ${memoryContext}${extensionsFooter.length > 0 ? `\n\n${extensionsFooter}` : ""}`;
2522
+ const traceId = crypto.randomUUID();
2523
+ const startedAt = Date.now();
2524
+ this.emit({ kind: "llm_start", traceId, model: this.config.model, operation: "day_summary", input: memoryContext.slice(0, 4000) });
2525
+
2526
+ if (this.shouldUseLocalLlm) {
2527
+ try {
2528
+ const localResponse = await this.localLlm.chatCompletion(
2529
+ [
2530
+ { role: "system", content: `${instructionText}
2531
+
2532
+ Return valid JSON only.` },
2533
+ { role: "user", content: userPrompt },
2534
+ ],
2535
+ {
2536
+ temperature: 0.2,
2537
+ maxTokens: 2048,
2538
+ operation: "day_summary",
2539
+ priority: "background",
2540
+ },
2541
+ );
2542
+ const normalized = this.normalizeDaySummaryResult(this.parseJsonObject(localResponse?.content));
2543
+ if (normalized) {
2544
+ this.emit({ kind: "llm_end", traceId, model: this.config.localLlmModel, operation: "day_summary", durationMs: Date.now() - startedAt, output: JSON.stringify(normalized).slice(0, 2000) });
2545
+ log.debug(`generated day summary via local LLM (${normalized.bullets.length} bullets)`);
2546
+ return normalized;
2547
+ }
2548
+ if (!this.config.localLlmFallback) {
2549
+ this.emit({ kind: "llm_error", traceId, model: this.config.localLlmModel, operation: "day_summary", durationMs: Date.now() - startedAt, error: "local LLM returned invalid JSON and fallback disabled" });
2550
+ log.warn("day summary skipped — local LLM returned invalid JSON and fallback disabled");
2551
+ return null;
2552
+ }
2553
+ } catch (err) {
2554
+ if (!this.config.localLlmFallback) {
2555
+ this.emit({ kind: "llm_error", traceId, model: this.config.localLlmModel, operation: "day_summary", durationMs: Date.now() - startedAt, error: String(err) });
2556
+ log.warn(`day summary skipped — local LLM failed and fallback disabled: ${err}`);
2557
+ return null;
2558
+ }
2559
+ }
2560
+ }
2561
+
2562
+ const fallbackResult = await this.parseWithGatewayFallback(
2563
+ traceId,
2564
+ "day_summary",
2565
+ startedAt,
2566
+ DaySummaryResultSchema,
2567
+ [
2568
+ { role: "system", content: `${instructionText}
2569
+
2570
+ Return valid JSON only.` },
2571
+ { role: "user", content: userPrompt },
2572
+ ],
2573
+ { temperature: 0.2, maxTokens: 2048 },
2574
+ );
2575
+ if (fallbackResult) {
2576
+ const normalized = this.normalizeDaySummaryResult(fallbackResult);
2577
+ if (normalized) {
2578
+ log.debug(`generated day summary via fallback (${normalized.bullets.length} bullets)`);
2579
+ return normalized;
2580
+ }
2581
+ }
2582
+
2583
+ // Direct Responses API fallback (AGENTS.md-compliant: never Chat Completions)
2584
+ if (this.shouldUseDirectClient) {
2585
+ try {
2586
+ const response = await (this.client as any).responses.create({
2587
+ model: this.config.model,
2588
+ instructions: `${instructionText}\n\nReturn valid JSON only.`,
2589
+ input: userPrompt,
2590
+ max_output_tokens: 2048,
2591
+ });
2592
+ const rawText = typeof response.output_text === "string" ? response.output_text : JSON.stringify(response.output_text ?? "");
2593
+ const normalized = this.normalizeDaySummaryResult(this.parseJsonObject(rawText));
2594
+ if (normalized) {
2595
+ this.emit({ kind: "llm_end", traceId, model: this.config.model, operation: "day_summary", durationMs: Date.now() - startedAt, output: JSON.stringify(normalized).slice(0, 2000) });
2596
+ log.debug(`generated day summary via Responses API (${normalized.bullets.length} bullets)`);
2597
+ return normalized;
2598
+ }
2599
+ this.emit({ kind: "llm_error", traceId, model: this.config.model, operation: "day_summary", durationMs: Date.now() - startedAt, error: "Responses API returned unparseable output" });
2600
+ } catch (err) {
2601
+ this.emit({ kind: "llm_error", traceId, model: this.config.model, operation: "day_summary", durationMs: Date.now() - startedAt, error: `Responses API failed: ${err}` });
2602
+ }
2603
+ }
2604
+
2605
+ this.emit({ kind: "llm_error", traceId, model: this.config.model, operation: "day_summary", durationMs: Date.now() - startedAt, error: "all generation paths exhausted (local LLM + gateway + Responses API)" });
2606
+ log.warn("day summary skipped — all generation paths exhausted");
2607
+ return null;
2608
+ }
2609
+
2610
+
2611
+ /**
2612
+ * Summarize a batch of old memories into a compact summary (Phase 4A).
2613
+ */
2614
+ async summarizeMemories(
2615
+ memories: Array<{ id: string; content: string; category: string; created: string }>,
2616
+ ): Promise<MemorySummaryResult | null> {
2617
+ if (memories.length === 0) return null;
2618
+
2619
+ const memoryList = memories
2620
+ .map((m) => `[${m.id}] (${m.category}, ${m.created.slice(0, 10)})\n${m.content}`)
2621
+ .join("\n\n");
2622
+
2623
+ try {
2624
+ const instructionText = `You are a memory summarization system. You are given a batch of old memories that need to be compressed into a summary.
2625
+
2626
+ Your task:
2627
+ 1. Write a concise summary paragraph (2-4 sentences) capturing the essence of these memories
2628
+ 2. Extract the 5-10 most important facts that should be preserved
2629
+ 3. List the key entities mentioned
2630
+
2631
+ Guidelines:
2632
+ - Preserve specific, actionable information
2633
+ - Merge redundant details into single statements
2634
+ - Focus on durable insights, not transient details
2635
+ - Maintain any preferences, decisions, or corrections as key facts
2636
+
2637
+ Respond with valid JSON matching this schema:
2638
+ {
2639
+ "summaryText": "concise summary paragraph",
2640
+ "keyFacts": ["fact 1", "fact 2"],
2641
+ "keyEntities": ["entity-1", "entity-2"]
2642
+ }`;
2643
+
2644
+ if (this.shouldUseLocalLlm) {
2645
+ try {
2646
+ const localResponse = await this.localLlm.chatCompletion(
2647
+ [
2648
+ { role: "system", content: instructionText },
2649
+ { role: "user", content: `Summarize these ${memories.length} memories:\n\n${memoryList}` },
2650
+ ],
2651
+ {
2652
+ temperature: 0.3,
2653
+ maxTokens: 4096,
2654
+ operation: "memory_summarization",
2655
+ priority: "background",
2656
+ },
2657
+ );
2658
+ const normalized = this.normalizeMemorySummaryResult(this.parseJsonObject(localResponse?.content));
2659
+ if (normalized) {
2660
+ log.debug(
2661
+ `summarized ${memories.length} memories into ${normalized.keyFacts.length} key facts via local LLM`,
2662
+ );
2663
+ return normalized;
2664
+ }
2665
+ if (!this.config.localLlmFallback) {
2666
+ log.warn("summarization skipped — local LLM returned invalid JSON and cloud fallback is disabled");
2667
+ return null;
2668
+ }
2669
+ } catch (err) {
2670
+ if (!this.config.localLlmFallback) {
2671
+ log.warn(`summarization skipped — local LLM failed and cloud fallback is disabled: ${err}`);
2672
+ return null;
2673
+ }
2674
+ }
2675
+ }
2676
+
2677
+ if (!this.shouldUseDirectClient) {
2678
+ const fallbackResponse = await this.fallbackLlm.chatCompletion(
2679
+ [
2680
+ { role: "system", content: instructionText },
2681
+ { role: "user", content: `Summarize these ${memories.length} memories:\n\n${memoryList}` },
2682
+ ],
2683
+ this.withGatewayAgent({ temperature: 0.3, maxTokens: 4096 }),
2684
+ );
2685
+ const normalized = this.normalizeMemorySummaryResult(this.parseJsonObject(fallbackResponse?.content));
2686
+ if (normalized) {
2687
+ log.debug(`summarized ${memories.length} memories into ${normalized.keyFacts.length} key facts via fallback`);
2688
+ return normalized;
2689
+ }
2690
+ log.warn("summarization skipped — no OpenAI API key and fallback unavailable");
2691
+ return null;
2692
+ }
2693
+
2694
+ const response = await this.client!.chat.completions.create({
2695
+ model: this.config.model,
2696
+ messages: [
2697
+ { role: "system", content: instructionText },
2698
+ { role: "user", content: `Summarize these ${memories.length} memories:\n\n${memoryList}` },
2699
+ ],
2700
+ ...buildChatCompletionTokenLimit(this.config.model, 4096, {
2701
+ assumeOpenAI: this.directClientUsesOpenAiTokenSemantics(),
2702
+ }),
2703
+ });
2704
+
2705
+ const normalized = this.normalizeMemorySummaryResult(
2706
+ this.parseJsonObject(response.choices?.[0]?.message?.content),
2707
+ );
2708
+ if (normalized) {
2709
+ log.debug(`summarized ${memories.length} memories into ${normalized.keyFacts.length} key facts`);
2710
+ return normalized;
2711
+ }
2712
+
2713
+ return null;
2714
+ } catch (err) {
2715
+ log.error("memory summarization failed", err);
2716
+ return null;
2717
+ }
2718
+ }
2719
+ }