@remnic/core 10.0.38 → 10.0.40

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 (444) hide show
  1. package/dist/access-admin-ops-surface.js +73 -73
  2. package/dist/access-audit.js +2 -2
  3. package/dist/access-authorization-probe.js +74 -74
  4. package/dist/access-boundary.js +73 -73
  5. package/dist/access-citation-namespace.js +2 -2
  6. package/dist/access-cli.js +162 -161
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-extraction-force-flush.js +73 -73
  9. package/dist/access-http-briefing.js +74 -74
  10. package/dist/access-http-query.js +75 -75
  11. package/dist/access-http.js +98 -98
  12. package/dist/access-identity-continuity-surface.js +73 -73
  13. package/dist/access-lcm-surface.js +73 -73
  14. package/dist/access-mcp.js +87 -87
  15. package/dist/access-namespace-preflight.js +73 -73
  16. package/dist/access-observe-write-category.js +15 -15
  17. package/dist/access-observe-write-surface.js +73 -73
  18. package/dist/access-offline-manifest.js +12 -12
  19. package/dist/access-operations-batch.js +77 -77
  20. package/dist/access-operations.js +84 -84
  21. package/dist/access-recall-concurrency.js +73 -73
  22. package/dist/access-recall-diagnostics.js +2 -2
  23. package/dist/access-recall-response.js +73 -73
  24. package/dist/access-recall-surface.js +73 -73
  25. package/dist/access-schema.js +10 -10
  26. package/dist/access-service.d.ts +2 -2
  27. package/dist/access-service.d.ts.map +1 -1
  28. package/dist/access-service.js +73 -73
  29. package/dist/access-surface-catalog.js +75 -75
  30. package/dist/active-recall.js +29 -29
  31. package/dist/adapters/index.js +4 -4
  32. package/dist/adapters/registry.js +2 -2
  33. package/dist/{auto-sync-432USQDH.js → auto-sync-BNCBMU6H.js} +7 -7
  34. package/dist/briefing.js +19 -19
  35. package/dist/buffer-types.d.ts +59 -0
  36. package/dist/buffer-types.d.ts.map +1 -0
  37. package/dist/buffer-types.js +1 -0
  38. package/dist/buffer.d.ts +14 -22
  39. package/dist/buffer.d.ts.map +1 -1
  40. package/dist/buffer.js +11 -11
  41. package/dist/calibration.js +2 -2
  42. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-GWVG7LGC.js} +2 -2
  43. package/dist/causal-behavior.js +2 -2
  44. package/dist/causal-chain.js +2 -2
  45. package/dist/causal-consolidation.js +22 -22
  46. package/dist/causal-retrieval.js +2 -2
  47. package/dist/causal-trajectory-graph.js +2 -2
  48. package/dist/causal-trajectory.js +1 -1
  49. package/dist/{chunk-MKE5DP3S.js → chunk-23ZMQONL.js} +2 -2
  50. package/dist/{chunk-3K5IEJTK.js → chunk-2AV2U2SZ.js} +3 -3
  51. package/dist/{chunk-HJ3UTGIW.js → chunk-2JNBPOZ2.js} +3 -3
  52. package/dist/{chunk-5ANUG4JZ.js → chunk-2T6KEOUL.js} +2 -2
  53. package/dist/{chunk-EEOYF3IB.js → chunk-3RJDYZJE.js} +2 -2
  54. package/dist/{chunk-P5CUEB2Q.js → chunk-4QLKVZH6.js} +8 -8
  55. package/dist/{chunk-XTBEXZFN.js → chunk-5SF7JOJB.js} +4 -4
  56. package/dist/{chunk-53BLBDV6.js → chunk-5TRU6QQC.js} +2 -2
  57. package/dist/{chunk-DKHX2FHI.js → chunk-64WHWIPL.js} +2 -2
  58. package/dist/{chunk-TVIGN3XZ.js → chunk-6FRFVIKC.js} +10 -10
  59. package/dist/{chunk-JIA435P3.js → chunk-6ITNEPJO.js} +2 -2
  60. package/dist/{chunk-WEK3OUGO.js → chunk-6X3J4NLZ.js} +10 -5
  61. package/dist/chunk-6X3J4NLZ.js.map +1 -0
  62. package/dist/{chunk-FGEY2AGV.js → chunk-7BCYAJKP.js} +2 -2
  63. package/dist/{chunk-PSQ2P2T3.js → chunk-7XZ7TOLQ.js} +3 -3
  64. package/dist/{chunk-6JEEY42N.js → chunk-AER3HD3B.js} +2 -2
  65. package/dist/{chunk-POGPI5GR.js → chunk-AY6LO7GD.js} +2 -2
  66. package/dist/{chunk-HXFIILGF.js → chunk-BFPF5N3H.js} +4 -4
  67. package/dist/{chunk-WCBLPKG3.js → chunk-BIYVUPC6.js} +172 -143
  68. package/dist/chunk-BIYVUPC6.js.map +1 -0
  69. package/dist/{chunk-KQAFEZQX.js → chunk-BUKK5SWA.js} +2 -2
  70. package/dist/{chunk-LHBOFROT.js → chunk-CHNZPOWX.js} +4 -3
  71. package/dist/{chunk-LHBOFROT.js.map → chunk-CHNZPOWX.js.map} +1 -1
  72. package/dist/{chunk-6F5BGIAF.js → chunk-CNAL35D5.js} +3 -3
  73. package/dist/{chunk-YC2C67F7.js → chunk-CRXGQRNA.js} +3 -3
  74. package/dist/{chunk-WRDFYSUC.js → chunk-CZVNOTLR.js} +2 -2
  75. package/dist/{chunk-VPO53QMG.js → chunk-D6DOLQYK.js} +2 -2
  76. package/dist/{chunk-RGKXF4W3.js → chunk-E5IEI2MR.js} +2 -2
  77. package/dist/{chunk-Z2T5BPOZ.js → chunk-EIYJK7OD.js} +2 -2
  78. package/dist/{chunk-FRAL3EU4.js → chunk-EKPRLTAE.js} +2 -2
  79. package/dist/{chunk-MCPSMWUC.js → chunk-EPEDMEHO.js} +10 -10
  80. package/dist/{chunk-MCPSMWUC.js.map → chunk-EPEDMEHO.js.map} +1 -1
  81. package/dist/{chunk-TYGLV6ZG.js → chunk-EYGOM6AI.js} +2 -2
  82. package/dist/{chunk-TYGLV6ZG.js.map → chunk-EYGOM6AI.js.map} +1 -1
  83. package/dist/{chunk-I75BWXRZ.js → chunk-F2RDRE7M.js} +2 -2
  84. package/dist/{chunk-2AKIVSHP.js → chunk-FAKRTS3Y.js} +2 -2
  85. package/dist/{chunk-MZWHIAOL.js → chunk-FC3ZFEXF.js} +2 -2
  86. package/dist/{chunk-3PLAL2KP.js → chunk-FJ264QBP.js} +17 -9
  87. package/dist/chunk-FJ264QBP.js.map +1 -0
  88. package/dist/{chunk-7VFNGQ4V.js → chunk-FLDXEJFX.js} +2 -2
  89. package/dist/{chunk-TYLNWE4D.js → chunk-FNEYSTB6.js} +2 -2
  90. package/dist/{chunk-XHX67LX6.js → chunk-FPHU6KVI.js} +2 -2
  91. package/dist/{chunk-JH4FCVWJ.js → chunk-G6OFEGUR.js} +2 -2
  92. package/dist/{chunk-WCIDCJOQ.js → chunk-GAUS4XGN.js} +3 -3
  93. package/dist/{chunk-SZPPCEYT.js → chunk-GDOI4U4D.js} +2 -2
  94. package/dist/{chunk-63YPKHAU.js → chunk-GLB6VV3J.js} +2 -2
  95. package/dist/{chunk-3XVBGGF3.js → chunk-GRN4BVPM.js} +2 -2
  96. package/dist/{chunk-ZAP7BKTZ.js → chunk-GZMHBRIR.js} +6 -6
  97. package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
  98. package/dist/{chunk-TXP7Q7T5.js → chunk-HN35OH2C.js} +2 -2
  99. package/dist/{chunk-SWZOAQUM.js → chunk-HSNV4CSW.js} +57 -57
  100. package/dist/{chunk-GFYFKXWZ.js → chunk-HUDPXTXW.js} +2 -2
  101. package/dist/{chunk-C2KOW2UQ.js → chunk-I7CWAJHX.js} +2 -2
  102. package/dist/{chunk-L2JQSG5M.js → chunk-I7TR2OCU.js} +2 -2
  103. package/dist/{chunk-5DCS4C4S.js → chunk-ICTJOOWZ.js} +4 -4
  104. package/dist/{chunk-NH6GZEQN.js → chunk-IF6EG22P.js} +10 -10
  105. package/dist/chunk-IF6EG22P.js.map +1 -0
  106. package/dist/{chunk-STM4T335.js → chunk-IJH6METJ.js} +2 -2
  107. package/dist/{chunk-6SYS5YX3.js → chunk-IJJWM2ZA.js} +2 -2
  108. package/dist/{chunk-3AODCBKX.js → chunk-IWDFHVPJ.js} +6 -6
  109. package/dist/{chunk-XHVO3CGX.js → chunk-JGS55BAS.js} +53 -2
  110. package/dist/chunk-JGS55BAS.js.map +1 -0
  111. package/dist/{chunk-L7PGPH4E.js → chunk-JJSMSS45.js} +2 -2
  112. package/dist/{chunk-74AY3S7U.js → chunk-JNLYHJG7.js} +4 -4
  113. package/dist/{chunk-OLH4XCLU.js → chunk-JXQTECMU.js} +2 -2
  114. package/dist/{chunk-6QOL4G76.js → chunk-KQXRVELF.js} +2 -2
  115. package/dist/{chunk-PJRKLB4Z.js → chunk-L5PM7B3I.js} +16 -2
  116. package/dist/{chunk-PJRKLB4Z.js.map → chunk-L5PM7B3I.js.map} +1 -1
  117. package/dist/{chunk-VGRM43X6.js → chunk-LB6HIXQC.js} +2 -2
  118. package/dist/{chunk-N7DVBHU3.js → chunk-LR7RW4T6.js} +3 -3
  119. package/dist/{chunk-AYBAC2N4.js → chunk-LWIU5BMA.js} +3 -3
  120. package/dist/{chunk-FZVZYU5C.js → chunk-LXN737FX.js} +2 -2
  121. package/dist/{chunk-7ZRRJ6RG.js → chunk-MGYZENUY.js} +2 -2
  122. package/dist/{chunk-YQ6SLFNZ.js → chunk-MPJDQC4G.js} +3 -3
  123. package/dist/{chunk-7HXZOWTR.js → chunk-N3PUGD7E.js} +2 -2
  124. package/dist/{chunk-EGNONTVT.js → chunk-NBEM5QEH.js} +6 -6
  125. package/dist/{chunk-QO5KDQGG.js → chunk-NFJWB27H.js} +20 -18
  126. package/dist/chunk-NFJWB27H.js.map +1 -0
  127. package/dist/{chunk-JF3MEIHO.js → chunk-O67H7OXJ.js} +4 -4
  128. package/dist/{chunk-OLHN4ZNT.js → chunk-ONIVKGNC.js} +3 -3
  129. package/dist/{chunk-2NLLXCJG.js → chunk-OWHERGF2.js} +2 -2
  130. package/dist/{chunk-I73ZSORJ.js → chunk-OXSTKCYK.js} +4 -4
  131. package/dist/{chunk-UFM3PJDT.js → chunk-PARM6OYL.js} +1 -1
  132. package/dist/chunk-PARM6OYL.js.map +1 -0
  133. package/dist/{chunk-VSSQIBVU.js → chunk-PJXSBXEC.js} +2 -2
  134. package/dist/{chunk-4B37KNMP.js → chunk-PW63SXFM.js} +2 -2
  135. package/dist/{chunk-TPKB7T6Z.js → chunk-Q6UZREJK.js} +2 -2
  136. package/dist/{chunk-X247CYL6.js → chunk-Q77SLBWT.js} +4 -4
  137. package/dist/{chunk-TAEJ2MRR.js → chunk-QFD7OANX.js} +62 -62
  138. package/dist/chunk-QFD7OANX.js.map +1 -0
  139. package/dist/{chunk-5K23NQRI.js → chunk-QGNZ5QPV.js} +2 -2
  140. package/dist/{chunk-UJDTXWMT.js → chunk-QIMGULGN.js} +2 -2
  141. package/dist/{chunk-E24RUQEN.js → chunk-QNIYL5SK.js} +2 -2
  142. package/dist/{chunk-KOY25HUB.js → chunk-QYAXMXOB.js} +2 -2
  143. package/dist/{chunk-J7G656MY.js → chunk-R42VUN5V.js} +4 -4
  144. package/dist/{chunk-LOMSBYNS.js → chunk-RPHCSMTQ.js} +309 -106
  145. package/dist/chunk-RPHCSMTQ.js.map +1 -0
  146. package/dist/{chunk-AYXRK264.js → chunk-RROFIAYI.js} +9 -9
  147. package/dist/{chunk-YYGZ4EIY.js → chunk-RUAIO5UA.js} +2 -2
  148. package/dist/{chunk-E5PVMB3I.js → chunk-SDXBR5QO.js} +2 -2
  149. package/dist/{chunk-OKKBOTRA.js → chunk-SLVR3U4E.js} +2 -2
  150. package/dist/{chunk-CUWOSRCS.js → chunk-SUKPM6DV.js} +2 -2
  151. package/dist/{chunk-TDCR6PQL.js → chunk-TWRPWJJT.js} +2 -2
  152. package/dist/{chunk-6DTIYAAN.js → chunk-UGV256H3.js} +2 -2
  153. package/dist/{chunk-DG4H56SK.js → chunk-UIK3U36O.js} +2 -2
  154. package/dist/{chunk-KOAVX2IT.js → chunk-UKIIAEJF.js} +2 -2
  155. package/dist/{chunk-XLEP7SS3.js → chunk-UMSH4I3E.js} +2 -2
  156. package/dist/{chunk-X7Y7WX73.js → chunk-UVYI6VIX.js} +1 -1
  157. package/dist/chunk-VA3GNAWQ.js +58 -0
  158. package/dist/chunk-VA3GNAWQ.js.map +1 -0
  159. package/dist/{chunk-4P4VLPGB.js → chunk-VECRSYQG.js} +1 -1
  160. package/dist/{chunk-GGI6FLO6.js → chunk-VHBGJAYC.js} +2 -2
  161. package/dist/{chunk-NMKETOAX.js → chunk-VSHWPOUB.js} +2 -2
  162. package/dist/{chunk-VC3X7XP3.js → chunk-VTZSIG6O.js} +2 -2
  163. package/dist/{chunk-UBN5AOLT.js → chunk-W5EO4USJ.js} +2 -2
  164. package/dist/{chunk-Y7427SL7.js → chunk-WCGJK4NQ.js} +4 -4
  165. package/dist/{chunk-4IZ6AC2U.js → chunk-WJMAWDAU.js} +2 -2
  166. package/dist/{chunk-JH2ALMPR.js → chunk-WR5POHQ3.js} +2 -2
  167. package/dist/{chunk-EHIKY2IA.js → chunk-WU5WTCH5.js} +2 -2
  168. package/dist/{chunk-QKXPOLQD.js → chunk-WUBGMV3G.js} +6 -6
  169. package/dist/{chunk-FKTMWDN2.js → chunk-WUL6LDJ4.js} +2 -2
  170. package/dist/{chunk-5UPOKIFP.js → chunk-XBHUMPIQ.js} +12 -12
  171. package/dist/{chunk-Y5ZHZKZD.js → chunk-XJ77IOAD.js} +2 -2
  172. package/dist/{chunk-U7HJBODU.js → chunk-XKTHJHSQ.js} +5 -5
  173. package/dist/{chunk-FQFMLOAC.js → chunk-XLUMO5H2.js} +20 -20
  174. package/dist/chunk-XLUMO5H2.js.map +1 -0
  175. package/dist/{chunk-JFI3ATK4.js → chunk-XMYWYJM2.js} +2 -2
  176. package/dist/{chunk-64S6X4IU.js → chunk-XTN54NML.js} +4 -4
  177. package/dist/{chunk-CVMAOPAH.js → chunk-XXBCUIYQ.js} +10 -10
  178. package/dist/{chunk-QOUKZ2N3.js → chunk-XXW3P3RX.js} +2 -2
  179. package/dist/{chunk-RWRHGE75.js → chunk-Y473XFTD.js} +2 -2
  180. package/dist/{chunk-VGFNTY3Y.js → chunk-YFXRLRKP.js} +2 -2
  181. package/dist/{chunk-DAOMWGO2.js → chunk-ZX5N5D6M.js} +4 -4
  182. package/dist/cli.js +121 -121
  183. package/dist/coding/export-okf-codegraph.js +20 -20
  184. package/dist/coding/pre-action-gate.js +2 -2
  185. package/dist/compounding/engine.js +21 -21
  186. package/dist/config.d.ts.map +1 -1
  187. package/dist/config.js +29 -29
  188. package/dist/connectors/codex-materialize-runner.js +19 -19
  189. package/dist/connectors/index.js +19 -19
  190. package/dist/contradiction/index.js +5 -5
  191. package/dist/convergence-refresh.js +21 -21
  192. package/dist/corpus-watermark.js +21 -21
  193. package/dist/deep-recall-http-glue.js +11 -11
  194. package/dist/deep-recall.js +2 -2
  195. package/dist/entity-retrieval.js +20 -20
  196. package/dist/event-order-recall.js +2 -2
  197. package/dist/explicit-capture.js +6 -6
  198. package/dist/external-wiki-access.js +74 -74
  199. package/dist/extraction-error-classification.d.ts +15 -0
  200. package/dist/extraction-error-classification.d.ts.map +1 -1
  201. package/dist/extraction-error-classification.js +7 -3
  202. package/dist/extraction-window-drain.d.ts +48 -0
  203. package/dist/extraction-window-drain.d.ts.map +1 -0
  204. package/dist/extraction-window-drain.js +14 -0
  205. package/dist/extraction.js +28 -28
  206. package/dist/{graph-edge-decay-2BZTY2IM.js → graph-edge-decay-3HVLMT6U.js} +5 -5
  207. package/dist/graph-edge-decay-3HVLMT6U.js.map +1 -0
  208. package/dist/graph-jsonl.js +3 -3
  209. package/dist/graph-snapshot.js +3 -3
  210. package/dist/graph.js +4 -4
  211. package/dist/harmonic-retrieval.js +20 -20
  212. package/dist/harmonic-source-lookup.js +2 -2
  213. package/dist/index.js +209 -208
  214. package/dist/lcm/engine.js +2 -2
  215. package/dist/lcm/index.js +7 -7
  216. package/dist/local-llm.js +2 -2
  217. package/dist/location/index.js +22 -22
  218. package/dist/maintenance/memory-governance.js +19 -19
  219. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +19 -19
  220. package/dist/maintenance/rebuild-memory-projection-runner.js +21 -21
  221. package/dist/maintenance/rebuild-memory-projection.js +20 -20
  222. package/dist/memory-browse-operations.js +74 -74
  223. package/dist/memory-browse.js +6 -6
  224. package/dist/memory-subject.js +3 -3
  225. package/dist/namespaces/migrate.js +20 -20
  226. package/dist/namespaces/search.d.ts +1 -0
  227. package/dist/namespaces/search.d.ts.map +1 -1
  228. package/dist/namespaces/search.js +11 -9
  229. package/dist/namespaces/storage.js +19 -19
  230. package/dist/offline-sync-changeset-generations.js +3 -3
  231. package/dist/offline-sync-embedding-generation.js +2 -2
  232. package/dist/offline-sync-file-io.js +2 -2
  233. package/dist/offline-sync-impression-drain.js +3 -3
  234. package/dist/offline-sync-snapshot-stream.js +8 -8
  235. package/dist/offline-sync-upload-staging.js +3 -3
  236. package/dist/offline-sync.js +9 -9
  237. package/dist/operator-doctor-corpus.js +22 -22
  238. package/dist/operator-toolkit.js +40 -40
  239. package/dist/orchestration/extraction-drain.d.ts +90 -0
  240. package/dist/orchestration/extraction-drain.d.ts.map +1 -0
  241. package/dist/orchestration/extraction-run-helpers.d.ts +3 -0
  242. package/dist/orchestration/extraction-run-helpers.d.ts.map +1 -1
  243. package/dist/orchestration/extraction-run.d.ts +4 -1
  244. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  245. package/dist/orchestration/maintenance.js +33 -33
  246. package/dist/orchestration/qmd-result-resolver.d.ts +5 -0
  247. package/dist/orchestration/qmd-result-resolver.d.ts.map +1 -1
  248. package/dist/orchestration/recall-search-pipeline.d.ts.map +1 -1
  249. package/dist/orchestration/turn-ingestion.d.ts +3 -0
  250. package/dist/orchestration/turn-ingestion.d.ts.map +1 -1
  251. package/dist/orchestrator.d.ts +8 -0
  252. package/dist/orchestrator.d.ts.map +1 -1
  253. package/dist/orchestrator.js +162 -161
  254. package/dist/qmd.js +5 -5
  255. package/dist/recall-explain-renderer.js +3 -3
  256. package/dist/recall-navigate-access.js +2 -2
  257. package/dist/recall-navigate-disclosure.js +2 -2
  258. package/dist/recall-navigate.js +2 -2
  259. package/dist/recall-navigation-operations.js +74 -74
  260. package/dist/recall-navigation.js +73 -73
  261. package/dist/recall-state-view-admission.js +6 -6
  262. package/dist/recall-why-service.js +6 -6
  263. package/dist/recall-xray-cli.js +4 -4
  264. package/dist/recall-xray-renderer.js +3 -3
  265. package/dist/recall-xray.js +2 -2
  266. package/dist/reconcile/manifest.js +3 -3
  267. package/dist/{resolution-4ZEQPXUZ.js → resolution-NNYG37YB.js} +6 -6
  268. package/dist/resolution-NNYG37YB.js.map +1 -0
  269. package/dist/resume-bundles.js +30 -30
  270. package/dist/retrieval-agents.js +2 -2
  271. package/dist/salvage-envelope.js +5 -5
  272. package/dist/search/factory.js +6 -6
  273. package/dist/search/index.js +10 -10
  274. package/dist/secure-store/index.js +2 -2
  275. package/dist/semantic-consolidation.js +20 -20
  276. package/dist/semantic-rule-promotion.js +19 -19
  277. package/dist/semantic-rule-verifier.js +19 -19
  278. package/dist/source-agent-qualifier.js +23 -23
  279. package/dist/standup/build.js +19 -19
  280. package/dist/storage/dropped-extraction-chunk.d.ts +8 -0
  281. package/dist/storage/dropped-extraction-chunk.d.ts.map +1 -0
  282. package/dist/storage.d.ts +1 -0
  283. package/dist/storage.d.ts.map +1 -1
  284. package/dist/storage.js +18 -18
  285. package/dist/subject-config.js +4 -4
  286. package/dist/summarizer.js +3 -3
  287. package/dist/support-passport/index.js +76 -76
  288. package/dist/transfer/backup.js +2 -2
  289. package/dist/transfer/capsule-export.js +2 -2
  290. package/dist/transfer/capsule-import.js +2 -2
  291. package/dist/transfer/export-okf.js +20 -20
  292. package/dist/types.d.ts +7 -0
  293. package/dist/types.d.ts.map +1 -1
  294. package/dist/types.js +1 -1
  295. package/dist/verified-recall.js +19 -19
  296. package/dist/who-knows-cli.js +20 -20
  297. package/dist/who-knows.js +19 -19
  298. package/dist/write-envelope.js +4 -4
  299. package/package.json +2 -2
  300. package/src/access-entity-principal.test.ts +190 -0
  301. package/src/access-http.ts +2 -2
  302. package/src/access-operations-batch.ts +2 -2
  303. package/src/access-service.ts +7 -6
  304. package/src/buffer-drop-extraction-chunk.test.ts +196 -0
  305. package/src/buffer-types.ts +65 -0
  306. package/src/buffer.ts +60 -62
  307. package/src/chat/chat-executor.ts +1 -1
  308. package/src/config.ts +8 -0
  309. package/src/extraction-error-classification.ts +23 -0
  310. package/src/extraction-local-failure-class.test.ts +55 -0
  311. package/src/extraction-window-drain.test.ts +123 -0
  312. package/src/extraction-window-drain.ts +110 -0
  313. package/src/extraction.ts +9 -9
  314. package/src/namespaces/search.ts +1 -1
  315. package/src/orchestration/extraction-drain.ts +261 -0
  316. package/src/orchestration/extraction-run-helpers.ts +8 -0
  317. package/src/orchestration/extraction-run.test.ts +83 -2
  318. package/src/orchestration/extraction-run.ts +5 -4
  319. package/src/orchestration/qmd-result-resolver.ts +26 -1
  320. package/src/orchestration/recall-search-pipeline.ts +11 -12
  321. package/src/orchestration/turn-ingestion.ts +9 -5
  322. package/src/orchestrator.ts +21 -0
  323. package/src/secure-store/secure-fs.ts +3 -1
  324. package/src/storage/dropped-extraction-chunk.ts +27 -0
  325. package/src/storage-dropped-chunk-filenames.test.ts +30 -0
  326. package/src/storage.ts +6 -7
  327. package/src/types.ts +7 -0
  328. package/dist/chunk-3PLAL2KP.js.map +0 -1
  329. package/dist/chunk-FQFMLOAC.js.map +0 -1
  330. package/dist/chunk-LOMSBYNS.js.map +0 -1
  331. package/dist/chunk-NH6GZEQN.js.map +0 -1
  332. package/dist/chunk-QO5KDQGG.js.map +0 -1
  333. package/dist/chunk-TAEJ2MRR.js.map +0 -1
  334. package/dist/chunk-UFM3PJDT.js.map +0 -1
  335. package/dist/chunk-WCBLPKG3.js.map +0 -1
  336. package/dist/chunk-WEK3OUGO.js.map +0 -1
  337. package/dist/chunk-XHVO3CGX.js.map +0 -1
  338. /package/dist/{auto-sync-432USQDH.js.map → auto-sync-BNCBMU6H.js.map} +0 -0
  339. /package/dist/{capsule-crypto-7FJQINUR.js.map → buffer-types.js.map} +0 -0
  340. /package/dist/{graph-edge-decay-2BZTY2IM.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  341. /package/dist/{chunk-MKE5DP3S.js.map → chunk-23ZMQONL.js.map} +0 -0
  342. /package/dist/{chunk-3K5IEJTK.js.map → chunk-2AV2U2SZ.js.map} +0 -0
  343. /package/dist/{chunk-HJ3UTGIW.js.map → chunk-2JNBPOZ2.js.map} +0 -0
  344. /package/dist/{chunk-5ANUG4JZ.js.map → chunk-2T6KEOUL.js.map} +0 -0
  345. /package/dist/{chunk-EEOYF3IB.js.map → chunk-3RJDYZJE.js.map} +0 -0
  346. /package/dist/{chunk-P5CUEB2Q.js.map → chunk-4QLKVZH6.js.map} +0 -0
  347. /package/dist/{chunk-XTBEXZFN.js.map → chunk-5SF7JOJB.js.map} +0 -0
  348. /package/dist/{chunk-53BLBDV6.js.map → chunk-5TRU6QQC.js.map} +0 -0
  349. /package/dist/{chunk-DKHX2FHI.js.map → chunk-64WHWIPL.js.map} +0 -0
  350. /package/dist/{chunk-TVIGN3XZ.js.map → chunk-6FRFVIKC.js.map} +0 -0
  351. /package/dist/{chunk-JIA435P3.js.map → chunk-6ITNEPJO.js.map} +0 -0
  352. /package/dist/{chunk-FGEY2AGV.js.map → chunk-7BCYAJKP.js.map} +0 -0
  353. /package/dist/{chunk-PSQ2P2T3.js.map → chunk-7XZ7TOLQ.js.map} +0 -0
  354. /package/dist/{chunk-6JEEY42N.js.map → chunk-AER3HD3B.js.map} +0 -0
  355. /package/dist/{chunk-POGPI5GR.js.map → chunk-AY6LO7GD.js.map} +0 -0
  356. /package/dist/{chunk-HXFIILGF.js.map → chunk-BFPF5N3H.js.map} +0 -0
  357. /package/dist/{chunk-KQAFEZQX.js.map → chunk-BUKK5SWA.js.map} +0 -0
  358. /package/dist/{chunk-6F5BGIAF.js.map → chunk-CNAL35D5.js.map} +0 -0
  359. /package/dist/{chunk-YC2C67F7.js.map → chunk-CRXGQRNA.js.map} +0 -0
  360. /package/dist/{chunk-WRDFYSUC.js.map → chunk-CZVNOTLR.js.map} +0 -0
  361. /package/dist/{chunk-VPO53QMG.js.map → chunk-D6DOLQYK.js.map} +0 -0
  362. /package/dist/{chunk-RGKXF4W3.js.map → chunk-E5IEI2MR.js.map} +0 -0
  363. /package/dist/{chunk-Z2T5BPOZ.js.map → chunk-EIYJK7OD.js.map} +0 -0
  364. /package/dist/{chunk-FRAL3EU4.js.map → chunk-EKPRLTAE.js.map} +0 -0
  365. /package/dist/{chunk-I75BWXRZ.js.map → chunk-F2RDRE7M.js.map} +0 -0
  366. /package/dist/{chunk-2AKIVSHP.js.map → chunk-FAKRTS3Y.js.map} +0 -0
  367. /package/dist/{chunk-MZWHIAOL.js.map → chunk-FC3ZFEXF.js.map} +0 -0
  368. /package/dist/{chunk-7VFNGQ4V.js.map → chunk-FLDXEJFX.js.map} +0 -0
  369. /package/dist/{chunk-TYLNWE4D.js.map → chunk-FNEYSTB6.js.map} +0 -0
  370. /package/dist/{chunk-XHX67LX6.js.map → chunk-FPHU6KVI.js.map} +0 -0
  371. /package/dist/{chunk-JH4FCVWJ.js.map → chunk-G6OFEGUR.js.map} +0 -0
  372. /package/dist/{chunk-WCIDCJOQ.js.map → chunk-GAUS4XGN.js.map} +0 -0
  373. /package/dist/{chunk-SZPPCEYT.js.map → chunk-GDOI4U4D.js.map} +0 -0
  374. /package/dist/{chunk-63YPKHAU.js.map → chunk-GLB6VV3J.js.map} +0 -0
  375. /package/dist/{chunk-3XVBGGF3.js.map → chunk-GRN4BVPM.js.map} +0 -0
  376. /package/dist/{chunk-ZAP7BKTZ.js.map → chunk-GZMHBRIR.js.map} +0 -0
  377. /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
  378. /package/dist/{chunk-TXP7Q7T5.js.map → chunk-HN35OH2C.js.map} +0 -0
  379. /package/dist/{chunk-SWZOAQUM.js.map → chunk-HSNV4CSW.js.map} +0 -0
  380. /package/dist/{chunk-GFYFKXWZ.js.map → chunk-HUDPXTXW.js.map} +0 -0
  381. /package/dist/{chunk-C2KOW2UQ.js.map → chunk-I7CWAJHX.js.map} +0 -0
  382. /package/dist/{chunk-L2JQSG5M.js.map → chunk-I7TR2OCU.js.map} +0 -0
  383. /package/dist/{chunk-5DCS4C4S.js.map → chunk-ICTJOOWZ.js.map} +0 -0
  384. /package/dist/{chunk-STM4T335.js.map → chunk-IJH6METJ.js.map} +0 -0
  385. /package/dist/{chunk-6SYS5YX3.js.map → chunk-IJJWM2ZA.js.map} +0 -0
  386. /package/dist/{chunk-3AODCBKX.js.map → chunk-IWDFHVPJ.js.map} +0 -0
  387. /package/dist/{chunk-L7PGPH4E.js.map → chunk-JJSMSS45.js.map} +0 -0
  388. /package/dist/{chunk-74AY3S7U.js.map → chunk-JNLYHJG7.js.map} +0 -0
  389. /package/dist/{chunk-OLH4XCLU.js.map → chunk-JXQTECMU.js.map} +0 -0
  390. /package/dist/{chunk-6QOL4G76.js.map → chunk-KQXRVELF.js.map} +0 -0
  391. /package/dist/{chunk-VGRM43X6.js.map → chunk-LB6HIXQC.js.map} +0 -0
  392. /package/dist/{chunk-N7DVBHU3.js.map → chunk-LR7RW4T6.js.map} +0 -0
  393. /package/dist/{chunk-AYBAC2N4.js.map → chunk-LWIU5BMA.js.map} +0 -0
  394. /package/dist/{chunk-FZVZYU5C.js.map → chunk-LXN737FX.js.map} +0 -0
  395. /package/dist/{chunk-7ZRRJ6RG.js.map → chunk-MGYZENUY.js.map} +0 -0
  396. /package/dist/{chunk-YQ6SLFNZ.js.map → chunk-MPJDQC4G.js.map} +0 -0
  397. /package/dist/{chunk-7HXZOWTR.js.map → chunk-N3PUGD7E.js.map} +0 -0
  398. /package/dist/{chunk-EGNONTVT.js.map → chunk-NBEM5QEH.js.map} +0 -0
  399. /package/dist/{chunk-JF3MEIHO.js.map → chunk-O67H7OXJ.js.map} +0 -0
  400. /package/dist/{chunk-OLHN4ZNT.js.map → chunk-ONIVKGNC.js.map} +0 -0
  401. /package/dist/{chunk-2NLLXCJG.js.map → chunk-OWHERGF2.js.map} +0 -0
  402. /package/dist/{chunk-I73ZSORJ.js.map → chunk-OXSTKCYK.js.map} +0 -0
  403. /package/dist/{chunk-VSSQIBVU.js.map → chunk-PJXSBXEC.js.map} +0 -0
  404. /package/dist/{chunk-4B37KNMP.js.map → chunk-PW63SXFM.js.map} +0 -0
  405. /package/dist/{chunk-TPKB7T6Z.js.map → chunk-Q6UZREJK.js.map} +0 -0
  406. /package/dist/{chunk-X247CYL6.js.map → chunk-Q77SLBWT.js.map} +0 -0
  407. /package/dist/{chunk-5K23NQRI.js.map → chunk-QGNZ5QPV.js.map} +0 -0
  408. /package/dist/{chunk-UJDTXWMT.js.map → chunk-QIMGULGN.js.map} +0 -0
  409. /package/dist/{chunk-E24RUQEN.js.map → chunk-QNIYL5SK.js.map} +0 -0
  410. /package/dist/{chunk-KOY25HUB.js.map → chunk-QYAXMXOB.js.map} +0 -0
  411. /package/dist/{chunk-J7G656MY.js.map → chunk-R42VUN5V.js.map} +0 -0
  412. /package/dist/{chunk-AYXRK264.js.map → chunk-RROFIAYI.js.map} +0 -0
  413. /package/dist/{chunk-YYGZ4EIY.js.map → chunk-RUAIO5UA.js.map} +0 -0
  414. /package/dist/{chunk-E5PVMB3I.js.map → chunk-SDXBR5QO.js.map} +0 -0
  415. /package/dist/{chunk-OKKBOTRA.js.map → chunk-SLVR3U4E.js.map} +0 -0
  416. /package/dist/{chunk-CUWOSRCS.js.map → chunk-SUKPM6DV.js.map} +0 -0
  417. /package/dist/{chunk-TDCR6PQL.js.map → chunk-TWRPWJJT.js.map} +0 -0
  418. /package/dist/{chunk-6DTIYAAN.js.map → chunk-UGV256H3.js.map} +0 -0
  419. /package/dist/{chunk-DG4H56SK.js.map → chunk-UIK3U36O.js.map} +0 -0
  420. /package/dist/{chunk-KOAVX2IT.js.map → chunk-UKIIAEJF.js.map} +0 -0
  421. /package/dist/{chunk-XLEP7SS3.js.map → chunk-UMSH4I3E.js.map} +0 -0
  422. /package/dist/{chunk-X7Y7WX73.js.map → chunk-UVYI6VIX.js.map} +0 -0
  423. /package/dist/{chunk-4P4VLPGB.js.map → chunk-VECRSYQG.js.map} +0 -0
  424. /package/dist/{chunk-GGI6FLO6.js.map → chunk-VHBGJAYC.js.map} +0 -0
  425. /package/dist/{chunk-NMKETOAX.js.map → chunk-VSHWPOUB.js.map} +0 -0
  426. /package/dist/{chunk-VC3X7XP3.js.map → chunk-VTZSIG6O.js.map} +0 -0
  427. /package/dist/{chunk-UBN5AOLT.js.map → chunk-W5EO4USJ.js.map} +0 -0
  428. /package/dist/{chunk-Y7427SL7.js.map → chunk-WCGJK4NQ.js.map} +0 -0
  429. /package/dist/{chunk-4IZ6AC2U.js.map → chunk-WJMAWDAU.js.map} +0 -0
  430. /package/dist/{chunk-JH2ALMPR.js.map → chunk-WR5POHQ3.js.map} +0 -0
  431. /package/dist/{chunk-EHIKY2IA.js.map → chunk-WU5WTCH5.js.map} +0 -0
  432. /package/dist/{chunk-QKXPOLQD.js.map → chunk-WUBGMV3G.js.map} +0 -0
  433. /package/dist/{chunk-FKTMWDN2.js.map → chunk-WUL6LDJ4.js.map} +0 -0
  434. /package/dist/{chunk-5UPOKIFP.js.map → chunk-XBHUMPIQ.js.map} +0 -0
  435. /package/dist/{chunk-Y5ZHZKZD.js.map → chunk-XJ77IOAD.js.map} +0 -0
  436. /package/dist/{chunk-U7HJBODU.js.map → chunk-XKTHJHSQ.js.map} +0 -0
  437. /package/dist/{chunk-JFI3ATK4.js.map → chunk-XMYWYJM2.js.map} +0 -0
  438. /package/dist/{chunk-64S6X4IU.js.map → chunk-XTN54NML.js.map} +0 -0
  439. /package/dist/{chunk-CVMAOPAH.js.map → chunk-XXBCUIYQ.js.map} +0 -0
  440. /package/dist/{chunk-QOUKZ2N3.js.map → chunk-XXW3P3RX.js.map} +0 -0
  441. /package/dist/{chunk-RWRHGE75.js.map → chunk-Y473XFTD.js.map} +0 -0
  442. /package/dist/{chunk-VGFNTY3Y.js.map → chunk-YFXRLRKP.js.map} +0 -0
  443. /package/dist/{chunk-DAOMWGO2.js.map → chunk-ZX5N5D6M.js.map} +0 -0
  444. /package/dist/{resolution-4ZEQPXUZ.js.map → extraction-window-drain.js.map} +0 -0
package/src/buffer.ts CHANGED
@@ -31,6 +31,9 @@ import {
31
31
 
32
32
  export type TriggerDecision = "extract_now" | "extract_batch" | "keep_buffering";
33
33
 
34
+ export type { BufferSurpriseProbe } from "./buffer-types.js";
35
+ import type { BufferSurpriseProbe, SurpriseTelemetryQueueEntry } from "./buffer-types.js";
36
+
34
37
  export interface AddTurnOutcome {
35
38
  decision: TriggerDecision;
36
39
  extractionTurns?: BufferTurn[];
@@ -46,33 +49,6 @@ export interface RetainedTurnCleanupOptions {
46
49
  codingContext?: import("./types.js").CodingContext;
47
50
  }
48
51
 
49
- /**
50
- * Optional surprise probe injected into `SmartBuffer`.
51
- *
52
- * Computes a D-MEM-style novelty score in `[0, 1]` for an incoming turn.
53
- * The buffer treats the probe as purely additive: if it is not provided, if
54
- * the feature flag is off, or if the probe throws/times out, the buffer
55
- * falls back to the existing signal/turn-count/time triggers unchanged.
56
- *
57
- * Callers are responsible for sampling recent memories and passing them
58
- * through the embedding pipeline — the buffer does not want to know about
59
- * storage, embeddings, or QMD.
60
- *
61
- * @param bufferKey Identifier for the active buffer (session/thread).
62
- * @param turn The incoming turn whose novelty is being scored.
63
- * @param recentTurns Turns already buffered for this key (most recent first
64
- * is NOT guaranteed — treat as unordered corpus).
65
- * @returns A surprise score in `[0, 1]`, or `null` if no score could be
66
- * produced (e.g. empty corpus, probe declined to embed).
67
- */
68
- export interface BufferSurpriseProbe {
69
- scoreTurn(
70
- bufferKey: string,
71
- turn: BufferTurn,
72
- recentTurns: readonly BufferTurn[],
73
- ): Promise<number | null>;
74
- }
75
-
76
52
  const MAX_BUFFER_ENTRY_COUNT = 200;
77
53
  const isAcceptedTurn = (t: { acceptedCapture?: boolean; writeNamespaceOverride?: string }): boolean =>
78
54
  t.acceptedCapture === true || t.writeNamespaceOverride !== undefined;
@@ -96,41 +72,6 @@ const BUFFER_SAVE_MAX_DEFER_MULTIPLIER = 5;
96
72
  */
97
73
  const MAX_SET_TIMEOUT_MS = 2_147_483_647;
98
74
 
99
- /**
100
- * Minimal data carried on the serialized telemetry write chain
101
- * (issue #563 PR 3).
102
- *
103
- * We intentionally do NOT capture the full `BufferTurn` here: under
104
- * slow filesystem latency the chain can back up, and retaining
105
- * `turn.content` for every pending append causes memory pressure on
106
- * large conversations. Only the fields the ledger row actually needs
107
- * cross the chain boundary.
108
- */
109
- interface SurpriseTelemetryQueueEntry {
110
- bufferKey: string;
111
- turnRole: "user" | "assistant";
112
- sessionKey: string | null;
113
- surpriseScore: number;
114
- triggered: boolean;
115
- turnCountInWindow: number;
116
- /**
117
- * ISO timestamp captured at the moment the turn was scored, NOT when
118
- * the ledger append eventually runs. Backpressure on the serialized
119
- * write chain could otherwise shift event timestamps away from the
120
- * real decision moment and distort the distribution report (p90
121
- * inflated, current-threshold row misidentified).
122
- */
123
- timestamp: string;
124
- /**
125
- * Threshold value in force when `triggered` was computed. Must be
126
- * snapshot here rather than read from `config` at emit time — a
127
- * concurrent config change between queue and write would otherwise
128
- * record `triggered=true` against a newer threshold the operator
129
- * never set, distorting precision/recall interpretation.
130
- */
131
- threshold: number;
132
- }
133
-
134
75
  interface AddTurnMutationResult {
135
76
  decision: TriggerDecision;
136
77
  signalLevel: SignalLevel;
@@ -1148,6 +1089,63 @@ export class SmartBuffer {
1148
1089
  });
1149
1090
  }
1150
1091
 
1092
+ /**
1093
+ * Remove an extraction-window chunk from the buffer WITHOUT recording an
1094
+ * extraction (trigger-drain drop path). Used when a chunk fails extraction
1095
+ * repeatedly and must be dropped so the session can drain; the caller is
1096
+ * responsible for the side-file copy and logging. Returns the number of
1097
+ * live turns dropped; 0 when the turns no longer match. `allowNonPrefix`
1098
+ * matches by identity, like `clearAfterExtraction`, for callers (native
1099
+ * groups) whose chunk can sit behind a pinned capture.
1100
+ */
1101
+ async dropExtractionChunk(
1102
+ bufferKey = "default",
1103
+ turns: readonly BufferTurn[],
1104
+ options?: { allowNonPrefix?: boolean },
1105
+ ): Promise<number> {
1106
+ return await this.enqueueMutation(async () => {
1107
+ await this.loadUnlocked();
1108
+ const entry = this.entryFor(bufferKey);
1109
+ // A window's head can be deferred-retained turns (retainDeferredTurns): take those out of the chunk first.
1110
+ const retained = [...(entry.retainedTurns ?? [])];
1111
+ const liveTurns: BufferTurn[] = [];
1112
+ for (const turn of turns) {
1113
+ const at = retained.findIndex((kept) => bufferTurnsEqual(kept, turn));
1114
+ if (at >= 0) retained.splice(at, 1);
1115
+ else liveTurns.push(turn);
1116
+ }
1117
+ const retainedDropped = (entry.retainedTurns?.length ?? 0) - retained.length;
1118
+ let remaining = entry.turns;
1119
+ if (options?.allowNonPrefix === true) {
1120
+ remaining = [...entry.turns];
1121
+ for (const turn of liveTurns) {
1122
+ const at = remaining.findIndex((live) => bufferTurnsEqual(live, turn));
1123
+ if (at >= 0) remaining.splice(at, 1);
1124
+ }
1125
+ } else {
1126
+ remaining = entry.turns.slice(matchingQueuedExtractionPrefixLength(entry.turns, liveTurns));
1127
+ }
1128
+ const matchedCount = entry.turns.length - remaining.length + retainedDropped;
1129
+ if (matchedCount === 0) return 0;
1130
+ if (this.saveTimer) {
1131
+ clearTimeout(this.saveTimer);
1132
+ this.saveTimer = null;
1133
+ }
1134
+ entry.turns = remaining;
1135
+ if (retainedDropped > 0) {
1136
+ if (retained.length > 0) entry.retainedTurns = retained;
1137
+ else delete entry.retainedTurns;
1138
+ }
1139
+ if (bufferKey === "default") {
1140
+ this.state.turns = entry.turns;
1141
+ }
1142
+ this.persistence.markDirty(bufferKey);
1143
+ this.pruneEntries([bufferKey]);
1144
+ await this.saveNowRetainingPendingOnFailure("dropExtractionChunk");
1145
+ return matchedCount;
1146
+ });
1147
+ }
1148
+
1151
1149
  getExtractionCount(bufferKey = "default"): number {
1152
1150
  return this.peekEntry(bufferKey)?.extractionCount ?? 0;
1153
1151
  }
@@ -60,7 +60,7 @@ export function createChatExecutor(bindings: ChatExecutorBindings): ChatToolExec
60
60
  },
61
61
 
62
62
  async entityGet(name: string): Promise<string> {
63
- const result = await service.entityGet(name, namespace);
63
+ const result = await service.entityGet(name, namespace, principal);
64
64
  return JSON.stringify(result);
65
65
  },
66
66
 
package/src/config.ts CHANGED
@@ -2868,6 +2868,14 @@ export function parseConfig(
2868
2868
  extractionBreakerAuthCooldownMs:
2869
2869
  typeof cfg.extractionBreakerAuthCooldownMs === "number" && cfg.extractionBreakerAuthCooldownMs >= 0
2870
2870
  ? cfg.extractionBreakerAuthCooldownMs : 1_800_000,
2871
+ // Trigger-drain drop threshold: consecutive failed extraction attempts
2872
+ // before the failing buffer chunk is dropped (side-file copied first).
2873
+ extractionChunkDropAfterFailures: parseIntegerAtLeast(
2874
+ cfg.extractionChunkDropAfterFailures,
2875
+ 5,
2876
+ 1,
2877
+ "extractionChunkDropAfterFailures",
2878
+ ),
2871
2879
  // Local LLM fast tier (v9.1)
2872
2880
  localLlmFastEnabled: cfg.localLlmFastEnabled === true,
2873
2881
  localLlmFastModel:
@@ -85,3 +85,26 @@ export function formatExtractionParseFailureLog(input: {
85
85
  `traceId=${input.traceId}`,
86
86
  ].join(" ");
87
87
  }
88
+
89
+ /**
90
+ * The local endpoint answered, and what it said rejects this input (unusable content, or a client error that is not
91
+ * auth, timeout or rate limiting): the chunk's problem, not an outage. Retried like an empty parse, and a trigger-drain
92
+ * chunk that keeps drawing it is dropped after its recovery copy is saved.
93
+ */
94
+ export class LocalLlmRejectedError extends Error {
95
+ constructor(readonly httpStatus?: number) {
96
+ super(httpStatus === undefined ? "local LLM answered with unusable content" : `local LLM rejected the input (HTTP ${httpStatus})`);
97
+ this.name = "LocalLlmRejectedError";
98
+ }
99
+ }
100
+
101
+ /** What the local client recorded about a failed call (`failureDiag`). */
102
+ export interface LocalFailureDiag {
103
+ lastError?: unknown;
104
+ }
105
+
106
+ /** The HTTP status when the local endpoint answered and refuses this input (400, 413, 422); else undefined. A wrong model name or route (404, 405, 410), auth, timeout and rate limiting are the setup's problem, not the chunk's. */
107
+ export function localRejectionStatus(diag: LocalFailureDiag): number | undefined {
108
+ const status = httpStatusFromError(diag.lastError);
109
+ return status !== undefined && [400, 413, 422].includes(status) ? status : undefined;
110
+ }
@@ -0,0 +1,55 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { parseConfig } from "./config.js";
5
+ import { ExtractionEngine } from "./extraction.js";
6
+ import type { BufferTurn } from "./types.js";
7
+
8
+ // Follow-up to #3266: the trigger drain drops a chunk after N failures, but not for an outage. Whether a local-model failure
9
+ // is the chunk's fault is decided here, by what the local endpoint did.
10
+ const TURNS: BufferTurn[] = [
11
+ { role: "user", content: "I keep my backups on the NAS in the garage.", timestamp: "2026-10-30T01:00:00.000Z" },
12
+ { role: "assistant", content: "Noted.", timestamp: "2026-10-30T01:00:05.000Z" },
13
+ ];
14
+
15
+ type LocalMessages = Array<{ role: string; content: string }>;
16
+ type LocalStub = (messages: LocalMessages, options: { failureDiag?: { lastError?: Error } }) => Promise<{ content: string } | null>;
17
+
18
+ async function extractWith(local: LocalStub) {
19
+ const engine = new ExtractionEngine(parseConfig({ localLlmEnabled: true, localLlmModel: "fixture-local", localLlmFallback: false }));
20
+ const modelRegistry = { calculateContextSizes: () => ({ maxInputChars: 8_000, maxOutputTokens: 1_000, description: "fixture" }) };
21
+ assert.equal(Reflect.set(engine, "localLlm", { chatCompletion: local }), true);
22
+ assert.equal(Reflect.set(engine, "modelRegistry", modelRegistry), true);
23
+ return await engine.extract(TURNS);
24
+ }
25
+
26
+ const withStatus = (status: number) => Object.assign(new Error("local LLM http failure"), { status });
27
+
28
+ test("a local endpoint that answers with something unusable rejects the chunk: parse_empty", async () => {
29
+ const result = await extractWith(async () => ({ content: "I cannot help with that request." }));
30
+ assert.equal(result.extractionFailure, "local_llm_error");
31
+ assert.equal(result.extractionFailureClass, "parse_empty");
32
+ });
33
+
34
+ test("a local endpoint that answers with a client error rejects the chunk: parse_empty", async () => {
35
+ for (const status of [400, 413, 422]) {
36
+ const result = await extractWith(async (_messages, options) => {
37
+ if (options.failureDiag) options.failureDiag.lastError = withStatus(status);
38
+ return null;
39
+ });
40
+ assert.equal(result.extractionFailureClass, "parse_empty", `HTTP ${status}`);
41
+ }
42
+ });
43
+
44
+ test("not the chunk's fault: unreachable, timeout, 5xx, 429, auth and a wrong model or route (404) stay provider_retryable", async () => {
45
+ const outage = await extractWith(async () => null);
46
+ assert.equal(outage.extractionFailure, "local_llm_unavailable");
47
+ assert.equal(outage.extractionFailureClass, "provider_retryable");
48
+ for (const status of [401, 403, 404, 405, 408, 410, 429, 500, 503]) {
49
+ const result = await extractWith(async (_messages, options) => {
50
+ if (options.failureDiag) options.failureDiag.lastError = withStatus(status);
51
+ return null;
52
+ });
53
+ assert.equal(result.extractionFailureClass, "provider_retryable", `HTTP ${status}`);
54
+ }
55
+ });
@@ -0,0 +1,123 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ chunkExtractionWindow,
5
+ classifyDrainResult,
6
+ extractionWindowBudgetChars,
7
+ mergeDrainResults,
8
+ } from "./extraction-window-drain.js";
9
+ import type { ExtractionRunResult } from "./orchestration/extraction-run.js";
10
+ import type { BufferTurn } from "./types.js";
11
+
12
+ function makeTurn(content: string, sessionKey = "s1"): BufferTurn {
13
+ return { role: "user", content, timestamp: "2026-10-29T00:00:00.000Z", sessionKey };
14
+ }
15
+
16
+ function skippedResult(reason: string): ExtractionRunResult {
17
+ return { status: "skipped", reason, persistedCount: 0, durableOutputCount: 0 };
18
+ }
19
+
20
+ test("chunkExtractionWindow returns a single chunk when the window fits the budget", () => {
21
+ const turns = [makeTurn("a".repeat(10)), makeTurn("b".repeat(10))];
22
+ const chunks = chunkExtractionWindow(turns, 100);
23
+ assert.equal(chunks.length, 1);
24
+ assert.deepEqual(chunks[0], turns);
25
+ });
26
+
27
+ test("chunkExtractionWindow splits an oversized window into budget-fitting chunks in order", () => {
28
+ const turns = [
29
+ makeTurn("a".repeat(60)),
30
+ makeTurn("b".repeat(60)),
31
+ makeTurn("c".repeat(60)),
32
+ makeTurn("d".repeat(60)),
33
+ ];
34
+ const chunks = chunkExtractionWindow(turns, 100);
35
+ assert.equal(chunks.length, 4);
36
+ for (const chunk of chunks) {
37
+ assert.ok(chunk.every((turn) => turn.content.length <= 100));
38
+ }
39
+ assert.deepEqual(
40
+ chunks.flat().map((turn) => turn.content[0]),
41
+ ["a", "b", "c", "d"],
42
+ );
43
+ assert.deepEqual(chunks.flat(), turns);
44
+ });
45
+
46
+ test("chunkExtractionWindow gives a single oversized turn its own chunk", () => {
47
+ const turns = [makeTurn("x".repeat(250)), makeTurn("y".repeat(10))];
48
+ const chunks = chunkExtractionWindow(turns, 100);
49
+ assert.equal(chunks.length, 2);
50
+ assert.equal(chunks[0]?.length, 1);
51
+ assert.equal(chunks[1]?.length, 1);
52
+ });
53
+
54
+ test("chunkExtractionWindow treats a non-positive budget as no chunking", () => {
55
+ const turns = [makeTurn("a".repeat(500)), makeTurn("b".repeat(500))];
56
+ assert.equal(chunkExtractionWindow(turns, 0).length, 1);
57
+ assert.equal(chunkExtractionWindow(turns, Number.POSITIVE_INFINITY).length, 1);
58
+ });
59
+
60
+ test("chunkExtractionWindow returns no chunks for an empty window", () => {
61
+ assert.deepEqual(chunkExtractionWindow([], 100), []);
62
+ });
63
+
64
+ test("classifyDrainResult treats completed runs and benign skips as drained", () => {
65
+ assert.equal(classifyDrainResult({ status: "completed", persistedCount: 1, durableOutputCount: 2 }), "drained");
66
+ assert.equal(classifyDrainResult(skippedResult("below_threshold")), "drained");
67
+ assert.equal(classifyDrainResult(skippedResult("cron_session")), "drained");
68
+ assert.equal(classifyDrainResult(skippedResult("empty_normalized_turns")), "drained");
69
+ assert.equal(classifyDrainResult(skippedResult("processed_fingerprint")), "drained");
70
+ });
71
+
72
+ test("classifyDrainResult reports breaker/backoff skips as deferred", () => {
73
+ assert.equal(classifyDrainResult(skippedResult("provider_circuit_open")), "deferred");
74
+ assert.equal(classifyDrainResult(skippedResult("extraction_backoff")), "deferred");
75
+ });
76
+
77
+ test("classifyDrainResult reports empty extraction results as failed", () => {
78
+ assert.equal(
79
+ classifyDrainResult({ ...skippedResult("empty_extraction_result"), retainedBuffer: true }),
80
+ "failed",
81
+ "an empty result that kept the window for retry is a chunk failure",
82
+ );
83
+ assert.equal(
84
+ classifyDrainResult(skippedResult("empty_extraction_result")),
85
+ "drained",
86
+ "an empty result that cleared its turns extracted fine and found nothing durable",
87
+ );
88
+ });
89
+
90
+ test("mergeDrainResults sums counts, completes when any chunk completes, keeps the last skip reason", () => {
91
+ const first = skippedResult("empty_extraction_result");
92
+ const second: ExtractionRunResult = { status: "completed", persistedCount: 3, durableOutputCount: 4 };
93
+ // A completed chunk means the drain made durable progress.
94
+ const merged = mergeDrainResults(mergeDrainResults(null, first), second);
95
+ assert.equal(merged.status, "completed");
96
+ assert.equal(merged.persistedCount, 3);
97
+ assert.equal(merged.durableOutputCount, 4);
98
+ // All chunks skipped: the aggregate stays skipped with the last reason.
99
+ const mergedFailed = mergeDrainResults(
100
+ mergeDrainResults(null, skippedResult("provider_circuit_open")),
101
+ first,
102
+ );
103
+ assert.equal(mergedFailed.status, "skipped");
104
+ assert.equal(mergedFailed.reason, "empty_extraction_result");
105
+ assert.equal(mergedFailed.persistedCount, 0);
106
+ });
107
+
108
+ test("extractionWindowBudgetChars applies the learned local window only when the local model extracts", () => {
109
+ const registry = { calculateContextSizes: () => ({ maxInputChars: 12_000 }) };
110
+ const base = { registry, model: "local-model", maxContext: undefined };
111
+
112
+ assert.equal(extractionWindowBudgetChars({ ...base, localExtractionActive: true }), 12_000);
113
+ assert.equal(
114
+ extractionWindowBudgetChars({ ...base, localExtractionActive: false }),
115
+ Number.POSITIVE_INFINITY,
116
+ "gateway or disabled local LLM: the local window does not describe the extractor",
117
+ );
118
+ assert.equal(
119
+ extractionWindowBudgetChars({ ...base, registry: undefined, localExtractionActive: true }),
120
+ Number.POSITIVE_INFINITY,
121
+ "no registry (legacy host or test double): no budget",
122
+ );
123
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Trigger-drain helpers (extraction trigger stall fix).
3
+ *
4
+ * A session whose buffered window outgrows the extraction model's context
5
+ * never drains: the trigger fires `extract_batch` on every observe, the
6
+ * oversized request fails, nothing is cleared, and the window only grows.
7
+ * These helpers split such a window into budget-fitting chunks and classify
8
+ * each extraction run so the caller can drain successes, defer on breaker/
9
+ * backoff retention, and drop repeatedly failing chunks.
10
+ */
11
+
12
+ import type { ExtractionRunResult } from "./orchestration/extraction-run.js";
13
+
14
+ /**
15
+ * Split `turns` into chunks whose summed content length stays within
16
+ * `maxChars`. Chunks keep turn order and never split a turn; a single turn
17
+ * longer than the budget becomes its own chunk (the extraction prompt still
18
+ * clamps per-turn content). A non-positive or non-finite budget, or an empty
19
+ * window, yields at most one chunk (no chunking).
20
+ */
21
+ export function chunkExtractionWindow<T extends { content: string }>(
22
+ turns: readonly T[],
23
+ maxChars: number,
24
+ ): T[][] {
25
+ if (turns.length === 0) return [];
26
+ const budget =
27
+ Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) : Number.POSITIVE_INFINITY;
28
+ const chunks: T[][] = [];
29
+ let current: T[] = [];
30
+ let currentChars = 0;
31
+ for (const turn of turns) {
32
+ const turnChars = turn.content.length;
33
+ if (current.length > 0 && currentChars + turnChars > budget) {
34
+ chunks.push(current);
35
+ current = [];
36
+ currentChars = 0;
37
+ }
38
+ current.push(turn);
39
+ currentChars += turnChars;
40
+ }
41
+ if (current.length > 0) chunks.push(current);
42
+ return chunks;
43
+ }
44
+
45
+ export type DrainVerdict = "drained" | "deferred" | "failed";
46
+
47
+ /**
48
+ * Run results that deliberately RETAIN the buffer (provider breaker open,
49
+ * per-fingerprint backoff) are deferrals, not chunk failures — counting them
50
+ * would drop good content during a provider outage.
51
+ */
52
+ const DRAIN_DEFERRAL_REASONS = {
53
+ provider_circuit_open: true,
54
+ extraction_backoff: true,
55
+ };
56
+
57
+ export function classifyDrainResult(result: ExtractionRunResult): DrainVerdict {
58
+ if (result.status !== "skipped") return "drained";
59
+ // An empty result is a failure only when the run kept the window for retry;
60
+ // otherwise it extracted fine, found nothing durable, and cleared its turns.
61
+ if (result.reason === "empty_extraction_result") {
62
+ return result.retainedBuffer === true ? "failed" : "drained";
63
+ }
64
+ if (result.reason !== undefined && result.reason in DRAIN_DEFERRAL_REASONS) return "deferred";
65
+ return "drained";
66
+ }
67
+
68
+ /**
69
+ * Combine chunk results into the single `ExtractionRunResult` the queue
70
+ * contract settles with: counts sum, any completed chunk completes the drain,
71
+ * and the last non-completed reason wins.
72
+ */
73
+ export function mergeDrainResults(
74
+ previous: ExtractionRunResult | null,
75
+ next: ExtractionRunResult,
76
+ ): ExtractionRunResult {
77
+ if (previous === null) return next;
78
+ const completed = previous.status === "completed" || next.status === "completed";
79
+ const merged: ExtractionRunResult = {
80
+ status: completed ? "completed" : "skipped",
81
+ persistedCount: previous.persistedCount + next.persistedCount,
82
+ durableOutputCount: previous.durableOutputCount + next.durableOutputCount,
83
+ };
84
+ if (!completed) merged.reason = next.reason ?? previous.reason;
85
+ if (previous.postPersistMetadataFailed === true || next.postPersistMetadataFailed === true) {
86
+ merged.postPersistMetadataFailed = true;
87
+ }
88
+ return merged;
89
+ }
90
+
91
+ /** The slice of ModelRegistry the drain budget needs. */
92
+ export interface WindowBudgetRegistry {
93
+ calculateContextSizes(modelId: string, maxContextOverride?: number): { maxInputChars: number };
94
+ }
95
+
96
+ /**
97
+ * Character budget for one extraction request. The learned local-model window
98
+ * only describes the extractor when the local LLM does the extracting; with a
99
+ * gateway extractor, a disabled local LLM, or no registry (legacy hosts, test
100
+ * doubles) there is no budget and the window runs in one request as before.
101
+ */
102
+ export function extractionWindowBudgetChars(input: {
103
+ registry: WindowBudgetRegistry | undefined;
104
+ localExtractionActive: boolean;
105
+ model: string;
106
+ maxContext: number | undefined;
107
+ }): number {
108
+ if (!input.registry || !input.localExtractionActive) return Number.POSITIVE_INFINITY;
109
+ return input.registry.calculateContextSizes(input.model, input.maxContext).maxInputChars;
110
+ }
package/src/extraction.ts CHANGED
@@ -38,7 +38,7 @@ import {
38
38
  import { applyGroundingWithConnector, headerConnector, renderExtractionConversation, resolveSourceConnector, type ExtractionGroundingContext } from "./source-agent-qualifier.js";
39
39
  import { isMemoryCategory } from "./write-envelope.js";
40
40
  export { classifyExtractionThrownError, classifyFallbackParseFailure, formatExtractionParseFailureLog } from "./extraction-error-classification.js";
41
- import { classifyExtractionThrownError, classifyFallbackParseFailure, formatExtractionParseFailureLog } from "./extraction-error-classification.js";
41
+ import { LocalLlmRejectedError, classifyExtractionThrownError, classifyFallbackParseFailure, formatExtractionParseFailureLog, localRejectionStatus, type LocalFailureDiag } from "./extraction-error-classification.js";
42
42
  import { AMBIENT_CAPTURE_PROMPT_SECTION_COMPACT, clampAmbientCaptureConfidence } from "./ambient-provenance.js";
43
43
  import {
44
44
  CONSOLIDATION_RESPONSE_SCHEMA,
@@ -1244,7 +1244,7 @@ export class ExtractionEngine {
1244
1244
  entities: [],
1245
1245
  questions: [],
1246
1246
  extractionFailure: "local_llm_error",
1247
- extractionFailureClass: "provider_retryable",
1247
+ extractionFailureClass: err instanceof LocalLlmRejectedError ? "parse_empty" : classifyExtractionThrownError(err),
1248
1248
  };
1249
1249
  }
1250
1250
  log.info("extraction: local LLM error, falling back to gateway default AI:", err);
@@ -1494,9 +1494,8 @@ ${extractionResponseShape(this.config.extraction?.spanMode ?? "off")}
1494
1494
  Conversation:
1495
1495
  ${truncatedConversation}`;
1496
1496
 
1497
- log.debug(
1498
- `extractWithLocalLlm: calling localLlm.chatCompletion with prompt length ${localPrompt.length}...`,
1499
- );
1497
+ log.debug(`extractWithLocalLlm: calling localLlm.chatCompletion with prompt length ${localPrompt.length}...`);
1498
+ const failureDiag: LocalFailureDiag = {};
1500
1499
  const response = await this.localLlm.chatCompletion(
1501
1500
  [
1502
1501
  { role: "system", content: "You are a memory extraction system. Output valid JSON only." },
@@ -1512,9 +1511,12 @@ ${truncatedConversation}`;
1512
1511
  : undefined,
1513
1512
  priority: "background",
1514
1513
  signal,
1514
+ failureDiag,
1515
1515
  },
1516
1516
  );
1517
1517
  if (!response?.content) {
1518
+ const rejected = localRejectionStatus(failureDiag);
1519
+ if (rejected !== undefined) throw new LocalLlmRejectedError(rejected); // the endpoint is up and refuses this input
1518
1520
  log.debug("extractWithLocalLlm: chatCompletion returned null or empty content");
1519
1521
  return null;
1520
1522
  }
@@ -1547,12 +1549,10 @@ ${truncatedConversation}`;
1547
1549
  log.debug("extractWithLocalLlm: JSON parse failed, attempting partial extraction...");
1548
1550
  const partial = this.extractPartialFacts(content);
1549
1551
  if (partial.facts.length > 0 || partial.entities.length > 0) {
1550
- log.debug(
1551
- `extractWithLocalLlm: extracted ${partial.facts.length} partial facts from truncated JSON`,
1552
- );
1552
+ log.debug(`extractWithLocalLlm: extracted ${partial.facts.length} partial facts from truncated JSON`);
1553
1553
  return partial;
1554
1554
  }
1555
- return null;
1555
+ throw new LocalLlmRejectedError(); // the endpoint answered and nothing in the answer is usable
1556
1556
  }
1557
1557
 
1558
1558
  /**
@@ -700,7 +700,7 @@ export function normalizeQmdResultPath(resultPath: string, collection: string):
700
700
  }
701
701
  return value;
702
702
  }
703
- function resolveNamespaceResultPath(
703
+ export function resolveNamespaceResultPath(
704
704
  memoryDir: string,
705
705
  collection: string,
706
706
  resultPath: string,