@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
@@ -40,7 +40,7 @@ import {
40
40
  } from "../capabilities.js";
41
41
  import { parseFlexibleIsoTimestamp } from "../utils/iso-timestamp.js";
42
42
  import { log } from "../logger.js";
43
- import type { PluginConfig, BufferTurn, ExtractionResult } from "../types.js";
43
+ import type { PluginConfig, BufferTurn, ExtractionResult, MetaState, ExtractionFailureClass } from "../types.js";
44
44
  import type { TierMigrationCycleSummary } from "../recall-state.js";
45
45
 
46
46
  export interface ExtractionRunResult {
@@ -130,6 +130,66 @@ export function deriveTopicsFromExtraction(result: ExtractionResult): string[] {
130
130
  return [...topics].slice(0, 16);
131
131
  }
132
132
 
133
+ /** Bounded cap on persisted per-fingerprint retry-state entries (per namespace meta). */
134
+ const EXTRACTION_RETRY_STATE_MAX_ENTRIES = 500;
135
+
136
+ interface ExtractionRetryStateEntry {
137
+ attempts: number;
138
+ nextEligibleAtMs: number;
139
+ firstFailedAtMs: number;
140
+ lastFailureClass: ExtractionFailureClass;
141
+ }
142
+
143
+ export interface ExtractionResilienceStatus {
144
+ breaker: {
145
+ state: "closed" | "open" | "half_open";
146
+ openUntilMs: number;
147
+ consecutiveFailures: number;
148
+ lastReason: string;
149
+ };
150
+ backoffFingerprintCount: number;
151
+ }
152
+
153
+ /**
154
+ * Pure backoff scheduler. Returns the absolute ms timestamp at which a
155
+ * fingerprint that has failed `attempts` times becomes eligible again:
156
+ * exponential via the configured schedule, clamped to `maxBackoffMs`, with
157
+ * ±`jitterRatio` multiplicative jitter. `rng` is injectable for deterministic
158
+ * tests. Never returns a timestamp before `now`.
159
+ */
160
+ export function computeExtractionRetryNextEligibleMs(
161
+ attempts: number,
162
+ scheduleMs: readonly number[],
163
+ maxBackoffMs: number,
164
+ jitterRatio: number,
165
+ now: number,
166
+ rng: () => number = Math.random,
167
+ ): number {
168
+ const cap = Number.isFinite(maxBackoffMs) && maxBackoffMs > 0 ? maxBackoffMs : 0;
169
+ if (!Array.isArray(scheduleMs) || scheduleMs.length === 0) {
170
+ return now + cap;
171
+ }
172
+ const idx = Math.min(Math.max(attempts - 1, 0), scheduleMs.length - 1);
173
+ const step = scheduleMs[idx];
174
+ const base = Math.min(typeof step === "number" && step > 0 ? step : cap, cap);
175
+ const jitter = 1 + (rng() * 2 - 1) * jitterRatio;
176
+ return now + Math.max(0, Math.round(base * jitter));
177
+ }
178
+
179
+ /**
180
+ * Cap the persisted retry-state array to the newest `maxEntries` by
181
+ * firstFailedAt. Guards `maxEntries <= 0` so a zero cap returns [] rather than
182
+ * `slice(-0)`, which returns ALL entries (AGENTS.md rule 17).
183
+ */
184
+ export function capExtractionRetryStateEntries<T extends { firstFailedAt: string }>(
185
+ entries: readonly T[],
186
+ maxEntries: number,
187
+ ): T[] {
188
+ if (maxEntries <= 0) return [];
189
+ const sorted = [...entries].sort((a, b) => a.firstFailedAt.localeCompare(b.firstFailedAt));
190
+ return sorted.slice(-maxEntries);
191
+ }
192
+
133
193
  /**
134
194
  * Coordinates the extraction run pipeline. Holds the dedupe fingerprint
135
195
  * cache (`recentExtractionFingerprints`) and delegates all side effects
@@ -137,6 +197,21 @@ export function deriveTopicsFromExtraction(result: ExtractionResult): string[] {
137
197
  */
138
198
  export class ExtractionRunCoordinator {
139
199
  private readonly recentExtractionFingerprints = new Map<string, number>();
200
+ // Per-fingerprint extraction retry/backoff state (extraction hot-loop
201
+ // hardening). Outer key = namespace, inner key = fingerprint. Hydrated lazily
202
+ // from namespace-scoped meta so the hot path is a Map lookup; persisted back
203
+ // to meta on change (restart-safe, cross-process coherent via the shared
204
+ // meta.json). The breaker below is intentionally per-process in-memory
205
+ // (mirrors local-llm.cooldownUntilMs); a restart clears it but the persisted
206
+ // backoff still throttles, so a restart can't resurrect the hot loop.
207
+ private readonly extractionRetryState = new Map<string, Map<string, ExtractionRetryStateEntry>>();
208
+ private readonly hydratedRetryNamespaces = new Set<string>();
209
+ private providerBreaker: {
210
+ consecutiveFailures: number;
211
+ openUntilMs: number;
212
+ state: "closed" | "open" | "half_open";
213
+ lastReason: string;
214
+ } = { consecutiveFailures: 0, openUntilMs: 0, state: "closed", lastReason: "" };
140
215
 
141
216
  constructor(private readonly deps: ExtractionRunCoordinatorDeps) {}
142
217
 
@@ -238,6 +313,181 @@ export class ExtractionRunCoordinator {
238
313
  }
239
314
  }
240
315
 
316
+ // -------------------------------------------------------------------------
317
+ // Extraction retry/backoff + circuit breaker (extraction hot-loop hardening)
318
+ // -------------------------------------------------------------------------
319
+
320
+ /** Lazily hydrate the in-memory retry-state mirror for a namespace from meta.
321
+ * One-time per namespace per coordinator; the hot path is then a Map lookup.
322
+ * Cross-process coherence: a fresh coordinator hydrates from the shared
323
+ * meta.json, so a failure persisted by another process is honored here. */
324
+ private hydrateRetryStateFromMeta(namespace: string, meta: MetaState): void {
325
+ if (this.hydratedRetryNamespaces.has(namespace)) return;
326
+ this.hydratedRetryNamespaces.add(namespace);
327
+ const nsMap = this.extractionRetryState.get(namespace) ?? new Map<string, ExtractionRetryStateEntry>();
328
+ for (const entry of meta.extractionRetryState ?? []) {
329
+ const nextEligibleAtMs = Date.parse(entry.nextEligibleAt);
330
+ const firstFailedAtMs = Date.parse(entry.firstFailedAt);
331
+ nsMap.set(entry.fingerprint, {
332
+ attempts: entry.attempts,
333
+ nextEligibleAtMs: Number.isFinite(nextEligibleAtMs) ? nextEligibleAtMs : 0,
334
+ firstFailedAtMs: Number.isFinite(firstFailedAtMs) ? firstFailedAtMs : Date.now(),
335
+ lastFailureClass: entry.lastFailureClass,
336
+ });
337
+ }
338
+ this.extractionRetryState.set(namespace, nsMap);
339
+ }
340
+
341
+ private logBreakerTransition(state: "closed" | "open" | "half_open"): void {
342
+ // Once per state change (not per attempt) — AGENTS.md observability rule.
343
+ log.warn("extraction: provider circuit breaker transition", {
344
+ state,
345
+ consecutiveFailures: this.providerBreaker.consecutiveFailures,
346
+ lastReason: this.providerBreaker.lastReason,
347
+ openUntilMs: this.providerBreaker.openUntilMs,
348
+ });
349
+ }
350
+
351
+ /** True while the breaker suppresses non-forced attempts. Flips an expired
352
+ * `open` breaker to `half_open`, which allows exactly one probe (the next
353
+ * non-forced attempt) because the extraction queue is a serial drain. */
354
+ private isProviderBreakerOpen(now: number): boolean {
355
+ if (now < this.providerBreaker.openUntilMs) return true;
356
+ if (this.providerBreaker.state === "open") {
357
+ this.providerBreaker.state = "half_open";
358
+ this.logBreakerTransition("half_open");
359
+ }
360
+ return false;
361
+ }
362
+
363
+ private onProviderFailure(cls: ExtractionFailureClass, now: number): void {
364
+ this.providerBreaker.consecutiveFailures += 1;
365
+ const trip =
366
+ cls === "auth_config" ||
367
+ // A failed half-open probe re-opens immediately (standard breaker
368
+ // semantics). Without this, an auth_config-opened breaker (which trips
369
+ // below the threshold) would get stuck half_open after a transient
370
+ // probe failure and stop suppressing (cursor review).
371
+ this.providerBreaker.state === "half_open" ||
372
+ this.providerBreaker.consecutiveFailures >= this.config.extractionBreakerFailureThreshold;
373
+ if (!trip) return;
374
+ const cooldown =
375
+ cls === "auth_config"
376
+ ? this.config.extractionBreakerAuthCooldownMs
377
+ : this.config.extractionBreakerCooldownMs;
378
+ this.providerBreaker.openUntilMs = now + Math.max(0, cooldown);
379
+ this.providerBreaker.lastReason = cls;
380
+ if (this.providerBreaker.state !== "open") {
381
+ this.providerBreaker.state = "open";
382
+ this.logBreakerTransition("open");
383
+ }
384
+ }
385
+
386
+ private resetProviderBreakerOnSuccess(): void {
387
+ const wasDisturbed =
388
+ this.providerBreaker.state !== "closed" ||
389
+ this.providerBreaker.consecutiveFailures > 0 ||
390
+ this.providerBreaker.openUntilMs > 0;
391
+ if (!wasDisturbed) return;
392
+ const wasOpen = this.providerBreaker.state !== "closed";
393
+ this.providerBreaker.consecutiveFailures = 0;
394
+ this.providerBreaker.openUntilMs = 0;
395
+ this.providerBreaker.lastReason = "";
396
+ this.providerBreaker.state = "closed";
397
+ if (wasOpen) this.logBreakerTransition("closed");
398
+ }
399
+
400
+ /** Rebuild `meta.extractionRetryState` from the in-memory namespace mirror,
401
+ * bounded to the newest entries, and prune the mirror to match. */
402
+ private persistRetryStateToMeta(namespace: string, meta: MetaState): void {
403
+ const nsMap = this.extractionRetryState.get(namespace);
404
+ const entries = nsMap
405
+ ? Array.from(nsMap.entries()).map(([fingerprint, st]) => ({
406
+ fingerprint,
407
+ attempts: st.attempts,
408
+ nextEligibleAt: new Date(st.nextEligibleAtMs).toISOString(),
409
+ firstFailedAt: new Date(st.firstFailedAtMs).toISOString(),
410
+ lastFailureClass: st.lastFailureClass,
411
+ }))
412
+ : [];
413
+ const capped = capExtractionRetryStateEntries(entries, EXTRACTION_RETRY_STATE_MAX_ENTRIES);
414
+ meta.extractionRetryState = capped;
415
+ if (nsMap && capped.length < nsMap.size) {
416
+ const kept = new Set(capped.map((e) => e.fingerprint));
417
+ for (const key of Array.from(nsMap.keys())) {
418
+ if (!kept.has(key)) nsMap.delete(key);
419
+ }
420
+ }
421
+ }
422
+ /** Record an extraction failure into per-fingerprint backoff state + breaker,
423
+ * persisting to namespace meta. Fail-open: never throws upward. */
424
+ private async recordExtractionFailure(
425
+ storage: StorageManager,
426
+ namespace: string,
427
+ fingerprint: string,
428
+ cls: ExtractionFailureClass,
429
+ meta: MetaState,
430
+ now: number,
431
+ ): Promise<void> {
432
+ try {
433
+ this.hydrateRetryStateFromMeta(namespace, meta);
434
+ const nsMap = this.extractionRetryState.get(namespace) ?? new Map<string, ExtractionRetryStateEntry>();
435
+ const prev = nsMap.get(fingerprint);
436
+ const attempts = (prev?.attempts ?? 0) + 1;
437
+ const firstFailedAtMs = prev?.firstFailedAtMs ?? now;
438
+ let nextEligibleAtMs: number;
439
+ if (cls === "parse_empty" && attempts > this.config.extractionParseEmptyMaxAttempts) {
440
+ // parse_empty exhausted its attempt budget → long-park (still never
441
+ // marked processed). A dead gateway also yields unparseable output.
442
+ nextEligibleAtMs = now + Math.max(0, this.config.extractionRetryMaxBackoffMs);
443
+ } else {
444
+ nextEligibleAtMs = computeExtractionRetryNextEligibleMs(
445
+ attempts,
446
+ this.config.extractionRetryScheduleMs,
447
+ this.config.extractionRetryMaxBackoffMs,
448
+ this.config.extractionRetryJitterRatio,
449
+ now,
450
+ );
451
+ }
452
+ nsMap.set(fingerprint, { attempts, nextEligibleAtMs, firstFailedAtMs, lastFailureClass: cls });
453
+ this.extractionRetryState.set(namespace, nsMap);
454
+ this.onProviderFailure(cls, now);
455
+ this.persistRetryStateToMeta(namespace, meta);
456
+ await storage.saveMeta(meta);
457
+ } catch (err) {
458
+ // Fail-open: recording failure state must never crash observe/recall.
459
+ log.warn("runExtraction: failed to record extraction retry state (non-fatal)", err);
460
+ }
461
+ }
462
+
463
+ /** Delete a fingerprint's retry entry from the mirror + meta. Returns whether
464
+ * meta changed (so the caller can decide to persist). Does not touch the
465
+ * breaker — callers pair this with `resetProviderBreakerOnSuccess`. */
466
+ private clearExtractionRetryEntry(namespace: string, fingerprint: string, meta: MetaState): boolean {
467
+ const nsMap = this.extractionRetryState.get(namespace);
468
+ if (!nsMap?.has(fingerprint)) return false;
469
+ nsMap.delete(fingerprint);
470
+ this.persistRetryStateToMeta(namespace, meta);
471
+ return true;
472
+ }
473
+
474
+ /** Live in-process resilience snapshot (breaker + backoff population). */
475
+ getExtractionResilienceStatus(): ExtractionResilienceStatus {
476
+ let backoffFingerprintCount = 0;
477
+ for (const nsMap of this.extractionRetryState.values()) {
478
+ backoffFingerprintCount += nsMap.size;
479
+ }
480
+ return {
481
+ breaker: {
482
+ state: this.providerBreaker.state,
483
+ openUntilMs: this.providerBreaker.openUntilMs,
484
+ consecutiveFailures: this.providerBreaker.consecutiveFailures,
485
+ lastReason: this.providerBreaker.lastReason,
486
+ },
487
+ backoffFingerprintCount,
488
+ };
489
+ }
490
+
241
491
  // -------------------------------------------------------------------------
242
492
  // Main extraction pipeline
243
493
  // -------------------------------------------------------------------------
@@ -268,6 +518,14 @@ export class ExtractionRunCoordinator {
268
518
  * (un-prefixed) key and storage is pinned via `writeNamespaceOverride`.
269
519
  */
270
520
  principalOverride?: string;
521
+ /**
522
+ * Force the extractor call, bypassing the retry-backoff / circuit-breaker
523
+ * gate (extraction hot-loop hardening). Set by explicit flush paths
524
+ * (before_reset / session flush / replay / bulk import) that already pass
525
+ * `skipDedupeCheck: true` — AGENTS.md rule 18. A forced attempt still
526
+ * records its failure into retry-state/breaker.
527
+ */
528
+ forceExtractionAttempt?: boolean;
271
529
  } = {}
272
530
  ): Promise<ExtractionRunResult> {
273
531
  log.debug(`running extraction on ${turns.length} turns`);
@@ -463,6 +721,54 @@ export class ExtractionRunCoordinator {
463
721
  };
464
722
  }
465
723
 
724
+ // Extraction retry/backoff + circuit-breaker gate (extraction hot-loop
725
+ // hardening). Suppresses re-attempts of a recently-failed fingerprint and
726
+ // short-circuits every fingerprint while the provider breaker is open, so a
727
+ // failing LLM endpoint is not hammered once per observe. Forced flushes and
728
+ // fail-closed callers bypass the gate (rule 18) but still record failures.
729
+ // On a suppressed attempt the buffer is deliberately NOT cleared, so no
730
+ // extractable turn is dropped — it is retried after cooldown/nextEligibleAt.
731
+ const forcedExtractionAttempt =
732
+ options.failOnExtractionFailure === true || options.forceExtractionAttempt === true;
733
+ // Resolve the gate through the shared lifecycle capability plan (issue
734
+ // #1523) rather than reading the raw config flag at each call site.
735
+ const extractionRetryEnabled = resolveMemoryLifecycleCapabilities(this.config).extractionRetry;
736
+ if (extractionFingerprint && !forcedExtractionAttempt && extractionRetryEnabled) {
737
+ try {
738
+ meta ??= await storage.loadMeta();
739
+ this.hydrateRetryStateFromMeta(selfNamespace, meta);
740
+ const nowMs = Date.now();
741
+ let suppressReason: "provider_circuit_open" | "extraction_backoff" | null = null;
742
+ if (this.isProviderBreakerOpen(nowMs)) {
743
+ suppressReason = "provider_circuit_open";
744
+ } else {
745
+ const st = this.extractionRetryState.get(selfNamespace)?.get(extractionFingerprint);
746
+ if (st && nowMs < st.nextEligibleAtMs) suppressReason = "extraction_backoff";
747
+ }
748
+ if (suppressReason) {
749
+ // Parity with the other skip paths: passive correction capture is
750
+ // local (no provider call) and must not be delayed by a provider
751
+ // cooldown — "stop calling me X" style turns should register during
752
+ // a 30-minute breaker window too (codex review). Fail-open.
753
+ try {
754
+ await this.deps.maybeCapturePassiveCorrections(normalizedTurns as BufferTurn[], {
755
+ sessionKey,
756
+ principal,
757
+ namespace: selfNamespace,
758
+ bufferKey,
759
+ isLiveSession: clearBufferAfterExtraction,
760
+ });
761
+ } catch (captureErr) {
762
+ log.warn("runExtraction: passive correction capture failed on suppressed attempt (non-fatal)", captureErr);
763
+ }
764
+ return { status: "skipped", reason: suppressReason, persistedCount: 0, durableOutputCount: 0 };
765
+ }
766
+ } catch (err) {
767
+ // Fail-open: a gate error must never block extraction.
768
+ log.warn("runExtraction: extraction retry gate check failed; proceeding (fail-open)", err);
769
+ }
770
+ }
771
+
466
772
  // Pass existing entity names so the LLM can reuse them instead of inventing variants
467
773
  const existingEntities = await storage.listEntityNames();
468
774
  const result = await raceRecallAbort(
@@ -517,6 +823,54 @@ export class ExtractionRunCoordinator {
517
823
  typeof result.extractionFailure === "string" && result.extractionFailure.trim().length > 0
518
824
  ? result.extractionFailure
519
825
  : undefined;
826
+ let recordedRetryFailure = false;
827
+ // Record failure into backoff/breaker state, or heal on success. Runs for
828
+ // every result path (empty and durable), before the fail-closed throw, so a
829
+ // forced flush still records its failure (rule 18). Gated by
830
+ // extractionRetryEnabled so a disabled feature restores prior behavior.
831
+ if (extractionFingerprint && extractionRetryEnabled) {
832
+ if (extractionFailure) {
833
+ try {
834
+ meta ??= await storage.loadMeta();
835
+ await this.recordExtractionFailure(
836
+ storage,
837
+ selfNamespace,
838
+ extractionFingerprint,
839
+ result.extractionFailureClass ?? "provider_retryable",
840
+ meta,
841
+ Date.now(),
842
+ );
843
+ // The buffer is retained below so the backoff gate can re-attempt
844
+ // these turns after nextEligibleAt — clearing would orphan the
845
+ // retry state.
846
+ recordedRetryFailure = true;
847
+ } catch (err) {
848
+ // Fail-open (codex review): retry bookkeeping is best-effort — a
849
+ // corrupt or locked meta store must not reject the extraction task.
850
+ // With no backoff recorded there is nothing to retain for, so the
851
+ // pre-#1908 clear-buffer behavior applies below.
852
+ log.warn("runExtraction: failed to load meta for retry bookkeeping (non-fatal)", err);
853
+ }
854
+ } else {
855
+ // Provider responded without failure → breaker heals; clear any
856
+ // parked backoff for this fingerprint so it proceeds normally.
857
+ this.resetProviderBreakerOnSuccess();
858
+ try {
859
+ meta ??= await storage.loadMeta();
860
+ // Hydrate the in-memory mirror from persisted meta before clearing.
861
+ // A forced flush (forceExtractionAttempt) bypasses the retry gate and
862
+ // never hydrated, so without this a stale persisted backoff entry
863
+ // would survive a successful forced flush and keep blocking normal
864
+ // extraction until the timer expired (cursor/codex review).
865
+ this.hydrateRetryStateFromMeta(selfNamespace, meta);
866
+ if (this.clearExtractionRetryEntry(selfNamespace, extractionFingerprint, meta)) {
867
+ await storage.saveMeta(meta);
868
+ }
869
+ } catch (err) {
870
+ log.warn("runExtraction: failed to persist cleared retry state (non-fatal)", err);
871
+ }
872
+ }
873
+ }
520
874
  if (options.failOnExtractionFailure && extractionFailure) {
521
875
  throw new Error(`extraction failed: ${extractionFailure}`);
522
876
  }
@@ -550,7 +904,17 @@ export class ExtractionRunCoordinator {
550
904
  bufferKey,
551
905
  isLiveSession: clearBufferAfterExtraction,
552
906
  });
553
- await clearBuffer();
907
+ if (recordedRetryFailure) {
908
+ // Retain the failed turns so the backoff gate can re-attempt them after
909
+ // nextEligibleAt. Clearing here would lose the data the retry state
910
+ // points at (cursor high + codex P1). Trigger/forced paths already
911
+ // retain via clearBufferAfterExtraction=false; this covers the normal
912
+ // live-session path. Overflow is bounded by MAX_BUFFER_ENTRY_COUNT
913
+ // with a loud eviction log.
914
+ log.debug("runExtraction: retaining buffer for backoff retry after failure");
915
+ } else {
916
+ await clearBuffer();
917
+ }
554
918
  return { status: "skipped", reason: "empty_extraction_result", persistedCount: 0, durableOutputCount: 0 };
555
919
  }
556
920
 
@@ -52,6 +52,7 @@ export interface SessionContextDeps {
52
52
  skipUserTurnThreshold?: boolean;
53
53
  extractionDeadlineMs?: number;
54
54
  failOnExtractionFailure?: boolean;
55
+ forceExtractionAttempt?: boolean;
55
56
  onTaskSettled?: (
56
57
  error?: unknown,
57
58
  result?: ExtractionRunResult,
@@ -347,6 +348,7 @@ export class SessionContextCoordinator {
347
348
  bufferKey,
348
349
  clearBufferAfterExtraction: true,
349
350
  skipDedupeCheck: true,
351
+ forceExtractionAttempt: true,
350
352
  abortSignal: options.abortSignal,
351
353
  onTaskSettled: (error) => (error ? reject(error) : resolve()),
352
354
  })
@@ -68,6 +68,7 @@ export interface TurnIngestionDeps {
68
68
  skipUserTurnThreshold?: boolean;
69
69
  extractionDeadlineMs?: number;
70
70
  failOnExtractionFailure?: boolean;
71
+ forceExtractionAttempt?: boolean;
71
72
  onTaskSettled?: (
72
73
  error?: unknown,
73
74
  result?: ExtractionRunResult,
@@ -288,6 +289,7 @@ export class TurnIngestionCoordinator {
288
289
  new Promise<void>((resolve, reject) => {
289
290
  void this.deps.queueBufferedExtraction(sessionSlice, "trigger_mode", {
290
291
  skipDedupeCheck: true,
292
+ forceExtractionAttempt: true,
291
293
  clearBufferAfterExtraction: false,
292
294
  skipCharThreshold: true,
293
295
  skipUserTurnThreshold: true,
@@ -463,6 +465,7 @@ export class TurnIngestionCoordinator {
463
465
  (resolve, reject) => {
464
466
  void this.deps.queueBufferedExtraction(sessionSlice, "trigger_mode", {
465
467
  skipDedupeCheck: true,
468
+ forceExtractionAttempt: true,
466
469
  clearBufferAfterExtraction: false,
467
470
  skipCharThreshold: true,
468
471
  skipUserTurnThreshold: true,
@@ -606,6 +609,7 @@ export class TurnIngestionCoordinator {
606
609
  skipUserTurnThreshold?: boolean;
607
610
  extractionDeadlineMs?: number;
608
611
  failOnExtractionFailure?: boolean;
612
+ forceExtractionAttempt?: boolean;
609
613
  onTaskSettled?: (
610
614
  error?: unknown,
611
615
  result?: ExtractionRunResult,
@@ -700,6 +704,7 @@ export class TurnIngestionCoordinator {
700
704
  failOnExtractionFailure: options.failOnExtractionFailure === true,
701
705
  writeNamespaceOverride: options.writeNamespaceOverride,
702
706
  principalOverride: options.principalOverride,
707
+ forceExtractionAttempt: options.forceExtractionAttempt === true,
703
708
  });
704
709
  settleTask(undefined, result);
705
710
  } catch (err) {
@@ -1060,11 +1060,45 @@ test("runExtraction does not persist processed fingerprints for failed empty ext
1060
1060
  assert.equal(result.status, "skipped");
1061
1061
  assert.equal(result.reason, "empty_extraction_result");
1062
1062
  assert.equal(persistCalls, 0);
1063
- assert.equal(saveMetaCalls, 0);
1064
- assert.equal(clearCalls, 1);
1063
+ // #1908: a failed extraction now persists per-fingerprint retry-state to meta (one
1064
+ // saveMeta call), but MUST NOT record a processed fingerprint — the invariant
1065
+ // below (processedExtractionFingerprints stays []) is the real contract.
1066
+ assert.ok(saveMetaCalls <= 1, `expected at most one retry-state meta save, got ${saveMetaCalls}`);
1067
+ // #1908: a retryable failure retains the buffer (clearCalls===0) so the backoff
1068
+ // gate can re-attempt the turns after nextEligibleAt — clearing would orphan
1069
+ // the persisted retry state (cursor high + codex P1).
1070
+ assert.equal(clearCalls, 0);
1065
1071
  assert.deepEqual(meta.processedExtractionFingerprints, []);
1066
1072
  });
1067
1073
 
1074
+ test("runExtraction clears the buffer on a failed extraction when extractionRetryEnabled=false (behavior parity)", async () => {
1075
+ // With retry disabled the failure path must match pre-#1908 behavior: the
1076
+ // buffer is cleared (no retry state is recorded, so there is nothing to retain).
1077
+ const config = parseConfig({ extractionRetryEnabled: false });
1078
+ config.extractionMinChars = 0;
1079
+ config.extractionMinUserTurns = 1;
1080
+ let clearCalls = 0;
1081
+ const orchestrator = Object.create(Orchestrator.prototype) as any;
1082
+ orchestrator.config = config;
1083
+ orchestrator.buffer = { clearAfterExtraction: async () => { clearCalls += 1; } };
1084
+ orchestrator.storageRouter = {
1085
+ storageFor: async () => ({
1086
+ listEntityNames: async () => [],
1087
+ loadMeta: async () => ({ extractionCount: 0, lastExtractionAt: null, lastConsolidationAt: null, totalMemories: 0, totalEntities: 0, processedExtractionFingerprints: [] }),
1088
+ saveMeta: async () => {},
1089
+ }),
1090
+ };
1091
+ orchestrator.extraction = {
1092
+ extract: async () => ({ facts: [], entities: [], questions: [], profileUpdates: [], extractionFailure: "gateway_unavailable" }),
1093
+ };
1094
+ orchestrator.persistExtraction = async () => [];
1095
+ const turns = [{ ...makeTurn("session-retry-off", "failed gateway"), logicalSessionKey: "logical-thread:retry-off", turnFingerprint: "fp-retry-off", persistProcessedFingerprint: true }];
1096
+ const result = await orchestrator.runExtraction(turns, { bufferKey: "logical-thread:retry-off" });
1097
+ assert.equal(result.status, "skipped");
1098
+ assert.equal(result.reason, "empty_extraction_result");
1099
+ assert.equal(clearCalls, 1, "retry disabled → buffer cleared (pre-#1908 behavior)");
1100
+ });
1101
+
1068
1102
  test("runExtraction preserves empty-result buffers when fingerprint persistence fails", async () => {
1069
1103
  const config = parseConfig({});
1070
1104
  config.extractionMinChars = 0;
@@ -106,7 +106,11 @@ import { RecallRerankCoordinator } from "./orchestration/recall-rerank-coordinat
106
106
  import { RecallSectionCoordinator } from "./orchestration/recall-section-coordinator.js";
107
107
  import { QmdResultResolver, qmdCollectionPathParts, qmdResultPathCandidates } from "./orchestration/qmd-result-resolver.js";
108
108
  import { ContradictionLinkingCoordinator } from "./orchestration/contradiction-linking-coordinator.js";
109
- import { ExtractionRunCoordinator, type ExtractionRunResult } from "./orchestration/extraction-run.js";
109
+ import {
110
+ ExtractionRunCoordinator,
111
+ type ExtractionRunResult,
112
+ type ExtractionResilienceStatus,
113
+ } from "./orchestration/extraction-run.js";
110
114
  import { ConsolidationRunCoordinator } from "./orchestration/consolidation-run.js";
111
115
  import { ExtractionPersistCoordinator } from "./orchestration/extraction-persist.js";
112
116
  import { RecallInternalCoordinator } from "./orchestration/recall-internal.js";
@@ -2937,6 +2941,7 @@ export class Orchestrator {
2937
2941
  skipUserTurnThreshold?: boolean;
2938
2942
  extractionDeadlineMs?: number;
2939
2943
  failOnExtractionFailure?: boolean;
2944
+ forceExtractionAttempt?: boolean;
2940
2945
  onTaskSettled?: (
2941
2946
  error?: unknown,
2942
2947
  result?: ExtractionRunResult,
@@ -3010,6 +3015,12 @@ export class Orchestrator {
3010
3015
  return this.extractionRunCoordinator.runExtraction(...args);
3011
3016
  }
3012
3017
 
3018
+ /** Live extraction resilience snapshot (provider circuit breaker + per-
3019
+ * fingerprint backoff population) for status/doctor surfaces. */
3020
+ getExtractionResilienceStatus(): ExtractionResilienceStatus {
3021
+ return this.extractionRunCoordinator.getExtractionResilienceStatus();
3022
+ }
3023
+
3013
3024
  private async recordProcessedExtractionFingerprint(
3014
3025
  storage: StorageManager,
3015
3026
  fingerprint: string,
package/src/storage.ts CHANGED
@@ -110,6 +110,7 @@ import type {
110
110
  BufferSurpriseEvent,
111
111
  MemorySummary,
112
112
  MetaState,
113
+ ExtractionFailureClass,
113
114
  CompressionGuidelineOptimizerState,
114
115
  PluginConfig,
115
116
  ScoredEntity,
@@ -2263,6 +2264,38 @@ export interface MemoryWriteResult {
2263
2264
  blockedBy?: string;
2264
2265
  }
2265
2266
 
2267
+ /**
2268
+ * Defensively parse the persisted per-fingerprint extraction retry state from
2269
+ * meta.json. Mirrors the tolerance of the `processedExtractionFingerprints`
2270
+ * loader: unknown/legacy shapes are dropped, never thrown on. Uses `in`/typeof
2271
+ * narrowing (no inline casts) so a malformed entry can't slip through typed.
2272
+ */
2273
+ function parseExtractionRetryStateEntries(
2274
+ raw: unknown,
2275
+ ): NonNullable<MetaState["extractionRetryState"]> {
2276
+ if (!Array.isArray(raw)) return [];
2277
+ const valid: NonNullable<MetaState["extractionRetryState"]> = [];
2278
+ for (const entry of raw) {
2279
+ if (!entry || typeof entry !== "object") continue;
2280
+ if (!("fingerprint" in entry) || typeof entry.fingerprint !== "string") continue;
2281
+ if (!("attempts" in entry) || typeof entry.attempts !== "number" || !Number.isFinite(entry.attempts)) continue;
2282
+ if (!("nextEligibleAt" in entry) || typeof entry.nextEligibleAt !== "string") continue;
2283
+ if (!("firstFailedAt" in entry) || typeof entry.firstFailedAt !== "string") continue;
2284
+ if (!("lastFailureClass" in entry)) continue;
2285
+ const cls = entry.lastFailureClass;
2286
+ if (cls !== "provider_retryable" && cls !== "parse_empty" && cls !== "auth_config") continue;
2287
+ const lastFailureClass: ExtractionFailureClass = cls;
2288
+ valid.push({
2289
+ fingerprint: entry.fingerprint,
2290
+ attempts: entry.attempts,
2291
+ nextEligibleAt: entry.nextEligibleAt,
2292
+ firstFailedAt: entry.firstFailedAt,
2293
+ lastFailureClass,
2294
+ });
2295
+ }
2296
+ return valid;
2297
+ }
2298
+
2266
2299
  export class StorageManager {
2267
2300
  private knowledgeIndexCache: { result: string; builtAt: number } | null = null;
2268
2301
  private artifactIndexCache: { memories: MemoryFile[]; loadedAtMs: number; writeVersion: number } | null = null;
@@ -4917,6 +4950,7 @@ export class StorageManager {
4917
4950
  observedAt: (entry as { observedAt: string }).observedAt,
4918
4951
  }))
4919
4952
  : [],
4953
+ extractionRetryState: parseExtractionRetryStateEntries(parsed.extractionRetryState),
4920
4954
  };
4921
4955
  } catch (err) {
4922
4956
  if (err instanceof SecureStoreLockedError) throw err;