@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.
- package/dist/access-admin-ops-surface.d.ts +6 -6
- package/dist/access-admin-ops-surface.js +16 -16
- package/dist/access-boundary.d.ts +6 -6
- package/dist/access-boundary.js +17 -17
- package/dist/access-cli.js +56 -55
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +6 -6
- package/dist/access-http.js +22 -22
- package/dist/access-identity-continuity-surface.d.ts +5 -5
- package/dist/access-identity-continuity-surface.js +16 -16
- package/dist/access-lcm-surface.d.ts +6 -6
- package/dist/access-lcm-surface.js +16 -16
- package/dist/access-mcp.d.ts +6 -6
- package/dist/access-mcp.js +21 -21
- package/dist/access-observe-write-surface.d.ts +6 -6
- package/dist/access-observe-write-surface.js +16 -16
- package/dist/access-operations-batch.js +19 -19
- package/dist/access-operations.d.ts +9 -9
- package/dist/access-operations.js +20 -20
- package/dist/access-recall-surface.d.ts +6 -6
- package/dist/access-recall-surface.js +16 -16
- package/dist/access-schema.d.ts +64 -64
- package/dist/{access-service-Fw9AYEqG.d.ts → access-service-CJ5hrLqR.d.ts} +4 -4
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +16 -16
- package/dist/access-surface-catalog.d.ts +6 -6
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +3 -3
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/{auto-sync-4A5YBWYO.js → auto-sync-676N6N6Y.js} +4 -4
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +5 -5
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +1 -1
- package/dist/capabilities.d.ts +4 -2
- package/dist/capabilities.js +1 -1
- package/dist/{catalog-CnS_MP-8.d.ts → catalog-XCOyxXKw.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +7 -7
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-SFHI7JBE.js → chunk-2FSHROYK.js} +3 -3
- package/dist/{chunk-WHZ7MK72.js → chunk-4YUPLX7U.js} +3 -3
- package/dist/{chunk-ZIKJD6NL.js → chunk-53Y3TETS.js} +2 -2
- package/dist/{chunk-UI4FVU6T.js → chunk-5CJVLG3H.js} +2 -2
- package/dist/{chunk-BAVVI5GP.js → chunk-7PND4PUC.js} +3 -3
- package/dist/{chunk-NREKOGZ2.js → chunk-ARD5NE4M.js} +3 -3
- package/dist/{chunk-GCTJG5MP.js → chunk-AWHOZTF6.js} +4 -4
- package/dist/{chunk-VSF7AAGE.js → chunk-BFPETA3S.js} +3 -2
- package/dist/chunk-BFPETA3S.js.map +1 -0
- package/dist/{chunk-ZGPDSEKG.js → chunk-BFVEEB7W.js} +2 -2
- package/dist/{chunk-AZK3NEHL.js → chunk-BLXNPBJI.js} +2 -2
- package/dist/chunk-C7SRUWBW.js +54 -0
- package/dist/chunk-C7SRUWBW.js.map +1 -0
- package/dist/{chunk-VTRF5QBX.js → chunk-CBOHQNTJ.js} +2 -2
- package/dist/{chunk-ZSDRGPMV.js → chunk-D6KNJRGX.js} +2 -2
- package/dist/{chunk-2V6L3L56.js → chunk-DDHT5J5M.js} +33 -8
- package/dist/chunk-DDHT5J5M.js.map +1 -0
- package/dist/{chunk-3WULMX34.js → chunk-DSWYYEIK.js} +10 -1
- package/dist/chunk-DSWYYEIK.js.map +1 -0
- package/dist/{chunk-U5JP7FCM.js → chunk-E47TABDR.js} +2 -2
- package/dist/{chunk-7TEWOG3R.js → chunk-FMECZFUT.js} +2 -2
- package/dist/{chunk-QWGNDWKB.js → chunk-FNMFJWBY.js} +2 -2
- package/dist/{chunk-CZGIIADK.js → chunk-FXGC5UUH.js} +56 -11
- package/dist/chunk-FXGC5UUH.js.map +1 -0
- package/dist/{chunk-JIKJPDQJ.js → chunk-GKCCHHZK.js} +15 -5
- package/dist/chunk-GKCCHHZK.js.map +1 -0
- package/dist/{chunk-EDD2G35Z.js → chunk-GWMTFDHF.js} +4 -4
- package/dist/{chunk-VCW74PNH.js → chunk-H67QFYUK.js} +2 -2
- package/dist/{chunk-5ELELET5.js → chunk-HT2DQ27U.js} +3 -3
- package/dist/{chunk-VU3SVYMA.js → chunk-HTIIXNJQ.js} +2 -2
- package/dist/{chunk-I73G4UH7.js → chunk-IALLX7EE.js} +2 -2
- package/dist/{chunk-PE6XR4GG.js → chunk-IKQTFQMI.js} +2 -2
- package/dist/{chunk-XZ5I7OZR.js → chunk-KIPLG7S7.js} +4 -4
- package/dist/{chunk-QLVLW7UD.js → chunk-KSBEEMQF.js} +17 -5
- package/dist/chunk-KSBEEMQF.js.map +1 -0
- package/dist/{chunk-RU73ZROD.js → chunk-KWJP22WP.js} +12 -12
- package/dist/{chunk-MNUHL34L.js → chunk-LKT755LP.js} +2 -2
- package/dist/{chunk-PI23H7FV.js → chunk-M7VGG3ID.js} +3 -3
- package/dist/{chunk-PWSZNML3.js → chunk-NGJT7IW5.js} +284 -38
- package/dist/chunk-NGJT7IW5.js.map +1 -0
- package/dist/{chunk-HVE3UVAB.js → chunk-NS4GEMLR.js} +3 -3
- package/dist/{chunk-ZMXXHUEZ.js → chunk-O24E5CFS.js} +3 -3
- package/dist/{chunk-3BZDBG7U.js → chunk-PML3GU4Q.js} +3 -3
- package/dist/{chunk-DQ2CJYUM.js → chunk-PSQ32V5P.js} +3 -3
- package/dist/{chunk-7XY6TJLM.js → chunk-PUPQ2PMC.js} +3 -3
- package/dist/{chunk-EBVUFGJ4.js → chunk-QFOLNA2C.js} +2 -2
- package/dist/{chunk-METJNEIB.js → chunk-QMUERLZK.js} +1 -1
- package/dist/chunk-QMUERLZK.js.map +1 -0
- package/dist/{chunk-PQG2AOL4.js → chunk-QRYNEVHP.js} +28 -4
- package/dist/{chunk-PQG2AOL4.js.map → chunk-QRYNEVHP.js.map} +1 -1
- package/dist/{chunk-CVEEJKRD.js → chunk-RQTCDICG.js} +2 -2
- package/dist/{chunk-F7AE7L6G.js → chunk-RZEJDF3S.js} +2 -2
- package/dist/{chunk-YFBTVOMB.js → chunk-SYNYI4ZM.js} +2 -2
- package/dist/{chunk-BRDJ2HFP.js → chunk-TRWPCR6P.js} +2 -2
- package/dist/{chunk-YAN6NTJN.js → chunk-UKA7GNI2.js} +3 -3
- package/dist/{chunk-TQNRI55H.js → chunk-UR6PBX2J.js} +4 -51
- package/dist/chunk-UR6PBX2J.js.map +1 -0
- package/dist/{chunk-XH2Y6UIW.js → chunk-VD56CJRR.js} +4 -4
- package/dist/{chunk-J3XHPFEU.js → chunk-VXMWDXIJ.js} +2 -2
- package/dist/{chunk-QT2RZNRT.js → chunk-VY6LKKVN.js} +2 -2
- package/dist/{chunk-PRTA6444.js → chunk-W4PGTAS2.js} +2 -2
- package/dist/{chunk-WX4XTAOI.js → chunk-WGE3OYC3.js} +7 -7
- package/dist/{chunk-RVLE2SNP.js → chunk-X6FSCBJZ.js} +22 -22
- package/dist/{chunk-6MWGGPJJ.js → chunk-Y4EMHIQ5.js} +7 -7
- package/dist/{chunk-IGCQHJSQ.js → chunk-Z3HJ5L3Z.js} +2 -2
- package/dist/{chunk-O2EL7EGG.js → chunk-ZQ7UBUZ6.js} +2 -2
- package/dist/{chunk-5SORASJK.js → chunk-ZXUG3AFP.js} +2 -2
- package/dist/{cli-PGF41MN6.d.ts → cli-CtECZr1q.d.ts} +5 -5
- package/dist/cli.d.ts +8 -8
- package/dist/cli.js +37 -36
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +7 -7
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/contradiction/index.js +2 -2
- package/dist/{contradiction-scan-SSKCAMLX.js → contradiction-scan-J57T5QV7.js} +3 -3
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/day-summary.js +2 -2
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +6 -6
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-faithfulness.js +2 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-judge.js +2 -2
- package/dist/extraction.d.ts +17 -2
- package/dist/extraction.js +12 -7
- package/dist/fallback-llm.d.ts +5 -2
- package/dist/fallback-llm.js +1 -1
- package/dist/{first-start-migration-4I3VDSYS.js → first-start-migration-TI67YMLG.js} +2 -2
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.js +56 -55
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/live-connectors-runner.js +3 -2
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-llm.js +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +5 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.js +6 -6
- package/dist/mcp-memory-inspector-app.d.ts +6 -6
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +10 -10
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/principal.js +2 -2
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +6 -6
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +16 -16
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/compression-guideline-coordinator.js +2 -2
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +10 -10
- package/dist/{orchestrator-xePY-WPe.d.ts → orchestrator-CKctWsw9.d.ts} +51 -4
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +56 -55
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-Zgwz-Kne.d.ts → pipeline-BWwGIys5.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-B6Xwfn5_.d.ts → qmd-DFuUrBWW.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-planner-llm.js +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +52 -52
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/embed-helper.js +2 -2
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +3 -3
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +3 -3
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-ClfD_CWg.d.ts → semantic-consolidation-1Wlr7NWL.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +5 -5
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/shared-context/manager.js +2 -2
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-ZrHZ0dSM.d.ts → storage-BQwP90sb.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +4 -4
- package/dist/summarizer.d.ts +1 -1
- package/dist/summarizer.js +4 -4
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/{tier-stats-5XF4UBRQ.js → tier-stats-KV7IWCJR.js} +2 -2
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-BbtQZBq9.d.ts → types-DgIkyAsK.d.ts} +39 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +5 -5
- package/package.json +2 -2
- package/src/buffer-session.test.ts +40 -8
- package/src/buffer.ts +41 -17
- package/src/capabilities.test.ts +1 -0
- package/src/capabilities.ts +4 -0
- package/src/config.test.ts +19 -0
- package/src/config.ts +29 -0
- package/src/extraction.test.ts +39 -0
- package/src/extraction.ts +72 -2
- package/src/fallback-llm.ts +26 -5
- package/src/operator-toolkit.ts +53 -0
- package/src/orchestration/extraction-run.test.ts +639 -0
- package/src/orchestration/extraction-run.ts +366 -2
- package/src/orchestration/session-context.ts +2 -0
- package/src/orchestration/turn-ingestion.ts +5 -0
- package/src/orchestrator-flush.test.ts +36 -2
- package/src/orchestrator.ts +12 -1
- package/src/storage.ts +34 -0
- package/src/types.ts +40 -0
- package/dist/chunk-2V6L3L56.js.map +0 -1
- package/dist/chunk-3WULMX34.js.map +0 -1
- package/dist/chunk-CZGIIADK.js.map +0 -1
- package/dist/chunk-JIKJPDQJ.js.map +0 -1
- package/dist/chunk-METJNEIB.js.map +0 -1
- package/dist/chunk-PWSZNML3.js.map +0 -1
- package/dist/chunk-QLVLW7UD.js.map +0 -1
- package/dist/chunk-TQNRI55H.js.map +0 -1
- package/dist/chunk-VSF7AAGE.js.map +0 -1
- /package/dist/{auto-sync-4A5YBWYO.js.map → auto-sync-676N6N6Y.js.map} +0 -0
- /package/dist/{chunk-SFHI7JBE.js.map → chunk-2FSHROYK.js.map} +0 -0
- /package/dist/{chunk-WHZ7MK72.js.map → chunk-4YUPLX7U.js.map} +0 -0
- /package/dist/{chunk-ZIKJD6NL.js.map → chunk-53Y3TETS.js.map} +0 -0
- /package/dist/{chunk-UI4FVU6T.js.map → chunk-5CJVLG3H.js.map} +0 -0
- /package/dist/{chunk-BAVVI5GP.js.map → chunk-7PND4PUC.js.map} +0 -0
- /package/dist/{chunk-NREKOGZ2.js.map → chunk-ARD5NE4M.js.map} +0 -0
- /package/dist/{chunk-GCTJG5MP.js.map → chunk-AWHOZTF6.js.map} +0 -0
- /package/dist/{chunk-ZGPDSEKG.js.map → chunk-BFVEEB7W.js.map} +0 -0
- /package/dist/{chunk-AZK3NEHL.js.map → chunk-BLXNPBJI.js.map} +0 -0
- /package/dist/{chunk-VTRF5QBX.js.map → chunk-CBOHQNTJ.js.map} +0 -0
- /package/dist/{chunk-ZSDRGPMV.js.map → chunk-D6KNJRGX.js.map} +0 -0
- /package/dist/{chunk-U5JP7FCM.js.map → chunk-E47TABDR.js.map} +0 -0
- /package/dist/{chunk-7TEWOG3R.js.map → chunk-FMECZFUT.js.map} +0 -0
- /package/dist/{chunk-QWGNDWKB.js.map → chunk-FNMFJWBY.js.map} +0 -0
- /package/dist/{chunk-EDD2G35Z.js.map → chunk-GWMTFDHF.js.map} +0 -0
- /package/dist/{chunk-VCW74PNH.js.map → chunk-H67QFYUK.js.map} +0 -0
- /package/dist/{chunk-5ELELET5.js.map → chunk-HT2DQ27U.js.map} +0 -0
- /package/dist/{chunk-VU3SVYMA.js.map → chunk-HTIIXNJQ.js.map} +0 -0
- /package/dist/{chunk-I73G4UH7.js.map → chunk-IALLX7EE.js.map} +0 -0
- /package/dist/{chunk-PE6XR4GG.js.map → chunk-IKQTFQMI.js.map} +0 -0
- /package/dist/{chunk-XZ5I7OZR.js.map → chunk-KIPLG7S7.js.map} +0 -0
- /package/dist/{chunk-RU73ZROD.js.map → chunk-KWJP22WP.js.map} +0 -0
- /package/dist/{chunk-MNUHL34L.js.map → chunk-LKT755LP.js.map} +0 -0
- /package/dist/{chunk-PI23H7FV.js.map → chunk-M7VGG3ID.js.map} +0 -0
- /package/dist/{chunk-HVE3UVAB.js.map → chunk-NS4GEMLR.js.map} +0 -0
- /package/dist/{chunk-ZMXXHUEZ.js.map → chunk-O24E5CFS.js.map} +0 -0
- /package/dist/{chunk-3BZDBG7U.js.map → chunk-PML3GU4Q.js.map} +0 -0
- /package/dist/{chunk-DQ2CJYUM.js.map → chunk-PSQ32V5P.js.map} +0 -0
- /package/dist/{chunk-7XY6TJLM.js.map → chunk-PUPQ2PMC.js.map} +0 -0
- /package/dist/{chunk-EBVUFGJ4.js.map → chunk-QFOLNA2C.js.map} +0 -0
- /package/dist/{chunk-CVEEJKRD.js.map → chunk-RQTCDICG.js.map} +0 -0
- /package/dist/{chunk-F7AE7L6G.js.map → chunk-RZEJDF3S.js.map} +0 -0
- /package/dist/{chunk-YFBTVOMB.js.map → chunk-SYNYI4ZM.js.map} +0 -0
- /package/dist/{chunk-BRDJ2HFP.js.map → chunk-TRWPCR6P.js.map} +0 -0
- /package/dist/{chunk-YAN6NTJN.js.map → chunk-UKA7GNI2.js.map} +0 -0
- /package/dist/{chunk-XH2Y6UIW.js.map → chunk-VD56CJRR.js.map} +0 -0
- /package/dist/{chunk-J3XHPFEU.js.map → chunk-VXMWDXIJ.js.map} +0 -0
- /package/dist/{chunk-QT2RZNRT.js.map → chunk-VY6LKKVN.js.map} +0 -0
- /package/dist/{chunk-PRTA6444.js.map → chunk-W4PGTAS2.js.map} +0 -0
- /package/dist/{chunk-WX4XTAOI.js.map → chunk-WGE3OYC3.js.map} +0 -0
- /package/dist/{chunk-RVLE2SNP.js.map → chunk-X6FSCBJZ.js.map} +0 -0
- /package/dist/{chunk-6MWGGPJJ.js.map → chunk-Y4EMHIQ5.js.map} +0 -0
- /package/dist/{chunk-IGCQHJSQ.js.map → chunk-Z3HJ5L3Z.js.map} +0 -0
- /package/dist/{chunk-O2EL7EGG.js.map → chunk-ZQ7UBUZ6.js.map} +0 -0
- /package/dist/{chunk-5SORASJK.js.map → chunk-ZXUG3AFP.js.map} +0 -0
- /package/dist/{contradiction-scan-SSKCAMLX.js.map → contradiction-scan-J57T5QV7.js.map} +0 -0
- /package/dist/{first-start-migration-4I3VDSYS.js.map → first-start-migration-TI67YMLG.js.map} +0 -0
- /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
|
-
|
|
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
|
-
|
|
1064
|
-
|
|
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;
|
package/src/orchestrator.ts
CHANGED
|
@@ -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 {
|
|
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;
|