@remnic/core 9.6.39 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +16 -16
  3. package/dist/access-boundary.d.ts +6 -6
  4. package/dist/access-boundary.js +17 -17
  5. package/dist/access-cli.js +56 -55
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-http.d.ts +6 -6
  8. package/dist/access-http.js +22 -22
  9. package/dist/access-identity-continuity-surface.d.ts +5 -5
  10. package/dist/access-identity-continuity-surface.js +16 -16
  11. package/dist/access-lcm-surface.d.ts +6 -6
  12. package/dist/access-lcm-surface.js +16 -16
  13. package/dist/access-mcp.d.ts +6 -6
  14. package/dist/access-mcp.js +21 -21
  15. package/dist/access-observe-write-surface.d.ts +6 -6
  16. package/dist/access-observe-write-surface.js +16 -16
  17. package/dist/access-operations-batch.js +19 -19
  18. package/dist/access-operations.d.ts +9 -9
  19. package/dist/access-operations.js +20 -20
  20. package/dist/access-recall-surface.d.ts +6 -6
  21. package/dist/access-recall-surface.js +16 -16
  22. package/dist/access-schema.d.ts +64 -64
  23. package/dist/{access-service-Fw9AYEqG.d.ts → access-service-CJ5hrLqR.d.ts} +4 -4
  24. package/dist/access-service.d.ts +6 -6
  25. package/dist/access-service.js +16 -16
  26. package/dist/access-surface-catalog.d.ts +6 -6
  27. package/dist/action-confidence.d.ts +1 -1
  28. package/dist/active-memory-bridge.d.ts +1 -1
  29. package/dist/active-memory-bridge.js +3 -3
  30. package/dist/active-recall.d.ts +1 -1
  31. package/dist/active-recall.js +1 -1
  32. package/dist/{auto-sync-4A5YBWYO.js → auto-sync-676N6N6Y.js} +4 -4
  33. package/dist/behavior-learner.d.ts +1 -1
  34. package/dist/behavior-signals.d.ts +1 -1
  35. package/dist/bootstrap.d.ts +5 -5
  36. package/dist/briefing.d.ts +2 -2
  37. package/dist/briefing.js +5 -5
  38. package/dist/buffer-surprise-report.d.ts +1 -1
  39. package/dist/buffer.d.ts +2 -2
  40. package/dist/buffer.js +2 -2
  41. package/dist/bulk-import/index.d.ts +3 -3
  42. package/dist/calibration.d.ts +1 -1
  43. package/dist/calibration.js +1 -1
  44. package/dist/capabilities.d.ts +4 -2
  45. package/dist/capabilities.js +1 -1
  46. package/dist/{catalog-CnS_MP-8.d.ts → catalog-XCOyxXKw.d.ts} +1 -1
  47. package/dist/causal-behavior.d.ts +1 -1
  48. package/dist/causal-consolidation.d.ts +1 -1
  49. package/dist/causal-consolidation.js +7 -7
  50. package/dist/causal-trajectory-graph.d.ts +1 -1
  51. package/dist/{chunk-SFHI7JBE.js → chunk-2FSHROYK.js} +3 -3
  52. package/dist/{chunk-WHZ7MK72.js → chunk-4YUPLX7U.js} +3 -3
  53. package/dist/{chunk-ZIKJD6NL.js → chunk-53Y3TETS.js} +2 -2
  54. package/dist/{chunk-UI4FVU6T.js → chunk-5CJVLG3H.js} +2 -2
  55. package/dist/{chunk-BAVVI5GP.js → chunk-7PND4PUC.js} +3 -3
  56. package/dist/{chunk-NREKOGZ2.js → chunk-ARD5NE4M.js} +3 -3
  57. package/dist/{chunk-GCTJG5MP.js → chunk-AWHOZTF6.js} +4 -4
  58. package/dist/{chunk-VSF7AAGE.js → chunk-BFPETA3S.js} +3 -2
  59. package/dist/chunk-BFPETA3S.js.map +1 -0
  60. package/dist/{chunk-ZGPDSEKG.js → chunk-BFVEEB7W.js} +2 -2
  61. package/dist/{chunk-AZK3NEHL.js → chunk-BLXNPBJI.js} +2 -2
  62. package/dist/chunk-C7SRUWBW.js +54 -0
  63. package/dist/chunk-C7SRUWBW.js.map +1 -0
  64. package/dist/{chunk-VTRF5QBX.js → chunk-CBOHQNTJ.js} +2 -2
  65. package/dist/{chunk-ZSDRGPMV.js → chunk-D6KNJRGX.js} +2 -2
  66. package/dist/{chunk-2V6L3L56.js → chunk-DDHT5J5M.js} +33 -8
  67. package/dist/chunk-DDHT5J5M.js.map +1 -0
  68. package/dist/{chunk-3WULMX34.js → chunk-DSWYYEIK.js} +10 -1
  69. package/dist/chunk-DSWYYEIK.js.map +1 -0
  70. package/dist/{chunk-U5JP7FCM.js → chunk-E47TABDR.js} +2 -2
  71. package/dist/{chunk-7TEWOG3R.js → chunk-FMECZFUT.js} +2 -2
  72. package/dist/{chunk-QWGNDWKB.js → chunk-FNMFJWBY.js} +2 -2
  73. package/dist/{chunk-CZGIIADK.js → chunk-FXGC5UUH.js} +56 -11
  74. package/dist/chunk-FXGC5UUH.js.map +1 -0
  75. package/dist/{chunk-JIKJPDQJ.js → chunk-GKCCHHZK.js} +15 -5
  76. package/dist/chunk-GKCCHHZK.js.map +1 -0
  77. package/dist/{chunk-EDD2G35Z.js → chunk-GWMTFDHF.js} +4 -4
  78. package/dist/{chunk-VCW74PNH.js → chunk-H67QFYUK.js} +2 -2
  79. package/dist/{chunk-5ELELET5.js → chunk-HT2DQ27U.js} +3 -3
  80. package/dist/{chunk-VU3SVYMA.js → chunk-HTIIXNJQ.js} +2 -2
  81. package/dist/{chunk-I73G4UH7.js → chunk-IALLX7EE.js} +2 -2
  82. package/dist/{chunk-PE6XR4GG.js → chunk-IKQTFQMI.js} +2 -2
  83. package/dist/{chunk-XZ5I7OZR.js → chunk-KIPLG7S7.js} +4 -4
  84. package/dist/{chunk-QLVLW7UD.js → chunk-KSBEEMQF.js} +17 -5
  85. package/dist/chunk-KSBEEMQF.js.map +1 -0
  86. package/dist/{chunk-RU73ZROD.js → chunk-KWJP22WP.js} +12 -12
  87. package/dist/{chunk-MNUHL34L.js → chunk-LKT755LP.js} +2 -2
  88. package/dist/{chunk-PI23H7FV.js → chunk-M7VGG3ID.js} +3 -3
  89. package/dist/{chunk-PWSZNML3.js → chunk-NGJT7IW5.js} +284 -38
  90. package/dist/chunk-NGJT7IW5.js.map +1 -0
  91. package/dist/{chunk-HVE3UVAB.js → chunk-NS4GEMLR.js} +3 -3
  92. package/dist/{chunk-ZMXXHUEZ.js → chunk-O24E5CFS.js} +3 -3
  93. package/dist/{chunk-3BZDBG7U.js → chunk-PML3GU4Q.js} +3 -3
  94. package/dist/{chunk-DQ2CJYUM.js → chunk-PSQ32V5P.js} +3 -3
  95. package/dist/{chunk-7XY6TJLM.js → chunk-PUPQ2PMC.js} +3 -3
  96. package/dist/{chunk-EBVUFGJ4.js → chunk-QFOLNA2C.js} +2 -2
  97. package/dist/{chunk-METJNEIB.js → chunk-QMUERLZK.js} +1 -1
  98. package/dist/chunk-QMUERLZK.js.map +1 -0
  99. package/dist/{chunk-PQG2AOL4.js → chunk-QRYNEVHP.js} +28 -4
  100. package/dist/{chunk-PQG2AOL4.js.map → chunk-QRYNEVHP.js.map} +1 -1
  101. package/dist/{chunk-CVEEJKRD.js → chunk-RQTCDICG.js} +2 -2
  102. package/dist/{chunk-F7AE7L6G.js → chunk-RZEJDF3S.js} +2 -2
  103. package/dist/{chunk-YFBTVOMB.js → chunk-SYNYI4ZM.js} +2 -2
  104. package/dist/{chunk-BRDJ2HFP.js → chunk-TRWPCR6P.js} +2 -2
  105. package/dist/{chunk-YAN6NTJN.js → chunk-UKA7GNI2.js} +3 -3
  106. package/dist/{chunk-TQNRI55H.js → chunk-UR6PBX2J.js} +4 -51
  107. package/dist/chunk-UR6PBX2J.js.map +1 -0
  108. package/dist/{chunk-XH2Y6UIW.js → chunk-VD56CJRR.js} +4 -4
  109. package/dist/{chunk-J3XHPFEU.js → chunk-VXMWDXIJ.js} +2 -2
  110. package/dist/{chunk-QT2RZNRT.js → chunk-VY6LKKVN.js} +2 -2
  111. package/dist/{chunk-PRTA6444.js → chunk-W4PGTAS2.js} +2 -2
  112. package/dist/{chunk-WX4XTAOI.js → chunk-WGE3OYC3.js} +7 -7
  113. package/dist/{chunk-RVLE2SNP.js → chunk-X6FSCBJZ.js} +22 -22
  114. package/dist/{chunk-6MWGGPJJ.js → chunk-Y4EMHIQ5.js} +7 -7
  115. package/dist/{chunk-IGCQHJSQ.js → chunk-Z3HJ5L3Z.js} +2 -2
  116. package/dist/{chunk-O2EL7EGG.js → chunk-ZQ7UBUZ6.js} +2 -2
  117. package/dist/{chunk-5SORASJK.js → chunk-ZXUG3AFP.js} +2 -2
  118. package/dist/{cli-PGF41MN6.d.ts → cli-CtECZr1q.d.ts} +5 -5
  119. package/dist/cli.d.ts +8 -8
  120. package/dist/cli.js +37 -36
  121. package/dist/compounding/engine.d.ts +2 -2
  122. package/dist/compounding/engine.js +7 -7
  123. package/dist/compounding/preference-consolidator.d.ts +1 -1
  124. package/dist/compression-optimizer.d.ts +1 -1
  125. package/dist/config.d.ts +1 -1
  126. package/dist/config.js +1 -1
  127. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  128. package/dist/connectors/codex-materialize-runner.js +5 -5
  129. package/dist/connectors/codex-materialize.d.ts +1 -1
  130. package/dist/connectors/index.d.ts +1 -1
  131. package/dist/connectors/index.js +7 -7
  132. package/dist/consolidation-provenance-check.d.ts +2 -2
  133. package/dist/consolidation-undo.d.ts +2 -2
  134. package/dist/contradiction/index.d.ts +2 -2
  135. package/dist/contradiction/index.js +2 -2
  136. package/dist/{contradiction-scan-SSKCAMLX.js → contradiction-scan-J57T5QV7.js} +3 -3
  137. package/dist/conversation-index/backend.d.ts +1 -1
  138. package/dist/conversation-index/chunker.d.ts +1 -1
  139. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  140. package/dist/conversation-index/indexer.d.ts +1 -1
  141. package/dist/conversation-index/search.d.ts +1 -1
  142. package/dist/day-summary.d.ts +1 -1
  143. package/dist/day-summary.js +2 -2
  144. package/dist/delinearize.d.ts +1 -1
  145. package/dist/direct-answer-wiring.d.ts +1 -1
  146. package/dist/direct-answer.d.ts +1 -1
  147. package/dist/embedding-fallback.d.ts +1 -1
  148. package/dist/embedding-fallback.js +2 -2
  149. package/dist/enrichment/index.d.ts +1 -1
  150. package/dist/entity-retrieval.d.ts +2 -2
  151. package/dist/entity-retrieval.js +6 -6
  152. package/dist/entity-schema.d.ts +1 -1
  153. package/dist/explicit-capture.d.ts +5 -5
  154. package/dist/explicit-capture.js +2 -2
  155. package/dist/extraction-faithfulness.d.ts +1 -1
  156. package/dist/extraction-faithfulness.js +2 -2
  157. package/dist/extraction-judge-telemetry.d.ts +1 -1
  158. package/dist/extraction-judge-training.d.ts +1 -1
  159. package/dist/extraction-judge.d.ts +1 -1
  160. package/dist/extraction-judge.js +2 -2
  161. package/dist/extraction.d.ts +17 -2
  162. package/dist/extraction.js +12 -7
  163. package/dist/fallback-llm.d.ts +5 -2
  164. package/dist/fallback-llm.js +1 -1
  165. package/dist/{first-start-migration-4I3VDSYS.js → first-start-migration-TI67YMLG.js} +2 -2
  166. package/dist/graph-dashboard-diff.d.ts +1 -1
  167. package/dist/graph-dashboard-key.d.ts +1 -1
  168. package/dist/graph-dashboard-parser.d.ts +1 -1
  169. package/dist/graph-edge-reinforcement.d.ts +1 -1
  170. package/dist/graph-snapshot.d.ts +1 -1
  171. package/dist/graph.d.ts +1 -1
  172. package/dist/identity-continuity.d.ts +1 -1
  173. package/dist/importance.d.ts +1 -1
  174. package/dist/importers/index.d.ts +1 -1
  175. package/dist/index.d.ts +13 -13
  176. package/dist/index.js +56 -55
  177. package/dist/intent.d.ts +1 -1
  178. package/dist/lcm/engine.d.ts +1 -1
  179. package/dist/lcm/index.d.ts +1 -1
  180. package/dist/lcm/tools.d.ts +1 -1
  181. package/dist/lifecycle.d.ts +1 -1
  182. package/dist/live-connectors-runner.d.ts +1 -1
  183. package/dist/live-connectors-runner.js +3 -2
  184. package/dist/local-llm.d.ts +1 -1
  185. package/dist/local-llm.js +2 -2
  186. package/dist/local-model-endpoint.d.ts +1 -1
  187. package/dist/maintenance/memory-governance.d.ts +1 -1
  188. package/dist/maintenance/memory-governance.js +5 -5
  189. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
  190. package/dist/maintenance/rebuild-memory-projection.js +6 -6
  191. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  192. package/dist/memory-action-policy.d.ts +1 -1
  193. package/dist/memory-cache.d.ts +1 -1
  194. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  195. package/dist/memory-projection-store.d.ts +1 -1
  196. package/dist/memory-provenance.d.ts +1 -1
  197. package/dist/memory-worth-outcomes.d.ts +2 -2
  198. package/dist/models-json.d.ts +1 -1
  199. package/dist/namespaces/migrate.d.ts +3 -3
  200. package/dist/namespaces/migrate.js +10 -10
  201. package/dist/namespaces/principal.d.ts +1 -1
  202. package/dist/namespaces/principal.js +2 -2
  203. package/dist/namespaces/search.d.ts +1 -1
  204. package/dist/namespaces/search.js +4 -4
  205. package/dist/namespaces/storage.d.ts +3 -3
  206. package/dist/namespaces/storage.js +6 -6
  207. package/dist/native-knowledge.d.ts +1 -1
  208. package/dist/operator-toolkit.d.ts +2 -2
  209. package/dist/operator-toolkit.js +16 -16
  210. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  211. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  212. package/dist/orchestration/maintenance.d.ts +2 -2
  213. package/dist/orchestration/maintenance.js +10 -10
  214. package/dist/{orchestrator-xePY-WPe.d.ts → orchestrator-CKctWsw9.d.ts} +51 -4
  215. package/dist/orchestrator.d.ts +5 -5
  216. package/dist/orchestrator.js +56 -55
  217. package/dist/patterns-cli.d.ts +1 -1
  218. package/dist/{pipeline-Zgwz-Kne.d.ts → pipeline-BWwGIys5.d.ts} +1 -1
  219. package/dist/policy-runtime.d.ts +1 -1
  220. package/dist/provenance.d.ts +1 -1
  221. package/dist/{qmd-B6Xwfn5_.d.ts → qmd-DFuUrBWW.d.ts} +1 -1
  222. package/dist/qmd-preflight.d.ts +2 -2
  223. package/dist/qmd-recall-cache.d.ts +1 -1
  224. package/dist/qmd.d.ts +2 -2
  225. package/dist/recall-disclosure-escalation.d.ts +1 -1
  226. package/dist/recall-explain-renderer.d.ts +1 -1
  227. package/dist/recall-explain-renderer.js +3 -3
  228. package/dist/recall-planner-llm.d.ts +1 -1
  229. package/dist/recall-planner-llm.js +1 -1
  230. package/dist/recall-state.d.ts +1 -1
  231. package/dist/recall-tag-filter.d.ts +1 -1
  232. package/dist/recall-timings.d.ts +1 -1
  233. package/dist/recall-xray-cli.d.ts +1 -1
  234. package/dist/recall-xray-cli.js +4 -4
  235. package/dist/recall-xray-renderer.d.ts +1 -1
  236. package/dist/recall-xray-renderer.js +3 -3
  237. package/dist/recall-xray.d.ts +1 -1
  238. package/dist/recall-xray.js +2 -2
  239. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  240. package/dist/replay/normalizers/claude.d.ts +1 -1
  241. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  242. package/dist/replay/normalizers/shared.d.ts +1 -1
  243. package/dist/replay/runner.d.ts +1 -1
  244. package/dist/replay/types.d.ts +1 -1
  245. package/dist/resolve-auth-token.d.ts +1 -1
  246. package/dist/resume-bundles.js +2 -2
  247. package/dist/retrieval-agents.d.ts +2 -2
  248. package/dist/retrieval-tiers.d.ts +1 -1
  249. package/dist/routing/engine.d.ts +1 -1
  250. package/dist/routing/store.d.ts +1 -1
  251. package/dist/schemas.d.ts +52 -52
  252. package/dist/search/embed-helper.d.ts +1 -1
  253. package/dist/search/embed-helper.js +2 -2
  254. package/dist/search/factory.d.ts +1 -1
  255. package/dist/search/factory.js +3 -3
  256. package/dist/search/index.d.ts +1 -1
  257. package/dist/search/index.js +3 -3
  258. package/dist/search/lancedb-backend.d.ts +1 -1
  259. package/dist/search/meilisearch-backend.d.ts +1 -1
  260. package/dist/search/noop-backend.d.ts +1 -1
  261. package/dist/search/orama-backend.d.ts +1 -1
  262. package/dist/search/port.d.ts +1 -1
  263. package/dist/search/remote-backend.d.ts +1 -1
  264. package/dist/{semantic-consolidation-ClfD_CWg.d.ts → semantic-consolidation-1Wlr7NWL.d.ts} +1 -1
  265. package/dist/semantic-consolidation.d.ts +2 -2
  266. package/dist/semantic-consolidation.js +7 -7
  267. package/dist/semantic-rule-promotion.js +5 -5
  268. package/dist/semantic-rule-verifier.d.ts +1 -1
  269. package/dist/semantic-rule-verifier.js +5 -5
  270. package/dist/session-observer-bands.d.ts +1 -1
  271. package/dist/session-observer-state.d.ts +1 -1
  272. package/dist/shared-context/manager.d.ts +9 -9
  273. package/dist/shared-context/manager.js +2 -2
  274. package/dist/signal.d.ts +1 -1
  275. package/dist/{storage-ZrHZ0dSM.d.ts → storage-BQwP90sb.d.ts} +1 -1
  276. package/dist/storage.d.ts +2 -2
  277. package/dist/storage.js +4 -4
  278. package/dist/summarizer.d.ts +1 -1
  279. package/dist/summarizer.js +4 -4
  280. package/dist/summary-snapshot.d.ts +1 -1
  281. package/dist/temporal-supersession.d.ts +2 -2
  282. package/dist/temporal-timeline-recall.d.ts +1 -1
  283. package/dist/temporal-validity.d.ts +1 -1
  284. package/dist/threading.d.ts +1 -1
  285. package/dist/tier-migration.d.ts +2 -2
  286. package/dist/tier-routing.d.ts +1 -1
  287. package/dist/{tier-stats-5XF4UBRQ.js → tier-stats-KV7IWCJR.js} +2 -2
  288. package/dist/topics.d.ts +1 -1
  289. package/dist/transcript.d.ts +1 -1
  290. package/dist/transfer/types.d.ts +66 -66
  291. package/dist/trust-score-stage.d.ts +1 -1
  292. package/dist/trust-score.d.ts +1 -1
  293. package/dist/{types-BbtQZBq9.d.ts → types-DgIkyAsK.d.ts} +39 -1
  294. package/dist/types.d.ts +1 -1
  295. package/dist/types.js +1 -1
  296. package/dist/utility-runtime.d.ts +1 -1
  297. package/dist/verified-recall.js +5 -5
  298. package/package.json +2 -2
  299. package/src/buffer-session.test.ts +40 -8
  300. package/src/buffer.ts +41 -17
  301. package/src/capabilities.test.ts +1 -0
  302. package/src/capabilities.ts +4 -0
  303. package/src/config.test.ts +19 -0
  304. package/src/config.ts +29 -0
  305. package/src/extraction.test.ts +39 -0
  306. package/src/extraction.ts +72 -2
  307. package/src/fallback-llm.ts +26 -5
  308. package/src/operator-toolkit.ts +53 -0
  309. package/src/orchestration/extraction-run.test.ts +639 -0
  310. package/src/orchestration/extraction-run.ts +366 -2
  311. package/src/orchestration/session-context.ts +2 -0
  312. package/src/orchestration/turn-ingestion.ts +5 -0
  313. package/src/orchestrator-flush.test.ts +36 -2
  314. package/src/orchestrator.ts +12 -1
  315. package/src/storage.ts +34 -0
  316. package/src/types.ts +40 -0
  317. package/dist/chunk-2V6L3L56.js.map +0 -1
  318. package/dist/chunk-3WULMX34.js.map +0 -1
  319. package/dist/chunk-CZGIIADK.js.map +0 -1
  320. package/dist/chunk-JIKJPDQJ.js.map +0 -1
  321. package/dist/chunk-METJNEIB.js.map +0 -1
  322. package/dist/chunk-PWSZNML3.js.map +0 -1
  323. package/dist/chunk-QLVLW7UD.js.map +0 -1
  324. package/dist/chunk-TQNRI55H.js.map +0 -1
  325. package/dist/chunk-VSF7AAGE.js.map +0 -1
  326. /package/dist/{auto-sync-4A5YBWYO.js.map → auto-sync-676N6N6Y.js.map} +0 -0
  327. /package/dist/{chunk-SFHI7JBE.js.map → chunk-2FSHROYK.js.map} +0 -0
  328. /package/dist/{chunk-WHZ7MK72.js.map → chunk-4YUPLX7U.js.map} +0 -0
  329. /package/dist/{chunk-ZIKJD6NL.js.map → chunk-53Y3TETS.js.map} +0 -0
  330. /package/dist/{chunk-UI4FVU6T.js.map → chunk-5CJVLG3H.js.map} +0 -0
  331. /package/dist/{chunk-BAVVI5GP.js.map → chunk-7PND4PUC.js.map} +0 -0
  332. /package/dist/{chunk-NREKOGZ2.js.map → chunk-ARD5NE4M.js.map} +0 -0
  333. /package/dist/{chunk-GCTJG5MP.js.map → chunk-AWHOZTF6.js.map} +0 -0
  334. /package/dist/{chunk-ZGPDSEKG.js.map → chunk-BFVEEB7W.js.map} +0 -0
  335. /package/dist/{chunk-AZK3NEHL.js.map → chunk-BLXNPBJI.js.map} +0 -0
  336. /package/dist/{chunk-VTRF5QBX.js.map → chunk-CBOHQNTJ.js.map} +0 -0
  337. /package/dist/{chunk-ZSDRGPMV.js.map → chunk-D6KNJRGX.js.map} +0 -0
  338. /package/dist/{chunk-U5JP7FCM.js.map → chunk-E47TABDR.js.map} +0 -0
  339. /package/dist/{chunk-7TEWOG3R.js.map → chunk-FMECZFUT.js.map} +0 -0
  340. /package/dist/{chunk-QWGNDWKB.js.map → chunk-FNMFJWBY.js.map} +0 -0
  341. /package/dist/{chunk-EDD2G35Z.js.map → chunk-GWMTFDHF.js.map} +0 -0
  342. /package/dist/{chunk-VCW74PNH.js.map → chunk-H67QFYUK.js.map} +0 -0
  343. /package/dist/{chunk-5ELELET5.js.map → chunk-HT2DQ27U.js.map} +0 -0
  344. /package/dist/{chunk-VU3SVYMA.js.map → chunk-HTIIXNJQ.js.map} +0 -0
  345. /package/dist/{chunk-I73G4UH7.js.map → chunk-IALLX7EE.js.map} +0 -0
  346. /package/dist/{chunk-PE6XR4GG.js.map → chunk-IKQTFQMI.js.map} +0 -0
  347. /package/dist/{chunk-XZ5I7OZR.js.map → chunk-KIPLG7S7.js.map} +0 -0
  348. /package/dist/{chunk-RU73ZROD.js.map → chunk-KWJP22WP.js.map} +0 -0
  349. /package/dist/{chunk-MNUHL34L.js.map → chunk-LKT755LP.js.map} +0 -0
  350. /package/dist/{chunk-PI23H7FV.js.map → chunk-M7VGG3ID.js.map} +0 -0
  351. /package/dist/{chunk-HVE3UVAB.js.map → chunk-NS4GEMLR.js.map} +0 -0
  352. /package/dist/{chunk-ZMXXHUEZ.js.map → chunk-O24E5CFS.js.map} +0 -0
  353. /package/dist/{chunk-3BZDBG7U.js.map → chunk-PML3GU4Q.js.map} +0 -0
  354. /package/dist/{chunk-DQ2CJYUM.js.map → chunk-PSQ32V5P.js.map} +0 -0
  355. /package/dist/{chunk-7XY6TJLM.js.map → chunk-PUPQ2PMC.js.map} +0 -0
  356. /package/dist/{chunk-EBVUFGJ4.js.map → chunk-QFOLNA2C.js.map} +0 -0
  357. /package/dist/{chunk-CVEEJKRD.js.map → chunk-RQTCDICG.js.map} +0 -0
  358. /package/dist/{chunk-F7AE7L6G.js.map → chunk-RZEJDF3S.js.map} +0 -0
  359. /package/dist/{chunk-YFBTVOMB.js.map → chunk-SYNYI4ZM.js.map} +0 -0
  360. /package/dist/{chunk-BRDJ2HFP.js.map → chunk-TRWPCR6P.js.map} +0 -0
  361. /package/dist/{chunk-YAN6NTJN.js.map → chunk-UKA7GNI2.js.map} +0 -0
  362. /package/dist/{chunk-XH2Y6UIW.js.map → chunk-VD56CJRR.js.map} +0 -0
  363. /package/dist/{chunk-J3XHPFEU.js.map → chunk-VXMWDXIJ.js.map} +0 -0
  364. /package/dist/{chunk-QT2RZNRT.js.map → chunk-VY6LKKVN.js.map} +0 -0
  365. /package/dist/{chunk-PRTA6444.js.map → chunk-W4PGTAS2.js.map} +0 -0
  366. /package/dist/{chunk-WX4XTAOI.js.map → chunk-WGE3OYC3.js.map} +0 -0
  367. /package/dist/{chunk-RVLE2SNP.js.map → chunk-X6FSCBJZ.js.map} +0 -0
  368. /package/dist/{chunk-6MWGGPJJ.js.map → chunk-Y4EMHIQ5.js.map} +0 -0
  369. /package/dist/{chunk-IGCQHJSQ.js.map → chunk-Z3HJ5L3Z.js.map} +0 -0
  370. /package/dist/{chunk-O2EL7EGG.js.map → chunk-ZQ7UBUZ6.js.map} +0 -0
  371. /package/dist/{chunk-5SORASJK.js.map → chunk-ZXUG3AFP.js.map} +0 -0
  372. /package/dist/{contradiction-scan-SSKCAMLX.js.map → contradiction-scan-J57T5QV7.js.map} +0 -0
  373. /package/dist/{first-start-migration-4I3VDSYS.js.map → first-start-migration-TI67YMLG.js.map} +0 -0
  374. /package/dist/{tier-stats-5XF4UBRQ.js.map → tier-stats-KV7IWCJR.js.map} +0 -0
@@ -492,13 +492,19 @@ test("getTurns prepends retained deferred turns before live turns", async () =>
492
492
  assert.equal(all[1]?.content, "new live turn");
493
493
  });
494
494
 
495
- test("SmartBuffer never prunes logical session buffers that still have pending turns", async () => {
495
+ test("SmartBuffer prefers pruning empty entries and bounds pending-turn buffers at the cap (#1908 policy)", async () => {
496
+ // Pre-#1908 this contract was "never prune non-empty entries". With retry
497
+ // retention (failed extractions keep their turns), unbounded non-empty
498
+ // growth during a provider outage would eventually OOM the daemon — the
499
+ // documented policy (#1908 step 7) is: retain up to MAX_BUFFER_ENTRY_COUNT
500
+ // session entries, then prune the OLDEST sessions with a loud warning.
501
+ // Empty entries are still evicted first.
496
502
  const entries = Object.fromEntries(
497
503
  Array.from({ length: 205 }, (_, index) => [
498
504
  `thread-${index}`,
499
505
  {
500
506
  turns: [makeTurn(`thread-${index}`, `memory ${index}`)],
501
- lastExtractionAt: null,
507
+ lastExtractionAt: new Date(1_700_000_000_000 + index * 60_000).toISOString(),
502
508
  extractionCount: 0,
503
509
  },
504
510
  ]),
@@ -516,14 +522,40 @@ test("SmartBuffer never prunes logical session buffers that still have pending t
516
522
  ...entries,
517
523
  },
518
524
  });
519
- const buffer = new SmartBuffer(parseConfig({}), storage as any);
525
+ const buffer = new SmartBuffer(parseConfig({}), storage as unknown as ConstructorParameters<typeof SmartBuffer>[1]);
520
526
 
521
527
  await buffer.addTurn("active-thread", makeTurn("active-thread", "pending memory"));
522
528
 
523
529
  const persistedKeys = Object.keys(storage.saved?.entries ?? {});
524
- assert.equal(persistedKeys.length, 207);
525
- assert.ok(persistedKeys.includes("default"));
526
- assert.ok(persistedKeys.includes("active-thread"));
527
- assert.ok(persistedKeys.includes("thread-0"));
528
- assert.ok(persistedKeys.includes("thread-204"));
530
+ assert.ok(persistedKeys.length <= 201, `bounded at the cap plus default (got ${persistedKeys.length})`);
531
+ assert.ok(persistedKeys.includes("default"), "default entry never pruned");
532
+ assert.ok(persistedKeys.includes("active-thread"), "the just-written key is retained");
533
+ assert.ok(!persistedKeys.includes("thread-0"), "oldest pending entry pruned once over the cap");
534
+ assert.ok(persistedKeys.includes("thread-204"), "newest pending entry survives");
535
+ });
536
+
537
+ test("pruneEntries evicts oldest NON-empty session entries past the cap (#1908 retry-retention bound)", async () => {
538
+ // Retained failed extractions keep their turns in the buffer, so during a
539
+ // provider outage non-empty entries can exceed MAX_BUFFER_ENTRY_COUNT (200).
540
+ // The cap is the documented bound: the oldest non-empty sessions must be
541
+ // evicted (loudly) rather than growing without bound (codex review).
542
+ const entries: NonNullable<BufferState["entries"]> = {};
543
+ for (let i = 0; i < 210; i += 1) {
544
+ const at = new Date(1_700_000_000_000 + i * 60_000).toISOString();
545
+ entries[`session-${String(i).padStart(3, "0")}`] = {
546
+ turns: [{ role: "user", content: `retained turn ${i}`, timestamp: at, sessionKey: `session-${i}` }],
547
+ lastExtractionAt: at,
548
+ extractionCount: 0,
549
+ };
550
+ }
551
+ const storage = new FakeStorage({ turns: [], lastExtractionAt: null, extractionCount: 0, entries });
552
+ const buffer = new SmartBuffer(parseConfig({}), storage as unknown as ConstructorParameters<typeof SmartBuffer>[1]);
553
+ await buffer.load();
554
+ await buffer.addTurn("session-newest", makeTurn("session-newest", "new turn"));
555
+
556
+ const savedKeys = Object.keys(storage.saved?.entries ?? {});
557
+ assert.ok(savedKeys.length <= 200, `entries bounded at MAX_BUFFER_ENTRY_COUNT (got ${savedKeys.length})`);
558
+ assert.ok(savedKeys.includes("session-newest"), "the just-written key is retained");
559
+ assert.equal(buffer.getTurns("session-000").length, 0, "oldest non-empty entry was evicted");
560
+ assert.ok(buffer.getTurns("session-209").length > 0, "newest pre-existing entry survives");
529
561
  });
package/src/buffer.ts CHANGED
@@ -206,28 +206,52 @@ export class SmartBuffer {
206
206
  if (keys.length <= MAX_BUFFER_ENTRY_COUNT) return;
207
207
 
208
208
  const insertionOrder = new Map(keys.map((key, index) => [key, index]));
209
- const removable = keys
210
- .filter((key) => key !== "default" && !retainKeys.includes(key))
211
- .filter((key) => (entries[key]?.turns.length ?? 0) === 0)
212
- .sort((left, right) => {
213
- const leftAt = this.entryActivityAt(entries[left] ?? {
214
- turns: [],
215
- lastExtractionAt: null,
216
- extractionCount: 0,
217
- });
218
- const rightAt = this.entryActivityAt(entries[right] ?? {
219
- turns: [],
220
- lastExtractionAt: null,
221
- extractionCount: 0,
222
- });
223
- if (leftAt !== rightAt) return leftAt - rightAt;
224
- return (insertionOrder.get(left) ?? 0) - (insertionOrder.get(right) ?? 0);
209
+ const byOldestActivity = (left: string, right: string): number => {
210
+ const leftAt = this.entryActivityAt(entries[left] ?? {
211
+ turns: [],
212
+ lastExtractionAt: null,
213
+ extractionCount: 0,
225
214
  });
215
+ const rightAt = this.entryActivityAt(entries[right] ?? {
216
+ turns: [],
217
+ lastExtractionAt: null,
218
+ extractionCount: 0,
219
+ });
220
+ if (leftAt !== rightAt) return leftAt - rightAt;
221
+ return (insertionOrder.get(left) ?? 0) - (insertionOrder.get(right) ?? 0);
222
+ };
223
+ const candidates = keys.filter((key) => key !== "default" && !retainKeys.includes(key));
224
+ const emptyRemovable = candidates
225
+ .filter((key) => (entries[key]?.turns.length ?? 0) === 0)
226
+ .sort(byOldestActivity);
226
227
 
227
228
  const removableCount = Math.max(0, keys.length - MAX_BUFFER_ENTRY_COUNT);
228
- for (const key of removable.slice(0, removableCount)) {
229
+ let evicted = emptyRemovable.slice(0, removableCount);
230
+ if (evicted.length < removableCount) {
231
+ // Retained failed extractions (#1908 retry retention) keep their turns in
232
+ // the buffer, so empty entries alone may not bring the map back under the
233
+ // cap during a provider outage. The cap is the documented bound — evict
234
+ // the oldest NON-empty sessions too rather than growing unboundedly
235
+ // (codex review).
236
+ const nonEmptyRemovable = candidates
237
+ .filter((key) => (entries[key]?.turns.length ?? 0) > 0)
238
+ .sort(byOldestActivity);
239
+ evicted = evicted.concat(nonEmptyRemovable.slice(0, removableCount - evicted.length));
240
+ }
241
+ for (const key of evicted) {
229
242
  delete entries[key];
230
243
  }
244
+ if (evicted.length > 0) {
245
+ // Loud degradation (extraction hot-loop hardening, Step 7): during a
246
+ // prolonged provider outage the breaker holds extraction off and buffered
247
+ // sessions accumulate. Eviction of the oldest empty session entries past
248
+ // MAX_BUFFER_ENTRY_COUNT is bounded and expected, but must be observable —
249
+ // not silent — so operators can see buffer pressure.
250
+ log.warn(
251
+ `buffer: pruned ${evicted.length} oldest session entr${evicted.length === 1 ? "y" : "ies"} ` +
252
+ `past MAX_BUFFER_ENTRY_COUNT=${MAX_BUFFER_ENTRY_COUNT}; buffered turn data for those sessions was dropped`,
253
+ );
254
+ }
231
255
  }
232
256
 
233
257
  private async loadUnlocked(): Promise<void> {
@@ -366,6 +366,7 @@ const LIFECYCLE_FIELD_TO_FLAG: Record<keyof MemoryLifecycleCapabilitySet, string
366
366
  extractionScopeClassification: "extractionScopeClassificationEnabled",
367
367
  extractionJudge: "extractionJudgeEnabled",
368
368
  extractionDedupe: "extractionDedupeEnabled",
369
+ extractionRetry: "extractionRetryEnabled",
369
370
  extractionTelemetryPrefilter: "extractionTelemetryPrefilterEnabled",
370
371
  extractionJudgeTelemetry: "extractionJudgeTelemetryEnabled",
371
372
  embeddingFallback: "embeddingFallbackEnabled",
@@ -257,6 +257,8 @@ export interface MemoryLifecycleCapabilitySet {
257
257
  readonly extractionJudgeTelemetry: boolean;
258
258
  /** `embeddingFallbackEnabled` — semantic-dedup / archive-search embedding fallback. */
259
259
  readonly embeddingFallback: boolean;
260
+ /** `extractionRetryEnabled` — per-fingerprint backoff + provider circuit breaker on the extraction retry path. */
261
+ readonly extractionRetry: boolean;
260
262
  }
261
263
 
262
264
  /**
@@ -275,6 +277,7 @@ export type MemoryLifecycleConfigProjection = Pick<
275
277
  | "extractionTelemetryPrefilterEnabled"
276
278
  | "extractionJudgeTelemetryEnabled"
277
279
  | "embeddingFallbackEnabled"
280
+ | "extractionRetryEnabled"
278
281
  >;
279
282
 
280
283
  /**
@@ -299,6 +302,7 @@ export function resolveMemoryLifecycleCapabilities(
299
302
  extractionTelemetryPrefilter: config.extractionTelemetryPrefilterEnabled,
300
303
  extractionJudgeTelemetry: config.extractionJudgeTelemetryEnabled,
301
304
  embeddingFallback: config.embeddingFallbackEnabled,
305
+ extractionRetry: config.extractionRetryEnabled,
302
306
  });
303
307
  }
304
308
 
@@ -2142,3 +2142,22 @@ test("parseConfig correction.maxAffected rejects non-integers (Number.isInteger,
2142
2142
  assert.equal(parseConfig({ correctionMaxAffected: 20 }).correctionMaxAffected, 20);
2143
2143
  assert.equal(parseConfig({}).correctionMaxAffected, 10, "absent → documented default");
2144
2144
  });
2145
+
2146
+ test("parseConfig rejects non-positive extraction retry/breaker numerics and falls back to defaults (codex review, rule 17)", () => {
2147
+ const c = parseConfig({
2148
+ extractionRetryMaxBackoffMs: 0,
2149
+ extractionRetryJitterRatio: -0.5,
2150
+ extractionParseEmptyMaxAttempts: 0,
2151
+ extractionBreakerFailureThreshold: -1,
2152
+ extractionBreakerCooldownMs: -5, // 0 is a valid "half-open immediately" escape hatch; only negative is rejected
2153
+ extractionBreakerAuthCooldownMs: -100,
2154
+ extractionRetryScheduleMs: [1000, 0, -5],
2155
+ });
2156
+ assert.equal(c.extractionRetryMaxBackoffMs, 21_600_000, "non-positive cap -> default");
2157
+ assert.equal(c.extractionRetryJitterRatio, 0.2, "out-of-range jitter -> default");
2158
+ assert.equal(c.extractionParseEmptyMaxAttempts, 3, "non-positive attempts -> default");
2159
+ assert.equal(c.extractionBreakerFailureThreshold, 5, "non-positive threshold -> default");
2160
+ assert.equal(c.extractionBreakerCooldownMs, 300_000, "non-positive cooldown -> default");
2161
+ assert.equal(c.extractionBreakerAuthCooldownMs, 1_800_000, "non-positive auth cooldown -> default");
2162
+ assert.deepEqual(c.extractionRetryScheduleMs, [60_000, 300_000, 1_800_000, 7_200_000], "schedule with non-positive entry -> default");
2163
+ });
package/src/config.ts CHANGED
@@ -2826,6 +2826,35 @@ export function parseConfig(
2826
2826
  typeof cfg.localLlm400TripThreshold === "number" ? cfg.localLlm400TripThreshold : 5,
2827
2827
  localLlm400CooldownMs:
2828
2828
  typeof cfg.localLlm400CooldownMs === "number" ? cfg.localLlm400CooldownMs : 120_000,
2829
+ // Extraction retry/backoff + circuit breaker (extraction hot-loop hardening)
2830
+ extractionRetryEnabled: coerceBooleanLike(cfg.extractionRetryEnabled) ?? true,
2831
+ extractionRetryScheduleMs:
2832
+ Array.isArray(cfg.extractionRetryScheduleMs) &&
2833
+ cfg.extractionRetryScheduleMs.length > 0 &&
2834
+ cfg.extractionRetryScheduleMs.every((n) => typeof n === "number" && Number.isFinite(n) && n > 0)
2835
+ ? (cfg.extractionRetryScheduleMs as number[])
2836
+ : [60_000, 300_000, 1_800_000, 7_200_000],
2837
+ extractionRetryMaxBackoffMs:
2838
+ typeof cfg.extractionRetryMaxBackoffMs === "number" && cfg.extractionRetryMaxBackoffMs > 0
2839
+ ? cfg.extractionRetryMaxBackoffMs : 21_600_000,
2840
+ extractionRetryJitterRatio:
2841
+ typeof cfg.extractionRetryJitterRatio === "number" &&
2842
+ cfg.extractionRetryJitterRatio >= 0 && cfg.extractionRetryJitterRatio <= 1
2843
+ ? cfg.extractionRetryJitterRatio : 0.2,
2844
+ extractionParseEmptyMaxAttempts:
2845
+ typeof cfg.extractionParseEmptyMaxAttempts === "number" &&
2846
+ Number.isInteger(cfg.extractionParseEmptyMaxAttempts) && cfg.extractionParseEmptyMaxAttempts > 0
2847
+ ? cfg.extractionParseEmptyMaxAttempts : 3,
2848
+ extractionBreakerFailureThreshold:
2849
+ typeof cfg.extractionBreakerFailureThreshold === "number" &&
2850
+ Number.isInteger(cfg.extractionBreakerFailureThreshold) && cfg.extractionBreakerFailureThreshold > 0
2851
+ ? cfg.extractionBreakerFailureThreshold : 5,
2852
+ extractionBreakerCooldownMs:
2853
+ typeof cfg.extractionBreakerCooldownMs === "number" && cfg.extractionBreakerCooldownMs >= 0
2854
+ ? cfg.extractionBreakerCooldownMs : 300_000,
2855
+ extractionBreakerAuthCooldownMs:
2856
+ typeof cfg.extractionBreakerAuthCooldownMs === "number" && cfg.extractionBreakerAuthCooldownMs >= 0
2857
+ ? cfg.extractionBreakerAuthCooldownMs : 1_800_000,
2829
2858
  // Local LLM fast tier (v9.1)
2830
2859
  localLlmFastEnabled: cfg.localLlmFastEnabled === true,
2831
2860
  localLlmFastModel:
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Extraction failure-classification tests (extraction hot-loop hardening).
3
+ *
4
+ * The retry/backoff + circuit-breaker layer keys off `ExtractionFailureClass`.
5
+ * These tests lock the mapping from synthetic provider errors (429/500/401/
6
+ * no-models/parse-empty) to the coarse class, reusing the shared
7
+ * `isTransientHttpError` classifier rather than a bespoke copy.
8
+ */
9
+
10
+ import assert from "node:assert/strict";
11
+ import test from "node:test";
12
+
13
+ import { classifyExtractionThrownError, classifyFallbackParseFailure } from "./extraction.js";
14
+
15
+ test("classifyExtractionThrownError: 401/403 → auth_config", () => {
16
+ assert.equal(classifyExtractionThrownError({ status: 401 }), "auth_config");
17
+ assert.equal(classifyExtractionThrownError({ status: 403 }), "auth_config");
18
+ assert.equal(classifyExtractionThrownError({ statusCode: 401 }), "auth_config");
19
+ });
20
+
21
+ test("classifyExtractionThrownError: 429/5xx → provider_retryable", () => {
22
+ assert.equal(classifyExtractionThrownError({ status: 429 }), "provider_retryable");
23
+ assert.equal(classifyExtractionThrownError({ status: 500 }), "provider_retryable");
24
+ assert.equal(classifyExtractionThrownError({ status: 503 }), "provider_retryable");
25
+ });
26
+
27
+ test("classifyExtractionThrownError: bare network error / unknown → provider_retryable (fail-open toward retry)", () => {
28
+ assert.equal(classifyExtractionThrownError(new Error("socket hang up")), "provider_retryable");
29
+ // A terminal non-auth 4xx still defaults to retryable (capped by the attempt budget).
30
+ assert.equal(classifyExtractionThrownError({ status: 400 }), "provider_retryable");
31
+ assert.equal(classifyExtractionThrownError(null), "provider_retryable");
32
+ assert.equal(classifyExtractionThrownError("not an object"), "provider_retryable");
33
+ });
34
+
35
+ test("classifyFallbackParseFailure: maps the gateway parse-failure reasons", () => {
36
+ assert.equal(classifyFallbackParseFailure("no_models"), "auth_config");
37
+ assert.equal(classifyFallbackParseFailure("http_error"), "provider_retryable");
38
+ assert.equal(classifyFallbackParseFailure("empty"), "parse_empty");
39
+ });
package/src/extraction.ts CHANGED
@@ -26,6 +26,7 @@ import type {
26
26
  GatewayConfig,
27
27
  MemoryCategory,
28
28
  DaySummaryResult as DaySummaryResultShape,
29
+ ExtractionFailureClass,
29
30
  } from "./types.js";
30
31
  import { ModelRegistry } from "./model-registry.js";
31
32
  import { extractJsonCandidates } from "./json-extract.js";
@@ -38,6 +39,7 @@ import { normalizeProcedureSteps } from "./procedural/procedure-types.js";
38
39
  import { normalizeReasoningTrace } from "./reasoning-trace-types.js";
39
40
  import { looksLikeMechanicalTelemetryTranscript } from "./telemetry-transcript.js";
40
41
  import { buildFactProvenance, type ProvenanceTurnInput } from "./provenance.js";
42
+ import { isTransientHttpError } from "./connectors/live/transient-errors.js";
41
43
  import { resolvePipelineProcessingCapabilities } from "./capabilities.js";
42
44
  import { resolveMemoryLifecycleCapabilities,
43
45
  resolveLocalLlmCapabilities,resolveRecallAuxiliaryCapabilities } from "./capabilities.js";
@@ -46,6 +48,56 @@ type ExtractionQuestion = ExtractionResult["questions"][number];
46
48
  type ExtractedFactResult = ExtractionResult["facts"][number];
47
49
  type ExtractedEntityResult = ExtractionResult["entities"][number];
48
50
  type ExtractedRelationshipResult = NonNullable<ExtractionResult["relationships"]>[number];
51
+
52
+ /**
53
+ * Read a numeric HTTP status off a thrown error without re-implementing the
54
+ * shared transient classifier. Uses `in`/typeof narrowing (no inline casts).
55
+ */
56
+ function httpStatusFromError(err: unknown): number | undefined {
57
+ if (!err || typeof err !== "object") return undefined;
58
+ if ("status" in err && typeof err.status === "number" && Number.isFinite(err.status)) return err.status;
59
+ if ("statusCode" in err && typeof err.statusCode === "number" && Number.isFinite(err.statusCode)) {
60
+ return err.statusCode;
61
+ }
62
+ return undefined;
63
+ }
64
+
65
+ /**
66
+ * Map a thrown extraction error to a coarse failure class for the retry/breaker
67
+ * layer. 401/403 → `auth_config` (misconfigured provider, open the breaker);
68
+ * transient (429/5xx/network, per the shared `isTransientHttpError`) → back off;
69
+ * anything else defaults to `provider_retryable` (fail-open toward retry, but
70
+ * capped by the per-fingerprint attempt budget). Never throws.
71
+ */
72
+ export function classifyExtractionThrownError(err: unknown): ExtractionFailureClass {
73
+ try {
74
+ const status = httpStatusFromError(err);
75
+ if (status === 401 || status === 403) return "auth_config";
76
+ if (isTransientHttpError(err)) return "provider_retryable";
77
+ return "provider_retryable";
78
+ } catch {
79
+ return "provider_retryable";
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Map the gateway fallback's discriminated parse-failure reason to a coarse
85
+ * extraction failure class. "no models configured" is an auth/config problem;
86
+ * an HTTP/transport failure backs off; an unparseable-but-present response is a
87
+ * genuine empty-parse.
88
+ */
89
+ export function classifyFallbackParseFailure(
90
+ reason: "no_models" | "empty" | "http_error",
91
+ ): ExtractionFailureClass {
92
+ switch (reason) {
93
+ case "no_models":
94
+ return "auth_config";
95
+ case "http_error":
96
+ return "provider_retryable";
97
+ case "empty":
98
+ return "parse_empty";
99
+ }
100
+ }
49
101
  const PROACTIVE_MIN_CONFIDENCE = 0.8;
50
102
  const CONSOLIDATION_RESPONSE_SCHEMA = `{
51
103
  "items": [
@@ -975,7 +1027,7 @@ export class ExtractionEngine {
975
1027
  options: { temperature?: number; maxTokens?: number } = {},
976
1028
  ): Promise<T | null> {
977
1029
  const detailed = await this.fallbackLlm.parseWithSchemaDetailed(messages, schema, this.withGatewayAgent(options));
978
- if (detailed?.result) {
1030
+ if ("modelUsed" in detailed) {
979
1031
  const durationMs = Date.now() - startedAtMs;
980
1032
  this.emit({
981
1033
  kind: "llm_end",
@@ -1119,11 +1171,16 @@ export class ExtractionEngine {
1119
1171
  localLlm: resolveLocalLlmCapabilities(this.config).localLlm,
1120
1172
  });
1121
1173
  this.profiler.startSpan("total", extractionTraceId);
1174
+ // True when a local or direct extractor was attempted before the gateway
1175
+ // fallback, so a gateway "no models" result doesn't re-classify a real
1176
+ // primary failure as auth_config (codex review: preserve direct/local failures).
1177
+ let primaryExtractorAttempted = false;
1122
1178
 
1123
1179
  try {
1124
1180
  // Try local LLM first if enabled
1125
1181
  if (this.shouldUseLocalLlm) {
1126
1182
  this.profiler.startSpan("local-llm", extractionTraceId);
1183
+ primaryExtractorAttempted = true;
1127
1184
  try {
1128
1185
  const localResult = await this.extractWithLocalLlm(conversation, existingEntities);
1129
1186
  if (localResult) {
@@ -1144,6 +1201,7 @@ export class ExtractionEngine {
1144
1201
  entities: [],
1145
1202
  questions: [],
1146
1203
  extractionFailure: "local_llm_unavailable",
1204
+ extractionFailureClass: "provider_retryable",
1147
1205
  };
1148
1206
  }
1149
1207
  log.info("extraction: local LLM unavailable, falling back to gateway default AI");
@@ -1156,6 +1214,7 @@ export class ExtractionEngine {
1156
1214
  entities: [],
1157
1215
  questions: [],
1158
1216
  extractionFailure: "local_llm_error",
1217
+ extractionFailureClass: "provider_retryable",
1159
1218
  };
1160
1219
  }
1161
1220
  log.info("extraction: local LLM error, falling back to gateway default AI:", err);
@@ -1168,6 +1227,7 @@ export class ExtractionEngine {
1168
1227
  // Try direct OpenAI-compatible client (Scryr, OpenRouter, etc.)
1169
1228
  if (this.shouldUseDirectClient) {
1170
1229
  this.profiler.startSpan("direct-client", extractionTraceId);
1230
+ primaryExtractorAttempted = true;
1171
1231
  try {
1172
1232
  const directResult = await this.extractWithDirectClient(conversation, existingEntities);
1173
1233
  if (directResult) {
@@ -1250,7 +1310,7 @@ export class ExtractionEngine {
1250
1310
 
1251
1311
  const fallbackDurationMs = Date.now() - fallbackStartTime;
1252
1312
 
1253
- if (detailed?.result && Array.isArray(detailed.result.facts)) {
1313
+ if ("modelUsed" in detailed && Array.isArray(detailed.result.facts)) {
1254
1314
  const result = detailed.result;
1255
1315
  this.emit({
1256
1316
  kind: "llm_end", traceId: fallbackTraceId, model: detailed.modelUsed, operation: "extraction",
@@ -1299,12 +1359,21 @@ export class ExtractionEngine {
1299
1359
  durationMs: fallbackDurationMs, error: "fallback returned no parsed output",
1300
1360
  });
1301
1361
  log.warn("extraction fallback returned no parsed output");
1362
+ const fallbackParseFailureClass: ExtractionFailureClass =
1363
+ detailed.result === null
1364
+ ? detailed.failureReason === "no_models" && primaryExtractorAttempted
1365
+ ? // Gateway had no models, but a local/direct extractor already failed —
1366
+ // the root cause is the primary (transient), not gateway auth/config.
1367
+ "provider_retryable"
1368
+ : classifyFallbackParseFailure(detailed.failureReason)
1369
+ : "parse_empty";
1302
1370
  return {
1303
1371
  facts: [],
1304
1372
  profileUpdates: [],
1305
1373
  entities: [],
1306
1374
  questions: [],
1307
1375
  extractionFailure: "fallback_no_parsed_output",
1376
+ extractionFailureClass: fallbackParseFailureClass,
1308
1377
  };
1309
1378
  } catch (err) {
1310
1379
  this.emit({
@@ -1318,6 +1387,7 @@ export class ExtractionEngine {
1318
1387
  entities: [],
1319
1388
  questions: [],
1320
1389
  extractionFailure: "fallback_failed",
1390
+ extractionFailureClass: classifyExtractionThrownError(err),
1321
1391
  };
1322
1392
  } finally {
1323
1393
  try { this.profiler.endSpan("gateway-fallback", extractionTraceId); } catch { /* span may already be closed */ }
@@ -280,9 +280,30 @@ export class FallbackLlmClient {
280
280
  messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
281
281
  schema: { parse: (data: unknown) => T },
282
282
  options: FallbackLlmOptions = {},
283
- ): Promise<{ result: T; modelUsed: string } | null> {
284
- const response = await this.chatCompletion(messages, options);
285
- if (!response?.content) return null;
283
+ ): Promise<
284
+ | { result: T; modelUsed: string }
285
+ | { result: null; failureReason: "no_models" | "empty" | "http_error" }
286
+ > {
287
+ let response: FallbackLlmResponse | null;
288
+ try {
289
+ response = await this.chatCompletion(messages, options);
290
+ } catch (err) {
291
+ // Caller aborts must propagate (e.g. recall planner cancellation) — do
292
+ // not swallow them as a provider failure, or abort-driven callers lose
293
+ // cancellation and treat it as an extraction error (codex review).
294
+ if (options.signal?.aborted) throw err;
295
+ log.warn("fallback LLM: chatCompletion threw during structured parse:", err);
296
+ return { result: null, failureReason: "http_error" };
297
+ }
298
+ if (!response?.content) {
299
+ // chatCompletion returns null both when no models are configured
300
+ // (auth/config) and when every configured model errored (transient).
301
+ // Disambiguate via the resolved model chain so the retry layer can pick
302
+ // the right failure class.
303
+ const hasModels =
304
+ this.getModelChain(options.agentId, options.model, options.modelChain).length > 0;
305
+ return { result: null, failureReason: hasModels ? "http_error" : "no_models" };
306
+ }
286
307
 
287
308
  try {
288
309
  const candidates = extractJsonCandidates(response.content);
@@ -294,10 +315,10 @@ export class FallbackLlmClient {
294
315
  // keep trying other candidates
295
316
  }
296
317
  }
297
- return null;
318
+ return { result: null, failureReason: "empty" };
298
319
  } catch (err) {
299
320
  log.warn("fallback LLM: failed to parse structured output:", err);
300
- return null;
321
+ return { result: null, failureReason: "empty" };
301
322
  }
302
323
  }
303
324
 
@@ -1272,6 +1272,59 @@ export async function runOperatorDoctor(options: OperatorDoctorOptions): Promise
1272
1272
  details: meta,
1273
1273
  });
1274
1274
 
1275
+ // Extraction resilience (extraction hot-loop hardening). Surfaces the
1276
+ // persisted per-fingerprint retry/backoff state so an operator can see when
1277
+ // the extraction provider is failing without tailing logs. auth/config
1278
+ // failures are an error (misconfigured provider, will not self-heal at the
1279
+ // current cadence); transient backoff is a warn; a clean state is ok. Reads
1280
+ // the already-loaded meta — no extra I/O, cross-process safe (the in-memory
1281
+ // breaker lives in the daemon; this derives from durable state).
1282
+ const retryEntries = meta.extractionRetryState ?? [];
1283
+ const authConfigFailures = retryEntries.filter((entry) => entry.lastFailureClass === "auth_config");
1284
+ const nowMs = Date.now();
1285
+ const parkedFingerprints = retryEntries.filter((entry) => {
1286
+ const eligibleAt = Date.parse(entry.nextEligibleAt);
1287
+ return Number.isFinite(eligibleAt) && eligibleAt > nowMs;
1288
+ });
1289
+ // The in-memory breaker opens only after extractionBreakerFailureThreshold
1290
+ // CONSECUTIVE provider failures, each of which parks a fingerprint — so
1291
+ // doctor infers "likely open" only when the parked transient count reaches
1292
+ // that threshold. A single parked fingerprint is ordinary backoff, not a
1293
+ // suspended-extraction signal (cursor review: don't overclaim). The breaker
1294
+ // itself is per-process (daemon-only), hence the durable-signal inference.
1295
+ const providerRetryableParked = parkedFingerprints.filter(
1296
+ (entry) => entry.lastFailureClass === "provider_retryable",
1297
+ );
1298
+ const breakerLikelyOpen =
1299
+ providerRetryableParked.length >= Math.max(1, config.extractionBreakerFailureThreshold);
1300
+ const resilienceStatus: "ok" | "warn" | "error" =
1301
+ authConfigFailures.length > 0 ? "error" : parkedFingerprints.length > 0 ? "warn" : "ok";
1302
+ checks.push({
1303
+ key: "extraction-resilience",
1304
+ status: resilienceStatus,
1305
+ summary:
1306
+ resilienceStatus === "error"
1307
+ ? `Extraction provider has ${authConfigFailures.length} auth/config failure(s); the circuit breaker opens immediately and extraction is suspended.`
1308
+ : resilienceStatus === "warn"
1309
+ ? `${parkedFingerprints.length} extraction fingerprint(s) in backoff after transient provider failures${
1310
+ breakerLikelyOpen ? "; the provider circuit breaker is likely OPEN in the daemon (non-forced extraction is suspended until it half-opens)" : ""
1311
+ }.`
1312
+ : "No extraction provider failures are currently parked.",
1313
+ remediation:
1314
+ resilienceStatus === "error"
1315
+ ? 'Check the extraction LLM gateway/model configuration (a 401/403 or "no models configured" was seen). Extraction resumes automatically once the provider is reachable.'
1316
+ : resilienceStatus === "warn"
1317
+ ? "Transient provider failures (e.g. 429/5xx). Backoff clears automatically as the provider recovers; no action needed unless it persists."
1318
+ : undefined,
1319
+ details: {
1320
+ backoffFingerprintCount: retryEntries.length,
1321
+ authConfigFailureCount: authConfigFailures.length,
1322
+ parkedFingerprintCount: parkedFingerprints.length,
1323
+ providerRetryableParkedCount: providerRetryableParked.length,
1324
+ breakerLikelyOpen,
1325
+ },
1326
+ });
1327
+
1275
1328
  // Memory projection health (issue #1829). Surfaces a present-but-unopenable
1276
1329
  // projection (wrong ABI / corrupt) that previously degraded every memory
1277
1330
  // list to a silent full-corpus scan. Absent and openable are both "ok".