@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
package/src/config.ts ADDED
@@ -0,0 +1,3807 @@
1
+ import path from "node:path";
2
+ import type {
3
+ CodexCompactionFlushMode,
4
+ CodingModeConfig,
5
+ ContradictionScanConfig,
6
+ CodexCompatConfig,
7
+ DreamingConfig,
8
+ DreamsLightSleepConfig,
9
+ DreamsRemConfig,
10
+ DreamsDeepSleepConfig,
11
+ DreamsPhasesConfig,
12
+ ProceduralConfig,
13
+ HeartbeatConfig,
14
+ IdentityInjectionMode,
15
+ MemoryOsPresetName,
16
+ PluginConfig,
17
+ PrincipalRule,
18
+ RecallPipelineConfig,
19
+ RecallSectionConfig,
20
+ ReasoningEffort,
21
+ SemanticChunkingConfigShape,
22
+ SessionObserverBandConfig,
23
+ SlotBehaviorConfig,
24
+ SlotMismatchMode,
25
+ TriggerMode,
26
+ } from "./types.js";
27
+ import { log } from "./logger.js";
28
+ import { cloneDefaultSessionObserverBands } from "./session-observer-bands.js";
29
+ import { readEnvVar, resolveHomeDir } from "./runtime/env.js";
30
+ import { normalizeEntitySchemas } from "./entity-schema.js";
31
+ // Finding 4 (#394): use the shared coerce helper instead of inlining the same
32
+ // boolean-coercion logic that connectors/index.ts already exports. The helper
33
+ // lives in connectors/coerce.ts (a tiny, dependency-free module) so neither
34
+ // config.ts → connectors/index.ts nor the reverse circular import arises.
35
+ import { coerceBool, coerceInstallExtension, coerceNumber } from "./connectors/coerce.js";
36
+
37
+ const DEFAULT_MEMORY_DIR = path.join(
38
+ resolveHomeDir(),
39
+ ".openclaw",
40
+ "workspace",
41
+ "memory",
42
+ "local",
43
+ );
44
+
45
+ const DEFAULT_WORKSPACE_DIR = path.join(
46
+ resolveHomeDir(),
47
+ ".openclaw",
48
+ "workspace",
49
+ );
50
+
51
+ const DEFAULT_INIT_GATE_TIMEOUT_MS = 30_000;
52
+ const CLIENT_SECRET_FIELD = ["client", "Secret"].join("") as "clientSecret";
53
+ const REFRESH_TOKEN_FIELD = ["refresh", "Token"].join("") as "refreshToken";
54
+ const LEGACY_ACTIVE_RECALL_CUSTOM_FIELD = [
55
+ "activeRecall",
56
+ "Prompt",
57
+ "Override",
58
+ ].join("") as "activeRecallPromptOverride";
59
+
60
+ function parseBoundedIntegerMs(
61
+ value: unknown,
62
+ fallback: number,
63
+ min: number,
64
+ max: number,
65
+ ): number {
66
+ const coerced = coerceNumber(value);
67
+ if (coerced === undefined) return fallback;
68
+ return Math.min(max, Math.max(min, Math.floor(coerced)));
69
+ }
70
+
71
+ function parsePositiveInteger(value: unknown, keyName: string): number | undefined {
72
+ if (value === undefined || value === null) return undefined;
73
+ const coerced = coerceNumber(value);
74
+ if (
75
+ coerced === undefined ||
76
+ !Number.isFinite(coerced) ||
77
+ !Number.isInteger(coerced) ||
78
+ coerced <= 0
79
+ ) {
80
+ throw new Error(
81
+ `${keyName} must be a positive integer; got ${JSON.stringify(value)}`,
82
+ );
83
+ }
84
+ return coerced;
85
+ }
86
+
87
+ function parseBoundedPositiveInteger(
88
+ value: unknown,
89
+ min: number,
90
+ max: number,
91
+ keyName: string,
92
+ ): number | undefined {
93
+ const parsed = parsePositiveInteger(value, keyName);
94
+ if (parsed === undefined) return undefined;
95
+ return Math.max(min, Math.min(max, parsed));
96
+ }
97
+
98
+ function parseQmdSupportedVersion(value: unknown): string {
99
+ if (value === undefined || value === null) return "2.5.1";
100
+ if (typeof value !== "string") {
101
+ throw new Error(`qmdSupportedVersion must be a semantic version string; got ${JSON.stringify(value)}`);
102
+ }
103
+ const normalized = value.trim();
104
+ if (!/^\d+\.\d+\.\d+$/.test(normalized)) {
105
+ throw new Error(
106
+ `qmdSupportedVersion must be a semantic version string like "2.5.1"; got ${JSON.stringify(value)}`,
107
+ );
108
+ }
109
+ return normalized;
110
+ }
111
+
112
+ function parseQmdGpuBackend(value: unknown): "auto" | "metal" | "cuda" | "vulkan" | "false" | undefined {
113
+ if (value === undefined || value === null) return undefined;
114
+ if (value === false) return "false";
115
+ if (typeof value !== "string") {
116
+ throw new Error(`qmdGpuBackend must be one of "auto", "metal", "cuda", "vulkan", or false; got ${JSON.stringify(value)}`);
117
+ }
118
+ const normalized = value.trim().toLowerCase();
119
+ if (
120
+ normalized === "auto" ||
121
+ normalized === "metal" ||
122
+ normalized === "cuda" ||
123
+ normalized === "vulkan" ||
124
+ normalized === "false"
125
+ ) {
126
+ return normalized;
127
+ }
128
+ throw new Error(`qmdGpuBackend must be one of "auto", "metal", "cuda", "vulkan", or false; got ${JSON.stringify(value)}`);
129
+ }
130
+
131
+ function parseQmdChunkStrategy(value: unknown): "auto" | "regex" {
132
+ if (value === undefined || value === null) return "auto";
133
+ if (typeof value !== "string") {
134
+ throw new Error(`qmdChunkStrategy must be "auto" or "regex"; got ${JSON.stringify(value)}`);
135
+ }
136
+ const normalized = value.trim().toLowerCase();
137
+ if (normalized === "auto" || normalized === "regex") return normalized;
138
+ throw new Error(`qmdChunkStrategy must be "auto" or "regex"; got ${JSON.stringify(value)}`);
139
+ }
140
+
141
+ function parseOptionalNonEmptyString(value: unknown): string | undefined {
142
+ if (typeof value !== "string") return undefined;
143
+ const normalized = value.trim();
144
+ return normalized.length > 0 ? normalized : undefined;
145
+ }
146
+
147
+ function parseIntegerAtLeast(
148
+ value: unknown,
149
+ fallback: number,
150
+ min: number,
151
+ keyName: string,
152
+ ): number {
153
+ if (value === undefined || value === null) return fallback;
154
+ const coerced = coerceNumber(value);
155
+ if (
156
+ coerced === undefined ||
157
+ !Number.isFinite(coerced) ||
158
+ !Number.isInteger(coerced) ||
159
+ coerced < min
160
+ ) {
161
+ throw new Error(
162
+ `${keyName} must be an integer greater than or equal to ${min}; got ${JSON.stringify(value)}`,
163
+ );
164
+ }
165
+ return coerced;
166
+ }
167
+
168
+ // Coerce common string/number representations of a boolean to a real boolean.
169
+ // Returns `undefined` when the value cannot be interpreted, so callers can
170
+ // fall back to their own default. Guards against the "string `false` is
171
+ // truthy" footgun (CLAUDE.md gotcha #36) when config values arrive from
172
+ // CLI/env/JSON sources where booleans are sometimes string-typed.
173
+ function coerceBooleanLike(value: unknown): boolean | undefined {
174
+ if (typeof value === "boolean") return value;
175
+ if (typeof value === "number") {
176
+ if (value === 1) return true;
177
+ if (value === 0) return false;
178
+ return undefined;
179
+ }
180
+ if (typeof value === "string") {
181
+ const normalized = value.trim().toLowerCase();
182
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on") {
183
+ return true;
184
+ }
185
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "off") {
186
+ return false;
187
+ }
188
+ }
189
+ return undefined;
190
+ }
191
+
192
+ export function isOpenaiApiKeyDisabled(value: unknown): boolean {
193
+ return value === false || (typeof value === "string" && value.trim().toLowerCase() === "false");
194
+ }
195
+
196
+ /**
197
+ * Detect a SecretRef-shaped object (issue #757) without resolving it.
198
+ * SecretRefs are preserved verbatim through `parseConfig` and resolved at
199
+ * service-start time via `resolveAgentAccessAuthToken` (which delegates to
200
+ * OpenClaw's gateway resolver). Standalone Remnic does not resolve these.
201
+ */
202
+ function isSecretRefShape(value: unknown): value is import("./types.js").SecretRef {
203
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
204
+ const obj = value as Record<string, unknown>;
205
+ return typeof obj.source === "string" && obj.source.trim().length > 0;
206
+ }
207
+
208
+ /**
209
+ * Parse the `agentAccessHttp.authToken` field. Accepts:
210
+ * - `string` → env-expanded immediately (current behavior preserved)
211
+ * - `SecretRef` object (`{source, ...}`) → preserved verbatim for runtime
212
+ * resolution by OpenClaw's gateway secret resolver
213
+ * - `undefined` / empty string → fall back to env var, then `undefined`
214
+ * - Any other shape → throw with a clear, actionable error so operators can
215
+ * debug rather than chasing a generic startup failure (issue #757).
216
+ */
217
+ function parseAgentAccessAuthToken(raw: unknown): import("./types.js").AgentAccessAuthToken | undefined {
218
+ if (raw === undefined || raw === null) {
219
+ return readEnvVar("OPENCLAW_REMNIC_ACCESS_TOKEN") ?? readEnvVar("OPENCLAW_ENGRAM_ACCESS_TOKEN");
220
+ }
221
+ if (typeof raw === "string") {
222
+ if (raw.trim().length === 0) {
223
+ return readEnvVar("OPENCLAW_REMNIC_ACCESS_TOKEN") ?? readEnvVar("OPENCLAW_ENGRAM_ACCESS_TOKEN");
224
+ }
225
+ return resolveEnvVars(raw);
226
+ }
227
+ if (isSecretRefShape(raw)) {
228
+ return raw;
229
+ }
230
+ throw new Error(
231
+ "unsupported SecretRef shape for agentAccessHttp.authToken — " +
232
+ "expected a string or an object with a non-empty `source` field " +
233
+ "(see https://github.com/joshuaswarren/remnic/issues/757)",
234
+ );
235
+ }
236
+
237
+ export function resolveEnvVars(value: string): string {
238
+ const resolved = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, envVar: string) => {
239
+ const envValue = readEnvVar(envVar);
240
+ if (!envValue) {
241
+ throw new Error(`Environment variable ${envVar} is not set`);
242
+ }
243
+ return envValue;
244
+ });
245
+ const remaining = resolved.match(/\$\{[^}]*\}/);
246
+ if (remaining) {
247
+ throw new Error(`Malformed environment variable placeholder: ${remaining[0]}`);
248
+ }
249
+ return resolved;
250
+ }
251
+
252
+ function normalizeOpenaiBaseUrl(value: string | undefined, source: "config" | "env"): string | undefined {
253
+ if (!value) return undefined;
254
+ const trimmed = value.trim();
255
+ if (trimmed.length === 0) return undefined;
256
+
257
+ let parsed: URL;
258
+ try {
259
+ parsed = new URL(trimmed);
260
+ } catch {
261
+ log.warn(`ignoring invalid openaiBaseUrl from ${source}: not a valid URL`);
262
+ return undefined;
263
+ }
264
+
265
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
266
+ log.warn(
267
+ `ignoring openaiBaseUrl from ${source}: unsupported URL scheme (${parsed.protocol.replace(":", "")})`,
268
+ );
269
+ return undefined;
270
+ }
271
+
272
+ if (parsed.protocol === "http:") {
273
+ log.warn(`openaiBaseUrl from ${source} is using insecure http; prefer https`);
274
+ }
275
+
276
+ // Avoid duplicate slash behavior in downstream baseURL path joins.
277
+ let url = parsed.toString();
278
+ while (url.endsWith("/")) url = url.slice(0, -1);
279
+ return url;
280
+ }
281
+
282
+ function normalizeMemoryRelativeDir(raw: unknown, fallback: string): string {
283
+ if (typeof raw !== "string") return fallback;
284
+ const trimmed = raw.trim();
285
+ if (trimmed.length === 0) return fallback;
286
+
287
+ const normalized = trimmed
288
+ .replace(/\\/g, "/")
289
+ .split("/")
290
+ .filter((segment) => segment.length > 0 && segment !== "." && segment !== "..")
291
+ .join("/");
292
+ return normalized.length > 0 ? normalized : fallback;
293
+ }
294
+
295
+ /**
296
+ * Parse and validate the semanticChunkingConfig sub-object.
297
+ * Returns only recognized numeric/boolean fields with their correct types.
298
+ */
299
+ function parseContradictionScanConfig(raw: unknown): ContradictionScanConfig {
300
+ if (!raw || typeof raw !== "object") {
301
+ return {
302
+ enabled: false,
303
+ similarityFloor: 0.82,
304
+ topicOverlapFloor: 0.4,
305
+ maxPairsPerRun: 500,
306
+ cooldownDays: 14,
307
+ autoMergeDuplicates: false,
308
+ };
309
+ }
310
+ const src = raw as Record<string, unknown>;
311
+ const simFloor = coerceNumber(src.similarityFloor) ?? 0.82;
312
+ const topicFloor = coerceNumber(src.topicOverlapFloor) ?? 0.4;
313
+ const maxPairs = coerceNumber(src.maxPairsPerRun) ?? 500;
314
+ const cooldown = coerceNumber(src.cooldownDays) ?? 14;
315
+ return {
316
+ enabled: coerceBool(src.enabled) === true,
317
+ similarityFloor: Math.min(1, Math.max(0, simFloor)),
318
+ topicOverlapFloor: Math.min(1, Math.max(0, topicFloor)),
319
+ maxPairsPerRun: Math.max(1, maxPairs),
320
+ cooldownDays: Math.max(0, cooldown),
321
+ autoMergeDuplicates: coerceBool(src.autoMergeDuplicates) === true,
322
+ };
323
+ }
324
+
325
+ function parseSemanticChunkingConfig(
326
+ raw: unknown,
327
+ ): Partial<SemanticChunkingConfigShape> {
328
+ if (!raw || typeof raw !== "object") return {};
329
+ const src = raw as Record<string, unknown>;
330
+ const out: Partial<SemanticChunkingConfigShape> = {};
331
+
332
+ if (typeof src.targetTokens === "number") out.targetTokens = src.targetTokens;
333
+ if (typeof src.minTokens === "number") out.minTokens = src.minTokens;
334
+ if (typeof src.maxTokens === "number") out.maxTokens = src.maxTokens;
335
+ if (typeof src.smoothingWindowSize === "number") out.smoothingWindowSize = src.smoothingWindowSize;
336
+ if (typeof src.boundaryThresholdStdDevs === "number") out.boundaryThresholdStdDevs = src.boundaryThresholdStdDevs;
337
+ if (typeof src.embeddingBatchSize === "number") out.embeddingBatchSize = src.embeddingBatchSize;
338
+ if (typeof src.fallbackToRecursive === "boolean") out.fallbackToRecursive = src.fallbackToRecursive;
339
+
340
+ return out;
341
+ }
342
+
343
+ // Cursor review on PR #736: the default reasoning model used by the
344
+ // extraction pipeline (`config.model`) and the new peer profile
345
+ // reasoner (`peerProfileReasonerModel`) was hardcoded as `"gpt-5.5"`
346
+ // in two places. Centralize the default so both surfaces — and any
347
+ // future LLM-backed surface — always agree on the same model id.
348
+ // Sibling packages may keep their own constants only when they are scoped to
349
+ // their own subsystem and intentionally diverge. Shared reasoning-model
350
+ // defaults should use this constant.
351
+ export const DEFAULT_REASONING_MODEL = "gpt-5.5";
352
+
353
+ const VALID_EFFORTS: ReasoningEffort[] = ["none", "low", "medium", "high"];
354
+ const VALID_TRIGGERS: TriggerMode[] = ["smart", "every_n", "time_based"];
355
+ const VALID_IDENTITY_INJECTION_MODES: IdentityInjectionMode[] = ["recovery_only", "minimal", "full"];
356
+ const VALID_MEMORY_OS_PRESETS: MemoryOsPresetName[] = [
357
+ "conservative",
358
+ "balanced",
359
+ "research-max",
360
+ "local-llm-heavy",
361
+ ];
362
+ const VALID_SLOT_MISMATCH_MODES: SlotMismatchMode[] = ["error", "warn", "silent"];
363
+ const VALID_CODEX_COMPACTION_FLUSH_MODES: CodexCompactionFlushMode[] = ["signal", "heuristic", "auto"];
364
+ export const VALID_MEMORY_CATEGORIES = new Set([
365
+ "fact",
366
+ "preference",
367
+ "correction",
368
+ "entity",
369
+ "decision",
370
+ "relationship",
371
+ "principle",
372
+ "commitment",
373
+ "moment",
374
+ "skill",
375
+ "rule",
376
+ "procedure",
377
+ "reasoning_trace",
378
+ ]);
379
+
380
+ const DEFAULT_BEHAVIOR_LOOP_PROTECTED_PARAMS = [
381
+ "maxMemoryTokens",
382
+ "qmdMaxResults",
383
+ "qmdColdMaxResults",
384
+ "recallPlannerMaxQmdResultsMinimal",
385
+ "verbatimArtifactsMaxRecall",
386
+ ];
387
+
388
+ const MEMORY_OS_PRESET_ALIASES: Record<string, MemoryOsPresetName> = {
389
+ research: "research-max",
390
+ };
391
+
392
+ const MEMORY_OS_PRESETS: Record<MemoryOsPresetName, Record<string, unknown>> = {
393
+ conservative: {
394
+ maxMemoryTokens: 1500,
395
+ recallPlannerMaxQmdResultsMinimal: 2,
396
+ recallPlannerMaxQmdResultsFull: 5,
397
+ queryAwareIndexingEnabled: false,
398
+ verbatimArtifactsEnabled: false,
399
+ verbatimArtifactsMaxRecall: 2,
400
+ rerankEnabled: false,
401
+ localLlmEnabled: false,
402
+ localLlmFastEnabled: false,
403
+ multiGraphMemoryEnabled: false,
404
+ graphRecallEnabled: false,
405
+ graphAssistInFullModeEnabled: false,
406
+ proactiveExtractionEnabled: false,
407
+ contextCompressionActionsEnabled: false,
408
+ compressionGuidelineLearningEnabled: false,
409
+ compressionGuidelineSemanticRefinementEnabled: false,
410
+ maxProactiveQuestionsPerExtraction: 0,
411
+ maxCompressionTokensPerHour: 0,
412
+ behaviorLoopAutoTuneEnabled: false,
413
+ // Issue #567 PR 4/5 flipped `procedural.enabled` default to `true`.
414
+ // The conservative preset intentionally keeps the feature OFF to
415
+ // match its restrictive intent (no proactive extraction, no
416
+ // compression guideline learning, etc.). Users who want procedural
417
+ // memory on a conservative preset must set `procedural.enabled: true`
418
+ // explicitly.
419
+ procedural: { enabled: false },
420
+ },
421
+ balanced: {
422
+ maxMemoryTokens: 2000,
423
+ recallPlannerMaxQmdResultsMinimal: 4,
424
+ recallPlannerMaxQmdResultsFull: 8,
425
+ queryAwareIndexingEnabled: true,
426
+ verbatimArtifactsEnabled: true,
427
+ verbatimArtifactsMaxRecall: 4,
428
+ rerankEnabled: true,
429
+ rerankProvider: "local",
430
+ localLlmEnabled: false,
431
+ localLlmFastEnabled: false,
432
+ multiGraphMemoryEnabled: false,
433
+ graphRecallEnabled: false,
434
+ graphAssistInFullModeEnabled: false,
435
+ proactiveExtractionEnabled: false,
436
+ contextCompressionActionsEnabled: false,
437
+ compressionGuidelineLearningEnabled: false,
438
+ compressionGuidelineSemanticRefinementEnabled: false,
439
+ maxProactiveQuestionsPerExtraction: 2,
440
+ maxCompressionTokensPerHour: 1500,
441
+ behaviorLoopAutoTuneEnabled: false,
442
+ },
443
+ "research-max": {
444
+ maxMemoryTokens: 3200,
445
+ recallPlannerMaxQmdResultsMinimal: 6,
446
+ recallPlannerMaxQmdResultsFull: 12,
447
+ queryAwareIndexingEnabled: true,
448
+ verbatimArtifactsEnabled: true,
449
+ verbatimArtifactsMaxRecall: 6,
450
+ rerankEnabled: true,
451
+ rerankProvider: "local",
452
+ localLlmEnabled: false,
453
+ localLlmFastEnabled: false,
454
+ multiGraphMemoryEnabled: true,
455
+ graphRecallEnabled: true,
456
+ graphAssistInFullModeEnabled: true,
457
+ proactiveExtractionEnabled: true,
458
+ contextCompressionActionsEnabled: true,
459
+ compressionGuidelineLearningEnabled: true,
460
+ compressionGuidelineSemanticRefinementEnabled: true,
461
+ explicitCueRecallEnabled: true,
462
+ explicitCueRecallMaxChars: 3200,
463
+ lcmEnabled: true,
464
+ maxProactiveQuestionsPerExtraction: 4,
465
+ maxCompressionTokensPerHour: 3000,
466
+ behaviorLoopAutoTuneEnabled: true,
467
+ },
468
+ "local-llm-heavy": {
469
+ maxMemoryTokens: 2400,
470
+ recallPlannerMaxQmdResultsMinimal: 4,
471
+ recallPlannerMaxQmdResultsFull: 8,
472
+ queryAwareIndexingEnabled: true,
473
+ verbatimArtifactsEnabled: true,
474
+ verbatimArtifactsMaxRecall: 4,
475
+ rerankEnabled: true,
476
+ rerankProvider: "local",
477
+ localLlmEnabled: true,
478
+ localLlmFastEnabled: true,
479
+ embeddingFallbackProvider: "local",
480
+ localLlmFallback: true,
481
+ multiGraphMemoryEnabled: false,
482
+ graphRecallEnabled: false,
483
+ graphAssistInFullModeEnabled: false,
484
+ proactiveExtractionEnabled: true,
485
+ contextCompressionActionsEnabled: true,
486
+ compressionGuidelineLearningEnabled: true,
487
+ compressionGuidelineSemanticRefinementEnabled: false,
488
+ maxProactiveQuestionsPerExtraction: 2,
489
+ maxCompressionTokensPerHour: 1500,
490
+ behaviorLoopAutoTuneEnabled: false,
491
+ },
492
+ };
493
+
494
+ function resolveMemoryOsPreset(value: unknown): MemoryOsPresetName | undefined {
495
+ if (typeof value !== "string") return undefined;
496
+ const normalized = value.trim();
497
+ if (VALID_MEMORY_OS_PRESETS.includes(normalized as MemoryOsPresetName)) {
498
+ return normalized as MemoryOsPresetName;
499
+ }
500
+ return MEMORY_OS_PRESET_ALIASES[normalized];
501
+ }
502
+
503
+ export function parseConfig(raw: unknown): PluginConfig {
504
+ const baseCfg =
505
+ raw && typeof raw === "object" && !Array.isArray(raw)
506
+ ? (raw as Record<string, unknown>)
507
+ : {};
508
+ const memoryOsPreset = resolveMemoryOsPreset(baseCfg.memoryOsPreset);
509
+ let cfg: Record<string, unknown>;
510
+ if (memoryOsPreset) {
511
+ const preset = MEMORY_OS_PRESETS[memoryOsPreset];
512
+ // Deep-merge the `procedural` block specifically: the preset may pin a
513
+ // subset of keys (e.g. `conservative` pins `procedural: { enabled: false }`),
514
+ // and a user-provided `procedural` block with just `minOccurrences` or
515
+ // `lookbackDays` must NOT silently discard the preset's `enabled: false`.
516
+ // CLAUDE.md rule 22 (dedup config resolution) + Codex P1 on #609.
517
+ const presetProcedural =
518
+ preset.procedural &&
519
+ typeof preset.procedural === "object" &&
520
+ !Array.isArray(preset.procedural)
521
+ ? (preset.procedural as Record<string, unknown>)
522
+ : undefined;
523
+ const baseProcedural =
524
+ baseCfg.procedural &&
525
+ typeof baseCfg.procedural === "object" &&
526
+ !Array.isArray(baseCfg.procedural)
527
+ ? (baseCfg.procedural as Record<string, unknown>)
528
+ : undefined;
529
+ const mergedProcedural =
530
+ presetProcedural && baseProcedural
531
+ ? { ...presetProcedural, ...baseProcedural }
532
+ : (baseProcedural ?? presetProcedural);
533
+ cfg = {
534
+ ...preset,
535
+ ...baseCfg,
536
+ memoryOsPreset,
537
+ };
538
+ if (mergedProcedural !== undefined) {
539
+ cfg.procedural = mergedProcedural;
540
+ }
541
+ } else {
542
+ cfg = baseCfg;
543
+ }
544
+
545
+ const modelSource =
546
+ cfg.modelSource === "gateway" ? "gateway" : "plugin";
547
+
548
+ const openaiApiKeyDisabled = isOpenaiApiKeyDisabled(cfg.openaiApiKey);
549
+
550
+ let apiKey: string | undefined;
551
+ if (openaiApiKeyDisabled) {
552
+ // Explicit opt-out for local/gateway-only deployments. Without this,
553
+ // a stale process-level OPENAI_API_KEY can be captured even when the
554
+ // operator wants Remnic to use local LLMs and never try direct OpenAI.
555
+ apiKey = undefined;
556
+ } else if (typeof cfg.openaiApiKey === "string" && cfg.openaiApiKey.length > 0) {
557
+ apiKey = resolveEnvVars(cfg.openaiApiKey);
558
+ } else if (modelSource === "gateway") {
559
+ // Gateway mode deliberately delegates LLM calls to OpenClaw's model chain.
560
+ // Do not implicitly capture OPENAI_API_KEY from the Remnic process env here:
561
+ // doing so makes diagnostics look OpenAI-dependent and can accidentally
562
+ // route extraction through a stale direct key before gateway fallback.
563
+ apiKey = undefined;
564
+ } else {
565
+ apiKey = readEnvVar("OPENAI_API_KEY");
566
+ }
567
+
568
+ // API key is optional at load time — retrieval works without it.
569
+ // Extraction will log a warning if called without a key.
570
+
571
+ const model =
572
+ typeof cfg.model === "string" && cfg.model.length > 0
573
+ ? cfg.model
574
+ : DEFAULT_REASONING_MODEL;
575
+ const captureMode =
576
+ cfg.captureMode === "explicit" || cfg.captureMode === "hybrid"
577
+ ? cfg.captureMode
578
+ : "implicit";
579
+
580
+ const rawEffort = cfg.reasoningEffort as string | undefined;
581
+ const reasoningEffort: ReasoningEffort =
582
+ rawEffort && VALID_EFFORTS.includes(rawEffort as ReasoningEffort)
583
+ ? (rawEffort as ReasoningEffort)
584
+ : "low";
585
+
586
+ const rawTrigger = cfg.triggerMode as string | undefined;
587
+ const triggerMode: TriggerMode =
588
+ rawTrigger && VALID_TRIGGERS.includes(rawTrigger as TriggerMode)
589
+ ? (rawTrigger as TriggerMode)
590
+ : "smart";
591
+ const rawSlotBehavior =
592
+ cfg.slotBehavior && typeof cfg.slotBehavior === "object" && !Array.isArray(cfg.slotBehavior)
593
+ ? (cfg.slotBehavior as Record<string, unknown>)
594
+ : {};
595
+ const slotBehavior: SlotBehaviorConfig = {
596
+ requireExclusiveMemorySlot:
597
+ rawSlotBehavior.requireExclusiveMemorySlot !== false,
598
+ onSlotMismatch:
599
+ typeof rawSlotBehavior.onSlotMismatch === "string" &&
600
+ VALID_SLOT_MISMATCH_MODES.includes(
601
+ rawSlotBehavior.onSlotMismatch as SlotMismatchMode,
602
+ )
603
+ ? (rawSlotBehavior.onSlotMismatch as SlotMismatchMode)
604
+ : "error",
605
+ };
606
+ const rawDreaming =
607
+ cfg.dreaming && typeof cfg.dreaming === "object" && !Array.isArray(cfg.dreaming)
608
+ ? (cfg.dreaming as Record<string, unknown>)
609
+ : {};
610
+ const dreaming: DreamingConfig = {
611
+ enabled: rawDreaming.enabled === true,
612
+ journalPath:
613
+ typeof rawDreaming.journalPath === "string" && rawDreaming.journalPath.trim().length > 0
614
+ ? rawDreaming.journalPath.trim()
615
+ : "DREAMS.md",
616
+ maxEntries:
617
+ typeof rawDreaming.maxEntries === "number"
618
+ ? rawDreaming.maxEntries === 0
619
+ ? 0
620
+ : rawDreaming.maxEntries < 0
621
+ ? 500
622
+ : rawDreaming.maxEntries < 10
623
+ ? 500
624
+ : Math.min(10000, Math.floor(rawDreaming.maxEntries))
625
+ : 500,
626
+ injectRecentCount:
627
+ typeof rawDreaming.injectRecentCount === "number"
628
+ ? Math.min(20, Math.max(0, Math.floor(rawDreaming.injectRecentCount)))
629
+ : 3,
630
+ minIntervalMinutes:
631
+ typeof rawDreaming.minIntervalMinutes === "number"
632
+ ? Math.max(1, Math.floor(rawDreaming.minIntervalMinutes))
633
+ : 120,
634
+ narrativeModel:
635
+ typeof rawDreaming.narrativeModel === "string" && rawDreaming.narrativeModel.trim().length > 0
636
+ ? rawDreaming.narrativeModel.trim()
637
+ : null,
638
+ narrativePromptStyle:
639
+ rawDreaming.narrativePromptStyle === "diary" ||
640
+ rawDreaming.narrativePromptStyle === "analytical"
641
+ ? rawDreaming.narrativePromptStyle
642
+ : "reflective",
643
+ watchFile: rawDreaming.watchFile !== false,
644
+ };
645
+
646
+ // ── Dreams phases config (issue #678 PR 2/4) ─────────────────────────────
647
+ // The `dreams.phases.*` block groups existing top-level lifecycle / REM /
648
+ // deep-sleep gates under a unified namespace. When a value is explicitly
649
+ // set under `dreams.phases.*`, it WINS over the corresponding legacy
650
+ // top-level key. Legacy top-level keys continue to be parsed so existing
651
+ // configs work without modification.
652
+ //
653
+ // Precedence summary (highest → lowest):
654
+ // dreams.phases.lightSleep.enabled > lifecyclePolicyEnabled
655
+ // dreams.phases.rem.enabled > semanticConsolidationEnabled
656
+ // dreams.phases.deepSleep.enabled > (no legacy top-level equivalent)
657
+ //
658
+ // This block is intentionally a DIFFERENT namespace from `dreaming`
659
+ // (the diary surface — `surfaces/dreams.ts`). See docs/dreams.md.
660
+ const rawDreamsBlock =
661
+ cfg.dreams && typeof cfg.dreams === "object" && !Array.isArray(cfg.dreams)
662
+ ? (cfg.dreams as Record<string, unknown>)
663
+ : {};
664
+ const rawDreamsPhases =
665
+ rawDreamsBlock.phases && typeof rawDreamsBlock.phases === "object" && !Array.isArray(rawDreamsBlock.phases)
666
+ ? (rawDreamsBlock.phases as Record<string, unknown>)
667
+ : {};
668
+ const rawDreamsLightSleep =
669
+ rawDreamsPhases.lightSleep && typeof rawDreamsPhases.lightSleep === "object" && !Array.isArray(rawDreamsPhases.lightSleep)
670
+ ? (rawDreamsPhases.lightSleep as Record<string, unknown>)
671
+ : {};
672
+ const rawDreamsRem =
673
+ rawDreamsPhases.rem && typeof rawDreamsPhases.rem === "object" && !Array.isArray(rawDreamsPhases.rem)
674
+ ? (rawDreamsPhases.rem as Record<string, unknown>)
675
+ : {};
676
+ const rawDreamsDeepSleep =
677
+ rawDreamsPhases.deepSleep && typeof rawDreamsPhases.deepSleep === "object" && !Array.isArray(rawDreamsPhases.deepSleep)
678
+ ? (rawDreamsPhases.deepSleep as Record<string, unknown>)
679
+ : {};
680
+
681
+ // Resolve legacy top-level defaults that the phases mirror. We compute them
682
+ // here (before the return statement) so the phase parser can apply precedence
683
+ // without duplicating the clamping logic.
684
+ const legacyLifecyclePolicyEnabled = coerceBooleanLike(cfg.lifecyclePolicyEnabled) ?? true;
685
+ const legacyLifecyclePromoteHeatThreshold =
686
+ typeof cfg.lifecyclePromoteHeatThreshold === "number"
687
+ ? Math.min(1, Math.max(0, cfg.lifecyclePromoteHeatThreshold))
688
+ : 0.55;
689
+ const legacyLifecycleStaleDecayThreshold =
690
+ typeof cfg.lifecycleStaleDecayThreshold === "number"
691
+ ? Math.min(1, Math.max(0, cfg.lifecycleStaleDecayThreshold))
692
+ : 0.65;
693
+ const legacyLifecycleArchiveDecayThreshold =
694
+ typeof cfg.lifecycleArchiveDecayThreshold === "number"
695
+ ? Math.min(1, Math.max(0, cfg.lifecycleArchiveDecayThreshold))
696
+ : 0.85;
697
+ const legacySemanticConsolidationEnabled = cfg.semanticConsolidationEnabled === true;
698
+ const legacySemanticConsolidationIntervalHours =
699
+ typeof cfg.semanticConsolidationIntervalHours === "number"
700
+ ? Math.max(1, Math.floor(cfg.semanticConsolidationIntervalHours))
701
+ : 168;
702
+ const legacySemanticConsolidationThreshold =
703
+ typeof cfg.semanticConsolidationThreshold === "number" ? cfg.semanticConsolidationThreshold : 0.8;
704
+ const legacySemanticConsolidationMinClusterSize =
705
+ typeof cfg.semanticConsolidationMinClusterSize === "number"
706
+ ? Math.max(2, Math.floor(cfg.semanticConsolidationMinClusterSize))
707
+ : 3;
708
+ const legacySemanticConsolidationMaxPerRun =
709
+ typeof cfg.semanticConsolidationMaxPerRun === "number"
710
+ ? Math.max(0, Math.floor(cfg.semanticConsolidationMaxPerRun))
711
+ : 100;
712
+ const legacyConsolidationMinIntervalMs =
713
+ typeof cfg.consolidationMinIntervalMs === "number" ? cfg.consolidationMinIntervalMs : 10 * 60_000;
714
+ const legacyVersioningEnabled = cfg.versioningEnabled === true;
715
+ const legacyVersioningMaxPerPage =
716
+ typeof cfg.versioningMaxPerPage === "number"
717
+ ? Math.max(0, Math.floor(cfg.versioningMaxPerPage))
718
+ : 50;
719
+ const legacyDeepSleepEnabled =
720
+ cfg.nightlyGovernanceCronAutoRegister === true ||
721
+ cfg.qmdTierMigrationEnabled === true ||
722
+ legacyVersioningEnabled;
723
+
724
+ // Light sleep phase — dreams.phases.lightSleep.* wins when present.
725
+ const dreamsLightSleepEnabledRaw = coerceBooleanLike(rawDreamsLightSleep.enabled);
726
+ const dreamsLightSleep: DreamsLightSleepConfig = {
727
+ // new key wins; fall back to resolved legacy default
728
+ enabled: dreamsLightSleepEnabledRaw !== undefined ? dreamsLightSleepEnabledRaw : legacyLifecyclePolicyEnabled,
729
+ cadenceMs:
730
+ typeof rawDreamsLightSleep.cadenceMs === "number"
731
+ ? Math.max(0, Math.floor(rawDreamsLightSleep.cadenceMs))
732
+ : 0, // 0 = no override; orchestrator uses its own internal cadence
733
+ promoteHeatThreshold:
734
+ typeof rawDreamsLightSleep.promoteHeatThreshold === "number"
735
+ ? Math.min(1, Math.max(0, rawDreamsLightSleep.promoteHeatThreshold))
736
+ : legacyLifecyclePromoteHeatThreshold,
737
+ staleDecayThreshold:
738
+ typeof rawDreamsLightSleep.staleDecayThreshold === "number"
739
+ ? Math.min(1, Math.max(0, rawDreamsLightSleep.staleDecayThreshold))
740
+ : legacyLifecycleStaleDecayThreshold,
741
+ archiveDecayThreshold:
742
+ typeof rawDreamsLightSleep.archiveDecayThreshold === "number"
743
+ ? Math.min(1, Math.max(0, rawDreamsLightSleep.archiveDecayThreshold))
744
+ : legacyLifecycleArchiveDecayThreshold,
745
+ filterStaleEnabled:
746
+ rawDreamsLightSleep.filterStaleEnabled !== undefined
747
+ ? coerceBooleanLike(rawDreamsLightSleep.filterStaleEnabled) === true
748
+ : cfg.lifecycleFilterStaleEnabled === true,
749
+ };
750
+
751
+ // REM phase — dreams.phases.rem.* wins when present.
752
+ const dreamsRemEnabledRaw = coerceBooleanLike(rawDreamsRem.enabled);
753
+ const dreamsRem: DreamsRemConfig = {
754
+ enabled: dreamsRemEnabledRaw !== undefined ? dreamsRemEnabledRaw : legacySemanticConsolidationEnabled,
755
+ cadenceMs:
756
+ typeof rawDreamsRem.cadenceMs === "number"
757
+ ? Math.max(0, Math.floor(rawDreamsRem.cadenceMs))
758
+ : legacySemanticConsolidationIntervalHours * 3_600_000,
759
+ similarityThreshold:
760
+ typeof rawDreamsRem.similarityThreshold === "number"
761
+ ? Math.min(1, Math.max(0, rawDreamsRem.similarityThreshold))
762
+ : legacySemanticConsolidationThreshold,
763
+ minClusterSize:
764
+ typeof rawDreamsRem.minClusterSize === "number"
765
+ ? Math.max(2, Math.floor(rawDreamsRem.minClusterSize))
766
+ : legacySemanticConsolidationMinClusterSize,
767
+ maxPerRun:
768
+ typeof rawDreamsRem.maxPerRun === "number"
769
+ ? Math.max(0, Math.floor(rawDreamsRem.maxPerRun))
770
+ : legacySemanticConsolidationMaxPerRun,
771
+ minIntervalMs:
772
+ typeof rawDreamsRem.minIntervalMs === "number"
773
+ ? Math.max(0, Math.floor(rawDreamsRem.minIntervalMs))
774
+ : legacyConsolidationMinIntervalMs,
775
+ };
776
+
777
+ // Deep sleep phase — dreams.phases.deepSleep.* wins when present.
778
+ const dreamsDeepSleepEnabledRaw = coerceBooleanLike(rawDreamsDeepSleep.enabled);
779
+ const dreamsDeepSleep: DreamsDeepSleepConfig = {
780
+ enabled:
781
+ dreamsDeepSleepEnabledRaw !== undefined
782
+ ? dreamsDeepSleepEnabledRaw
783
+ : legacyDeepSleepEnabled,
784
+ enabledExplicitlySet: dreamsDeepSleepEnabledRaw !== undefined,
785
+ cadenceMs:
786
+ typeof rawDreamsDeepSleep.cadenceMs === "number"
787
+ ? Math.max(0, Math.floor(rawDreamsDeepSleep.cadenceMs))
788
+ : 24 * 3_600_000, // default: 24 h (mirrors nightly governance cron)
789
+ versioningEnabled:
790
+ rawDreamsDeepSleep.versioningEnabled !== undefined
791
+ ? coerceBooleanLike(rawDreamsDeepSleep.versioningEnabled) === true
792
+ : legacyVersioningEnabled,
793
+ versioningMaxPerPage:
794
+ typeof rawDreamsDeepSleep.versioningMaxPerPage === "number"
795
+ ? Math.max(0, Math.floor(rawDreamsDeepSleep.versioningMaxPerPage))
796
+ : legacyVersioningMaxPerPage,
797
+ };
798
+
799
+ const dreamsPhases: DreamsPhasesConfig = {
800
+ lightSleep: dreamsLightSleep,
801
+ rem: dreamsRem,
802
+ deepSleep: dreamsDeepSleep,
803
+ };
804
+ // ── End dreams phases ─────────────────────────────────────────────────────
805
+
806
+ const rawHeartbeat =
807
+ cfg.heartbeat && typeof cfg.heartbeat === "object" && !Array.isArray(cfg.heartbeat)
808
+ ? (cfg.heartbeat as Record<string, unknown>)
809
+ : {};
810
+ const heartbeat: HeartbeatConfig = {
811
+ enabled: rawHeartbeat.enabled === true,
812
+ journalPath:
813
+ typeof rawHeartbeat.journalPath === "string" && rawHeartbeat.journalPath.trim().length > 0
814
+ ? rawHeartbeat.journalPath.trim()
815
+ : "HEARTBEAT.md",
816
+ maxPreviousRuns:
817
+ typeof rawHeartbeat.maxPreviousRuns === "number"
818
+ ? Math.min(20, Math.max(0, Math.floor(rawHeartbeat.maxPreviousRuns)))
819
+ : 5,
820
+ watchFile: rawHeartbeat.watchFile !== false,
821
+ detectionMode:
822
+ rawHeartbeat.detectionMode === "runtime-signal" ||
823
+ rawHeartbeat.detectionMode === "heuristic"
824
+ ? rawHeartbeat.detectionMode
825
+ : "auto",
826
+ gateExtractionDuringHeartbeat:
827
+ rawHeartbeat.gateExtractionDuringHeartbeat !== false,
828
+ };
829
+ const rawCodexCompat =
830
+ cfg.codexCompat && typeof cfg.codexCompat === "object" && !Array.isArray(cfg.codexCompat)
831
+ ? (cfg.codexCompat as Record<string, unknown>)
832
+ : {};
833
+ const codexCompat: CodexCompatConfig = {
834
+ enabled: rawCodexCompat.enabled === true,
835
+ threadIdBufferKeying: rawCodexCompat.threadIdBufferKeying !== false,
836
+ compactionFlushMode:
837
+ typeof rawCodexCompat.compactionFlushMode === "string" &&
838
+ VALID_CODEX_COMPACTION_FLUSH_MODES.includes(
839
+ rawCodexCompat.compactionFlushMode as CodexCompactionFlushMode,
840
+ )
841
+ ? (rawCodexCompat.compactionFlushMode as CodexCompactionFlushMode)
842
+ : "auto",
843
+ fingerprintDedup: rawCodexCompat.fingerprintDedup !== false,
844
+ };
845
+
846
+ // Validate the shape of the `procedural` config block BEFORE applying the
847
+ // default-on behavior. Codex P2 on #609: a shorthand opt-out like
848
+ // `procedural: false` or `procedural: null` would previously normalize
849
+ // silently to `{}`, and the omitted-key branch would then enable the
850
+ // feature — the opposite of what the user asked for. Reject
851
+ // non-object shapes loudly per CLAUDE.md rule 51.
852
+ if (
853
+ cfg.procedural !== undefined &&
854
+ (cfg.procedural === null ||
855
+ typeof cfg.procedural !== "object" ||
856
+ Array.isArray(cfg.procedural))
857
+ ) {
858
+ throw new Error(
859
+ `procedural must be an object (got ${JSON.stringify(cfg.procedural)}). Use procedural: { enabled: false } to opt out; omit the key to use the default-on behavior (issue #567 PR 4).`,
860
+ );
861
+ }
862
+ const rawProcedural =
863
+ cfg.procedural && typeof cfg.procedural === "object" && !Array.isArray(cfg.procedural)
864
+ ? (cfg.procedural as Record<string, unknown>)
865
+ : {};
866
+ const proceduralMinCoerced = coerceNumber(rawProcedural.minOccurrences);
867
+ const proceduralMinRaw =
868
+ proceduralMinCoerced !== undefined
869
+ ? Math.floor(proceduralMinCoerced)
870
+ : 3;
871
+ const successFloorRaw = coerceNumber(rawProcedural.successFloor);
872
+ // Safer-by-default floor (issue #567 PR 3/5): raise from 0.7 to 0.75.
873
+ // Miner promotion now requires a stricter trajectory success rate before
874
+ // procedures become candidates, reducing false positives when procedural
875
+ // recall is enabled by default in slice 4.
876
+ const successFloor =
877
+ successFloorRaw !== undefined &&
878
+ successFloorRaw >= 0 &&
879
+ successFloorRaw <= 1
880
+ ? successFloorRaw
881
+ : 0.75;
882
+ const autoPromoteOccRaw = coerceNumber(rawProcedural.autoPromoteOccurrences);
883
+ const autoPromoteOccurrences =
884
+ autoPromoteOccRaw !== undefined && Number.isFinite(autoPromoteOccRaw)
885
+ ? autoPromoteOccRaw <= 0
886
+ ? 0
887
+ : Math.min(10_000, Math.max(1, Math.floor(autoPromoteOccRaw)))
888
+ : 8;
889
+ const lookbackCoerced = coerceNumber(rawProcedural.lookbackDays);
890
+ // Safer-by-default lookback (issue #567 PR 3/5): lower from 30 to 14 days.
891
+ // Shorter window keeps mined procedures more recent, which improves
892
+ // relevance once recall is on by default.
893
+ const lookbackDays =
894
+ lookbackCoerced !== undefined && Number.isFinite(lookbackCoerced)
895
+ ? Math.min(3650, Math.max(1, Math.floor(lookbackCoerced)))
896
+ : 14;
897
+ const recallMaxCoerced = coerceNumber(rawProcedural.recallMaxProcedures);
898
+ // Safer-by-default recall cap (issue #567 PR 3/5): lower from 3 to 2.
899
+ // Cap the injected procedure block so enabling procedural recall by
900
+ // default does not crowd out other recall sections.
901
+ const recallMaxProcedures =
902
+ recallMaxCoerced !== undefined && Number.isFinite(recallMaxCoerced)
903
+ ? Math.min(10, Math.max(1, Math.floor(recallMaxCoerced)))
904
+ : 2;
905
+ // Default-on procedural memory (issue #567 PR 4/5): if the user has NOT
906
+ // explicitly set `procedural.enabled`, enable it. Explicit `false` (or any
907
+ // value coerceBool reads as false: `"0"`, `"no"`, `"off"`, `false`) keeps
908
+ // the feature off. CLAUDE.md rules:
909
+ // - #30 — escape hatch remains for operators who want to stay opt-out.
910
+ // - #36 — "false"-ish strings coerce to false via coerceBool.
911
+ // - #51 — when the key IS present but the value can't be understood
912
+ // (typo like `"fales"` or a number like `0`), reject loudly instead
913
+ // of silently flipping the default. Silent fallback on bad input is
914
+ // how procedural memory would end up "fail-open" for typos.
915
+ const rawEnabledValue = rawProcedural.enabled;
916
+ let proceduralEnabled: boolean;
917
+ if (rawEnabledValue === undefined) {
918
+ proceduralEnabled = true;
919
+ } else {
920
+ const enabledCoerced = coerceBool(rawEnabledValue);
921
+ if (enabledCoerced === undefined) {
922
+ throw new Error(
923
+ `procedural.enabled must be a boolean or one of "true"/"false"/"1"/"0"/"yes"/"no"/"on"/"off" (got ${JSON.stringify(rawEnabledValue)}). Omit the key to use the default-on behavior (issue #567 PR 4).`,
924
+ );
925
+ }
926
+ proceduralEnabled = enabledCoerced;
927
+ }
928
+ const procedural: ProceduralConfig = {
929
+ enabled: proceduralEnabled,
930
+ /** `0` skips all mining (`minOccurrences_zero`); otherwise clusters need at least this many members. */
931
+ minOccurrences: Math.min(1000, Math.max(0, proceduralMinRaw)),
932
+ successFloor,
933
+ autoPromoteOccurrences,
934
+ autoPromoteEnabled: coerceBool(rawProcedural.autoPromoteEnabled) === true,
935
+ lookbackDays,
936
+ proceduralMiningCronAutoRegister: coerceBool(rawProcedural.proceduralMiningCronAutoRegister) === true,
937
+ recallMaxProcedures,
938
+ };
939
+
940
+ // Coding-agent project/branch scoping (issue #569)
941
+ const rawCodingMode =
942
+ cfg.codingMode && typeof cfg.codingMode === "object" && !Array.isArray(cfg.codingMode)
943
+ ? (cfg.codingMode as Record<string, unknown>)
944
+ : {};
945
+ // Default: projectScope=true (enabled), branchScope=false (opt-in).
946
+ // `coerceBool` treats "false"/"0"/"no"/"off" as false (CLAUDE.md #36).
947
+ const codingProjectScopeRaw = coerceBool(rawCodingMode.projectScope);
948
+ const codingBranchScopeRaw = coerceBool(rawCodingMode.branchScope);
949
+ const codingGlobalFallbackRaw = coerceBool(rawCodingMode.globalFallback);
950
+ const codingMode: CodingModeConfig = {
951
+ projectScope: codingProjectScopeRaw === undefined ? true : codingProjectScopeRaw,
952
+ branchScope: codingBranchScopeRaw === true,
953
+ // Default true — project-scoped sessions include the root namespace in
954
+ // read fallbacks so globally useful memories remain visible. CLAUDE.md #30.
955
+ globalFallback: codingGlobalFallbackRaw === undefined ? true : codingGlobalFallbackRaw,
956
+ };
957
+
958
+ const memoryDir =
959
+ typeof cfg.memoryDir === "string" && cfg.memoryDir.length > 0
960
+ ? cfg.memoryDir
961
+ : DEFAULT_MEMORY_DIR;
962
+ const rawIdentityInjectionMode = cfg.identityInjectionMode as string | undefined;
963
+ const identityInjectionMode: IdentityInjectionMode =
964
+ rawIdentityInjectionMode
965
+ && VALID_IDENTITY_INJECTION_MODES.includes(rawIdentityInjectionMode as IdentityInjectionMode)
966
+ ? (rawIdentityInjectionMode as IdentityInjectionMode)
967
+ : "recovery_only";
968
+ const identityContinuityEnabled = cfg.identityContinuityEnabled === true;
969
+ const sessionObserverBands: SessionObserverBandConfig[] = Array.isArray(cfg.sessionObserverBands)
970
+ ? (cfg.sessionObserverBands as Array<Record<string, unknown>>)
971
+ .map((band) => ({
972
+ maxBytes:
973
+ typeof band?.maxBytes === "number" ? Math.max(0, Math.floor(band.maxBytes)) : 0,
974
+ triggerDeltaBytes:
975
+ typeof band?.triggerDeltaBytes === "number"
976
+ ? Math.max(0, Math.floor(band.triggerDeltaBytes))
977
+ : 0,
978
+ triggerDeltaTokens:
979
+ typeof band?.triggerDeltaTokens === "number"
980
+ ? Math.max(0, Math.floor(band.triggerDeltaTokens))
981
+ : 0,
982
+ }))
983
+ .filter((band) => band.maxBytes > 0)
984
+ : cloneDefaultSessionObserverBands();
985
+
986
+ const principalRules: PrincipalRule[] = Array.isArray(cfg.principalFromSessionKeyRules)
987
+ ? (cfg.principalFromSessionKeyRules as any[]).map((r) => ({
988
+ match: typeof r?.match === "string" ? r.match : "",
989
+ principal: typeof r?.principal === "string" ? r.principal : "",
990
+ })).filter((r) => r.match.length > 0 && r.principal.length > 0)
991
+ : [];
992
+ const entitySchemas = normalizeEntitySchemas(cfg.entitySchemas);
993
+
994
+ // Optional file hygiene (memory file limits / truncation risk mitigation)
995
+ const rawHygiene =
996
+ cfg.fileHygiene && typeof cfg.fileHygiene === "object" && !Array.isArray(cfg.fileHygiene)
997
+ ? (cfg.fileHygiene as Record<string, unknown>)
998
+ : undefined;
999
+ const hygieneEnabled = rawHygiene?.enabled === true;
1000
+ const fileHygiene = hygieneEnabled
1001
+ ? {
1002
+ enabled: true,
1003
+ lintEnabled: rawHygiene?.lintEnabled !== false,
1004
+ lintBudgetBytes:
1005
+ typeof rawHygiene?.lintBudgetBytes === "number" ? rawHygiene.lintBudgetBytes : 20_000,
1006
+ lintWarnRatio:
1007
+ typeof rawHygiene?.lintWarnRatio === "number" ? rawHygiene.lintWarnRatio : 0.8,
1008
+ lintPaths: Array.isArray(rawHygiene?.lintPaths)
1009
+ ? (rawHygiene!.lintPaths as string[])
1010
+ : ["IDENTITY.md", "MEMORY.md"],
1011
+ rotateEnabled: rawHygiene?.rotateEnabled === true,
1012
+ rotateMaxBytes:
1013
+ typeof rawHygiene?.rotateMaxBytes === "number" ? rawHygiene.rotateMaxBytes : 18_000,
1014
+ rotateKeepTailChars:
1015
+ typeof rawHygiene?.rotateKeepTailChars === "number"
1016
+ ? rawHygiene.rotateKeepTailChars
1017
+ : 2000,
1018
+ rotatePaths: Array.isArray(rawHygiene?.rotatePaths)
1019
+ ? (rawHygiene!.rotatePaths as string[])
1020
+ : ["IDENTITY.md"],
1021
+ archiveDir:
1022
+ typeof rawHygiene?.archiveDir === "string" && rawHygiene.archiveDir.length > 0
1023
+ ? (rawHygiene.archiveDir as string)
1024
+ : ".engram-archive",
1025
+ runMinIntervalMs:
1026
+ typeof rawHygiene?.runMinIntervalMs === "number" ? rawHygiene.runMinIntervalMs : 5 * 60 * 1000,
1027
+ warningsLogEnabled: rawHygiene?.warningsLogEnabled === true,
1028
+ warningsLogPath:
1029
+ typeof rawHygiene?.warningsLogPath === "string" && rawHygiene.warningsLogPath.length > 0
1030
+ ? (rawHygiene.warningsLogPath as string)
1031
+ : "hygiene/warnings.md",
1032
+ indexEnabled: rawHygiene?.indexEnabled === true,
1033
+ indexPath:
1034
+ typeof rawHygiene?.indexPath === "string" && rawHygiene.indexPath.length > 0
1035
+ ? (rawHygiene.indexPath as string)
1036
+ : "ENGRAM_INDEX.md",
1037
+ }
1038
+ : undefined;
1039
+
1040
+ const rawNativeKnowledge =
1041
+ cfg.nativeKnowledge && typeof cfg.nativeKnowledge === "object" && !Array.isArray(cfg.nativeKnowledge)
1042
+ ? (cfg.nativeKnowledge as Record<string, unknown>)
1043
+ : undefined;
1044
+ const nativeKnowledge = rawNativeKnowledge?.enabled === true
1045
+ ? {
1046
+ enabled: true,
1047
+ includeFiles: Array.isArray(rawNativeKnowledge.includeFiles)
1048
+ ? (rawNativeKnowledge.includeFiles as unknown[])
1049
+ .filter((value): value is string => typeof value === "string")
1050
+ .map((value) => value.trim())
1051
+ .filter(Boolean)
1052
+ : ["IDENTITY.md", "MEMORY.md"],
1053
+ maxChunkChars:
1054
+ typeof rawNativeKnowledge.maxChunkChars === "number"
1055
+ ? Math.max(200, Math.floor(rawNativeKnowledge.maxChunkChars))
1056
+ : 900,
1057
+ maxResults:
1058
+ typeof rawNativeKnowledge.maxResults === "number"
1059
+ ? Math.max(0, Math.floor(rawNativeKnowledge.maxResults))
1060
+ : 4,
1061
+ maxChars:
1062
+ typeof rawNativeKnowledge.maxChars === "number"
1063
+ ? Math.max(0, Math.floor(rawNativeKnowledge.maxChars))
1064
+ : 2400,
1065
+ stateDir:
1066
+ normalizeMemoryRelativeDir(rawNativeKnowledge.stateDir, "state/native-knowledge"),
1067
+ openclawWorkspace:
1068
+ rawNativeKnowledge.openclawWorkspace &&
1069
+ typeof rawNativeKnowledge.openclawWorkspace === "object" &&
1070
+ !Array.isArray(rawNativeKnowledge.openclawWorkspace) &&
1071
+ (rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).enabled === true
1072
+ ? {
1073
+ enabled: true,
1074
+ bootstrapFiles: Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).bootstrapFiles)
1075
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).bootstrapFiles as unknown[])
1076
+ .filter((value): value is string => typeof value === "string")
1077
+ .map((value) => value.trim())
1078
+ .filter(Boolean)
1079
+ : ["IDENTITY.md", "MEMORY.md", "USER.md"],
1080
+ handoffGlobs: Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).handoffGlobs)
1081
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).handoffGlobs as unknown[])
1082
+ .filter((value): value is string => typeof value === "string")
1083
+ .map((value) => value.trim())
1084
+ .filter(Boolean)
1085
+ : ["**/*handoff*.md", "handoffs/**/*.md"],
1086
+ dailySummaryGlobs: Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).dailySummaryGlobs)
1087
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).dailySummaryGlobs as unknown[])
1088
+ .filter((value): value is string => typeof value === "string")
1089
+ .map((value) => value.trim())
1090
+ .filter(Boolean)
1091
+ : ["**/*daily*summary*.md", "summaries/**/*.md"],
1092
+ automationNoteGlobs: Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).automationNoteGlobs)
1093
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).automationNoteGlobs as unknown[])
1094
+ .filter((value): value is string => typeof value === "string")
1095
+ .map((value) => value.trim())
1096
+ .filter(Boolean)
1097
+ : [],
1098
+ workspaceDocGlobs: Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).workspaceDocGlobs)
1099
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).workspaceDocGlobs as unknown[])
1100
+ .filter((value): value is string => typeof value === "string")
1101
+ .map((value) => value.trim())
1102
+ .filter(Boolean)
1103
+ : [],
1104
+ excludeGlobs: [
1105
+ ".git/**",
1106
+ "node_modules/**",
1107
+ "dist/**",
1108
+ "build/**",
1109
+ "coverage/**",
1110
+ "**/*.log",
1111
+ "**/.env*",
1112
+ "**/*.pem",
1113
+ "**/*.key",
1114
+ ...(Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).excludeGlobs)
1115
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).excludeGlobs as unknown[])
1116
+ .filter((value): value is string => typeof value === "string")
1117
+ .map((value) => value.trim())
1118
+ .filter(Boolean)
1119
+ : []),
1120
+ ].filter((value, index, array) => array.indexOf(value) === index),
1121
+ sharedSafeGlobs: Array.isArray((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).sharedSafeGlobs)
1122
+ ? ((rawNativeKnowledge.openclawWorkspace as Record<string, unknown>).sharedSafeGlobs as unknown[])
1123
+ .filter((value): value is string => typeof value === "string")
1124
+ .map((value) => value.trim())
1125
+ .filter(Boolean)
1126
+ : [],
1127
+ }
1128
+ : undefined,
1129
+ obsidianVaults: Array.isArray(rawNativeKnowledge.obsidianVaults)
1130
+ ? (rawNativeKnowledge.obsidianVaults as unknown[])
1131
+ .filter((value): value is Record<string, unknown> => !!value && typeof value === "object" && !Array.isArray(value))
1132
+ .map((vault, index) => {
1133
+ const defaultId = `vault-${index + 1}`;
1134
+ return {
1135
+ id:
1136
+ typeof vault.id === "string" && vault.id.trim().length > 0
1137
+ ? vault.id.trim()
1138
+ : defaultId,
1139
+ rootDir:
1140
+ typeof vault.rootDir === "string" && vault.rootDir.trim().length > 0
1141
+ ? vault.rootDir.trim()
1142
+ : "",
1143
+ includeGlobs: Array.isArray(vault.includeGlobs)
1144
+ ? (vault.includeGlobs as unknown[])
1145
+ .filter((value): value is string => typeof value === "string")
1146
+ .map((value) => value.trim())
1147
+ .filter(Boolean)
1148
+ : ["**/*.md"],
1149
+ excludeGlobs: Array.isArray(vault.excludeGlobs)
1150
+ ? (vault.excludeGlobs as unknown[])
1151
+ .filter((value): value is string => typeof value === "string")
1152
+ .map((value) => value.trim())
1153
+ .filter(Boolean)
1154
+ : [".obsidian/**", "**/*.canvas", "**/*.png", "**/*.jpg", "**/*.jpeg", "**/*.gif", "**/*.pdf"],
1155
+ namespace:
1156
+ typeof vault.namespace === "string" && vault.namespace.trim().length > 0
1157
+ ? vault.namespace.trim()
1158
+ : undefined,
1159
+ privacyClass:
1160
+ typeof vault.privacyClass === "string" && vault.privacyClass.trim().length > 0
1161
+ ? vault.privacyClass.trim()
1162
+ : undefined,
1163
+ folderRules: Array.isArray(vault.folderRules)
1164
+ ? (vault.folderRules as unknown[])
1165
+ .filter((value): value is Record<string, unknown> => !!value && typeof value === "object" && !Array.isArray(value))
1166
+ .map((rule) => ({
1167
+ pathPrefix:
1168
+ typeof rule.pathPrefix === "string" && rule.pathPrefix.trim().length > 0
1169
+ ? rule.pathPrefix.trim()
1170
+ : "",
1171
+ namespace:
1172
+ typeof rule.namespace === "string" && rule.namespace.trim().length > 0
1173
+ ? rule.namespace.trim()
1174
+ : undefined,
1175
+ privacyClass:
1176
+ typeof rule.privacyClass === "string" && rule.privacyClass.trim().length > 0
1177
+ ? rule.privacyClass.trim()
1178
+ : undefined,
1179
+ }))
1180
+ .filter((rule) => rule.pathPrefix.length > 0)
1181
+ : [],
1182
+ dailyNotePatterns: Array.isArray(vault.dailyNotePatterns)
1183
+ ? (vault.dailyNotePatterns as unknown[])
1184
+ .filter((value): value is string => typeof value === "string")
1185
+ .map((value) => value.trim())
1186
+ .filter(Boolean)
1187
+ : ["YYYY-MM-DD"],
1188
+ materializeBacklinks: vault.materializeBacklinks === true,
1189
+ };
1190
+ })
1191
+ .filter((vault) => vault.rootDir.length > 0)
1192
+ : [],
1193
+ }
1194
+ : undefined;
1195
+
1196
+ const rawAgentAccessHttp =
1197
+ cfg.agentAccessHttp && typeof cfg.agentAccessHttp === "object" && !Array.isArray(cfg.agentAccessHttp)
1198
+ ? (cfg.agentAccessHttp as Record<string, unknown>)
1199
+ : undefined;
1200
+ const agentAccessAuthToken = parseAgentAccessAuthToken(rawAgentAccessHttp?.authToken);
1201
+ const agentAccessHttp = {
1202
+ enabled: rawAgentAccessHttp?.enabled === true,
1203
+ host:
1204
+ typeof rawAgentAccessHttp?.host === "string" && rawAgentAccessHttp.host.trim().length > 0
1205
+ ? rawAgentAccessHttp.host.trim()
1206
+ : "127.0.0.1",
1207
+ port:
1208
+ typeof rawAgentAccessHttp?.port === "number"
1209
+ ? Math.max(0, Math.floor(rawAgentAccessHttp.port))
1210
+ : 4318,
1211
+ [["auth", "Token"].join("")]: agentAccessAuthToken,
1212
+ principal:
1213
+ typeof rawAgentAccessHttp?.principal === "string" && rawAgentAccessHttp.principal.trim().length > 0
1214
+ ? resolveEnvVars(rawAgentAccessHttp.principal)
1215
+ : readEnvVar("OPENCLAW_ENGRAM_ACCESS_PRINCIPAL")?.trim() || undefined,
1216
+ maxBodyBytes:
1217
+ typeof rawAgentAccessHttp?.maxBodyBytes === "number"
1218
+ ? Math.max(1, Math.floor(rawAgentAccessHttp.maxBodyBytes))
1219
+ : 131072,
1220
+ };
1221
+
1222
+ let baseUrl: string | undefined;
1223
+ if (typeof cfg.openaiBaseUrl === "string" && cfg.openaiBaseUrl.length > 0) {
1224
+ baseUrl = normalizeOpenaiBaseUrl(resolveEnvVars(cfg.openaiBaseUrl), "config");
1225
+ } else {
1226
+ baseUrl = normalizeOpenaiBaseUrl(readEnvVar("OPENAI_BASE_URL"), "env");
1227
+ }
1228
+
1229
+ const sharedCrossSignalSemanticEnabled =
1230
+ cfg.sharedCrossSignalSemanticEnabled === true || cfg.crossSignalsSemanticEnabled === true;
1231
+ const sharedCrossSignalSemanticTimeoutMs =
1232
+ typeof cfg.sharedCrossSignalSemanticTimeoutMs === "number"
1233
+ ? Math.max(1, Math.floor(cfg.sharedCrossSignalSemanticTimeoutMs))
1234
+ : typeof cfg.crossSignalsSemanticTimeoutMs === "number"
1235
+ ? Math.max(1, Math.floor(cfg.crossSignalsSemanticTimeoutMs))
1236
+ : 4000;
1237
+ const recallPipelineConfig = buildRecallPipelineConfig(cfg);
1238
+
1239
+ return {
1240
+ openaiApiKey: apiKey,
1241
+ openaiBaseUrl: baseUrl,
1242
+ model,
1243
+ reasoningEffort,
1244
+ triggerMode,
1245
+ bufferMaxTurns:
1246
+ typeof cfg.bufferMaxTurns === "number" ? cfg.bufferMaxTurns : 5,
1247
+ bufferMaxMinutes:
1248
+ typeof cfg.bufferMaxMinutes === "number" ? cfg.bufferMaxMinutes : 15,
1249
+ // Surprise-gated buffer flush (issue #563, D-MEM). See types.ts for
1250
+ // semantics. Default off so PR 2 ships as a pure no-op until an operator
1251
+ // opts in. PR 4 benchmarks the flag and may flip the default.
1252
+ //
1253
+ // Use `coerceBool` rather than a strict `=== true` check: CLI operators
1254
+ // set booleans via `--config bufferSurpriseTriggerEnabled=true` which
1255
+ // arrives as the string `"true"` — the strict form would silently
1256
+ // leave the flag off. Matches the coercion contract established for
1257
+ // other boolean config keys (CLAUDE.md rule #36).
1258
+ bufferSurpriseTriggerEnabled:
1259
+ coerceBool(cfg.bufferSurpriseTriggerEnabled) === true,
1260
+ // Numeric surprise knobs go through `coerceNumber` so CLI operators
1261
+ // can pass `--config bufferSurpriseThreshold=0.5` without the string
1262
+ // silently dropping to the default. Matches the coercion contract
1263
+ // applied to other numeric config keys (CLAUDE.md rule #28).
1264
+ bufferSurpriseThreshold: clampSurpriseThreshold(
1265
+ coerceNumber(cfg.bufferSurpriseThreshold),
1266
+ 0.35,
1267
+ ),
1268
+ bufferSurpriseK: clampSurpriseK(
1269
+ coerceNumber(cfg.bufferSurpriseK),
1270
+ 5,
1271
+ ),
1272
+ bufferSurpriseRecentMemoryCount: clampSurpriseRecentMemoryCount(
1273
+ coerceNumber(cfg.bufferSurpriseRecentMemoryCount),
1274
+ 20,
1275
+ ),
1276
+ bufferSurpriseProbeTimeoutMs: clampSurpriseProbeTimeoutMs(
1277
+ coerceNumber(cfg.bufferSurpriseProbeTimeoutMs),
1278
+ 2000,
1279
+ ),
1280
+ consolidateEveryN:
1281
+ typeof cfg.consolidateEveryN === "number" ? cfg.consolidateEveryN : 3,
1282
+ highSignalPatterns: Array.isArray(cfg.highSignalPatterns)
1283
+ ? (cfg.highSignalPatterns as string[])
1284
+ : [],
1285
+ maxMemoryTokens:
1286
+ typeof cfg.maxMemoryTokens === "number" ? cfg.maxMemoryTokens : 2000,
1287
+ memoryOsPreset,
1288
+ qmdEnabled: cfg.qmdEnabled !== false,
1289
+ qmdCollection:
1290
+ typeof cfg.qmdCollection === "string"
1291
+ ? cfg.qmdCollection
1292
+ // TODO(#403): Keep legacy collection name for backwards compat so existing
1293
+ // installs don't lose their QMD vector store data on upgrade. New installs
1294
+ // can override via qmdCollection config. Consider migrating in a future PR.
1295
+ : "openclaw-engram",
1296
+ qmdMaxResults:
1297
+ typeof cfg.qmdMaxResults === "number" ? cfg.qmdMaxResults : 8,
1298
+ qmdColdTierEnabled: cfg.qmdColdTierEnabled === true,
1299
+ qmdColdCollection:
1300
+ typeof cfg.qmdColdCollection === "string" && cfg.qmdColdCollection.length > 0
1301
+ ? cfg.qmdColdCollection
1302
+ // TODO(#403): Keep legacy collection name for backwards compat.
1303
+ : "openclaw-engram-cold",
1304
+ qmdColdMaxResults:
1305
+ typeof cfg.qmdColdMaxResults === "number" ? cfg.qmdColdMaxResults : 8,
1306
+ // Issue #678 PR 2/4: gate hot/cold tier migration (a deep-sleep activity)
1307
+ // on dreams.phases.deepSleep.enabled. When deep sleep is disabled,
1308
+ // tier migration is forced off regardless of legacy flag.
1309
+ qmdTierMigrationEnabled: dreamsDeepSleep.enabled && cfg.qmdTierMigrationEnabled === true,
1310
+ qmdTierDemotionMinAgeDays:
1311
+ typeof cfg.qmdTierDemotionMinAgeDays === "number"
1312
+ ? Math.max(0, Math.floor(cfg.qmdTierDemotionMinAgeDays))
1313
+ : 14,
1314
+ qmdTierDemotionValueThreshold:
1315
+ typeof cfg.qmdTierDemotionValueThreshold === "number"
1316
+ ? Math.max(0, Math.min(1, cfg.qmdTierDemotionValueThreshold))
1317
+ : 0.35,
1318
+ qmdTierPromotionValueThreshold:
1319
+ typeof cfg.qmdTierPromotionValueThreshold === "number"
1320
+ ? Math.max(0, Math.min(1, cfg.qmdTierPromotionValueThreshold))
1321
+ : 0.7,
1322
+ qmdTierParityGraphEnabled: cfg.qmdTierParityGraphEnabled !== false,
1323
+ qmdTierParityHiMemEnabled: cfg.qmdTierParityHiMemEnabled !== false,
1324
+ qmdTierAutoBackfillEnabled: cfg.qmdTierAutoBackfillEnabled === true,
1325
+ qmdSupportedVersion: parseQmdSupportedVersion(cfg.qmdSupportedVersion),
1326
+ qmdAutoUpgradeEnabled: coerceBool(cfg.qmdAutoUpgradeEnabled) === true,
1327
+ qmdAutoUpgradeCheckIntervalMs: parseBoundedIntegerMs(
1328
+ cfg.qmdAutoUpgradeCheckIntervalMs,
1329
+ 24 * 60 * 60_000,
1330
+ 60_000,
1331
+ 30 * 24 * 60 * 60_000,
1332
+ ),
1333
+ qmdChunkStrategy: parseQmdChunkStrategy(cfg.qmdChunkStrategy),
1334
+ qmdCandidateLimit: parsePositiveInteger(cfg.qmdCandidateLimit, "qmdCandidateLimit"),
1335
+ qmdQueryRerankEnabled: coerceBooleanLike(cfg.qmdQueryRerankEnabled) ?? true,
1336
+ qmdIndexName: parseOptionalNonEmptyString(cfg.qmdIndexName),
1337
+ qmdForceCpu: coerceBooleanLike(cfg.qmdForceCpu) ?? false,
1338
+ qmdGpuBackend: parseQmdGpuBackend(cfg.qmdGpuBackend),
1339
+ qmdEmbedParallelism: parseBoundedPositiveInteger(
1340
+ cfg.qmdEmbedParallelism,
1341
+ 1,
1342
+ 8,
1343
+ "qmdEmbedParallelism",
1344
+ ),
1345
+ qmdEmbedModel: parseOptionalNonEmptyString(cfg.qmdEmbedModel),
1346
+ qmdRerankModel: parseOptionalNonEmptyString(cfg.qmdRerankModel),
1347
+ qmdGenerateModel: parseOptionalNonEmptyString(cfg.qmdGenerateModel),
1348
+ embeddingFallbackEnabled: cfg.embeddingFallbackEnabled !== false,
1349
+ embeddingFallbackProvider:
1350
+ cfg.embeddingFallbackProvider === "openai"
1351
+ ? "openai"
1352
+ : cfg.embeddingFallbackProvider === "local"
1353
+ ? "local"
1354
+ : "auto",
1355
+ embeddingFallbackModel:
1356
+ typeof cfg.embeddingFallbackModel === "string" && cfg.embeddingFallbackModel.length > 0
1357
+ ? cfg.embeddingFallbackModel
1358
+ : "",
1359
+ qmdPath:
1360
+ typeof cfg.qmdPath === "string" && cfg.qmdPath.length > 0
1361
+ ? cfg.qmdPath
1362
+ : undefined,
1363
+ memoryDir,
1364
+ debug: cfg.debug === true,
1365
+ identityEnabled: cfg.identityEnabled !== false,
1366
+ identityContinuityEnabled,
1367
+ identityInjectionMode,
1368
+ identityMaxInjectChars:
1369
+ typeof cfg.identityMaxInjectChars === "number"
1370
+ ? Math.max(0, Math.floor(cfg.identityMaxInjectChars))
1371
+ : 1200,
1372
+ continuityIncidentLoggingEnabled:
1373
+ typeof cfg.continuityIncidentLoggingEnabled === "boolean"
1374
+ ? cfg.continuityIncidentLoggingEnabled
1375
+ : identityContinuityEnabled,
1376
+ continuityAuditEnabled: cfg.continuityAuditEnabled === true,
1377
+ sessionObserverEnabled: cfg.sessionObserverEnabled === true,
1378
+ sessionObserverDebounceMs:
1379
+ typeof cfg.sessionObserverDebounceMs === "number"
1380
+ ? Math.max(0, Math.floor(cfg.sessionObserverDebounceMs))
1381
+ : 120_000,
1382
+ sessionObserverBands,
1383
+ injectQuestions: cfg.injectQuestions === true,
1384
+ commitmentDecayDays: parseIntegerAtLeast(
1385
+ cfg.commitmentDecayDays,
1386
+ 90,
1387
+ 1,
1388
+ "commitmentDecayDays",
1389
+ ),
1390
+ workspaceDir:
1391
+ typeof cfg.workspaceDir === "string" && cfg.workspaceDir.length > 0
1392
+ ? cfg.workspaceDir
1393
+ : DEFAULT_WORKSPACE_DIR,
1394
+ captureMode,
1395
+ fileHygiene,
1396
+ nativeKnowledge,
1397
+ agentAccessHttp,
1398
+ // Access tracking (Phase 1A)
1399
+ accessTrackingEnabled: cfg.accessTrackingEnabled !== false,
1400
+ accessTrackingBufferMaxSize:
1401
+ typeof cfg.accessTrackingBufferMaxSize === "number"
1402
+ ? cfg.accessTrackingBufferMaxSize
1403
+ : 100,
1404
+ // Retrieval options
1405
+ recencyWeight:
1406
+ typeof cfg.recencyWeight === "number" ? cfg.recencyWeight : 0.2,
1407
+ boostAccessCount: cfg.boostAccessCount !== false,
1408
+ recordEmptyRecallImpressions: cfg.recordEmptyRecallImpressions === true,
1409
+ // v2.2 Advanced Retrieval (safe defaults: off unless enabled)
1410
+ queryExpansionEnabled: cfg.queryExpansionEnabled === true,
1411
+ queryExpansionMaxQueries:
1412
+ typeof cfg.queryExpansionMaxQueries === "number"
1413
+ ? cfg.queryExpansionMaxQueries
1414
+ : 4,
1415
+ queryExpansionMinTokenLen:
1416
+ typeof cfg.queryExpansionMinTokenLen === "number"
1417
+ ? cfg.queryExpansionMinTokenLen
1418
+ : 3,
1419
+ rerankEnabled: cfg.rerankEnabled === true,
1420
+ rerankProvider:
1421
+ cfg.rerankProvider === "cloud" ? "cloud" : "local",
1422
+ rerankMaxCandidates:
1423
+ typeof cfg.rerankMaxCandidates === "number" ? cfg.rerankMaxCandidates : 20,
1424
+ rerankTimeoutMs:
1425
+ typeof cfg.rerankTimeoutMs === "number" ? cfg.rerankTimeoutMs : 8000,
1426
+ rerankCacheEnabled: cfg.rerankCacheEnabled !== false,
1427
+ rerankCacheTtlMs:
1428
+ typeof cfg.rerankCacheTtlMs === "number" ? cfg.rerankCacheTtlMs : 60 * 60 * 1000,
1429
+ feedbackEnabled: cfg.feedbackEnabled === true,
1430
+ // v2.2 Negative Examples (safe defaults: off unless enabled)
1431
+ negativeExamplesEnabled: cfg.negativeExamplesEnabled === true,
1432
+ negativeExamplesPenaltyPerHit:
1433
+ typeof cfg.negativeExamplesPenaltyPerHit === "number"
1434
+ ? cfg.negativeExamplesPenaltyPerHit
1435
+ : 0.05,
1436
+ negativeExamplesPenaltyCap:
1437
+ typeof cfg.negativeExamplesPenaltyCap === "number"
1438
+ ? cfg.negativeExamplesPenaltyCap
1439
+ : 0.25,
1440
+ // Chunking (Phase 2A)
1441
+ chunkingEnabled: cfg.chunkingEnabled === true, // Off by default initially
1442
+ chunkingTargetTokens:
1443
+ typeof cfg.chunkingTargetTokens === "number" ? cfg.chunkingTargetTokens : 200,
1444
+ chunkingMinTokens:
1445
+ typeof cfg.chunkingMinTokens === "number" ? cfg.chunkingMinTokens : 150,
1446
+ chunkingOverlapSentences:
1447
+ typeof cfg.chunkingOverlapSentences === "number" ? cfg.chunkingOverlapSentences : 2,
1448
+ // Semantic Chunking (Issue #368)
1449
+ semanticChunkingEnabled: cfg.semanticChunkingEnabled === true,
1450
+ semanticChunkingConfig: parseSemanticChunkingConfig(cfg.semanticChunkingConfig),
1451
+ // Contradiction Detection (Phase 2B)
1452
+ contradictionDetectionEnabled: cfg.contradictionDetectionEnabled === true, // Off by default initially
1453
+ contradictionSimilarityThreshold:
1454
+ typeof cfg.contradictionSimilarityThreshold === "number" ? cfg.contradictionSimilarityThreshold : 0.7,
1455
+ contradictionMinConfidence:
1456
+ typeof cfg.contradictionMinConfidence === "number" ? cfg.contradictionMinConfidence : 0.9,
1457
+ contradictionAutoResolve: cfg.contradictionAutoResolve !== false,
1458
+ // Contradiction Scan cron (issue #520)
1459
+ contradictionScan: parseContradictionScanConfig(cfg.contradictionScan),
1460
+ // Temporal Supersession (issue #375)
1461
+ temporalSupersessionEnabled: cfg.temporalSupersessionEnabled !== false, // On by default
1462
+ temporalSupersessionIncludeInRecall:
1463
+ cfg.temporalSupersessionIncludeInRecall === true, // Off by default
1464
+ // Direct-answer retrieval tier (issue #518). Default on — the
1465
+ // tier runs in observation mode: it annotates
1466
+ // LastRecallSnapshot.tierExplain but never short-circuits the
1467
+ // QMD path. Operators can opt out with
1468
+ // recallDirectAnswerEnabled=false.
1469
+ recallDirectAnswerEnabled:
1470
+ coerceBool(cfg.recallDirectAnswerEnabled) ?? true,
1471
+ // Disclosure auto-escalation (issue #677 PR 4/4). Default `manual`
1472
+ // so pre-#677 callers see unchanged behavior. Reject anything
1473
+ // outside the allow-list rather than silently defaulting (CLAUDE.md
1474
+ // rule 51).
1475
+ recallDisclosureEscalation: (() => {
1476
+ const raw = cfg.recallDisclosureEscalation;
1477
+ if (raw === undefined || raw === null) return "manual" as const;
1478
+ if (raw === "manual" || raw === "auto") return raw;
1479
+ throw new Error(
1480
+ `recallDisclosureEscalation must be "manual" or "auto" (got ${JSON.stringify(raw)}).`,
1481
+ );
1482
+ })(),
1483
+ recallDisclosureEscalationThreshold: (() => {
1484
+ const n = coerceNumber(cfg.recallDisclosureEscalationThreshold);
1485
+ return n !== undefined && n >= 0 && n <= 1 ? n : 0.5;
1486
+ })(),
1487
+ // Graph-based retrieval tier (issue #559 PR 4). Default `false` —
1488
+ // the tier ships off pending the `retrieval-graph` bench in PR 5.
1489
+ recallGraphEnabled:
1490
+ coerceBool(cfg.recallGraphEnabled) ?? false,
1491
+ recallGraphDamping: (() => {
1492
+ const n = coerceNumber(cfg.recallGraphDamping);
1493
+ return n !== undefined && n >= 0 && n < 1 ? n : 0.85;
1494
+ })(),
1495
+ // Fractional integer values (e.g. `0.5`) are REJECTED rather than
1496
+ // silently floored to zero — CLAUDE.md rule 51 ("Reject invalid
1497
+ // user input instead of silently defaulting"). Users who set a
1498
+ // fractional iteration cap almost certainly meant an integer and
1499
+ // quietly flooring their value to 0 turns off the tier without
1500
+ // warning.
1501
+ recallGraphIterations: (() => {
1502
+ if (cfg.recallGraphIterations === undefined) return 20;
1503
+ const n = coerceNumber(cfg.recallGraphIterations);
1504
+ if (n === undefined || !Number.isFinite(n) || n < 0 || n > 500) {
1505
+ throw new Error(
1506
+ `recallGraphIterations must be an integer in [0, 500] (got ${JSON.stringify(cfg.recallGraphIterations)}).`,
1507
+ );
1508
+ }
1509
+ if (!Number.isInteger(n)) {
1510
+ throw new Error(
1511
+ `recallGraphIterations must be an integer (got fractional value ${n}).`,
1512
+ );
1513
+ }
1514
+ return n;
1515
+ })(),
1516
+ recallGraphTopK: (() => {
1517
+ if (cfg.recallGraphTopK === undefined) return 50;
1518
+ const n = coerceNumber(cfg.recallGraphTopK);
1519
+ if (n === undefined || !Number.isFinite(n) || n < 0 || n > 10000) {
1520
+ throw new Error(
1521
+ `recallGraphTopK must be an integer in [0, 10000] (got ${JSON.stringify(cfg.recallGraphTopK)}).`,
1522
+ );
1523
+ }
1524
+ if (!Number.isInteger(n)) {
1525
+ throw new Error(
1526
+ `recallGraphTopK must be an integer (got fractional value ${n}).`,
1527
+ );
1528
+ }
1529
+ return n;
1530
+ })(),
1531
+ recallDirectAnswerTokenOverlapFloor: (() => {
1532
+ const n = coerceNumber(cfg.recallDirectAnswerTokenOverlapFloor);
1533
+ return n !== undefined && n >= 0 && n <= 1 ? n : 0.55;
1534
+ })(),
1535
+ recallDirectAnswerImportanceFloor: (() => {
1536
+ const n = coerceNumber(cfg.recallDirectAnswerImportanceFloor);
1537
+ return n !== undefined && n >= 0 && n <= 1 ? n : 0.7;
1538
+ })(),
1539
+ recallDirectAnswerAmbiguityMargin: (() => {
1540
+ const n = coerceNumber(cfg.recallDirectAnswerAmbiguityMargin);
1541
+ return n !== undefined && n >= 0 && n <= 1 ? n : 0.15;
1542
+ })(),
1543
+ recallDirectAnswerEligibleTaxonomyBuckets: Array.isArray(
1544
+ cfg.recallDirectAnswerEligibleTaxonomyBuckets,
1545
+ )
1546
+ ? (cfg.recallDirectAnswerEligibleTaxonomyBuckets as unknown[]).filter(
1547
+ (v): v is string => typeof v === "string" && v.length > 0,
1548
+ )
1549
+ : ["decisions", "principles", "conventions", "runbooks", "entities"],
1550
+ // Cross-namespace query-budget limiter (issue #565 PR 4/5).
1551
+ // Defaults to false — ships disabled so existing deployments are
1552
+ // unaffected. When enabled, the read path throttles a principal that
1553
+ // issues a burst of recalls against namespaces other than their own.
1554
+ recallCrossNamespaceBudgetEnabled:
1555
+ coerceBool(cfg.recallCrossNamespaceBudgetEnabled) ?? false,
1556
+ recallCrossNamespaceBudgetWindowMs: (() => {
1557
+ const n = coerceNumber(cfg.recallCrossNamespaceBudgetWindowMs);
1558
+ return n !== undefined && n > 0 ? Math.floor(n) : 60_000;
1559
+ })(),
1560
+ recallCrossNamespaceBudgetSoftLimit: (() => {
1561
+ const n = coerceNumber(cfg.recallCrossNamespaceBudgetSoftLimit);
1562
+ return n !== undefined && n >= 0 ? Math.floor(n) : 10;
1563
+ })(),
1564
+ recallCrossNamespaceBudgetHardLimit: (() => {
1565
+ const n = coerceNumber(cfg.recallCrossNamespaceBudgetHardLimit);
1566
+ return n !== undefined && n > 0 ? Math.floor(n) : 30;
1567
+ })(),
1568
+ // Recall-audit anomaly detector (issue #565 PR 5/5). Defaults off so
1569
+ // existing deployments are unaffected; enable explicitly to let the
1570
+ // access surfaces flag suspicious query patterns derived from the
1571
+ // audit trail. Thresholds floor AFTER validating the floored value
1572
+ // is still >= 1 — a `0.5` input that floors to 0 would turn every
1573
+ // detector into a flood-on-anything, flipping the default to
1574
+ // max-noise instead of max-silence.
1575
+ recallAuditAnomalyDetectionEnabled:
1576
+ coerceBool(cfg.recallAuditAnomalyDetectionEnabled) ?? false,
1577
+ recallAuditAnomalyWindowMs: (() => {
1578
+ const n = coerceNumber(cfg.recallAuditAnomalyWindowMs);
1579
+ if (n === undefined) return 5 * 60_000;
1580
+ const floored = Math.floor(n);
1581
+ return floored >= 1 ? floored : 5 * 60_000;
1582
+ })(),
1583
+ recallAuditAnomalyRepeatQueryLimit: (() => {
1584
+ const n = coerceNumber(cfg.recallAuditAnomalyRepeatQueryLimit);
1585
+ if (n === undefined) return 5;
1586
+ const floored = Math.floor(n);
1587
+ return floored >= 1 ? floored : 5;
1588
+ })(),
1589
+ recallAuditAnomalyNamespaceWalkLimit: (() => {
1590
+ const n = coerceNumber(cfg.recallAuditAnomalyNamespaceWalkLimit);
1591
+ if (n === undefined) return 3;
1592
+ const floored = Math.floor(n);
1593
+ return floored >= 1 ? floored : 3;
1594
+ })(),
1595
+ recallAuditAnomalyHighCardinalityLimit: (() => {
1596
+ const n = coerceNumber(cfg.recallAuditAnomalyHighCardinalityLimit);
1597
+ if (n === undefined) return 50;
1598
+ const floored = Math.floor(n);
1599
+ return floored >= 1 ? floored : 50;
1600
+ })(),
1601
+ recallAuditAnomalyRapidFireLimit: (() => {
1602
+ const n = coerceNumber(cfg.recallAuditAnomalyRapidFireLimit);
1603
+ if (n === undefined) return 30;
1604
+ const floored = Math.floor(n);
1605
+ return floored >= 1 ? floored : 30;
1606
+ })(),
1607
+
1608
+ // Memory Worth recall filter (issue #560 PR 4, default flipped in PR 5).
1609
+ // Bench result on the seeded fixture: precision@5 lifts from 0.00 to
1610
+ // 0.60 across all 50 cases with zero regressions. See
1611
+ // `runMemoryWorthBench` in memory-worth-bench.ts. Operators can still
1612
+ // opt out with recallMemoryWorthFilterEnabled=false.
1613
+ recallMemoryWorthFilterEnabled:
1614
+ coerceBool(cfg.recallMemoryWorthFilterEnabled) ?? true,
1615
+ recallMemoryWorthHalfLifeMs: (() => {
1616
+ const n = coerceNumber(cfg.recallMemoryWorthHalfLifeMs);
1617
+ return n !== undefined && n >= 0 ? n : 0;
1618
+ })(),
1619
+ // Memory Linking (Phase 3A)
1620
+ memoryLinkingEnabled: cfg.memoryLinkingEnabled === true, // Off by default initially
1621
+ // Conversation Threading (Phase 3B)
1622
+ threadingEnabled: cfg.threadingEnabled === true, // Off by default initially
1623
+ threadingGapMinutes:
1624
+ typeof cfg.threadingGapMinutes === "number" ? cfg.threadingGapMinutes : 30,
1625
+ // Memory Summarization (Phase 4A)
1626
+ summarizationEnabled: cfg.summarizationEnabled === true, // Off by default
1627
+ summarizationTriggerCount:
1628
+ typeof cfg.summarizationTriggerCount === "number" ? cfg.summarizationTriggerCount : 1000,
1629
+ summarizationRecentToKeep:
1630
+ typeof cfg.summarizationRecentToKeep === "number" ? cfg.summarizationRecentToKeep : 300,
1631
+ summarizationImportanceThreshold:
1632
+ typeof cfg.summarizationImportanceThreshold === "number" ? cfg.summarizationImportanceThreshold : 0.3,
1633
+ summarizationProtectedTags: Array.isArray(cfg.summarizationProtectedTags)
1634
+ ? (cfg.summarizationProtectedTags as string[])
1635
+ : ["commitment", "preference", "decision", "principle"],
1636
+ // Topic Extraction (Phase 4B)
1637
+ topicExtractionEnabled: cfg.topicExtractionEnabled !== false, // On by default
1638
+ topicExtractionTopN:
1639
+ typeof cfg.topicExtractionTopN === "number" ? cfg.topicExtractionTopN : 50,
1640
+ // Transcript & Context Preservation (v2.0)
1641
+ // Transcript archive
1642
+ transcriptEnabled: cfg.transcriptEnabled !== false, // default: true
1643
+ transcriptRetentionDays:
1644
+ typeof cfg.transcriptRetentionDays === "number" ? cfg.transcriptRetentionDays : 7,
1645
+ transcriptSkipChannelTypes: Array.isArray(cfg.transcriptSkipChannelTypes)
1646
+ ? (cfg.transcriptSkipChannelTypes as string[])
1647
+ : ["cron"], // default: skip cron transcripts
1648
+ // Transcript injection
1649
+ transcriptRecallHours:
1650
+ typeof cfg.transcriptRecallHours === "number" ? cfg.transcriptRecallHours : 12,
1651
+ maxTranscriptTurns:
1652
+ typeof cfg.maxTranscriptTurns === "number" ? cfg.maxTranscriptTurns : 50,
1653
+ maxTranscriptTokens:
1654
+ typeof cfg.maxTranscriptTokens === "number" ? cfg.maxTranscriptTokens : 1000,
1655
+ // Checkpoint
1656
+ checkpointEnabled: cfg.checkpointEnabled !== false, // default: true
1657
+ checkpointTurns:
1658
+ typeof cfg.checkpointTurns === "number" ? cfg.checkpointTurns : 15,
1659
+ // Compaction reset (opt-in, default: false)
1660
+ compactionResetEnabled: cfg.compactionResetEnabled === true,
1661
+ beforeResetTimeoutMs:
1662
+ typeof cfg.beforeResetTimeoutMs === "number"
1663
+ ? Math.min(30_000, Math.max(100, Math.floor(cfg.beforeResetTimeoutMs)))
1664
+ : 2_000,
1665
+ initGateTimeoutMs: parseBoundedIntegerMs(
1666
+ cfg.initGateTimeoutMs,
1667
+ DEFAULT_INIT_GATE_TIMEOUT_MS,
1668
+ 1_000,
1669
+ 120_000,
1670
+ ),
1671
+ flushOnResetEnabled: cfg.flushOnResetEnabled !== false,
1672
+ commandsListEnabled: cfg.commandsListEnabled !== false,
1673
+ openclawToolsEnabled: cfg.openclawToolsEnabled !== false,
1674
+ openclawToolSnippetMaxChars:
1675
+ typeof cfg.openclawToolSnippetMaxChars === "number"
1676
+ ? Math.min(4_000, Math.max(80, Math.floor(cfg.openclawToolSnippetMaxChars)))
1677
+ : 600,
1678
+ sessionTogglesEnabled: cfg.sessionTogglesEnabled !== false,
1679
+ verboseRecallVisibility: cfg.verboseRecallVisibility !== false,
1680
+ recallTranscriptsEnabled: cfg.recallTranscriptsEnabled === true,
1681
+ recallTranscriptRetentionDays:
1682
+ typeof cfg.recallTranscriptRetentionDays === "number"
1683
+ ? Math.min(365, Math.max(1, Math.floor(cfg.recallTranscriptRetentionDays)))
1684
+ : 30,
1685
+ respectBundledActiveMemoryToggle:
1686
+ cfg.respectBundledActiveMemoryToggle !== false,
1687
+ activeRecallEnabled: cfg.activeRecallEnabled === true,
1688
+ activeRecallAgents:
1689
+ Array.isArray(cfg.activeRecallAgents) && cfg.activeRecallAgents.length > 0
1690
+ ? cfg.activeRecallAgents
1691
+ .filter((value): value is string => typeof value === "string" && value.trim().length > 0)
1692
+ .map((value) => value.trim())
1693
+ : null,
1694
+ activeRecallAllowedChatTypes:
1695
+ Array.isArray(cfg.activeRecallAllowedChatTypes) &&
1696
+ cfg.activeRecallAllowedChatTypes.length > 0
1697
+ ? cfg.activeRecallAllowedChatTypes.filter(
1698
+ (value): value is "direct" | "group" | "channel" =>
1699
+ value === "direct" || value === "group" || value === "channel",
1700
+ )
1701
+ : ["direct", "group", "channel"],
1702
+ activeRecallQueryMode:
1703
+ cfg.activeRecallQueryMode === "message" ||
1704
+ cfg.activeRecallQueryMode === "full"
1705
+ ? cfg.activeRecallQueryMode
1706
+ : "recent",
1707
+ activeRecallPromptStyle:
1708
+ cfg.activeRecallPromptStyle === "strict" ||
1709
+ cfg.activeRecallPromptStyle === "contextual" ||
1710
+ cfg.activeRecallPromptStyle === "recall-heavy" ||
1711
+ cfg.activeRecallPromptStyle === "precision-heavy" ||
1712
+ cfg.activeRecallPromptStyle === "preference-only"
1713
+ ? cfg.activeRecallPromptStyle
1714
+ : "balanced",
1715
+ activeRecallCustomInstruction: (() => {
1716
+ const customInstruction =
1717
+ typeof cfg.activeRecallCustomInstruction === "string"
1718
+ ? cfg.activeRecallCustomInstruction
1719
+ : typeof cfg[LEGACY_ACTIVE_RECALL_CUSTOM_FIELD] === "string"
1720
+ ? cfg[LEGACY_ACTIVE_RECALL_CUSTOM_FIELD]
1721
+ : "";
1722
+ return customInstruction.trim().length > 0
1723
+ ? customInstruction.trim()
1724
+ : null;
1725
+ })(),
1726
+ activeRecallPromptAppend:
1727
+ typeof cfg.activeRecallPromptAppend === "string" &&
1728
+ cfg.activeRecallPromptAppend.trim().length > 0
1729
+ ? cfg.activeRecallPromptAppend.trim()
1730
+ : null,
1731
+ activeRecallMaxSummaryChars:
1732
+ typeof cfg.activeRecallMaxSummaryChars === "number"
1733
+ ? Math.min(1000, Math.max(40, Math.floor(cfg.activeRecallMaxSummaryChars)))
1734
+ : 220,
1735
+ activeRecallRecentUserTurns:
1736
+ typeof cfg.activeRecallRecentUserTurns === "number"
1737
+ ? Math.min(4, Math.max(0, Math.floor(cfg.activeRecallRecentUserTurns)))
1738
+ : 2,
1739
+ activeRecallRecentAssistantTurns:
1740
+ typeof cfg.activeRecallRecentAssistantTurns === "number"
1741
+ ? Math.min(3, Math.max(0, Math.floor(cfg.activeRecallRecentAssistantTurns)))
1742
+ : 1,
1743
+ activeRecallRecentUserChars:
1744
+ typeof cfg.activeRecallRecentUserChars === "number"
1745
+ ? Math.min(1000, Math.max(40, Math.floor(cfg.activeRecallRecentUserChars)))
1746
+ : 600,
1747
+ activeRecallRecentAssistantChars:
1748
+ typeof cfg.activeRecallRecentAssistantChars === "number"
1749
+ ? Math.min(1000, Math.max(40, Math.floor(cfg.activeRecallRecentAssistantChars)))
1750
+ : 400,
1751
+ activeRecallThinking:
1752
+ cfg.activeRecallThinking === "low" ||
1753
+ cfg.activeRecallThinking === "off" ||
1754
+ cfg.activeRecallThinking === "minimal" ||
1755
+ cfg.activeRecallThinking === "medium" ||
1756
+ cfg.activeRecallThinking === "high" ||
1757
+ cfg.activeRecallThinking === "xhigh" ||
1758
+ cfg.activeRecallThinking === "adaptive"
1759
+ ? cfg.activeRecallThinking
1760
+ : "low",
1761
+ activeRecallTimeoutMs:
1762
+ typeof cfg.activeRecallTimeoutMs === "number"
1763
+ ? Math.max(250, Math.floor(cfg.activeRecallTimeoutMs))
1764
+ : 15_000,
1765
+ activeRecallCacheTtlMs:
1766
+ typeof cfg.activeRecallCacheTtlMs === "number"
1767
+ ? cfg.activeRecallCacheTtlMs === 0
1768
+ ? 0
1769
+ : cfg.activeRecallCacheTtlMs < 0
1770
+ ? 15_000
1771
+ : Math.min(
1772
+ 120_000,
1773
+ Math.max(1, Math.floor(cfg.activeRecallCacheTtlMs)),
1774
+ )
1775
+ : 15_000,
1776
+ activeRecallModel:
1777
+ typeof cfg.activeRecallModel === "string" && cfg.activeRecallModel.trim().length > 0
1778
+ ? cfg.activeRecallModel.trim()
1779
+ : null,
1780
+ activeRecallModelFallbackPolicy:
1781
+ cfg.activeRecallModelFallbackPolicy === "resolved-only"
1782
+ ? "resolved-only"
1783
+ : "default-remote",
1784
+ activeRecallPersistTranscripts: cfg.activeRecallPersistTranscripts === true,
1785
+ activeRecallTranscriptDir:
1786
+ typeof cfg.activeRecallTranscriptDir === "string" &&
1787
+ cfg.activeRecallTranscriptDir.trim().length > 0
1788
+ ? cfg.activeRecallTranscriptDir.trim()
1789
+ : "active-recall",
1790
+ activeRecallEntityGraphDepth:
1791
+ typeof cfg.activeRecallEntityGraphDepth === "number"
1792
+ ? Math.min(3, Math.max(0, Math.floor(cfg.activeRecallEntityGraphDepth)))
1793
+ : 1,
1794
+ activeRecallIncludeCausalTrajectories:
1795
+ cfg.activeRecallIncludeCausalTrajectories === true,
1796
+ activeRecallIncludeDaySummary: cfg.activeRecallIncludeDaySummary === true,
1797
+ activeRecallAttachRecallExplain: cfg.activeRecallAttachRecallExplain === true,
1798
+ activeRecallAllowChainedActiveMemory:
1799
+ cfg.activeRecallAllowChainedActiveMemory === true,
1800
+ dreaming,
1801
+ dreamsPhases,
1802
+ procedural,
1803
+ // At-rest encryption (issue #690 PR 3/4)
1804
+ // coerceBool handles CLI string inputs: `--config secureStoreEnabled=true`
1805
+ // arrives as the string "true" which `=== true` would reject (CLAUDE.md #36).
1806
+ secureStoreEnabled: coerceBool(cfg.secureStoreEnabled) === true,
1807
+ secureStoreEncryptOnWrite: coerceBool(cfg.secureStoreEncryptOnWrite) !== false, // default: true
1808
+ codingMode,
1809
+ heartbeat,
1810
+ slotBehavior,
1811
+ codexCompat,
1812
+ // Hourly summaries
1813
+ hourlySummariesEnabled: cfg.hourlySummariesEnabled !== false, // default: true
1814
+ daySummaryEnabled: cfg.daySummaryEnabled !== false, // default: true
1815
+ hourlySummaryCronAutoRegister: cfg.hourlySummaryCronAutoRegister === true,
1816
+ // Codex P1 on PR 763 round 2: gate the nightly-governance cron
1817
+ // (deep-sleep's primary scheduled execution path) on
1818
+ // dreams.phases.deepSleep.enabled. When the phase is disabled the
1819
+ // cron must NOT auto-register, otherwise `deepSleep.enabled=false`
1820
+ // is a contract lie — deep-sleep keeps running.
1821
+ nightlyGovernanceCronAutoRegister:
1822
+ dreamsDeepSleep.enabled && cfg.nightlyGovernanceCronAutoRegister === true,
1823
+ summaryRecallHours:
1824
+ typeof cfg.summaryRecallHours === "number" ? cfg.summaryRecallHours : 24,
1825
+ maxSummaryCount:
1826
+ typeof cfg.maxSummaryCount === "number" ? cfg.maxSummaryCount : 6,
1827
+ summaryModel:
1828
+ typeof cfg.summaryModel === "string" && cfg.summaryModel.length > 0
1829
+ ? cfg.summaryModel
1830
+ : model, // default: same as extraction model
1831
+ // v2.4 Extended hourly summaries (default off)
1832
+ hourlySummariesExtendedEnabled: cfg.hourlySummariesExtendedEnabled === true,
1833
+ hourlySummariesIncludeToolStats: cfg.hourlySummariesIncludeToolStats === true,
1834
+ hourlySummariesIncludeSystemMessages: cfg.hourlySummariesIncludeSystemMessages === true,
1835
+ hourlySummariesMaxTurnsPerRun:
1836
+ typeof cfg.hourlySummariesMaxTurnsPerRun === "number" ? cfg.hourlySummariesMaxTurnsPerRun : 200,
1837
+ // v2.4 Conversation index (default off)
1838
+ conversationIndexEnabled: cfg.conversationIndexEnabled === true,
1839
+ conversationIndexBackend: cfg.conversationIndexBackend === "faiss" ? "faiss" : "qmd",
1840
+ conversationIndexQmdCollection:
1841
+ typeof cfg.conversationIndexQmdCollection === "string" && cfg.conversationIndexQmdCollection.length > 0
1842
+ ? cfg.conversationIndexQmdCollection
1843
+ // TODO(#403): Keep legacy collection name for backwards compat.
1844
+ : "openclaw-engram-conversations",
1845
+ conversationIndexRetentionDays:
1846
+ typeof cfg.conversationIndexRetentionDays === "number" ? cfg.conversationIndexRetentionDays : 30,
1847
+ conversationIndexMinUpdateIntervalMs:
1848
+ typeof cfg.conversationIndexMinUpdateIntervalMs === "number"
1849
+ ? cfg.conversationIndexMinUpdateIntervalMs
1850
+ : 15 * 60_000,
1851
+ conversationIndexEmbedOnUpdate: cfg.conversationIndexEmbedOnUpdate === true,
1852
+ conversationIndexFaissScriptPath:
1853
+ typeof cfg.conversationIndexFaissScriptPath === "string" && cfg.conversationIndexFaissScriptPath.trim().length > 0
1854
+ ? cfg.conversationIndexFaissScriptPath.trim()
1855
+ : undefined,
1856
+ conversationIndexFaissPythonBin:
1857
+ typeof cfg.conversationIndexFaissPythonBin === "string" && cfg.conversationIndexFaissPythonBin.trim().length > 0
1858
+ ? cfg.conversationIndexFaissPythonBin.trim()
1859
+ : undefined,
1860
+ conversationIndexFaissModelId:
1861
+ typeof cfg.conversationIndexFaissModelId === "string" && cfg.conversationIndexFaissModelId.trim().length > 0
1862
+ ? cfg.conversationIndexFaissModelId.trim()
1863
+ : "text-embedding-3-small",
1864
+ conversationIndexFaissIndexDir:
1865
+ typeof cfg.conversationIndexFaissIndexDir === "string" && cfg.conversationIndexFaissIndexDir.trim().length > 0
1866
+ ? cfg.conversationIndexFaissIndexDir.trim()
1867
+ : "state/conversation-index/faiss",
1868
+ conversationIndexFaissUpsertTimeoutMs:
1869
+ typeof cfg.conversationIndexFaissUpsertTimeoutMs === "number"
1870
+ ? Math.max(0, Math.floor(cfg.conversationIndexFaissUpsertTimeoutMs))
1871
+ : 30_000,
1872
+ conversationIndexFaissSearchTimeoutMs:
1873
+ typeof cfg.conversationIndexFaissSearchTimeoutMs === "number"
1874
+ ? Math.max(0, Math.floor(cfg.conversationIndexFaissSearchTimeoutMs))
1875
+ : 5_000,
1876
+ conversationIndexFaissHealthTimeoutMs:
1877
+ typeof cfg.conversationIndexFaissHealthTimeoutMs === "number"
1878
+ ? Math.max(0, Math.floor(cfg.conversationIndexFaissHealthTimeoutMs))
1879
+ : 2_000,
1880
+ conversationIndexFaissMaxBatchSize:
1881
+ typeof cfg.conversationIndexFaissMaxBatchSize === "number"
1882
+ ? Math.max(0, Math.floor(cfg.conversationIndexFaissMaxBatchSize))
1883
+ : 512,
1884
+ conversationIndexFaissMaxSearchK:
1885
+ typeof cfg.conversationIndexFaissMaxSearchK === "number"
1886
+ ? Math.max(0, Math.floor(cfg.conversationIndexFaissMaxSearchK))
1887
+ : 50,
1888
+ conversationRecallTopK:
1889
+ typeof cfg.conversationRecallTopK === "number" ? cfg.conversationRecallTopK : 3,
1890
+ conversationRecallMaxChars:
1891
+ typeof cfg.conversationRecallMaxChars === "number" ? cfg.conversationRecallMaxChars : 2500,
1892
+ conversationRecallTimeoutMs:
1893
+ typeof cfg.conversationRecallTimeoutMs === "number" ? cfg.conversationRecallTimeoutMs : 800,
1894
+ evalHarnessEnabled: cfg.evalHarnessEnabled === true,
1895
+ evalShadowModeEnabled: cfg.evalShadowModeEnabled === true,
1896
+ benchmarkBaselineSnapshotsEnabled: cfg.benchmarkBaselineSnapshotsEnabled === true,
1897
+ benchmarkDeltaReporterEnabled: cfg.benchmarkDeltaReporterEnabled === true,
1898
+ benchmarkStoredBaselineEnabled: cfg.benchmarkStoredBaselineEnabled === true,
1899
+ evalStoreDir:
1900
+ typeof cfg.evalStoreDir === "string" && cfg.evalStoreDir.trim().length > 0
1901
+ ? cfg.evalStoreDir.trim()
1902
+ : path.join(memoryDir, "state", "evals"),
1903
+ objectiveStateMemoryEnabled: cfg.objectiveStateMemoryEnabled === true,
1904
+ objectiveStateSnapshotWritesEnabled: cfg.objectiveStateSnapshotWritesEnabled === true,
1905
+ objectiveStateRecallEnabled: cfg.objectiveStateRecallEnabled === true,
1906
+ objectiveStateStoreDir:
1907
+ typeof cfg.objectiveStateStoreDir === "string" && cfg.objectiveStateStoreDir.trim().length > 0
1908
+ ? cfg.objectiveStateStoreDir.trim()
1909
+ : path.join(memoryDir, "state", "objective-state"),
1910
+ causalTrajectoryMemoryEnabled: cfg.causalTrajectoryMemoryEnabled === true,
1911
+ causalTrajectoryStoreDir:
1912
+ typeof cfg.causalTrajectoryStoreDir === "string" && cfg.causalTrajectoryStoreDir.trim().length > 0
1913
+ ? cfg.causalTrajectoryStoreDir.trim()
1914
+ : path.join(memoryDir, "state", "causal-trajectories"),
1915
+ causalTrajectoryRecallEnabled: cfg.causalTrajectoryRecallEnabled === true,
1916
+ actionGraphRecallEnabled: cfg.actionGraphRecallEnabled === true,
1917
+ trustZonesEnabled: cfg.trustZonesEnabled === true,
1918
+ quarantinePromotionEnabled: cfg.quarantinePromotionEnabled === true,
1919
+ trustZoneStoreDir:
1920
+ typeof cfg.trustZoneStoreDir === "string" && cfg.trustZoneStoreDir.trim().length > 0
1921
+ ? cfg.trustZoneStoreDir.trim()
1922
+ : path.join(memoryDir, "state", "trust-zones"),
1923
+ trustZoneRecallEnabled: cfg.trustZoneRecallEnabled === true,
1924
+ memoryPoisoningDefenseEnabled: cfg.memoryPoisoningDefenseEnabled === true,
1925
+ memoryRedTeamBenchEnabled: cfg.memoryRedTeamBenchEnabled === true,
1926
+ harmonicRetrievalEnabled: cfg.harmonicRetrievalEnabled === true,
1927
+ abstractionAnchorsEnabled: cfg.abstractionAnchorsEnabled === true,
1928
+ verifiedRecallEnabled: cfg.verifiedRecallEnabled === true,
1929
+ semanticRulePromotionEnabled: cfg.semanticRulePromotionEnabled === true,
1930
+ semanticRuleVerificationEnabled: cfg.semanticRuleVerificationEnabled === true,
1931
+ // Issue #678 PR 2/4: when `dreams.phases.rem.*` is set, the resolved
1932
+ // dreamsPhases value WINS over the legacy top-level key. The runtime
1933
+ // gates (orchestrator's `runSemanticConsolidation`) read these legacy
1934
+ // fields, so we must propagate the precedence here, not just in the
1935
+ // `dreamsPhases` object.
1936
+ semanticConsolidationEnabled: dreamsRem.enabled && dreamsRem.cadenceMs > 0,
1937
+ semanticConsolidationModel:
1938
+ typeof cfg.semanticConsolidationModel === "string" && cfg.semanticConsolidationModel.length > 0
1939
+ ? cfg.semanticConsolidationModel
1940
+ : "auto",
1941
+ semanticConsolidationThreshold: dreamsRem.similarityThreshold,
1942
+ semanticConsolidationMinClusterSize: dreamsRem.minClusterSize,
1943
+ semanticConsolidationExcludeCategories: Array.isArray(cfg.semanticConsolidationExcludeCategories)
1944
+ ? (cfg.semanticConsolidationExcludeCategories as unknown[]).filter(
1945
+ (c): c is string => typeof c === "string" && c.length > 0,
1946
+ )
1947
+ : ["correction", "commitment", "procedure"],
1948
+ // semanticConsolidationIntervalHours is derived from dreamsRem.cadenceMs
1949
+ // when an override is set (rounded up to the nearest hour). Preserve
1950
+ // explicit zero so legacy schedulers see the same disable-by-zero signal
1951
+ // as the dreams.phases.rem config; the runtime enabled flag is also
1952
+ // disabled above so zero does not mean "run every maintenance cycle".
1953
+ semanticConsolidationIntervalHours:
1954
+ rawDreamsRem.cadenceMs !== undefined
1955
+ ? Math.max(0, Math.ceil(dreamsRem.cadenceMs / 3_600_000))
1956
+ : legacySemanticConsolidationIntervalHours,
1957
+ semanticConsolidationMaxPerRun: dreamsRem.maxPerRun,
1958
+ // Operator-aware consolidation prompt (issue #561 PR 3). Defaults
1959
+ // to `false` to match sibling `*Enabled` flags' least-privileged
1960
+ // convention. Operators opt in by setting `true` (or truthy
1961
+ // coercions like "true", "1", "yes", "on") when they want the
1962
+ // consolidation LLM to emit SPLIT/MERGE/UPDATE operator selection
1963
+ // on the `derived_via` frontmatter field. Uses `coerceBool` per
1964
+ // Gotcha #36 so CLI / env-string inputs coerce correctly. When
1965
+ // disabled, `derived_via` is still populated via the cluster-shape
1966
+ // heuristic (chooseConsolidationOperator) so PR 2's provenance
1967
+ // wiring keeps working without operator-aware prompts.
1968
+ operatorAwareConsolidationEnabled:
1969
+ coerceBool(cfg.operatorAwareConsolidationEnabled) ?? false,
1970
+ // Pattern reinforcement (issue #687 PR 2/4). Defaults: off, weekly
1971
+ // cadence, min cluster size 3, target categories preference / fact
1972
+ // / decision. All bounds clamped at parse time so invalid inputs
1973
+ // (negative numbers, non-arrays, non-strings) fail safe to defaults
1974
+ // rather than crash the job.
1975
+ patternReinforcementEnabled:
1976
+ coerceBool(cfg.patternReinforcementEnabled) ?? false,
1977
+ patternReinforcementCadenceMs: (() => {
1978
+ const raw = coerceNumber(cfg.patternReinforcementCadenceMs);
1979
+ if (raw === undefined || !Number.isFinite(raw)) {
1980
+ return 7 * 24 * 60 * 60 * 1000;
1981
+ }
1982
+ // Allow 0 to disable cadence gating; otherwise clamp to >= 0.
1983
+ return Math.max(0, Math.floor(raw));
1984
+ })(),
1985
+ patternReinforcementMinCount: (() => {
1986
+ const raw = coerceNumber(cfg.patternReinforcementMinCount);
1987
+ if (raw === undefined || !Number.isFinite(raw)) return 3;
1988
+ // Clusters of 1 are degenerate; clusters of 2 are the minimum
1989
+ // meaningful pattern. Cap at a sane upper bound to prevent
1990
+ // accidentally locking out the job entirely.
1991
+ return Math.min(1000, Math.max(2, Math.floor(raw)));
1992
+ })(),
1993
+ patternReinforcementCategories: Array.isArray(cfg.patternReinforcementCategories)
1994
+ ? (cfg.patternReinforcementCategories as unknown[])
1995
+ .filter((v): v is string => typeof v === "string")
1996
+ .map((v) => v.trim())
1997
+ .filter((v) => v.length > 0)
1998
+ : ["preference", "fact", "decision"],
1999
+ // issue #687 PR 3/4: reinforcement recall boost config.
2000
+ reinforcementRecallBoostEnabled:
2001
+ coerceBool(cfg.reinforcementRecallBoostEnabled) ?? false,
2002
+ reinforcementRecallBoostWeight: (() => {
2003
+ if (cfg.reinforcementRecallBoostWeight === undefined) return 0.05;
2004
+ const n = coerceNumber(cfg.reinforcementRecallBoostWeight);
2005
+ if (n === undefined || !Number.isFinite(n) || n < 0 || n > 1) {
2006
+ throw new Error(
2007
+ `reinforcementRecallBoostWeight must be a number in [0, 1] (got ${JSON.stringify(cfg.reinforcementRecallBoostWeight)}).`,
2008
+ );
2009
+ }
2010
+ return n;
2011
+ })(),
2012
+ reinforcementRecallBoostMax: (() => {
2013
+ if (cfg.reinforcementRecallBoostMax === undefined) return 0.3;
2014
+ const n = coerceNumber(cfg.reinforcementRecallBoostMax);
2015
+ if (n === undefined || !Number.isFinite(n) || n < 0 || n > 1) {
2016
+ throw new Error(
2017
+ `reinforcementRecallBoostMax must be a number in [0, 1] (got ${JSON.stringify(cfg.reinforcementRecallBoostMax)}).`,
2018
+ );
2019
+ }
2020
+ return n;
2021
+ })(),
2022
+ // Async peer profile reasoner (issue #679 PR 2/5). Defaults to
2023
+ // `false` (opt-in) per Gotchas #30/#48 — least-privileged default.
2024
+ // `coerceBool` handles "true"/"1"/"yes"/"on" CLI strings (Gotcha
2025
+ // #36). Numeric thresholds clamp at zero (Gotcha #28 + #45 — 0
2026
+ // is a documented disable value for both, so the schema minimum
2027
+ // matches and we do NOT silently bump to 1).
2028
+ peerProfileReasonerEnabled:
2029
+ coerceBool(cfg.peerProfileReasonerEnabled) ?? false,
2030
+ // Cursor M on PR #736: use the routing alias `"auto"` rather than
2031
+ // a hardcoded model identifier. Matches the convention established
2032
+ // by sibling `semanticConsolidationModel`. Operators can still
2033
+ // override via config; the value is logged for telemetry only.
2034
+ peerProfileReasonerModel:
2035
+ typeof cfg.peerProfileReasonerModel === "string" &&
2036
+ cfg.peerProfileReasonerModel.trim().length > 0
2037
+ ? cfg.peerProfileReasonerModel.trim()
2038
+ : "auto",
2039
+ // Cursor L on PR #736: numeric config keys must coerce string CLI
2040
+ // values (Gotcha #28 — `--config peerProfileReasonerMaxFieldsPerRun=2`
2041
+ // arrives as the string "2"). Pre-fix `typeof === "number"`
2042
+ // rejected string inputs and silently fell back to defaults. The
2043
+ // shared `coerceNonNegativeInt` helper accepts both forms, throws
2044
+ // on invalid input (Gotcha #51 — reject rather than silently
2045
+ // default), and falls back to the documented default when the
2046
+ // key is absent. 0 is a valid documented disable value here, so
2047
+ // the helper does NOT bump to 1.
2048
+ peerProfileReasonerMinInteractions: coerceNonNegativeInt(
2049
+ cfg.peerProfileReasonerMinInteractions,
2050
+ 5,
2051
+ "peerProfileReasonerMinInteractions",
2052
+ ),
2053
+ peerProfileReasonerMaxFieldsPerRun: coerceNonNegativeInt(
2054
+ cfg.peerProfileReasonerMaxFieldsPerRun,
2055
+ 8,
2056
+ "peerProfileReasonerMaxFieldsPerRun",
2057
+ ),
2058
+ // Peer profile recall injection (issue #679 PR 3/5). Default-off
2059
+ // per Gotcha #30/#48 (least-privileged default, new feature gate).
2060
+ // `coerceBool` handles CLI string forms "true"/"1"/"yes"/"on"
2061
+ // (Gotcha #36). `coerceNonNegativeInt` handles string CLI values
2062
+ // (Gotcha #28) and documents 0 as the disable value (Gotcha #45 —
2063
+ // schema minimum must also be 0 so they stay consistent).
2064
+ peerProfileRecallEnabled:
2065
+ coerceBool(cfg.peerProfileRecallEnabled) ?? false,
2066
+ peerProfileRecallMaxFields: coerceNonNegativeInt(
2067
+ cfg.peerProfileRecallMaxFields,
2068
+ 5,
2069
+ "peerProfileRecallMaxFields",
2070
+ ),
2071
+ creationMemoryEnabled: cfg.creationMemoryEnabled === true,
2072
+ memoryUtilityLearningEnabled: cfg.memoryUtilityLearningEnabled === true,
2073
+ promotionByOutcomeEnabled: cfg.promotionByOutcomeEnabled === true,
2074
+ commitmentLedgerEnabled: cfg.commitmentLedgerEnabled === true,
2075
+ commitmentLifecycleEnabled: cfg.commitmentLifecycleEnabled === true,
2076
+ commitmentStaleDays:
2077
+ typeof cfg.commitmentStaleDays === "number" ? cfg.commitmentStaleDays : 14,
2078
+ commitmentLedgerDir:
2079
+ typeof cfg.commitmentLedgerDir === "string" && cfg.commitmentLedgerDir.trim().length > 0
2080
+ ? cfg.commitmentLedgerDir.trim()
2081
+ : path.join(memoryDir, "state", "commitment-ledger"),
2082
+ resumeBundlesEnabled: cfg.resumeBundlesEnabled === true,
2083
+ resumeBundleDir:
2084
+ typeof cfg.resumeBundleDir === "string" && cfg.resumeBundleDir.trim().length > 0
2085
+ ? cfg.resumeBundleDir.trim()
2086
+ : path.join(memoryDir, "state", "resume-bundles"),
2087
+ workProductRecallEnabled: cfg.workProductRecallEnabled === true,
2088
+ workTasksEnabled: cfg.workTasksEnabled === true,
2089
+ workProjectsEnabled: cfg.workProjectsEnabled === true,
2090
+ workTasksDir:
2091
+ typeof cfg.workTasksDir === "string" && cfg.workTasksDir.trim().length > 0
2092
+ ? cfg.workTasksDir.trim()
2093
+ : path.join(memoryDir, "work", "tasks"),
2094
+ workProjectsDir:
2095
+ typeof cfg.workProjectsDir === "string" && cfg.workProjectsDir.trim().length > 0
2096
+ ? cfg.workProjectsDir.trim()
2097
+ : path.join(memoryDir, "work", "projects"),
2098
+ workIndexEnabled: cfg.workIndexEnabled === true,
2099
+ workIndexDir:
2100
+ typeof cfg.workIndexDir === "string" && cfg.workIndexDir.trim().length > 0
2101
+ ? cfg.workIndexDir.trim()
2102
+ : path.join(memoryDir, "work", "index"),
2103
+ workTaskIndexEnabled: cfg.workTaskIndexEnabled === true,
2104
+ workProjectIndexEnabled: cfg.workProjectIndexEnabled === true,
2105
+ workIndexAutoRebuildEnabled: cfg.workIndexAutoRebuildEnabled === true,
2106
+ workIndexAutoRebuildDebounceMs:
2107
+ typeof cfg.workIndexAutoRebuildDebounceMs === "number" ? cfg.workIndexAutoRebuildDebounceMs : 1000,
2108
+ workProductLedgerDir:
2109
+ typeof cfg.workProductLedgerDir === "string" && cfg.workProductLedgerDir.trim().length > 0
2110
+ ? cfg.workProductLedgerDir.trim()
2111
+ : path.join(memoryDir, "state", "work-product-ledger"),
2112
+ abstractionNodeStoreDir:
2113
+ typeof cfg.abstractionNodeStoreDir === "string" && cfg.abstractionNodeStoreDir.trim().length > 0
2114
+ ? cfg.abstractionNodeStoreDir.trim()
2115
+ : path.join(memoryDir, "state", "abstraction-nodes"),
2116
+ // Local LLM Provider (v2.1)
2117
+ localLlmEnabled: cfg.localLlmEnabled === true || cfg.localLlmEnabled === "true", // default: false
2118
+ localLlmUrl:
2119
+ typeof cfg.localLlmUrl === "string" && cfg.localLlmUrl.length > 0
2120
+ ? cfg.localLlmUrl
2121
+ : "http://localhost:1234/v1",
2122
+ localLlmModel:
2123
+ typeof cfg.localLlmModel === "string" && cfg.localLlmModel.length > 0
2124
+ ? cfg.localLlmModel
2125
+ : "local-model",
2126
+ localLlmApiKey:
2127
+ typeof cfg.localLlmApiKey === "string" && cfg.localLlmApiKey.length > 0
2128
+ ? resolveEnvVars(cfg.localLlmApiKey)
2129
+ : undefined,
2130
+ localLlmHeaders:
2131
+ cfg.localLlmHeaders && typeof cfg.localLlmHeaders === "object" && !Array.isArray(cfg.localLlmHeaders)
2132
+ ? Object.fromEntries(
2133
+ Object.entries(cfg.localLlmHeaders as Record<string, unknown>)
2134
+ .filter(([, value]) => typeof value === "string")
2135
+ .map(([key, value]) => [key, String(value)]),
2136
+ )
2137
+ : undefined,
2138
+ localLlmAuthHeader: cfg.localLlmAuthHeader !== false,
2139
+ localLlmFallback: cfg.localLlmFallback !== false, // default: true
2140
+ localLlmHomeDir:
2141
+ typeof cfg.localLlmHomeDir === "string" && cfg.localLlmHomeDir.length > 0
2142
+ ? cfg.localLlmHomeDir
2143
+ : undefined,
2144
+ localLmsCliPath:
2145
+ typeof cfg.localLmsCliPath === "string" && cfg.localLmsCliPath.length > 0
2146
+ ? cfg.localLmsCliPath
2147
+ : undefined,
2148
+ localLmsBinDir:
2149
+ typeof cfg.localLmsBinDir === "string" && cfg.localLmsBinDir.length > 0
2150
+ ? cfg.localLmsBinDir
2151
+ : undefined,
2152
+ localLlmTimeoutMs:
2153
+ parseBoundedIntegerMs(cfg.localLlmTimeoutMs, 180_000, 1, 86_400_000),
2154
+ localLlmMaxContext:
2155
+ typeof cfg.localLlmMaxContext === "number" ? cfg.localLlmMaxContext : undefined,
2156
+ // Observability (disabled by default to avoid log spam)
2157
+ slowLogEnabled: cfg.slowLogEnabled === true,
2158
+ slowLogThresholdMs:
2159
+ typeof cfg.slowLogThresholdMs === "number" ? cfg.slowLogThresholdMs : 30_000,
2160
+ // Trace recall content — disabled by default; enable to send recalled memory text to trace subscribers
2161
+ traceRecallContent: cfg.traceRecallContent === true,
2162
+ // Performance profiling (opt-in, disabled by default)
2163
+ profilingEnabled: cfg.profilingEnabled === true,
2164
+ profilingStorageDir:
2165
+ typeof cfg.profilingStorageDir === "string" && cfg.profilingStorageDir.length > 0
2166
+ ? cfg.profilingStorageDir
2167
+ : path.join(memoryDir, "profiling"),
2168
+ profilingMaxTraces:
2169
+ typeof cfg.profilingMaxTraces === "number" && Number.isFinite(cfg.profilingMaxTraces)
2170
+ ? Math.max(0, cfg.profilingMaxTraces)
2171
+ : 100,
2172
+ // Extraction stability guards (P0/P1)
2173
+ extractionDedupeEnabled: cfg.extractionDedupeEnabled !== false,
2174
+ extractionDedupeWindowMs:
2175
+ typeof cfg.extractionDedupeWindowMs === "number" ? cfg.extractionDedupeWindowMs : 5 * 60_000,
2176
+ extractionMinChars:
2177
+ typeof cfg.extractionMinChars === "number" ? cfg.extractionMinChars : 40,
2178
+ extractionMinUserTurns:
2179
+ typeof cfg.extractionMinUserTurns === "number" ? cfg.extractionMinUserTurns : 1,
2180
+ extractionTelemetryPrefilterEnabled:
2181
+ coerceBool(cfg.extractionTelemetryPrefilterEnabled) !== false,
2182
+ extractionMaxTurnChars:
2183
+ typeof cfg.extractionMaxTurnChars === "number" ? cfg.extractionMaxTurnChars : 4000,
2184
+ extractionMaxFactsPerRun:
2185
+ typeof cfg.extractionMaxFactsPerRun === "number" ? cfg.extractionMaxFactsPerRun : 12,
2186
+ extractionMaxEntitiesPerRun:
2187
+ typeof cfg.extractionMaxEntitiesPerRun === "number" ? cfg.extractionMaxEntitiesPerRun : 6,
2188
+ extractionMaxQuestionsPerRun:
2189
+ typeof cfg.extractionMaxQuestionsPerRun === "number" ? cfg.extractionMaxQuestionsPerRun : 3,
2190
+ extractionMaxProfileUpdatesPerRun:
2191
+ typeof cfg.extractionMaxProfileUpdatesPerRun === "number" ? cfg.extractionMaxProfileUpdatesPerRun : 4,
2192
+ // Importance write-gate for trivial extracted content (issue #372).
2193
+ // Default "low" drops only "trivial" facts (greetings, single-word replies,
2194
+ // heartbeat pings); set to "normal" or higher to make the gate stricter.
2195
+ extractionMinImportanceLevel: ((): PluginConfig["extractionMinImportanceLevel"] => {
2196
+ const raw = cfg.extractionMinImportanceLevel;
2197
+ if (
2198
+ raw === "trivial" ||
2199
+ raw === "low" ||
2200
+ raw === "normal" ||
2201
+ raw === "high" ||
2202
+ raw === "critical"
2203
+ ) {
2204
+ return raw;
2205
+ }
2206
+ return "low";
2207
+ })(),
2208
+ // Extraction scope classification. When enabled, the extraction prompt
2209
+ // asks the LLM to classify each fact as "project" or "global". Global
2210
+ // facts are promoted to the shared namespace. Default true (rule 30 gate).
2211
+ extractionScopeClassificationEnabled:
2212
+ coerceBool(cfg.extractionScopeClassificationEnabled) !== false,
2213
+ // Extraction judge (issue #376). Opt-in LLM-as-judge fact-worthiness gate.
2214
+ extractionJudgeEnabled: cfg.extractionJudgeEnabled === true,
2215
+ extractionJudgeModel:
2216
+ typeof cfg.extractionJudgeModel === "string" ? cfg.extractionJudgeModel : "",
2217
+ extractionJudgeBatchSize:
2218
+ typeof cfg.extractionJudgeBatchSize === "number" && Number.isFinite(cfg.extractionJudgeBatchSize)
2219
+ ? Math.max(1, Math.round(cfg.extractionJudgeBatchSize))
2220
+ : 20,
2221
+ extractionJudgeShadow: cfg.extractionJudgeShadow === true,
2222
+ // Defer cap (issue #562 PR 2): max re-deferrals for the same candidate
2223
+ // text before the verdict is forcibly converted to reject.
2224
+ extractionJudgeMaxDeferrals:
2225
+ typeof cfg.extractionJudgeMaxDeferrals === "number" &&
2226
+ Number.isFinite(cfg.extractionJudgeMaxDeferrals) &&
2227
+ cfg.extractionJudgeMaxDeferrals >= 1
2228
+ ? Math.floor(cfg.extractionJudgeMaxDeferrals)
2229
+ : 2,
2230
+ // Judge telemetry (issue #562 PR 3): opt-in structured emit to the
2231
+ // observation ledger for defer-rate / latency metrics.
2232
+ // Uses `coerceBool` so CLI-style string inputs (`"true"`, `"false"`,
2233
+ // `"1"`, `"0"`) are accepted consistently with the rest of the
2234
+ // codebase (CLAUDE.md gotcha 36).
2235
+ extractionJudgeTelemetryEnabled:
2236
+ coerceBool(cfg.extractionJudgeTelemetryEnabled) === true,
2237
+ // Judge training-pair collection (issue #562 PR 4): opt-in shim for a
2238
+ // future GRPO training pipeline. Rows land under ~/.remnic/judge-
2239
+ // training/<date>.jsonl — NOT in the shared memory directory.
2240
+ // Uses `coerceBool` per CLAUDE.md gotcha 36 for CLI-string parity.
2241
+ collectJudgeTrainingPairs: coerceBool(cfg.collectJudgeTrainingPairs) === true,
2242
+ judgeTrainingDir:
2243
+ typeof cfg.judgeTrainingDir === "string" ? cfg.judgeTrainingDir : "",
2244
+ // Inline source attribution (issue #369). Opt-in to preserve
2245
+ // backwards compatibility with existing downstream consumers.
2246
+ inlineSourceAttributionEnabled: cfg.inlineSourceAttributionEnabled === true,
2247
+ inlineSourceAttributionFormat:
2248
+ typeof cfg.inlineSourceAttributionFormat === "string" &&
2249
+ cfg.inlineSourceAttributionFormat.trim().length > 0
2250
+ ? cfg.inlineSourceAttributionFormat
2251
+ : "[Source: agent={agent}, session={sessionId}, ts={ts}]",
2252
+ consolidationRequireNonZeroExtraction: cfg.consolidationRequireNonZeroExtraction !== false,
2253
+ // Issue #678 PR 2/4: dreams.phases.rem.minIntervalMs WINS when set.
2254
+ consolidationMinIntervalMs: dreamsRem.minIntervalMs,
2255
+ // QMD maintenance (debounced singleflight)
2256
+ qmdMaintenanceEnabled: cfg.qmdMaintenanceEnabled !== false,
2257
+ qmdMaintenanceDebounceMs:
2258
+ typeof cfg.qmdMaintenanceDebounceMs === "number" ? cfg.qmdMaintenanceDebounceMs : 30_000,
2259
+ qmdAutoEmbedEnabled: cfg.qmdAutoEmbedEnabled === true,
2260
+ qmdEmbedMinIntervalMs:
2261
+ typeof cfg.qmdEmbedMinIntervalMs === "number" ? cfg.qmdEmbedMinIntervalMs : 60 * 60_000,
2262
+ qmdUpdateTimeoutMs:
2263
+ typeof cfg.qmdUpdateTimeoutMs === "number" ? cfg.qmdUpdateTimeoutMs : 90_000,
2264
+ qmdUpdateMinIntervalMs:
2265
+ typeof cfg.qmdUpdateMinIntervalMs === "number" ? cfg.qmdUpdateMinIntervalMs : 15 * 60_000,
2266
+ // Local LLM resilience
2267
+ localLlmRetry5xxCount:
2268
+ typeof cfg.localLlmRetry5xxCount === "number" ? cfg.localLlmRetry5xxCount : 1,
2269
+ localLlmRetryBackoffMs:
2270
+ typeof cfg.localLlmRetryBackoffMs === "number" ? cfg.localLlmRetryBackoffMs : 400,
2271
+ localLlm400TripThreshold:
2272
+ typeof cfg.localLlm400TripThreshold === "number" ? cfg.localLlm400TripThreshold : 5,
2273
+ localLlm400CooldownMs:
2274
+ typeof cfg.localLlm400CooldownMs === "number" ? cfg.localLlm400CooldownMs : 120_000,
2275
+ // Local LLM fast tier (v9.1)
2276
+ localLlmFastEnabled: cfg.localLlmFastEnabled === true,
2277
+ localLlmFastModel:
2278
+ typeof cfg.localLlmFastModel === "string" && cfg.localLlmFastModel.length > 0
2279
+ ? cfg.localLlmFastModel
2280
+ : "",
2281
+ localLlmFastUrl:
2282
+ typeof cfg.localLlmFastUrl === "string" && cfg.localLlmFastUrl.length > 0
2283
+ ? cfg.localLlmFastUrl
2284
+ : typeof cfg.localLlmUrl === "string" && cfg.localLlmUrl.length > 0
2285
+ ? cfg.localLlmUrl
2286
+ : "http://localhost:1234/v1",
2287
+ localLlmFastTimeoutMs:
2288
+ typeof cfg.localLlmFastTimeoutMs === "number" ? cfg.localLlmFastTimeoutMs : 15_000,
2289
+ // Thinking-mode suppression on the main local LLM (issue #548).
2290
+ // Default true — extraction / consolidation produce structured
2291
+ // JSON and gain nothing from chain-of-thought; thinking-capable
2292
+ // models burn their token budget on reasoning and blow the
2293
+ // default 60s timeout. Operators who need thinking on the main
2294
+ // client (e.g. for narrative tasks) can set this to false via
2295
+ // config or --config CLI flag. The fast-tier `fastLlm` always
2296
+ // disables thinking and is unaffected by this flag.
2297
+ //
2298
+ // Injection is backend-gated inside LocalLlmClient: the
2299
+ // `chat_template_kwargs` field is only sent when the detected
2300
+ // backend is in `THINKING_COMPATIBLE_BACKENDS` (LM Studio, vLLM).
2301
+ // Strict OpenAI-compatible backends reject unknown request
2302
+ // fields with 400, so the client fails open on unknown backends
2303
+ // rather than tripping the 400 cooldown (Codex P1 on PR #550).
2304
+ localLlmDisableThinking:
2305
+ coerceBool(cfg.localLlmDisableThinking) ?? true,
2306
+ // Gateway config (passed from index.ts for fallback AI)
2307
+ gatewayConfig: cfg.gatewayConfig as PluginConfig["gatewayConfig"],
2308
+ // Gateway model source (v9.2) — route LLM calls through gateway agent model chain
2309
+ modelSource,
2310
+ gatewayAgentId:
2311
+ typeof cfg.gatewayAgentId === "string" && cfg.gatewayAgentId.length > 0
2312
+ ? cfg.gatewayAgentId
2313
+ : "",
2314
+ fastGatewayAgentId:
2315
+ typeof cfg.fastGatewayAgentId === "string" && cfg.fastGatewayAgentId.length > 0
2316
+ ? cfg.fastGatewayAgentId
2317
+ : "",
2318
+
2319
+ // v3.0 namespaces (default off)
2320
+ namespacesEnabled: cfg.namespacesEnabled === true,
2321
+ defaultNamespace:
2322
+ typeof cfg.defaultNamespace === "string" && cfg.defaultNamespace.length > 0 ? cfg.defaultNamespace : "default",
2323
+ sharedNamespace:
2324
+ typeof cfg.sharedNamespace === "string" && cfg.sharedNamespace.length > 0 ? cfg.sharedNamespace : "shared",
2325
+ principalFromSessionKeyMode:
2326
+ cfg.principalFromSessionKeyMode === "prefix"
2327
+ ? "prefix"
2328
+ : cfg.principalFromSessionKeyMode === "regex"
2329
+ ? "regex"
2330
+ : "map",
2331
+ principalFromSessionKeyRules: principalRules,
2332
+ namespacePolicies: Array.isArray(cfg.namespacePolicies)
2333
+ ? (cfg.namespacePolicies as any[]).map((p) => ({
2334
+ name: typeof p?.name === "string" ? p.name : "",
2335
+ readPrincipals: Array.isArray(p?.readPrincipals) ? p.readPrincipals.filter((x: any) => typeof x === "string") : [],
2336
+ writePrincipals: Array.isArray(p?.writePrincipals) ? p.writePrincipals.filter((x: any) => typeof x === "string") : [],
2337
+ includeInRecallByDefault: p?.includeInRecallByDefault === true,
2338
+ })).filter((p) => p.name.length > 0)
2339
+ : [],
2340
+ defaultRecallNamespaces: Array.isArray(cfg.defaultRecallNamespaces) ? ["self", "shared"].filter((x) => (cfg.defaultRecallNamespaces as any[]).includes(x)) as any : ["self", "shared"],
2341
+ cronRecallMode:
2342
+ cfg.cronRecallMode === "none"
2343
+ ? "none"
2344
+ : cfg.cronRecallMode === "allowlist"
2345
+ ? "allowlist"
2346
+ : "all",
2347
+ cronRecallAllowlist: Array.isArray(cfg.cronRecallAllowlist)
2348
+ ? (cfg.cronRecallAllowlist as unknown[]).filter((v): v is string => typeof v === "string" && v.length > 0)
2349
+ : [],
2350
+ cronRecallPolicyEnabled: cfg.cronRecallPolicyEnabled !== false,
2351
+ cronRecallNormalizedQueryMaxChars:
2352
+ typeof cfg.cronRecallNormalizedQueryMaxChars === "number"
2353
+ ? cfg.cronRecallNormalizedQueryMaxChars
2354
+ : 480,
2355
+ cronRecallInstructionHeavyTokenCap:
2356
+ typeof cfg.cronRecallInstructionHeavyTokenCap === "number"
2357
+ ? cfg.cronRecallInstructionHeavyTokenCap
2358
+ : 36,
2359
+ cronConversationRecallMode:
2360
+ cfg.cronConversationRecallMode === "always"
2361
+ ? "always"
2362
+ : cfg.cronConversationRecallMode === "never"
2363
+ ? "never"
2364
+ : "auto",
2365
+ autoPromoteToSharedEnabled: cfg.autoPromoteToSharedEnabled === true,
2366
+ autoPromoteToSharedCategories: Array.isArray(cfg.autoPromoteToSharedCategories)
2367
+ ? (cfg.autoPromoteToSharedCategories as any[]).filter((c) => c === "fact" || c === "correction" || c === "decision" || c === "preference")
2368
+ : ["fact", "correction", "decision", "preference"],
2369
+ autoPromoteMinConfidenceTier:
2370
+ cfg.autoPromoteMinConfidenceTier === "explicit"
2371
+ ? "explicit"
2372
+ : cfg.autoPromoteMinConfidenceTier === "implied"
2373
+ ? "implied"
2374
+ : "explicit",
2375
+ routingRulesEnabled: cfg.routingRulesEnabled === true,
2376
+ routingRulesStateFile:
2377
+ typeof cfg.routingRulesStateFile === "string" && cfg.routingRulesStateFile.trim().length > 0
2378
+ ? cfg.routingRulesStateFile.trim()
2379
+ : "state/routing-rules.json",
2380
+
2381
+ // v4.0 shared-context (default off)
2382
+ sharedContextEnabled: cfg.sharedContextEnabled === true,
2383
+ sharedContextDir:
2384
+ typeof cfg.sharedContextDir === "string" && cfg.sharedContextDir.length > 0 ? cfg.sharedContextDir : undefined,
2385
+ sharedContextMaxInjectChars:
2386
+ typeof cfg.sharedContextMaxInjectChars === "number" ? cfg.sharedContextMaxInjectChars : 4000,
2387
+ sharedCrossSignalSemanticEnabled,
2388
+ sharedCrossSignalSemanticTimeoutMs,
2389
+ sharedCrossSignalSemanticMaxCandidates:
2390
+ typeof cfg.sharedCrossSignalSemanticMaxCandidates === "number"
2391
+ ? Math.max(0, Math.floor(cfg.sharedCrossSignalSemanticMaxCandidates))
2392
+ : 120,
2393
+ // Backward-compatible aliases.
2394
+ crossSignalsSemanticEnabled: sharedCrossSignalSemanticEnabled,
2395
+ crossSignalsSemanticTimeoutMs: sharedCrossSignalSemanticTimeoutMs,
2396
+
2397
+ // v5.0 compounding (default off)
2398
+ compoundingEnabled: cfg.compoundingEnabled === true,
2399
+ compoundingWeeklyCronEnabled: cfg.compoundingWeeklyCronEnabled === true,
2400
+ compoundingSemanticEnabled: cfg.compoundingSemanticEnabled === true,
2401
+ compoundingSynthesisTimeoutMs:
2402
+ typeof cfg.compoundingSynthesisTimeoutMs === "number" ? cfg.compoundingSynthesisTimeoutMs : 15_000,
2403
+ compoundingInjectEnabled: cfg.compoundingInjectEnabled !== false,
2404
+
2405
+ // IRC (Inductive Rule Consolidation) — preference synthesis
2406
+ ircEnabled: cfg.ircEnabled !== false,
2407
+ ircMaxPreferences: typeof cfg.ircMaxPreferences === "number" ? cfg.ircMaxPreferences : 20,
2408
+ ircIncludeCorrections: cfg.ircIncludeCorrections !== false,
2409
+ ircMinConfidence: typeof cfg.ircMinConfidence === "number" ? cfg.ircMinConfidence : 0.3,
2410
+
2411
+ // CMC (Causal Memory Consolidation) — cross-session causal reasoning
2412
+ cmcEnabled: cfg.cmcEnabled === true,
2413
+ cmcStitchLookbackDays: typeof cfg.cmcStitchLookbackDays === "number" ? cfg.cmcStitchLookbackDays : 7,
2414
+ cmcStitchMinScore: typeof cfg.cmcStitchMinScore === "number" ? cfg.cmcStitchMinScore : 2.5,
2415
+ cmcStitchMaxEdgesPerTrajectory: typeof cfg.cmcStitchMaxEdgesPerTrajectory === "number" ? cfg.cmcStitchMaxEdgesPerTrajectory : 3,
2416
+ cmcConsolidationEnabled: cfg.cmcConsolidationEnabled === true,
2417
+ cmcConsolidationMinRecurrence: typeof cfg.cmcConsolidationMinRecurrence === "number" ? cfg.cmcConsolidationMinRecurrence : 3,
2418
+ cmcConsolidationMinSessions: typeof cfg.cmcConsolidationMinSessions === "number" ? cfg.cmcConsolidationMinSessions : 2,
2419
+ cmcConsolidationSuccessThreshold: typeof cfg.cmcConsolidationSuccessThreshold === "number" ? cfg.cmcConsolidationSuccessThreshold : 0.7,
2420
+ cmcRetrievalEnabled: cfg.cmcRetrievalEnabled === true,
2421
+ cmcRetrievalMaxDepth: typeof cfg.cmcRetrievalMaxDepth === "number" ? cfg.cmcRetrievalMaxDepth : 3,
2422
+ cmcRetrievalMaxChars: typeof cfg.cmcRetrievalMaxChars === "number" ? cfg.cmcRetrievalMaxChars : 800,
2423
+ cmcRetrievalCounterfactualBoost: typeof cfg.cmcRetrievalCounterfactualBoost === "number" ? cfg.cmcRetrievalCounterfactualBoost : 0.4,
2424
+ cmcBehaviorLearningEnabled: cfg.cmcBehaviorLearningEnabled === true,
2425
+ cmcBehaviorMinFrequency: typeof cfg.cmcBehaviorMinFrequency === "number" ? cfg.cmcBehaviorMinFrequency : 3,
2426
+ cmcBehaviorMinSessions: typeof cfg.cmcBehaviorMinSessions === "number" ? cfg.cmcBehaviorMinSessions : 2,
2427
+ cmcBehaviorConfidenceThreshold: typeof cfg.cmcBehaviorConfidenceThreshold === "number" ? cfg.cmcBehaviorConfidenceThreshold : 0.6,
2428
+ cmcLifecycleCausalImpactWeight: typeof cfg.cmcLifecycleCausalImpactWeight === "number" ? cfg.cmcLifecycleCausalImpactWeight : 0.05,
2429
+
2430
+ // PEDC (Prediction-Error-Driven Calibration) — model-user alignment
2431
+ calibrationEnabled: cfg.calibrationEnabled === true,
2432
+ calibrationMaxRulesPerRecall: typeof cfg.calibrationMaxRulesPerRecall === "number" ? cfg.calibrationMaxRulesPerRecall : 10,
2433
+ calibrationMaxChars: typeof cfg.calibrationMaxChars === "number" ? cfg.calibrationMaxChars : 1200,
2434
+
2435
+ // v7.0 Knowledge Graph Enhancement
2436
+ knowledgeIndexEnabled: cfg.knowledgeIndexEnabled !== false,
2437
+ knowledgeIndexMaxEntities:
2438
+ typeof cfg.knowledgeIndexMaxEntities === "number" ? cfg.knowledgeIndexMaxEntities : 40,
2439
+ knowledgeIndexMaxChars:
2440
+ typeof cfg.knowledgeIndexMaxChars === "number" ? cfg.knowledgeIndexMaxChars : 4000,
2441
+ entityRetrievalEnabled: cfg.entityRetrievalEnabled !== false,
2442
+ entityRetrievalMaxChars:
2443
+ typeof cfg.entityRetrievalMaxChars === "number" ? cfg.entityRetrievalMaxChars : 2400,
2444
+ entityRetrievalMaxHints:
2445
+ typeof cfg.entityRetrievalMaxHints === "number" ? cfg.entityRetrievalMaxHints : 2,
2446
+ entityRetrievalMaxSupportingFacts:
2447
+ typeof cfg.entityRetrievalMaxSupportingFacts === "number" ? cfg.entityRetrievalMaxSupportingFacts : 6,
2448
+ entityRetrievalMaxRelatedEntities:
2449
+ typeof cfg.entityRetrievalMaxRelatedEntities === "number" ? cfg.entityRetrievalMaxRelatedEntities : 3,
2450
+ entityRetrievalRecentTurns:
2451
+ typeof cfg.entityRetrievalRecentTurns === "number" ? cfg.entityRetrievalRecentTurns : 6,
2452
+ entitySchemas,
2453
+ recallBudgetChars: recallPipelineConfig.recallBudgetChars,
2454
+ recallOuterTimeoutMs:
2455
+ typeof cfg.recallOuterTimeoutMs === "number" ? Math.max(0, Math.floor(cfg.recallOuterTimeoutMs)) : 75_000,
2456
+ recallCoreDeadlineMs:
2457
+ typeof cfg.recallCoreDeadlineMs === "number" ? Math.max(0, Math.floor(cfg.recallCoreDeadlineMs)) : 75_000,
2458
+ recallEnrichmentDeadlineMs:
2459
+ typeof cfg.recallEnrichmentDeadlineMs === "number"
2460
+ ? Math.max(0, Math.floor(cfg.recallEnrichmentDeadlineMs))
2461
+ : 25_000,
2462
+ recallPipeline: recallPipelineConfig.pipeline,
2463
+ recallMmrEnabled: cfg.recallMmrEnabled !== false,
2464
+ recallMmrLambda:
2465
+ typeof cfg.recallMmrLambda === "number" && Number.isFinite(cfg.recallMmrLambda)
2466
+ ? Math.min(1, Math.max(0, cfg.recallMmrLambda))
2467
+ : 0.7,
2468
+ recallMmrTopN:
2469
+ typeof cfg.recallMmrTopN === "number" && Number.isFinite(cfg.recallMmrTopN)
2470
+ ? Math.max(0, Math.floor(cfg.recallMmrTopN))
2471
+ : 40,
2472
+ // Issue #564 PR 3: off by default; enable explicitly after bench validation.
2473
+ recallReasoningTraceBoostEnabled:
2474
+ coerceBool(cfg.recallReasoningTraceBoostEnabled) ?? false,
2475
+ qmdRecallCacheTtlMs:
2476
+ typeof cfg.qmdRecallCacheTtlMs === "number" ? Math.max(0, Math.floor(cfg.qmdRecallCacheTtlMs)) : 60_000,
2477
+ qmdRecallCacheStaleTtlMs:
2478
+ typeof cfg.qmdRecallCacheStaleTtlMs === "number"
2479
+ ? Math.max(0, Math.floor(cfg.qmdRecallCacheStaleTtlMs))
2480
+ : 10 * 60_000,
2481
+ qmdRecallCacheMaxEntries:
2482
+ typeof cfg.qmdRecallCacheMaxEntries === "number"
2483
+ ? Math.max(0, Math.floor(cfg.qmdRecallCacheMaxEntries))
2484
+ : 128,
2485
+ entityRelationshipsEnabled: cfg.entityRelationshipsEnabled !== false,
2486
+ entityActivityLogEnabled: cfg.entityActivityLogEnabled !== false,
2487
+ entityActivityLogMaxEntries:
2488
+ typeof cfg.entityActivityLogMaxEntries === "number" ? cfg.entityActivityLogMaxEntries : 20,
2489
+ entityAliasesEnabled: cfg.entityAliasesEnabled !== false,
2490
+ entitySummaryEnabled: cfg.entitySummaryEnabled !== false,
2491
+ entitySynthesisMaxTokens:
2492
+ typeof cfg.entitySynthesisMaxTokens === "number" && Number.isFinite(cfg.entitySynthesisMaxTokens)
2493
+ ? (() => {
2494
+ const tokens = Math.max(0, Math.floor(cfg.entitySynthesisMaxTokens));
2495
+ return tokens === 0 ? 0 : Math.max(10, tokens);
2496
+ })()
2497
+ : 500,
2498
+
2499
+ // Search backend abstraction
2500
+ searchBackend: (["qmd", "remote", "noop", "lancedb", "meilisearch", "orama"] as const).includes(cfg.searchBackend as any)
2501
+ ? (cfg.searchBackend as "qmd" | "remote" | "noop" | "lancedb" | "meilisearch" | "orama")
2502
+ : "qmd",
2503
+ remoteSearchBaseUrl: typeof cfg.remoteSearchBaseUrl === "string" ? cfg.remoteSearchBaseUrl : undefined,
2504
+ remoteSearchApiKey: typeof cfg.remoteSearchApiKey === "string" ? cfg.remoteSearchApiKey : undefined,
2505
+ remoteSearchTimeoutMs: typeof cfg.remoteSearchTimeoutMs === "number" ? cfg.remoteSearchTimeoutMs : 30_000,
2506
+
2507
+ // LanceDB backend
2508
+ lancedbEnabled: cfg.lancedbEnabled === true,
2509
+ lanceDbPath: typeof cfg.lanceDbPath === "string" ? cfg.lanceDbPath : path.join(memoryDir, "lancedb"),
2510
+ lanceEmbeddingDimension: typeof cfg.lanceEmbeddingDimension === "number" ? cfg.lanceEmbeddingDimension : 1536,
2511
+
2512
+ // Meilisearch backend
2513
+ meilisearchEnabled: cfg.meilisearchEnabled === true,
2514
+ meilisearchHost: typeof cfg.meilisearchHost === "string" ? cfg.meilisearchHost : "http://localhost:7700",
2515
+ meilisearchApiKey: typeof cfg.meilisearchApiKey === "string" ? cfg.meilisearchApiKey : undefined,
2516
+ meilisearchTimeoutMs: typeof cfg.meilisearchTimeoutMs === "number" ? cfg.meilisearchTimeoutMs : 30_000,
2517
+ meilisearchAutoIndex: cfg.meilisearchAutoIndex === true,
2518
+
2519
+ // Orama backend
2520
+ oramaEnabled: cfg.oramaEnabled === true,
2521
+ oramaDbPath: typeof cfg.oramaDbPath === "string" ? cfg.oramaDbPath : path.join(memoryDir, "orama"),
2522
+ oramaEmbeddingDimension: typeof cfg.oramaEmbeddingDimension === "number" ? cfg.oramaEmbeddingDimension : 1536,
2523
+
2524
+ // QMD daemon mode
2525
+ qmdDaemonEnabled: cfg.qmdDaemonEnabled !== false,
2526
+ qmdDaemonUrl:
2527
+ typeof cfg.qmdDaemonUrl === "string" && cfg.qmdDaemonUrl.length > 0
2528
+ ? cfg.qmdDaemonUrl
2529
+ : "http://localhost:8181/mcp",
2530
+ qmdDaemonRecheckIntervalMs:
2531
+ typeof cfg.qmdDaemonRecheckIntervalMs === "number" ? cfg.qmdDaemonRecheckIntervalMs : 15_000,
2532
+ qmdIntentHintsEnabled: cfg.qmdIntentHintsEnabled === true,
2533
+ qmdExplainEnabled: cfg.qmdExplainEnabled === true,
2534
+
2535
+ // v6.0 Fact deduplication & archival
2536
+ factDeduplicationEnabled: cfg.factDeduplicationEnabled !== false,
2537
+ // Issue #373 — write-time semantic similarity guard
2538
+ semanticDedupEnabled: cfg.semanticDedupEnabled !== false,
2539
+ // Guard against NaN / Infinity — Number.isFinite rejects both and falls
2540
+ // back to the documented default so the semantic dedup guard cannot be
2541
+ // silently disabled by a malformed config value.
2542
+ semanticDedupThreshold:
2543
+ typeof cfg.semanticDedupThreshold === "number" &&
2544
+ Number.isFinite(cfg.semanticDedupThreshold)
2545
+ ? Math.min(1, Math.max(0, cfg.semanticDedupThreshold))
2546
+ : 0.92,
2547
+ // Zero is a valid "disable candidate lookup" signal and must be preserved.
2548
+ // Only negative or non-finite values fall back to the default of 5.
2549
+ // Fractional values in (0, 1) floor to 0, which would silently disable
2550
+ // semantic dedup despite a clearly non-zero operator intent — clamp to 1.
2551
+ semanticDedupCandidates: (() => {
2552
+ const raw = cfg.semanticDedupCandidates;
2553
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) return 5;
2554
+ const n = Math.floor(raw);
2555
+ // Positive fractional input (e.g. 0.5) should mean "at least 1 candidate",
2556
+ // not "disabled". Only explicit 0 is the operator's disable signal.
2557
+ return raw > 0 && n === 0 ? 1 : n;
2558
+ })(),
2559
+ factArchivalEnabled: cfg.factArchivalEnabled === true,
2560
+ factArchivalAgeDays:
2561
+ typeof cfg.factArchivalAgeDays === "number" ? cfg.factArchivalAgeDays : 90,
2562
+ factArchivalMaxImportance:
2563
+ typeof cfg.factArchivalMaxImportance === "number" ? cfg.factArchivalMaxImportance : 0.3,
2564
+ factArchivalMaxAccessCount:
2565
+ typeof cfg.factArchivalMaxAccessCount === "number" ? cfg.factArchivalMaxAccessCount : 2,
2566
+ factArchivalProtectedCategories: Array.isArray(cfg.factArchivalProtectedCategories)
2567
+ ? (cfg.factArchivalProtectedCategories as any[]).filter((c) => typeof c === "string")
2568
+ : ["commitment", "preference", "decision", "principle", "procedure"],
2569
+ // Lifecycle policy engine (issue #686 PR 3/6 — flipped default to
2570
+ // `true`). Tier infrastructure (`tier-routing.ts`,
2571
+ // `tier-migration.ts`, separate cold QMD collection) has shipped
2572
+ // for several releases. Default-on lets the year-2 retention
2573
+ // story land for every install instead of staying gated behind
2574
+ // an opt-in flag the typical operator never reaches. Operators
2575
+ // who need pre-#686 behavior (no automatic hot↔cold migration,
2576
+ // no recall-time stale filtering) can set
2577
+ // `lifecyclePolicyEnabled: false` explicitly. Coerce string/number
2578
+ // boolean-likes (e.g. CLI `--config lifecyclePolicyEnabled=false`)
2579
+ // before applying the default — otherwise an explicit false-ish
2580
+ // input falls through and silently re-enables the policy.
2581
+ // Issue #678 PR 2/4: dreams.phases.lightSleep.* WINS over legacy keys.
2582
+ // The runtime gate (orchestrator's `runLifecyclePolicyPass`) reads
2583
+ // these legacy fields, so propagate the precedence here.
2584
+ lifecyclePolicyEnabled: dreamsLightSleep.enabled,
2585
+ lifecycleFilterStaleEnabled: dreamsLightSleep.filterStaleEnabled,
2586
+ lifecyclePromoteHeatThreshold: dreamsLightSleep.promoteHeatThreshold,
2587
+ lifecycleStaleDecayThreshold: dreamsLightSleep.staleDecayThreshold,
2588
+ lifecycleArchiveDecayThreshold: dreamsLightSleep.archiveDecayThreshold,
2589
+ lifecycleProtectedCategories: Array.isArray(cfg.lifecycleProtectedCategories)
2590
+ ? (cfg.lifecycleProtectedCategories as any[]).filter(
2591
+ (c): c is PluginConfig["lifecycleProtectedCategories"][number] =>
2592
+ typeof c === "string" && VALID_MEMORY_CATEGORIES.has(c),
2593
+ )
2594
+ : ["decision", "principle", "commitment", "preference", "procedure"],
2595
+ // Mirror the *resolved* lifecyclePolicyEnabled default (not the
2596
+ // raw input) — otherwise omitting both flags returns `false` for
2597
+ // metrics even though the policy is enabled by default since
2598
+ // #686 PR 3/6.
2599
+ // Issue #678 PR 2/4: mirror the resolved dreams light-sleep enabled flag
2600
+ // (which already incorporates dreams.phases precedence + legacy fallback).
2601
+ lifecycleMetricsEnabled:
2602
+ coerceBooleanLike(cfg.lifecycleMetricsEnabled) ?? dreamsLightSleep.enabled,
2603
+ // v8.3 proactive + policy learning (default off)
2604
+ proactiveExtractionEnabled: cfg.proactiveExtractionEnabled === true,
2605
+ contextCompressionActionsEnabled: cfg.contextCompressionActionsEnabled === true,
2606
+ compressionGuidelineLearningEnabled: cfg.compressionGuidelineLearningEnabled === true,
2607
+ compressionGuidelineSemanticRefinementEnabled:
2608
+ cfg.compressionGuidelineSemanticRefinementEnabled === true,
2609
+ compressionGuidelineSemanticTimeoutMs:
2610
+ typeof cfg.compressionGuidelineSemanticTimeoutMs === "number"
2611
+ ? Math.max(1, Math.floor(cfg.compressionGuidelineSemanticTimeoutMs))
2612
+ : 2500,
2613
+ maxProactiveQuestionsPerExtraction:
2614
+ typeof cfg.maxProactiveQuestionsPerExtraction === "number"
2615
+ ? Math.max(0, Math.floor(cfg.maxProactiveQuestionsPerExtraction))
2616
+ : 2,
2617
+ proactiveExtractionTimeoutMs:
2618
+ typeof cfg.proactiveExtractionTimeoutMs === "number"
2619
+ ? Math.max(0, Math.floor(cfg.proactiveExtractionTimeoutMs))
2620
+ : 2500,
2621
+ proactiveExtractionMaxTokens:
2622
+ typeof cfg.proactiveExtractionMaxTokens === "number"
2623
+ ? Math.max(0, Math.floor(cfg.proactiveExtractionMaxTokens))
2624
+ : 900,
2625
+ extractionMaxOutputTokens:
2626
+ typeof cfg.extractionMaxOutputTokens === "number"
2627
+ ? Math.max(1, Math.floor(cfg.extractionMaxOutputTokens))
2628
+ : 16384,
2629
+ proactiveExtractionCategoryAllowlist: Array.isArray(cfg.proactiveExtractionCategoryAllowlist)
2630
+ ? (cfg.proactiveExtractionCategoryAllowlist as unknown[]).filter(
2631
+ (category): category is PluginConfig["lifecycleProtectedCategories"][number] =>
2632
+ typeof category === "string" && VALID_MEMORY_CATEGORIES.has(category),
2633
+ )
2634
+ : undefined,
2635
+ maxCompressionTokensPerHour:
2636
+ typeof cfg.maxCompressionTokensPerHour === "number"
2637
+ ? Math.max(0, Math.floor(cfg.maxCompressionTokensPerHour))
2638
+ : 1500,
2639
+ behaviorLoopAutoTuneEnabled: cfg.behaviorLoopAutoTuneEnabled === true,
2640
+ behaviorLoopLearningWindowDays:
2641
+ typeof cfg.behaviorLoopLearningWindowDays === "number"
2642
+ ? Math.max(0, Math.floor(cfg.behaviorLoopLearningWindowDays))
2643
+ : 14,
2644
+ behaviorLoopMinSignalCount:
2645
+ typeof cfg.behaviorLoopMinSignalCount === "number"
2646
+ ? Math.max(0, Math.floor(cfg.behaviorLoopMinSignalCount))
2647
+ : 10,
2648
+ behaviorLoopMaxDeltaPerCycle:
2649
+ typeof cfg.behaviorLoopMaxDeltaPerCycle === "number"
2650
+ ? Math.min(1, Math.max(0, cfg.behaviorLoopMaxDeltaPerCycle))
2651
+ : 0.1,
2652
+ behaviorLoopProtectedParams: Array.isArray(cfg.behaviorLoopProtectedParams)
2653
+ ? (cfg.behaviorLoopProtectedParams as unknown[])
2654
+ .filter((param): param is string => typeof param === "string" && param.trim().length > 0)
2655
+ : [...DEFAULT_BEHAVIOR_LOOP_PROTECTED_PARAMS],
2656
+ // v8.0 phase 1
2657
+ recallPlannerEnabled: cfg.recallPlannerEnabled !== false,
2658
+ recallPlannerModel:
2659
+ typeof cfg.recallPlannerModel === "string" && cfg.recallPlannerModel.trim().length > 0
2660
+ ? cfg.recallPlannerModel.trim()
2661
+ : DEFAULT_REASONING_MODEL,
2662
+ recallPlannerTimeoutMs:
2663
+ typeof cfg.recallPlannerTimeoutMs === "number" ? cfg.recallPlannerTimeoutMs : 1500,
2664
+ recallPlannerUseResponsesApi: cfg.recallPlannerUseResponsesApi !== false,
2665
+ recallPlannerMaxPromptChars:
2666
+ typeof cfg.recallPlannerMaxPromptChars === "number" ? cfg.recallPlannerMaxPromptChars : 4000,
2667
+ recallPlannerMaxMemoryHints:
2668
+ typeof cfg.recallPlannerMaxMemoryHints === "number" ? cfg.recallPlannerMaxMemoryHints : 24,
2669
+ recallPlannerShadowMode: cfg.recallPlannerShadowMode === true,
2670
+ recallPlannerTelemetryEnabled: cfg.recallPlannerTelemetryEnabled !== false,
2671
+ recallPlannerMaxQmdResultsMinimal:
2672
+ typeof cfg.recallPlannerMaxQmdResultsMinimal === "number"
2673
+ ? cfg.recallPlannerMaxQmdResultsMinimal
2674
+ : 4,
2675
+ recallPlannerMaxQmdResultsFull:
2676
+ typeof cfg.recallPlannerMaxQmdResultsFull === "number" ? cfg.recallPlannerMaxQmdResultsFull : 8,
2677
+ intentRoutingEnabled: cfg.intentRoutingEnabled === true,
2678
+ intentRoutingBoost:
2679
+ typeof cfg.intentRoutingBoost === "number" ? cfg.intentRoutingBoost : 0.12,
2680
+ verbatimArtifactsEnabled: cfg.verbatimArtifactsEnabled === true,
2681
+ verbatimArtifactsMinConfidence:
2682
+ typeof cfg.verbatimArtifactsMinConfidence === "number"
2683
+ ? cfg.verbatimArtifactsMinConfidence
2684
+ : 0.8,
2685
+ verbatimArtifactsMaxRecall:
2686
+ typeof cfg.verbatimArtifactsMaxRecall === "number" ? cfg.verbatimArtifactsMaxRecall : 5,
2687
+ verbatimArtifactCategories: Array.isArray(cfg.verbatimArtifactCategories)
2688
+ ? (cfg.verbatimArtifactCategories as any[]).filter(
2689
+ (c): c is PluginConfig["verbatimArtifactCategories"][number] =>
2690
+ typeof c === "string" && VALID_MEMORY_CATEGORIES.has(c),
2691
+ )
2692
+ : ["decision", "correction", "principle", "commitment"],
2693
+ // v8.0 Phase 2A: Memory Boxes + Trace Weaving
2694
+ memoryBoxesEnabled: cfg.memoryBoxesEnabled === true,
2695
+ boxTopicShiftThreshold:
2696
+ typeof cfg.boxTopicShiftThreshold === "number" ? cfg.boxTopicShiftThreshold : 0.35,
2697
+ boxTimeGapMs:
2698
+ typeof cfg.boxTimeGapMs === "number" ? cfg.boxTimeGapMs : 30 * 60 * 1000,
2699
+ boxMaxMemories:
2700
+ typeof cfg.boxMaxMemories === "number" ? cfg.boxMaxMemories : 50,
2701
+ traceWeaverEnabled: cfg.traceWeaverEnabled === true,
2702
+ traceWeaverLookbackDays:
2703
+ typeof cfg.traceWeaverLookbackDays === "number" ? cfg.traceWeaverLookbackDays : 7,
2704
+ traceWeaverOverlapThreshold:
2705
+ typeof cfg.traceWeaverOverlapThreshold === "number" ? cfg.traceWeaverOverlapThreshold : 0.4,
2706
+ boxRecallDays:
2707
+ typeof cfg.boxRecallDays === "number" ? cfg.boxRecallDays : 3,
2708
+ // v8.0 Phase 2B: Episode/Note dual store (HiMem)
2709
+ episodeNoteModeEnabled: cfg.episodeNoteModeEnabled === true,
2710
+ // v8.1: Temporal + Tag Indexes (SwiftMem-inspired)
2711
+ queryAwareIndexingEnabled: cfg.queryAwareIndexingEnabled === true,
2712
+ queryAwareIndexingMaxCandidates:
2713
+ typeof cfg.queryAwareIndexingMaxCandidates === "number"
2714
+ ? Math.max(0, cfg.queryAwareIndexingMaxCandidates) // clamp: negative treated as 0 (no cap)
2715
+ : 200,
2716
+ temporalIndexWindowDays:
2717
+ typeof cfg.temporalIndexWindowDays === "number" ? cfg.temporalIndexWindowDays : 30,
2718
+ temporalIndexMaxEntries:
2719
+ typeof cfg.temporalIndexMaxEntries === "number" ? cfg.temporalIndexMaxEntries : 5000,
2720
+ temporalBoostRecentDays:
2721
+ typeof cfg.temporalBoostRecentDays === "number" ? cfg.temporalBoostRecentDays : 7,
2722
+ temporalBoostScore: typeof cfg.temporalBoostScore === "number" ? cfg.temporalBoostScore : 0.15,
2723
+ temporalDecayEnabled: cfg.temporalDecayEnabled !== false,
2724
+ tagMemoryEnabled: cfg.tagMemoryEnabled === true,
2725
+ tagMaxPerMemory: typeof cfg.tagMaxPerMemory === "number" ? cfg.tagMaxPerMemory : 5,
2726
+ tagIndexMaxEntries:
2727
+ typeof cfg.tagIndexMaxEntries === "number" ? cfg.tagIndexMaxEntries : 10000,
2728
+ tagRecallBoost: typeof cfg.tagRecallBoost === "number" ? cfg.tagRecallBoost : 0.15,
2729
+ tagRecallMaxMatches: typeof cfg.tagRecallMaxMatches === "number" ? cfg.tagRecallMaxMatches : 10,
2730
+ // v8.2: Multi-graph memory (PR 18)
2731
+ multiGraphMemoryEnabled: cfg.multiGraphMemoryEnabled === true,
2732
+ graphRecallEnabled: cfg.graphRecallEnabled === true,
2733
+ graphRecallMaxExpansions:
2734
+ typeof cfg.graphRecallMaxExpansions === "number" ? cfg.graphRecallMaxExpansions : 3,
2735
+ graphRecallMaxPerSeed:
2736
+ typeof cfg.graphRecallMaxPerSeed === "number" ? cfg.graphRecallMaxPerSeed : 5,
2737
+ graphRecallMinEdgeWeight:
2738
+ typeof cfg.graphRecallMinEdgeWeight === "number" ? cfg.graphRecallMinEdgeWeight : 0.1,
2739
+ graphRecallShadowEnabled: cfg.graphRecallShadowEnabled === true,
2740
+ graphRecallSnapshotEnabled: cfg.graphRecallSnapshotEnabled === true,
2741
+ graphRecallShadowSampleRate:
2742
+ typeof cfg.graphRecallShadowSampleRate === "number" ? cfg.graphRecallShadowSampleRate : 0.1,
2743
+ graphRecallExplainToolEnabled: cfg.graphRecallExplainToolEnabled === true,
2744
+ graphRecallStoreColdMirror: cfg.graphRecallStoreColdMirror === true,
2745
+ graphRecallColdMirrorCollection:
2746
+ typeof cfg.graphRecallColdMirrorCollection === "string" &&
2747
+ cfg.graphRecallColdMirrorCollection.trim().length > 0
2748
+ ? cfg.graphRecallColdMirrorCollection.trim()
2749
+ : undefined,
2750
+ graphRecallColdMirrorMinAgeDays:
2751
+ typeof cfg.graphRecallColdMirrorMinAgeDays === "number" ? cfg.graphRecallColdMirrorMinAgeDays : 7,
2752
+ graphRecallUseEntityPriors: cfg.graphRecallUseEntityPriors === true,
2753
+ graphRecallEntityPriorBoost:
2754
+ typeof cfg.graphRecallEntityPriorBoost === "number" ? cfg.graphRecallEntityPriorBoost : 0.2,
2755
+ graphRecallPreferHubSeeds: cfg.graphRecallPreferHubSeeds === true,
2756
+ graphRecallHubBias:
2757
+ typeof cfg.graphRecallHubBias === "number" ? cfg.graphRecallHubBias : 0.3,
2758
+ graphRecallRecencyHalfLifeDays:
2759
+ typeof cfg.graphRecallRecencyHalfLifeDays === "number" ? cfg.graphRecallRecencyHalfLifeDays : 30,
2760
+ graphRecallDampingFactor:
2761
+ typeof cfg.graphRecallDampingFactor === "number" ? cfg.graphRecallDampingFactor : 0.85,
2762
+ graphRecallMaxSeedNodes:
2763
+ typeof cfg.graphRecallMaxSeedNodes === "number" ? cfg.graphRecallMaxSeedNodes : 10,
2764
+ graphRecallMaxExpandedNodes:
2765
+ typeof cfg.graphRecallMaxExpandedNodes === "number" ? cfg.graphRecallMaxExpandedNodes : 30,
2766
+ graphRecallMaxTrailPerNode:
2767
+ typeof cfg.graphRecallMaxTrailPerNode === "number" ? cfg.graphRecallMaxTrailPerNode : 5,
2768
+ graphRecallMinSeedScore:
2769
+ typeof cfg.graphRecallMinSeedScore === "number" ? cfg.graphRecallMinSeedScore : 0.3,
2770
+ graphRecallExpansionScoreThreshold:
2771
+ typeof cfg.graphRecallExpansionScoreThreshold === "number"
2772
+ ? cfg.graphRecallExpansionScoreThreshold
2773
+ : 0.2,
2774
+ graphRecallExplainMaxPaths:
2775
+ typeof cfg.graphRecallExplainMaxPaths === "number" ? cfg.graphRecallExplainMaxPaths : 3,
2776
+ graphRecallExplainMaxChars:
2777
+ typeof cfg.graphRecallExplainMaxChars === "number" ? cfg.graphRecallExplainMaxChars : 500,
2778
+ graphRecallExplainEdgeLimit:
2779
+ typeof cfg.graphRecallExplainEdgeLimit === "number" ? cfg.graphRecallExplainEdgeLimit : 5,
2780
+ graphRecallExplainEnabled: cfg.graphRecallExplainEnabled === true,
2781
+ graphRecallEntityHintsEnabled: cfg.graphRecallEntityHintsEnabled === true,
2782
+ graphRecallEntityHintMax:
2783
+ typeof cfg.graphRecallEntityHintMax === "number" ? cfg.graphRecallEntityHintMax : 3,
2784
+ graphRecallEntityHintMaxChars:
2785
+ typeof cfg.graphRecallEntityHintMaxChars === "number" ? cfg.graphRecallEntityHintMaxChars : 200,
2786
+ graphRecallSnapshotDir:
2787
+ typeof cfg.graphRecallSnapshotDir === "string" && cfg.graphRecallSnapshotDir.trim().length > 0
2788
+ ? cfg.graphRecallSnapshotDir.trim()
2789
+ : path.join(memoryDir, "state", "graph"),
2790
+ graphRecallEnableTrace: cfg.graphRecallEnableTrace === true,
2791
+ graphRecallEnableDebug: cfg.graphRecallEnableDebug === true,
2792
+ graphExpandedIntentEnabled: cfg.graphExpandedIntentEnabled !== false,
2793
+ graphAssistInFullModeEnabled: cfg.graphAssistInFullModeEnabled !== false,
2794
+ graphAssistShadowEvalEnabled: cfg.graphAssistShadowEvalEnabled === true,
2795
+ graphAssistMinSeedResults:
2796
+ typeof cfg.graphAssistMinSeedResults === "number"
2797
+ ? Math.max(1, Math.floor(cfg.graphAssistMinSeedResults))
2798
+ : 3,
2799
+ entityGraphEnabled: cfg.entityGraphEnabled !== false,
2800
+ timeGraphEnabled: cfg.timeGraphEnabled !== false,
2801
+ graphWriteSessionAdjacencyEnabled: cfg.graphWriteSessionAdjacencyEnabled !== false,
2802
+ causalGraphEnabled: cfg.causalGraphEnabled !== false,
2803
+ maxGraphTraversalSteps:
2804
+ typeof cfg.maxGraphTraversalSteps === "number" ? Math.max(0, cfg.maxGraphTraversalSteps) : 3,
2805
+ graphActivationDecay:
2806
+ typeof cfg.graphActivationDecay === "number"
2807
+ ? Math.min(1, Math.max(0, cfg.graphActivationDecay))
2808
+ : 0.7,
2809
+ graphExpansionActivationWeight:
2810
+ typeof cfg.graphExpansionActivationWeight === "number"
2811
+ ? Math.min(1, Math.max(0, cfg.graphExpansionActivationWeight))
2812
+ : 0.65,
2813
+ graphExpansionBlendMin:
2814
+ typeof cfg.graphExpansionBlendMin === "number"
2815
+ ? Math.min(1, Math.max(0, cfg.graphExpansionBlendMin))
2816
+ : 0.05,
2817
+ graphExpansionBlendMax:
2818
+ typeof cfg.graphExpansionBlendMax === "number"
2819
+ ? Math.min(1, Math.max(0, cfg.graphExpansionBlendMax))
2820
+ : 0.95,
2821
+ maxEntityGraphEdgesPerMemory:
2822
+ typeof cfg.maxEntityGraphEdgesPerMemory === "number"
2823
+ ? Math.max(0, cfg.maxEntityGraphEdgesPerMemory)
2824
+ : 10,
2825
+ delinearizeEnabled: cfg.delinearizeEnabled !== false,
2826
+ recallConfidenceGateEnabled: cfg.recallConfidenceGateEnabled === true,
2827
+ recallConfidenceGateThreshold:
2828
+ typeof cfg.recallConfidenceGateThreshold === "number"
2829
+ ? Math.max(0, Math.min(1, cfg.recallConfidenceGateThreshold))
2830
+ : 0.12,
2831
+ causalRuleExtractionEnabled: cfg.causalRuleExtractionEnabled === true,
2832
+ memoryReconstructionEnabled: cfg.memoryReconstructionEnabled === true,
2833
+ memoryReconstructionMaxExpansions:
2834
+ typeof cfg.memoryReconstructionMaxExpansions === "number" ? Math.max(0, Math.round(cfg.memoryReconstructionMaxExpansions)) : 3,
2835
+ graphLateralInhibitionEnabled: cfg.graphLateralInhibitionEnabled !== false,
2836
+ graphLateralInhibitionBeta:
2837
+ typeof cfg.graphLateralInhibitionBeta === "number"
2838
+ ? Math.max(0, Math.min(1, cfg.graphLateralInhibitionBeta))
2839
+ : 0.15,
2840
+ graphLateralInhibitionTopM:
2841
+ typeof cfg.graphLateralInhibitionTopM === "number"
2842
+ ? Math.max(0, Math.round(cfg.graphLateralInhibitionTopM))
2843
+ : 7,
2844
+ // Issue #681 PR 2/3 — graph-edge confidence decay maintenance.
2845
+ // Boolean coerced via shared helper (gotcha #36: string "false" is truthy).
2846
+ graphEdgeDecayEnabled: coerceBooleanLike(cfg.graphEdgeDecayEnabled) ?? false,
2847
+ graphEdgeDecayCadenceMs:
2848
+ typeof cfg.graphEdgeDecayCadenceMs === "number" && Number.isFinite(cfg.graphEdgeDecayCadenceMs)
2849
+ ? Math.max(60_000, Math.floor(cfg.graphEdgeDecayCadenceMs))
2850
+ : 7 * 24 * 60 * 60 * 1000,
2851
+ graphEdgeDecayWindowMs:
2852
+ typeof cfg.graphEdgeDecayWindowMs === "number" && Number.isFinite(cfg.graphEdgeDecayWindowMs)
2853
+ ? Math.max(60_000, Math.floor(cfg.graphEdgeDecayWindowMs))
2854
+ : 90 * 24 * 60 * 60 * 1000,
2855
+ graphEdgeDecayPerWindow:
2856
+ typeof cfg.graphEdgeDecayPerWindow === "number" && Number.isFinite(cfg.graphEdgeDecayPerWindow)
2857
+ ? Math.max(0, Math.min(1, cfg.graphEdgeDecayPerWindow))
2858
+ : 0.1,
2859
+ graphEdgeDecayFloor:
2860
+ typeof cfg.graphEdgeDecayFloor === "number" && Number.isFinite(cfg.graphEdgeDecayFloor)
2861
+ ? Math.max(0, Math.min(1, cfg.graphEdgeDecayFloor))
2862
+ : 0.1,
2863
+ graphEdgeDecayVisibilityThreshold:
2864
+ typeof cfg.graphEdgeDecayVisibilityThreshold === "number" &&
2865
+ Number.isFinite(cfg.graphEdgeDecayVisibilityThreshold)
2866
+ ? Math.max(0, Math.min(1, cfg.graphEdgeDecayVisibilityThreshold))
2867
+ : 0.2,
2868
+ // Issue #681 PR 3/3 — confidence-aware traversal & PageRank refinement.
2869
+ // Floor clamps to [0, 1] so misconfigured input cannot accept negative
2870
+ // confidences or reject every edge. Iterations floors at 0 so a
2871
+ // documented 0 disables PageRank refinement and BFS scores pass through.
2872
+ graphTraversalConfidenceFloor:
2873
+ typeof cfg.graphTraversalConfidenceFloor === "number" &&
2874
+ Number.isFinite(cfg.graphTraversalConfidenceFloor)
2875
+ ? Math.min(1, Math.max(0, cfg.graphTraversalConfidenceFloor))
2876
+ : 0.2,
2877
+ graphTraversalPageRankIterations:
2878
+ typeof cfg.graphTraversalPageRankIterations === "number" &&
2879
+ Number.isFinite(cfg.graphTraversalPageRankIterations) &&
2880
+ cfg.graphTraversalPageRankIterations >= 0
2881
+ ? Math.floor(cfg.graphTraversalPageRankIterations)
2882
+ : 8,
2883
+ // v8.2: Temporal Memory Tree
2884
+ temporalMemoryTreeEnabled: cfg.temporalMemoryTreeEnabled === true,
2885
+ tmtHourlyMinMemories:
2886
+ typeof cfg.tmtHourlyMinMemories === "number" ? cfg.tmtHourlyMinMemories : 3,
2887
+ tmtSummaryMaxTokens:
2888
+ typeof cfg.tmtSummaryMaxTokens === "number" ? cfg.tmtSummaryMaxTokens : 300,
2889
+ explicitCueRecallEnabled: coerceBool(cfg.explicitCueRecallEnabled) === true,
2890
+ explicitCueRecallMaxChars:
2891
+ coerceNumber(cfg.explicitCueRecallMaxChars) !== undefined
2892
+ ? Math.max(0, Math.floor(coerceNumber(cfg.explicitCueRecallMaxChars)!))
2893
+ : 2400,
2894
+ explicitCueRecallMaxReferences:
2895
+ coerceNumber(cfg.explicitCueRecallMaxReferences) !== undefined
2896
+ ? Math.max(0, Math.floor(coerceNumber(cfg.explicitCueRecallMaxReferences)!))
2897
+ : 24,
2898
+ targetedFactRecallEnabled: coerceBool(cfg.targetedFactRecallEnabled) === true,
2899
+ targetedFactRecallMaxChars:
2900
+ parseIntegerAtLeast(cfg.targetedFactRecallMaxChars, 2400, 0, "targetedFactRecallMaxChars"),
2901
+ targetedFactRecallMaxResults:
2902
+ parseIntegerAtLeast(cfg.targetedFactRecallMaxResults, 48, 0, "targetedFactRecallMaxResults"),
2903
+ targetedFactRecallScanWindowTurns:
2904
+ parseIntegerAtLeast(cfg.targetedFactRecallScanWindowTurns, 8, 1, "targetedFactRecallScanWindowTurns"),
2905
+ targetedFactRecallScanWindowTokens:
2906
+ parseIntegerAtLeast(cfg.targetedFactRecallScanWindowTokens, 12_000, 1, "targetedFactRecallScanWindowTokens"),
2907
+ focusedListRecallEnabled: coerceBool(cfg.focusedListRecallEnabled) === true,
2908
+ focusedListRecallMaxChars:
2909
+ parseIntegerAtLeast(cfg.focusedListRecallMaxChars, 2600, 0, "focusedListRecallMaxChars"),
2910
+ focusedListRecallMaxResults:
2911
+ parseIntegerAtLeast(cfg.focusedListRecallMaxResults, 40, 0, "focusedListRecallMaxResults"),
2912
+ focusedListRecallScanWindowTurns:
2913
+ parseIntegerAtLeast(cfg.focusedListRecallScanWindowTurns, 64, 1, "focusedListRecallScanWindowTurns"),
2914
+ focusedListRecallScanWindowTokens:
2915
+ parseIntegerAtLeast(cfg.focusedListRecallScanWindowTokens, 14_000, 1, "focusedListRecallScanWindowTokens"),
2916
+ responseGuidanceRecallEnabled:
2917
+ coerceBool(cfg.responseGuidanceRecallEnabled) === true,
2918
+ responseGuidanceRecallMaxChars:
2919
+ parseIntegerAtLeast(cfg.responseGuidanceRecallMaxChars, 2400, 0, "responseGuidanceRecallMaxChars"),
2920
+ responseGuidanceRecallMaxResults:
2921
+ parseIntegerAtLeast(cfg.responseGuidanceRecallMaxResults, 48, 0, "responseGuidanceRecallMaxResults"),
2922
+ responseGuidanceRecallScanWindowTurns:
2923
+ parseIntegerAtLeast(cfg.responseGuidanceRecallScanWindowTurns, 64, 1, "responseGuidanceRecallScanWindowTurns"),
2924
+ responseGuidanceRecallScanWindowTokens:
2925
+ parseIntegerAtLeast(cfg.responseGuidanceRecallScanWindowTokens, 16_000, 1, "responseGuidanceRecallScanWindowTokens"),
2926
+ eventOrderRecallEnabled: coerceBool(cfg.eventOrderRecallEnabled) === true,
2927
+ eventOrderRecallMaxChars:
2928
+ parseIntegerAtLeast(cfg.eventOrderRecallMaxChars, 2400, 0, "eventOrderRecallMaxChars"),
2929
+ eventOrderRecallMaxResults:
2930
+ parseIntegerAtLeast(cfg.eventOrderRecallMaxResults, 24, 0, "eventOrderRecallMaxResults"),
2931
+ eventOrderRecallScanWindowTurns:
2932
+ parseIntegerAtLeast(cfg.eventOrderRecallScanWindowTurns, 12, 1, "eventOrderRecallScanWindowTurns"),
2933
+ eventOrderRecallScanWindowTokens:
2934
+ parseIntegerAtLeast(cfg.eventOrderRecallScanWindowTokens, 24_000, 1, "eventOrderRecallScanWindowTokens"),
2935
+ // Lossless Context Management (LCM)
2936
+ lcmEnabled: cfg.lcmEnabled === true,
2937
+ lcmLeafBatchSize:
2938
+ typeof cfg.lcmLeafBatchSize === "number" ? Math.max(2, Math.floor(cfg.lcmLeafBatchSize)) : 8,
2939
+ lcmRollupFanIn:
2940
+ typeof cfg.lcmRollupFanIn === "number" ? Math.max(2, Math.floor(cfg.lcmRollupFanIn)) : 4,
2941
+ lcmFreshTailTurns:
2942
+ typeof cfg.lcmFreshTailTurns === "number" ? Math.max(1, Math.floor(cfg.lcmFreshTailTurns)) : 16,
2943
+ lcmMaxDepth:
2944
+ typeof cfg.lcmMaxDepth === "number" ? Math.max(1, Math.floor(cfg.lcmMaxDepth)) : 5,
2945
+ lcmRecallBudgetShare:
2946
+ typeof cfg.lcmRecallBudgetShare === "number"
2947
+ ? Math.max(0, Math.min(1, cfg.lcmRecallBudgetShare))
2948
+ : 0.15,
2949
+ lcmDeterministicMaxTokens:
2950
+ typeof cfg.lcmDeterministicMaxTokens === "number"
2951
+ ? Math.max(64, Math.floor(cfg.lcmDeterministicMaxTokens))
2952
+ : 512,
2953
+ lcmTelemetryPrefilterEnabled:
2954
+ coerceBool(cfg.lcmTelemetryPrefilterEnabled) !== false,
2955
+ lcmArchiveRetentionDays:
2956
+ typeof cfg.lcmArchiveRetentionDays === "number"
2957
+ ? Math.max(1, Math.floor(cfg.lcmArchiveRetentionDays))
2958
+ : 90,
2959
+ lcmObserveConcurrency:
2960
+ parseIntegerAtLeast(cfg.lcmObserveConcurrency, 1, 1, "lcmObserveConcurrency"),
2961
+ messagePartsEnabled: coerceBooleanLike(cfg.messagePartsEnabled) === true,
2962
+ messagePartsRecallMaxResults:
2963
+ typeof cfg.messagePartsRecallMaxResults === "number"
2964
+ ? Math.max(0, Math.floor(cfg.messagePartsRecallMaxResults))
2965
+ : 6,
2966
+
2967
+ // v9.1 Parallel Specialized Retrieval
2968
+ parallelRetrievalEnabled: cfg.parallelRetrievalEnabled === true,
2969
+ parallelAgentWeights: (() => {
2970
+ const w = cfg.parallelAgentWeights as Record<string, unknown> | undefined;
2971
+ return {
2972
+ direct: typeof w?.direct === "number" ? Math.max(0, w.direct) : 1.0,
2973
+ contextual: typeof w?.contextual === "number" ? Math.max(0, w.contextual) : 0.7,
2974
+ temporal: typeof w?.temporal === "number" ? Math.max(0, w.temporal) : 0.85,
2975
+ };
2976
+ })(),
2977
+ parallelMaxResultsPerAgent:
2978
+ typeof cfg.parallelMaxResultsPerAgent === "number"
2979
+ ? Math.max(0, Math.floor(cfg.parallelMaxResultsPerAgent))
2980
+ : 20,
2981
+
2982
+ briefing: parseBriefingConfig(cfg.briefing),
2983
+
2984
+ // Codex CLI connector settings (install-time)
2985
+ codex: (() => {
2986
+ const raw =
2987
+ cfg.codex && typeof cfg.codex === "object" && !Array.isArray(cfg.codex)
2988
+ ? (cfg.codex as Record<string, unknown>)
2989
+ : {};
2990
+ // Coerce string "false"/"0"/"no" → false and "true"/"1"/"yes" → true so
2991
+ // that CLI inputs like --config installExtension=false are handled correctly.
2992
+ // Missing / undefined defaults to true (coerceInstallExtension returns
2993
+ // undefined for unknown values, so ?? true applies the default).
2994
+ const installExtension = coerceInstallExtension(raw.installExtension) ?? true;
2995
+ const codexHome =
2996
+ typeof raw.codexHome === "string" && raw.codexHome.trim().length > 0
2997
+ ? raw.codexHome.trim()
2998
+ : null;
2999
+ return { installExtension, codexHome };
3000
+ })(),
3001
+
3002
+ // Live connectors (issue #683 PR 2/N).
3003
+ //
3004
+ // Per CLAUDE.md gotcha #30 and #48, every concrete connector ships
3005
+ // disabled-by-default and the parser MUST reject malformed top-level
3006
+ // shapes loudly (gotcha #51) rather than silently producing an empty
3007
+ // object.
3008
+ //
3009
+ // We do NOT validate credential strings here — the connector module
3010
+ // re-validates on every sync pass via `validateGoogleDriveConfig`.
3011
+ // That keeps secret-store-driven values (which may legitimately be
3012
+ // empty until the operator runs setup) round-trippable through the
3013
+ // config layer without crashing the orchestrator at boot.
3014
+ connectors: (() => {
3015
+ if (
3016
+ cfg.connectors !== undefined &&
3017
+ (cfg.connectors === null ||
3018
+ typeof cfg.connectors !== "object" ||
3019
+ Array.isArray(cfg.connectors))
3020
+ ) {
3021
+ throw new Error(
3022
+ `connectors must be an object (got ${JSON.stringify(cfg.connectors)}). ` +
3023
+ `Use connectors: {} to opt out of every live connector.`,
3024
+ );
3025
+ }
3026
+ const rawConnectors =
3027
+ cfg.connectors && typeof cfg.connectors === "object" && !Array.isArray(cfg.connectors)
3028
+ ? (cfg.connectors as Record<string, unknown>)
3029
+ : {};
3030
+ // googleDrive (#683 PR 2/N)
3031
+ if (
3032
+ rawConnectors.googleDrive !== undefined &&
3033
+ (rawConnectors.googleDrive === null ||
3034
+ typeof rawConnectors.googleDrive !== "object" ||
3035
+ Array.isArray(rawConnectors.googleDrive))
3036
+ ) {
3037
+ throw new Error(
3038
+ `connectors.googleDrive must be an object (got ${JSON.stringify(rawConnectors.googleDrive)}).`,
3039
+ );
3040
+ }
3041
+ const rawDrive =
3042
+ rawConnectors.googleDrive &&
3043
+ typeof rawConnectors.googleDrive === "object" &&
3044
+ !Array.isArray(rawConnectors.googleDrive)
3045
+ ? (rawConnectors.googleDrive as Record<string, unknown>)
3046
+ : {};
3047
+ const driveEnabled = coerceBool(rawDrive.enabled) === true;
3048
+ const driveClientId =
3049
+ typeof rawDrive.clientId === "string" ? rawDrive.clientId : "";
3050
+ const driveClientSecret =
3051
+ typeof rawDrive[CLIENT_SECRET_FIELD] === "string"
3052
+ ? rawDrive[CLIENT_SECRET_FIELD]
3053
+ : "";
3054
+ const driveRefreshToken =
3055
+ typeof rawDrive[REFRESH_TOKEN_FIELD] === "string"
3056
+ ? rawDrive[REFRESH_TOKEN_FIELD]
3057
+ : "";
3058
+ const drivePollCoerced = coerceNumber(rawDrive.pollIntervalMs);
3059
+ let drivePollIntervalMs = 300_000;
3060
+ if (drivePollCoerced !== undefined) {
3061
+ if (
3062
+ !Number.isFinite(drivePollCoerced) ||
3063
+ !Number.isInteger(drivePollCoerced) ||
3064
+ drivePollCoerced < 1_000 ||
3065
+ drivePollCoerced > 86_400_000
3066
+ ) {
3067
+ throw new Error(
3068
+ `connectors.googleDrive.pollIntervalMs must be an integer in [1000, 86400000] ms (got ${JSON.stringify(rawDrive.pollIntervalMs)})`,
3069
+ );
3070
+ }
3071
+ drivePollIntervalMs = drivePollCoerced;
3072
+ }
3073
+ let driveFolderIds: string[] = [];
3074
+ if (rawDrive.folderIds !== undefined) {
3075
+ if (!Array.isArray(rawDrive.folderIds)) {
3076
+ throw new Error(
3077
+ `connectors.googleDrive.folderIds must be an array of strings (got ${typeof rawDrive.folderIds})`,
3078
+ );
3079
+ }
3080
+ const seen = new Set<string>();
3081
+ for (const value of rawDrive.folderIds) {
3082
+ if (typeof value !== "string") {
3083
+ throw new Error(
3084
+ `connectors.googleDrive.folderIds entries must be strings; found ${typeof value}`,
3085
+ );
3086
+ }
3087
+ const trimmed = value.trim();
3088
+ if (trimmed.length === 0) continue;
3089
+ if (seen.has(trimmed)) continue;
3090
+ seen.add(trimmed);
3091
+ driveFolderIds.push(trimmed);
3092
+ }
3093
+ }
3094
+ // notion (#683 PR 3/N)
3095
+ if (
3096
+ rawConnectors.notion !== undefined &&
3097
+ (rawConnectors.notion === null ||
3098
+ typeof rawConnectors.notion !== "object" ||
3099
+ Array.isArray(rawConnectors.notion))
3100
+ ) {
3101
+ throw new Error(
3102
+ `connectors.notion must be an object (got ${JSON.stringify(rawConnectors.notion)}).`,
3103
+ );
3104
+ }
3105
+ const rawNotion =
3106
+ rawConnectors.notion &&
3107
+ typeof rawConnectors.notion === "object" &&
3108
+ !Array.isArray(rawConnectors.notion)
3109
+ ? (rawConnectors.notion as Record<string, unknown>)
3110
+ : {};
3111
+ const notionEnabled = coerceBool(rawNotion.enabled) === true;
3112
+ const notionToken =
3113
+ typeof rawNotion.token === "string" ? rawNotion.token : "";
3114
+ const notionPollCoerced = coerceNumber(rawNotion.pollIntervalMs);
3115
+ let notionPollIntervalMs = 300_000;
3116
+ if (notionPollCoerced !== undefined) {
3117
+ if (
3118
+ !Number.isFinite(notionPollCoerced) ||
3119
+ !Number.isInteger(notionPollCoerced) ||
3120
+ notionPollCoerced < 1_000 ||
3121
+ notionPollCoerced > 86_400_000
3122
+ ) {
3123
+ throw new Error(
3124
+ `connectors.notion.pollIntervalMs must be an integer in [1000, 86400000] ms (got ${JSON.stringify(rawNotion.pollIntervalMs)})`,
3125
+ );
3126
+ }
3127
+ notionPollIntervalMs = notionPollCoerced;
3128
+ }
3129
+ let notionDatabaseIds: string[] = [];
3130
+ if (rawNotion.databaseIds !== undefined) {
3131
+ if (!Array.isArray(rawNotion.databaseIds)) {
3132
+ throw new Error(
3133
+ `connectors.notion.databaseIds must be an array of strings (got ${typeof rawNotion.databaseIds})`,
3134
+ );
3135
+ }
3136
+ const seen = new Set<string>();
3137
+ for (const value of rawNotion.databaseIds) {
3138
+ if (typeof value !== "string") {
3139
+ throw new Error(
3140
+ `connectors.notion.databaseIds entries must be strings; found ${typeof value}`,
3141
+ );
3142
+ }
3143
+ const trimmed = value.trim();
3144
+ if (trimmed.length === 0) continue;
3145
+ if (seen.has(trimmed)) continue;
3146
+ seen.add(trimmed);
3147
+ notionDatabaseIds.push(trimmed);
3148
+ }
3149
+ }
3150
+ // gmail (#683 PR 4/6)
3151
+ if (
3152
+ rawConnectors.gmail !== undefined &&
3153
+ (rawConnectors.gmail === null ||
3154
+ typeof rawConnectors.gmail !== "object" ||
3155
+ Array.isArray(rawConnectors.gmail))
3156
+ ) {
3157
+ throw new Error(
3158
+ `connectors.gmail must be an object (got ${JSON.stringify(rawConnectors.gmail)}).`,
3159
+ );
3160
+ }
3161
+ const rawGmail =
3162
+ rawConnectors.gmail &&
3163
+ typeof rawConnectors.gmail === "object" &&
3164
+ !Array.isArray(rawConnectors.gmail)
3165
+ ? (rawConnectors.gmail as Record<string, unknown>)
3166
+ : {};
3167
+ const gmailEnabled = coerceBool(rawGmail.enabled) === true;
3168
+ const gmailClientId =
3169
+ typeof rawGmail.clientId === "string" ? rawGmail.clientId : "";
3170
+ const gmailClientSecret =
3171
+ typeof rawGmail[CLIENT_SECRET_FIELD] === "string"
3172
+ ? rawGmail[CLIENT_SECRET_FIELD]
3173
+ : "";
3174
+ const gmailRefreshToken =
3175
+ typeof rawGmail[REFRESH_TOKEN_FIELD] === "string"
3176
+ ? rawGmail[REFRESH_TOKEN_FIELD]
3177
+ : "";
3178
+ const gmailUserId =
3179
+ typeof rawGmail.userId === "string" && rawGmail.userId.trim().length > 0
3180
+ ? rawGmail.userId.trim()
3181
+ : "me";
3182
+ const gmailQuery =
3183
+ typeof rawGmail.query === "string" ? rawGmail.query : "in:inbox";
3184
+ const gmailPollCoerced = coerceNumber(rawGmail.pollIntervalMs);
3185
+ let gmailPollIntervalMs = 300_000;
3186
+ if (rawGmail.pollIntervalMs !== undefined) {
3187
+ // CLAUDE.md gotcha #51: reject invalid values explicitly rather than
3188
+ // silently coercing to a default. Non-numeric strings, NaN, and
3189
+ // ±Infinity all cause coerceNumber to return undefined — treat that as
3190
+ // a configuration error rather than a quiet fallback.
3191
+ if (gmailPollCoerced === undefined) {
3192
+ throw new Error(
3193
+ `connectors.gmail.pollIntervalMs must be a finite number; got ${JSON.stringify(rawGmail.pollIntervalMs)}`,
3194
+ );
3195
+ }
3196
+ if (gmailPollCoerced <= 0) {
3197
+ throw new Error(
3198
+ `connectors.gmail.pollIntervalMs must be positive; got ${JSON.stringify(rawGmail.pollIntervalMs)}`,
3199
+ );
3200
+ }
3201
+ if (
3202
+ !Number.isInteger(gmailPollCoerced) ||
3203
+ gmailPollCoerced < 1_000 ||
3204
+ gmailPollCoerced > 86_400_000
3205
+ ) {
3206
+ throw new Error(
3207
+ `connectors.gmail.pollIntervalMs must be an integer in [1000, 86400000] ms (got ${JSON.stringify(rawGmail.pollIntervalMs)})`,
3208
+ );
3209
+ }
3210
+ gmailPollIntervalMs = gmailPollCoerced;
3211
+ }
3212
+
3213
+ // github (#683 PR 5/6)
3214
+ if (
3215
+ rawConnectors.github !== undefined &&
3216
+ (rawConnectors.github === null ||
3217
+ typeof rawConnectors.github !== "object" ||
3218
+ Array.isArray(rawConnectors.github))
3219
+ ) {
3220
+ throw new Error(
3221
+ `connectors.github must be an object (got ${JSON.stringify(rawConnectors.github)}).`,
3222
+ );
3223
+ }
3224
+ const rawGitHub =
3225
+ rawConnectors.github &&
3226
+ typeof rawConnectors.github === "object" &&
3227
+ !Array.isArray(rawConnectors.github)
3228
+ ? (rawConnectors.github as Record<string, unknown>)
3229
+ : {};
3230
+ const githubEnabled = coerceBool(rawGitHub.enabled) === true;
3231
+ const githubToken =
3232
+ typeof rawGitHub.token === "string" ? rawGitHub.token : "";
3233
+ const githubUserLogin =
3234
+ typeof rawGitHub.userLogin === "string" ? rawGitHub.userLogin : "";
3235
+ const githubPollCoerced = coerceNumber(rawGitHub.pollIntervalMs);
3236
+ let githubPollIntervalMs = 300_000;
3237
+ if (githubPollCoerced !== undefined) {
3238
+ if (
3239
+ !Number.isFinite(githubPollCoerced) ||
3240
+ !Number.isInteger(githubPollCoerced) ||
3241
+ githubPollCoerced < 1_000 ||
3242
+ githubPollCoerced > 86_400_000
3243
+ ) {
3244
+ throw new Error(
3245
+ `connectors.github.pollIntervalMs must be an integer in [1000, 86400000] ms (got ${JSON.stringify(rawGitHub.pollIntervalMs)})`,
3246
+ );
3247
+ }
3248
+ githubPollIntervalMs = githubPollCoerced;
3249
+ }
3250
+ let githubRepos: string[] = [];
3251
+ if (rawGitHub.repos !== undefined) {
3252
+ if (!Array.isArray(rawGitHub.repos)) {
3253
+ throw new Error(
3254
+ `connectors.github.repos must be an array of strings (got ${typeof rawGitHub.repos})`,
3255
+ );
3256
+ }
3257
+ const seen = new Set<string>();
3258
+ for (const value of rawGitHub.repos) {
3259
+ if (typeof value !== "string") {
3260
+ throw new Error(
3261
+ `connectors.github.repos entries must be strings; found ${typeof value}`,
3262
+ );
3263
+ }
3264
+ const trimmed = value.trim();
3265
+ if (trimmed.length === 0) continue;
3266
+ if (seen.has(trimmed)) continue;
3267
+ seen.add(trimmed);
3268
+ githubRepos.push(trimmed);
3269
+ }
3270
+ }
3271
+ const githubIncludeDiscussions = coerceBool(rawGitHub.includeDiscussions) === true;
3272
+ return {
3273
+ googleDrive: {
3274
+ enabled: driveEnabled,
3275
+ clientId: driveClientId,
3276
+ [CLIENT_SECRET_FIELD]: driveClientSecret,
3277
+ [REFRESH_TOKEN_FIELD]: driveRefreshToken,
3278
+ pollIntervalMs: drivePollIntervalMs,
3279
+ folderIds: driveFolderIds,
3280
+ },
3281
+ notion: {
3282
+ enabled: notionEnabled,
3283
+ token: notionToken,
3284
+ databaseIds: notionDatabaseIds,
3285
+ pollIntervalMs: notionPollIntervalMs,
3286
+ },
3287
+ gmail: {
3288
+ enabled: gmailEnabled,
3289
+ clientId: gmailClientId,
3290
+ [CLIENT_SECRET_FIELD]: gmailClientSecret,
3291
+ [REFRESH_TOKEN_FIELD]: gmailRefreshToken,
3292
+ userId: gmailUserId,
3293
+ query: gmailQuery,
3294
+ pollIntervalMs: gmailPollIntervalMs,
3295
+ },
3296
+ github: {
3297
+ enabled: githubEnabled,
3298
+ token: githubToken,
3299
+ userLogin: githubUserLogin,
3300
+ repos: githubRepos,
3301
+ pollIntervalMs: githubPollIntervalMs,
3302
+ includeDiscussions: githubIncludeDiscussions,
3303
+ },
3304
+ };
3305
+ })(),
3306
+
3307
+ // MECE Taxonomy (#366)
3308
+ // Coerce string booleans from CLI (e.g. --config taxonomyEnabled=true) — gotcha #36
3309
+ taxonomyEnabled: coerceBool(cfg.taxonomyEnabled) ?? false,
3310
+ taxonomyAutoGenResolver: coerceBool(cfg.taxonomyAutoGenResolver) ?? true,
3311
+
3312
+ // Codex CLI — native memory materialization (#378)
3313
+ codexMaterializeMemories: coerceBool(cfg.codexMaterializeMemories) ?? true,
3314
+ codexMaterializeNamespace:
3315
+ typeof cfg.codexMaterializeNamespace === "string" && cfg.codexMaterializeNamespace.trim().length > 0
3316
+ ? cfg.codexMaterializeNamespace.trim()
3317
+ : "auto",
3318
+ codexMaterializeMaxSummaryTokens: parseIntegerAtLeast(
3319
+ cfg.codexMaterializeMaxSummaryTokens,
3320
+ 4500,
3321
+ 0,
3322
+ "codexMaterializeMaxSummaryTokens",
3323
+ ),
3324
+ codexMaterializeRolloutRetentionDays: parseIntegerAtLeast(
3325
+ cfg.codexMaterializeRolloutRetentionDays,
3326
+ 30,
3327
+ 0,
3328
+ "codexMaterializeRolloutRetentionDays",
3329
+ ),
3330
+ codexMaterializeOnConsolidation: coerceBool(cfg.codexMaterializeOnConsolidation) ?? true,
3331
+ codexMaterializeOnSessionEnd: coerceBool(cfg.codexMaterializeOnSessionEnd) ?? true,
3332
+ // Codex CLI — marketplace integration (#418)
3333
+ codexMarketplaceEnabled: cfg.codexMarketplaceEnabled !== false, // default: true
3334
+
3335
+ // Page-level versioning (issue #371). Issue #678 PR 2/4:
3336
+ // dreams.phases.deepSleep.* WINS over legacy keys.
3337
+ versioningEnabled: dreamsDeepSleep.enabled && dreamsDeepSleep.versioningEnabled,
3338
+ versioningMaxPerPage: dreamsDeepSleep.versioningMaxPerPage,
3339
+ versioningSidecarDir:
3340
+ typeof cfg.versioningSidecarDir === "string" && cfg.versioningSidecarDir.trim().length > 0
3341
+ ? cfg.versioningSidecarDir.trim()
3342
+ : ".versions",
3343
+
3344
+ // Binary file lifecycle management (#367)
3345
+ binaryLifecycleEnabled: cfg.binaryLifecycleEnabled === true,
3346
+ binaryLifecycleGracePeriodDays:
3347
+ typeof cfg.binaryLifecycleGracePeriodDays === "number"
3348
+ ? Math.max(0, Math.floor(cfg.binaryLifecycleGracePeriodDays))
3349
+ : 7,
3350
+ binaryLifecycleBackendType: (() => {
3351
+ const valid = ["filesystem", "s3", "none"] as const;
3352
+ const raw = cfg.binaryLifecycleBackendType;
3353
+ if (typeof raw === "string" && (valid as readonly string[]).includes(raw)) {
3354
+ return raw as "filesystem" | "s3" | "none";
3355
+ }
3356
+ return "none" as const;
3357
+ })(),
3358
+ binaryLifecycleBackendPath:
3359
+ typeof cfg.binaryLifecycleBackendPath === "string"
3360
+ ? cfg.binaryLifecycleBackendPath.trim()
3361
+ : "",
3362
+
3363
+ // Codex citation parity (issue #379)
3364
+ citationsEnabled: cfg.citationsEnabled === true,
3365
+ citationsAutoDetect: cfg.citationsAutoDetect !== false,
3366
+
3367
+ // External enrichment pipeline (issue #365)
3368
+ enrichmentEnabled: cfg.enrichmentEnabled === true,
3369
+ enrichmentAutoOnCreate: cfg.enrichmentAutoOnCreate === true,
3370
+ enrichmentMaxCandidatesPerEntity:
3371
+ typeof cfg.enrichmentMaxCandidatesPerEntity === "number"
3372
+ ? Math.max(0, Math.floor(cfg.enrichmentMaxCandidatesPerEntity))
3373
+ : 20,
3374
+
3375
+ // Memory extensions discovery (#382)
3376
+ memoryExtensionsEnabled: cfg.memoryExtensionsEnabled !== false,
3377
+ memoryExtensionsRoot:
3378
+ typeof cfg.memoryExtensionsRoot === "string" && cfg.memoryExtensionsRoot.trim().length > 0
3379
+ ? cfg.memoryExtensionsRoot.trim()
3380
+ : "",
3381
+ };
3382
+ }
3383
+
3384
+ function parseBriefingConfig(raw: unknown): import("./types.js").BriefingConfig {
3385
+ const entry =
3386
+ raw && typeof raw === "object" && !Array.isArray(raw)
3387
+ ? (raw as Record<string, unknown>)
3388
+ : {};
3389
+ const defaultFormat =
3390
+ entry.defaultFormat === "json" || entry.defaultFormat === "markdown"
3391
+ ? (entry.defaultFormat as "markdown" | "json")
3392
+ : "markdown";
3393
+ const maxFollowupsRaw =
3394
+ typeof entry.maxFollowups === "number" && Number.isFinite(entry.maxFollowups)
3395
+ ? Math.floor(entry.maxFollowups)
3396
+ : 5;
3397
+ const maxFollowups = Math.max(0, Math.min(10, maxFollowupsRaw));
3398
+ return {
3399
+ enabled: entry.enabled !== false,
3400
+ defaultWindow:
3401
+ typeof entry.defaultWindow === "string" && entry.defaultWindow.trim().length > 0
3402
+ ? entry.defaultWindow.trim()
3403
+ : "yesterday",
3404
+ defaultFormat,
3405
+ maxFollowups,
3406
+ calendarSource:
3407
+ typeof entry.calendarSource === "string" && entry.calendarSource.trim().length > 0
3408
+ ? entry.calendarSource.trim()
3409
+ : null,
3410
+ saveByDefault: entry.saveByDefault === true,
3411
+ saveDir:
3412
+ typeof entry.saveDir === "string" && entry.saveDir.trim().length > 0
3413
+ ? entry.saveDir.trim()
3414
+ : null,
3415
+ llmFollowups: entry.llmFollowups !== false,
3416
+ };
3417
+ }
3418
+
3419
+ function clampNonNegativeNumber(value: unknown): number | undefined {
3420
+ if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
3421
+ return Math.max(0, Math.floor(value));
3422
+ }
3423
+
3424
+ /**
3425
+ * Cursor L on PR #736: numeric config keys must accept BOTH numbers
3426
+ * and CLI-string forms (`--config peerProfileReasonerMinInteractions=10`
3427
+ * arrives as the string `"10"` per CLAUDE.md Gotcha #28). Pre-fix
3428
+ * `typeof === "number"` rejected strings and silently fell back to the
3429
+ * default — operators thought their override applied.
3430
+ *
3431
+ * Behavior:
3432
+ * - `undefined` / `null` / missing → return `fallback`
3433
+ * - finite number ≥ 0 → return floor(value)
3434
+ * - string that parses to finite ≥ 0 → return floor(value)
3435
+ * - anything else (NaN, ±Infinity, negative, non-numeric string,
3436
+ * boolean, object) → throw an Error listing the offending key.
3437
+ *
3438
+ * Throwing matches Gotcha #51 — reject invalid input rather than
3439
+ * silently defaulting — and is consistent with how the surprise
3440
+ * knobs validate their inputs.
3441
+ */
3442
+ function coerceNonNegativeInt(
3443
+ value: unknown,
3444
+ fallback: number,
3445
+ keyName?: string,
3446
+ ): number {
3447
+ if (value === undefined || value === null) return fallback;
3448
+ const coerced = coerceNumber(value);
3449
+ if (coerced === undefined || coerced < 0) {
3450
+ const label = keyName ? ` (${keyName})` : "";
3451
+ throw new Error(
3452
+ `config value${label} must be a non-negative finite number; got ${JSON.stringify(value)}`,
3453
+ );
3454
+ }
3455
+ return Math.floor(coerced);
3456
+ }
3457
+
3458
+ // -----------------------------------------------------------------------------
3459
+ // Issue #563 buffer-surprise knobs — shared clamp helpers
3460
+ // -----------------------------------------------------------------------------
3461
+ //
3462
+ // Each helper takes a post-`coerceNumber` value (number | undefined) and a
3463
+ // fallback. This keeps the coercion layer (coerce CLI strings → number) and
3464
+ // the range layer (clamp to valid domain) cleanly separated — a common
3465
+ // source of post-merge fixes when these were inlined (CLAUDE.md rule #28).
3466
+
3467
+ function clampSurpriseThreshold(
3468
+ value: number | undefined,
3469
+ fallback: number,
3470
+ ): number {
3471
+ if (value === undefined) return fallback;
3472
+ // [0, 1] inclusive. 0 means "always flush on any score"; 1 means "never
3473
+ // flush on surprise alone" — both are valid-but-odd configurations.
3474
+ return Math.min(1, Math.max(0, value));
3475
+ }
3476
+
3477
+ function clampSurpriseK(
3478
+ value: number | undefined,
3479
+ fallback: number,
3480
+ ): number {
3481
+ if (value === undefined) return fallback;
3482
+ return Math.max(1, Math.floor(value));
3483
+ }
3484
+
3485
+ function clampSurpriseRecentMemoryCount(
3486
+ value: number | undefined,
3487
+ fallback: number,
3488
+ ): number {
3489
+ if (value === undefined) return fallback;
3490
+ return Math.max(0, Math.floor(value));
3491
+ }
3492
+
3493
+ function clampSurpriseProbeTimeoutMs(
3494
+ value: number | undefined,
3495
+ fallback: number,
3496
+ ): number {
3497
+ if (value === undefined) return fallback;
3498
+ // A zero or negative timeout would either skip the probe entirely or
3499
+ // fire instantly — both surprising behaviors that hide config errors.
3500
+ // Clamp to a minimum of 1ms and round to integer.
3501
+ return Math.max(1, Math.floor(value));
3502
+ }
3503
+
3504
+ function parseRecallSectionEntry(raw: unknown): RecallSectionConfig {
3505
+ const entry =
3506
+ raw && typeof raw === "object" && !Array.isArray(raw)
3507
+ ? (raw as Record<string, unknown>)
3508
+ : {};
3509
+ return {
3510
+ id: typeof entry.id === "string" ? entry.id.trim() : "",
3511
+ enabled: entry.enabled !== false,
3512
+ maxChars:
3513
+ entry.maxChars === null
3514
+ ? null
3515
+ : clampNonNegativeNumber(entry.maxChars),
3516
+ maxHints: clampNonNegativeNumber(entry.maxHints),
3517
+ maxSupportingFacts: clampNonNegativeNumber(entry.maxSupportingFacts),
3518
+ maxRelatedEntities: clampNonNegativeNumber(entry.maxRelatedEntities),
3519
+ consolidateTriggerLines: clampNonNegativeNumber(entry.consolidateTriggerLines),
3520
+ consolidateTargetLines: clampNonNegativeNumber(entry.consolidateTargetLines),
3521
+ maxEntities: clampNonNegativeNumber(entry.maxEntities),
3522
+ maxResults: clampNonNegativeNumber(entry.maxResults),
3523
+ recentTurns: clampNonNegativeNumber(entry.recentTurns),
3524
+ maxTurns: clampNonNegativeNumber(entry.maxTurns),
3525
+ maxTokens: clampNonNegativeNumber(entry.maxTokens),
3526
+ lookbackHours: clampNonNegativeNumber(entry.lookbackHours),
3527
+ maxCount: clampNonNegativeNumber(entry.maxCount),
3528
+ topK: clampNonNegativeNumber(entry.topK),
3529
+ timeoutMs: clampNonNegativeNumber(entry.timeoutMs),
3530
+ maxPatterns: clampNonNegativeNumber(entry.maxPatterns),
3531
+ maxRubrics: clampNonNegativeNumber(entry.maxRubrics),
3532
+ ...(entry.forceGeneric === undefined
3533
+ ? {}
3534
+ : { forceGeneric: coerceBool(entry.forceGeneric) === true }),
3535
+ };
3536
+ }
3537
+
3538
+ function buildDefaultRecallPipeline(cfg: Record<string, unknown>): RecallSectionConfig[] {
3539
+ return [
3540
+ {
3541
+ id: "shared-context",
3542
+ enabled: cfg.sharedContextEnabled === true,
3543
+ maxChars:
3544
+ typeof cfg.sharedContextMaxInjectChars === "number"
3545
+ ? Math.max(0, Math.floor(cfg.sharedContextMaxInjectChars))
3546
+ : 4000,
3547
+ },
3548
+ {
3549
+ id: "explicit-cue",
3550
+ enabled: coerceBool(cfg.explicitCueRecallEnabled) === true,
3551
+ maxChars:
3552
+ coerceNumber(cfg.explicitCueRecallMaxChars) !== undefined
3553
+ ? Math.max(0, Math.floor(coerceNumber(cfg.explicitCueRecallMaxChars)!))
3554
+ : 2400,
3555
+ maxResults:
3556
+ coerceNumber(cfg.explicitCueRecallMaxReferences) !== undefined
3557
+ ? Math.max(0, Math.floor(coerceNumber(cfg.explicitCueRecallMaxReferences)!))
3558
+ : 24,
3559
+ },
3560
+ {
3561
+ id: "targeted-facts",
3562
+ enabled: coerceBool(cfg.targetedFactRecallEnabled) === true,
3563
+ maxChars: parseIntegerAtLeast(cfg.targetedFactRecallMaxChars, 2400, 0, "targetedFactRecallMaxChars"),
3564
+ maxResults: parseIntegerAtLeast(cfg.targetedFactRecallMaxResults, 48, 0, "targetedFactRecallMaxResults"),
3565
+ maxTurns: parseIntegerAtLeast(cfg.targetedFactRecallScanWindowTurns, 8, 1, "targetedFactRecallScanWindowTurns"),
3566
+ maxTokens: parseIntegerAtLeast(cfg.targetedFactRecallScanWindowTokens, 12_000, 1, "targetedFactRecallScanWindowTokens"),
3567
+ },
3568
+ {
3569
+ id: "focused-list",
3570
+ enabled: coerceBool(cfg.focusedListRecallEnabled) === true,
3571
+ maxChars: parseIntegerAtLeast(cfg.focusedListRecallMaxChars, 2600, 0, "focusedListRecallMaxChars"),
3572
+ maxResults: parseIntegerAtLeast(cfg.focusedListRecallMaxResults, 40, 0, "focusedListRecallMaxResults"),
3573
+ maxTurns: parseIntegerAtLeast(cfg.focusedListRecallScanWindowTurns, 64, 1, "focusedListRecallScanWindowTurns"),
3574
+ maxTokens: parseIntegerAtLeast(cfg.focusedListRecallScanWindowTokens, 14_000, 1, "focusedListRecallScanWindowTokens"),
3575
+ },
3576
+ {
3577
+ id: "response-guidance",
3578
+ enabled: coerceBool(cfg.responseGuidanceRecallEnabled) === true,
3579
+ maxChars: parseIntegerAtLeast(cfg.responseGuidanceRecallMaxChars, 2400, 0, "responseGuidanceRecallMaxChars"),
3580
+ maxResults: parseIntegerAtLeast(cfg.responseGuidanceRecallMaxResults, 48, 0, "responseGuidanceRecallMaxResults"),
3581
+ maxTurns: parseIntegerAtLeast(cfg.responseGuidanceRecallScanWindowTurns, 64, 1, "responseGuidanceRecallScanWindowTurns"),
3582
+ maxTokens: parseIntegerAtLeast(cfg.responseGuidanceRecallScanWindowTokens, 16_000, 1, "responseGuidanceRecallScanWindowTokens"),
3583
+ },
3584
+ {
3585
+ id: "event-order",
3586
+ enabled: coerceBool(cfg.eventOrderRecallEnabled) === true,
3587
+ maxChars: parseIntegerAtLeast(cfg.eventOrderRecallMaxChars, 2400, 0, "eventOrderRecallMaxChars"),
3588
+ maxResults: parseIntegerAtLeast(cfg.eventOrderRecallMaxResults, 24, 0, "eventOrderRecallMaxResults"),
3589
+ maxTurns: parseIntegerAtLeast(cfg.eventOrderRecallScanWindowTurns, 12, 1, "eventOrderRecallScanWindowTurns"),
3590
+ maxTokens: parseIntegerAtLeast(cfg.eventOrderRecallScanWindowTokens, 24_000, 1, "eventOrderRecallScanWindowTokens"),
3591
+ },
3592
+ {
3593
+ id: "profile",
3594
+ enabled: true,
3595
+ consolidateTriggerLines: 100,
3596
+ consolidateTargetLines: 50,
3597
+ },
3598
+ {
3599
+ id: "identity-continuity",
3600
+ enabled: cfg.identityContinuityEnabled === true,
3601
+ },
3602
+ {
3603
+ id: "entity-retrieval",
3604
+ enabled: cfg.entityRetrievalEnabled !== false,
3605
+ maxChars:
3606
+ typeof cfg.entityRetrievalMaxChars === "number"
3607
+ ? Math.max(0, Math.floor(cfg.entityRetrievalMaxChars))
3608
+ : 2400,
3609
+ maxHints:
3610
+ typeof cfg.entityRetrievalMaxHints === "number"
3611
+ ? Math.max(0, Math.floor(cfg.entityRetrievalMaxHints))
3612
+ : 2,
3613
+ maxSupportingFacts:
3614
+ typeof cfg.entityRetrievalMaxSupportingFacts === "number"
3615
+ ? Math.max(0, Math.floor(cfg.entityRetrievalMaxSupportingFacts))
3616
+ : 6,
3617
+ maxRelatedEntities:
3618
+ typeof cfg.entityRetrievalMaxRelatedEntities === "number"
3619
+ ? Math.max(0, Math.floor(cfg.entityRetrievalMaxRelatedEntities))
3620
+ : 3,
3621
+ recentTurns:
3622
+ typeof cfg.entityRetrievalRecentTurns === "number"
3623
+ ? Math.max(0, Math.floor(cfg.entityRetrievalRecentTurns))
3624
+ : 6,
3625
+ },
3626
+ {
3627
+ id: "knowledge-index",
3628
+ enabled: cfg.knowledgeIndexEnabled !== false,
3629
+ maxChars:
3630
+ typeof cfg.knowledgeIndexMaxChars === "number"
3631
+ ? Math.max(0, Math.floor(cfg.knowledgeIndexMaxChars))
3632
+ : 4000,
3633
+ maxEntities:
3634
+ typeof cfg.knowledgeIndexMaxEntities === "number"
3635
+ ? Math.max(0, Math.floor(cfg.knowledgeIndexMaxEntities))
3636
+ : 40,
3637
+ },
3638
+ { id: "verbatim-artifacts", enabled: cfg.verbatimArtifactsEnabled === true },
3639
+ {
3640
+ id: "procedure-recall",
3641
+ // Default-on since issue #567 PR 4/5: the master `procedural.enabled`
3642
+ // gate now defaults to `true` when the key is omitted, so the recall
3643
+ // pipeline must stay in sync. Explicit `false` (or any coerceBool
3644
+ // falsy variant) still disables recall injection.
3645
+ //
3646
+ // CLAUDE.md rule 48 (least-privileged defaults) + Cursor review on #609:
3647
+ // never fail open on unrecognized values. Only `coerced === true` or
3648
+ // the "key omitted" path enables the section. `parseConfig` throws
3649
+ // on invalid values upstream, so this branch only ever sees boolean
3650
+ // results — `coerced === undefined` should never happen in practice,
3651
+ // but defense-in-depth keeps the section disabled if it ever does.
3652
+ enabled: (() => {
3653
+ const proceduralRaw =
3654
+ typeof cfg.procedural === "object" &&
3655
+ cfg.procedural !== null &&
3656
+ !Array.isArray(cfg.procedural)
3657
+ ? (cfg.procedural as { enabled?: unknown })
3658
+ : undefined;
3659
+ if (proceduralRaw === undefined) return true;
3660
+ const rawEnabled = proceduralRaw.enabled;
3661
+ if (rawEnabled === undefined) return true;
3662
+ return coerceBool(rawEnabled) === true;
3663
+ })(),
3664
+ maxChars: 2400,
3665
+ },
3666
+ { id: "memory-boxes", enabled: cfg.memoryBoxesEnabled === true },
3667
+ { id: "temporal-memory-tree", enabled: cfg.temporalMemoryTreeEnabled === true },
3668
+ { id: "lcm-compressed-history", enabled: cfg.lcmEnabled === true },
3669
+ {
3670
+ id: "objective-state",
3671
+ enabled: cfg.objectiveStateRecallEnabled === true,
3672
+ maxResults: 4,
3673
+ maxChars: 1800,
3674
+ },
3675
+ {
3676
+ id: "causal-trajectories",
3677
+ enabled: cfg.causalTrajectoryRecallEnabled === true,
3678
+ maxResults: 3,
3679
+ maxChars: 2200,
3680
+ },
3681
+ {
3682
+ id: "trust-zones",
3683
+ enabled: cfg.trustZoneRecallEnabled === true,
3684
+ maxResults: 3,
3685
+ maxChars: 1800,
3686
+ },
3687
+ {
3688
+ id: "harmonic-retrieval",
3689
+ enabled: cfg.harmonicRetrievalEnabled === true,
3690
+ maxResults: 3,
3691
+ maxChars: 2200,
3692
+ },
3693
+ {
3694
+ id: "verified-episodes",
3695
+ enabled: cfg.verifiedRecallEnabled === true,
3696
+ maxResults: 3,
3697
+ maxChars: 1800,
3698
+ },
3699
+ {
3700
+ id: "verified-rules",
3701
+ enabled: cfg.semanticRuleVerificationEnabled === true,
3702
+ maxResults: 3,
3703
+ maxChars: 1800,
3704
+ },
3705
+ {
3706
+ id: "work-products",
3707
+ enabled: cfg.workProductRecallEnabled === true,
3708
+ maxResults: 3,
3709
+ maxChars: 1800,
3710
+ },
3711
+ {
3712
+ id: "memories",
3713
+ enabled: true,
3714
+ maxResults:
3715
+ typeof cfg.qmdMaxResults === "number"
3716
+ ? Math.max(0, Math.floor(cfg.qmdMaxResults))
3717
+ : 8,
3718
+ },
3719
+ {
3720
+ id: "compression-guidelines",
3721
+ enabled: cfg.compressionGuidelineLearningEnabled === true,
3722
+ },
3723
+ {
3724
+ id: "native-knowledge",
3725
+ enabled: cfg.nativeKnowledge && typeof cfg.nativeKnowledge === "object"
3726
+ ? (cfg.nativeKnowledge as Record<string, unknown>).enabled === true
3727
+ : false,
3728
+ maxResults:
3729
+ cfg.nativeKnowledge && typeof cfg.nativeKnowledge === "object" &&
3730
+ typeof (cfg.nativeKnowledge as Record<string, unknown>).maxResults === "number"
3731
+ ? Math.max(0, Math.floor((cfg.nativeKnowledge as Record<string, unknown>).maxResults as number))
3732
+ : 4,
3733
+ maxChars:
3734
+ cfg.nativeKnowledge && typeof cfg.nativeKnowledge === "object" &&
3735
+ typeof (cfg.nativeKnowledge as Record<string, unknown>).maxChars === "number"
3736
+ ? Math.max(0, Math.floor((cfg.nativeKnowledge as Record<string, unknown>).maxChars as number))
3737
+ : 2400,
3738
+ },
3739
+ {
3740
+ id: "transcript",
3741
+ enabled: cfg.transcriptEnabled !== false,
3742
+ maxTurns:
3743
+ typeof cfg.maxTranscriptTurns === "number"
3744
+ ? Math.max(0, Math.floor(cfg.maxTranscriptTurns))
3745
+ : 50,
3746
+ maxTokens:
3747
+ typeof cfg.maxTranscriptTokens === "number"
3748
+ ? Math.max(0, Math.floor(cfg.maxTranscriptTokens))
3749
+ : 1000,
3750
+ lookbackHours:
3751
+ typeof cfg.transcriptRecallHours === "number"
3752
+ ? Math.max(0, Math.floor(cfg.transcriptRecallHours))
3753
+ : 12,
3754
+ },
3755
+ {
3756
+ id: "summaries",
3757
+ enabled: cfg.hourlySummariesEnabled !== false,
3758
+ maxCount:
3759
+ typeof cfg.maxSummaryCount === "number"
3760
+ ? Math.max(0, Math.floor(cfg.maxSummaryCount))
3761
+ : 6,
3762
+ lookbackHours:
3763
+ typeof cfg.summaryRecallHours === "number"
3764
+ ? Math.max(0, Math.floor(cfg.summaryRecallHours))
3765
+ : 24,
3766
+ },
3767
+ {
3768
+ id: "conversation-recall",
3769
+ enabled: cfg.conversationIndexEnabled === true,
3770
+ topK:
3771
+ typeof cfg.conversationRecallTopK === "number"
3772
+ ? Math.max(0, Math.floor(cfg.conversationRecallTopK))
3773
+ : 3,
3774
+ maxChars:
3775
+ typeof cfg.conversationRecallMaxChars === "number"
3776
+ ? Math.max(0, Math.floor(cfg.conversationRecallMaxChars))
3777
+ : 2500,
3778
+ timeoutMs:
3779
+ typeof cfg.conversationRecallTimeoutMs === "number"
3780
+ ? Math.max(0, Math.floor(cfg.conversationRecallTimeoutMs))
3781
+ : 800,
3782
+ },
3783
+ {
3784
+ id: "compounding",
3785
+ enabled: cfg.compoundingEnabled === true && cfg.compoundingInjectEnabled !== false,
3786
+ maxPatterns: 40,
3787
+ maxRubrics: 4,
3788
+ },
3789
+ { id: "questions", enabled: cfg.injectQuestions === true },
3790
+ ];
3791
+ }
3792
+
3793
+ function buildRecallPipelineConfig(cfg: Record<string, unknown>): RecallPipelineConfig {
3794
+ const maxMemoryTokens =
3795
+ typeof cfg.maxMemoryTokens === "number"
3796
+ ? Math.max(0, Math.floor(cfg.maxMemoryTokens))
3797
+ : 2000;
3798
+ const recallBudgetCharsRaw = clampNonNegativeNumber(cfg.recallBudgetChars);
3799
+ const recallBudgetChars = recallBudgetCharsRaw ?? maxMemoryTokens * 4;
3800
+
3801
+ const rawPipeline = cfg.recallPipeline;
3802
+ const pipeline = Array.isArray(rawPipeline)
3803
+ ? rawPipeline.map(parseRecallSectionEntry).filter((entry) => entry.id.length > 0)
3804
+ : buildDefaultRecallPipeline(cfg);
3805
+
3806
+ return { recallBudgetChars, pipeline };
3807
+ }