@remnic/core 9.69.47 → 9.69.49
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 +14 -14
- package/dist/access-admin-ops-surface.js +32 -30
- package/dist/access-authorization-probe.d.ts +14 -14
- package/dist/access-authorization-probe.js +33 -31
- package/dist/access-boundary.d.ts +14 -14
- package/dist/access-boundary.js +32 -30
- package/dist/access-cli.js +99 -98
- package/dist/access-cli.js.map +1 -1
- package/dist/access-coding-context-resolution.d.ts +1 -1
- package/dist/access-extraction-force-flush.d.ts +14 -14
- package/dist/access-extraction-force-flush.js +32 -30
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-briefing.d.ts +14 -14
- package/dist/access-http-briefing.js +33 -31
- package/dist/access-http-lcm-compaction.d.ts +14 -14
- package/dist/access-http-lifecycle-flush.d.ts +14 -14
- package/dist/access-http-offline-stream.d.ts +14 -14
- package/dist/access-http-query.d.ts +2 -2
- package/dist/access-http-query.js +34 -32
- package/dist/access-http.d.ts +15 -15
- package/dist/access-http.js +52 -50
- package/dist/access-identity-continuity-surface.d.ts +11 -11
- package/dist/access-identity-continuity-surface.js +32 -30
- package/dist/access-lcm-surface.d.ts +14 -14
- package/dist/access-lcm-surface.js +32 -30
- package/dist/access-mcp-shared-context-tools.d.ts +14 -14
- package/dist/access-mcp.d.ts +14 -14
- package/dist/access-mcp.js +42 -40
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-CetF9ziA.d.ts → access-namespace-preflight-DqRw_g-8.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +32 -30
- package/dist/access-observe-transcript.d.ts +11 -11
- package/dist/access-observe-write-category.d.ts +3 -3
- package/dist/access-observe-write-surface.d.ts +14 -14
- package/dist/access-observe-write-surface.js +32 -30
- package/dist/access-offline-manifest.d.ts +11 -11
- package/dist/access-operations-batch.js +34 -32
- package/dist/access-operations.d.ts +14 -14
- package/dist/access-operations.js +41 -39
- package/dist/access-recall-concurrency.d.ts +14 -14
- package/dist/access-recall-concurrency.js +32 -30
- package/dist/access-recall-response.d.ts +14 -14
- package/dist/access-recall-response.js +32 -30
- package/dist/access-recall-surface.d.ts +14 -14
- package/dist/access-recall-surface.js +32 -30
- package/dist/access-schema.d.ts +114 -114
- package/dist/{access-service-BXYrJ3pO.d.ts → access-service-70VHaltr.d.ts} +73 -73
- package/dist/access-service-helpers.d.ts +14 -14
- package/dist/access-service.d.ts +14 -14
- package/dist/access-service.js +32 -30
- package/dist/access-surface-catalog.d.ts +14 -14
- package/dist/access-surface-catalog.js +34 -32
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/access-wearables-meetings-surface.d.ts +4 -4
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +2 -2
- package/dist/activity/digest.d.ts +1 -1
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/ambient-provenance.d.ts +1 -1
- package/dist/artifact-search.d.ts +1 -1
- package/dist/background-generation.d.ts +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +11 -11
- package/dist/briefing-window.d.ts +2 -2
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +11 -11
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/buffer.js +5 -5
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-Bns9zpl3.d.ts → catalog-Byi9prz9.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 +12 -12
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-DE4ZGH2N.js → chunk-2IVHOQRY.js} +8 -3
- package/dist/chunk-2IVHOQRY.js.map +1 -0
- package/dist/{chunk-MQT3WP3F.js → chunk-3MX4GWJO.js} +2 -2
- package/dist/{chunk-PXJ6HE2W.js → chunk-3RABJOGD.js} +2 -2
- package/dist/{chunk-V2XNMIAM.js → chunk-4WSR7MYV.js} +2 -2
- package/dist/{chunk-6A6HSXWV.js → chunk-5WIGM42T.js} +2 -2
- package/dist/{chunk-GGROFIYX.js → chunk-6DYCSSHN.js} +2 -2
- package/dist/{chunk-55XWRL6F.js → chunk-6VLOKESS.js} +2 -2
- package/dist/{chunk-OS4JVTHR.js → chunk-76RTJ533.js} +3 -3
- package/dist/{chunk-4LPIVP77.js → chunk-7VTQETLT.js} +2 -2
- package/dist/{chunk-X2NFFBTO.js → chunk-A7GQBJP5.js} +4 -4
- package/dist/{chunk-G2SJRFGP.js → chunk-AIKUSXIS.js} +6 -6
- package/dist/{chunk-MNG536IS.js → chunk-AJLQH4RO.js} +13 -13
- package/dist/{chunk-XEHLYA5Z.js → chunk-AOMVOC2E.js} +3 -3
- package/dist/{chunk-XXLH6KFO.js → chunk-BHXY5FXL.js} +2 -2
- package/dist/{chunk-PA67GXYJ.js → chunk-C3YEB7CR.js} +5 -5
- package/dist/{chunk-SK6HEQTW.js → chunk-DHTOYRDE.js} +2 -2
- package/dist/{chunk-WQW6KPVC.js → chunk-DNAD57VY.js} +1 -1
- package/dist/chunk-DNAD57VY.js.map +1 -0
- package/dist/{chunk-P3YNEMYY.js → chunk-E64HBW7M.js} +2 -2
- package/dist/{chunk-NMA7RQPR.js → chunk-EBLKS57G.js} +2 -2
- package/dist/{chunk-2QX65IEC.js → chunk-EHUUXLLA.js} +2 -2
- package/dist/{chunk-LYMJ36UI.js → chunk-EST3J4LX.js} +2 -2
- package/dist/{chunk-B6VDWDFN.js → chunk-EUP4AXIW.js} +2 -2
- package/dist/{chunk-XZUHXKM3.js → chunk-FLZJMXFV.js} +2 -2
- package/dist/{chunk-GXJSLNYO.js → chunk-FVRZDZ7J.js} +2 -2
- package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
- package/dist/{chunk-2JFSQWFH.js → chunk-HIBNECBQ.js} +5 -5
- package/dist/{chunk-7ZNJN5WZ.js → chunk-IHOXK2I6.js} +55 -6
- package/dist/chunk-IHOXK2I6.js.map +1 -0
- package/dist/{chunk-QLCMOHRE.js → chunk-IIDXU2CY.js} +2 -2
- package/dist/{chunk-2NKY7OC6.js → chunk-IMJPJY7V.js} +2 -2
- package/dist/{chunk-JYXLW22X.js → chunk-JG4AP36X.js} +2 -2
- package/dist/{chunk-3RKVSECD.js → chunk-JI5Q6EOY.js} +7 -7
- package/dist/{chunk-XKHRNAK7.js → chunk-JIWZVOQM.js} +2 -2
- package/dist/{chunk-7YUX52XZ.js → chunk-JVM3CEGL.js} +3 -3
- package/dist/{chunk-QB4VXCFN.js → chunk-K26LFJE4.js} +2 -2
- package/dist/{chunk-Y5VT7BIT.js → chunk-K4KE5MYE.js} +2 -2
- package/dist/{chunk-TQSCR5DU.js → chunk-KG2OOAFF.js} +6 -6
- package/dist/{chunk-VV5GCNKG.js → chunk-KHLNIDWB.js} +2 -2
- package/dist/{chunk-LIAM7U2J.js → chunk-LE2UESGS.js} +2 -2
- package/dist/{chunk-YXWMZAAN.js → chunk-LLFMZYFS.js} +2 -2
- package/dist/{chunk-PPJLJD2D.js → chunk-LX32KV2C.js} +2 -2
- package/dist/{chunk-44BILQ7U.js → chunk-N57PUWUE.js} +39 -39
- package/dist/{chunk-V7IONNTJ.js → chunk-NDWBT2VV.js} +2 -2
- package/dist/{chunk-DRLZB3CN.js → chunk-OTGIAJQ2.js} +2 -2
- package/dist/{chunk-T3T5HTZZ.js → chunk-P36WML7L.js} +2 -2
- package/dist/{chunk-SJ7ZNUXB.js → chunk-PAQ7YGJY.js} +2 -2
- package/dist/{chunk-YOSLKZLZ.js → chunk-PSQRBCGR.js} +2 -2
- package/dist/{chunk-FGRJJBFO.js → chunk-PZHN7R35.js} +2 -2
- package/dist/{chunk-QMWKENNK.js → chunk-Q4AUIFUA.js} +4 -4
- package/dist/{chunk-CD7VTZIG.js → chunk-QQA6VTOY.js} +2 -2
- package/dist/{chunk-ROBGBT3D.js → chunk-RBHQA2UU.js} +50 -50
- package/dist/{chunk-HG74GEVY.js → chunk-RS53SDCA.js} +2 -2
- package/dist/{chunk-SS33RR4F.js → chunk-SKYYPVBW.js} +8 -8
- package/dist/{chunk-OSTXIFZW.js → chunk-SNOAFXKX.js} +38 -4
- package/dist/chunk-SNOAFXKX.js.map +1 -0
- package/dist/{chunk-EWBDJQ5Y.js → chunk-SYJZUVTN.js} +3 -3
- package/dist/{chunk-IO7K7GES.js → chunk-T3F2HWIG.js} +2 -2
- package/dist/{chunk-BMENHAJL.js → chunk-T3OLYUU6.js} +2 -2
- package/dist/{chunk-AS4YKT75.js → chunk-TUSY4KRE.js} +207 -44
- package/dist/chunk-TUSY4KRE.js.map +1 -0
- package/dist/{chunk-QGAKUOKU.js → chunk-U3ZRHNNG.js} +2 -2
- package/dist/{chunk-LIDJWAQV.js → chunk-UB4UVALS.js} +2 -2
- package/dist/chunk-UD6W67JM.js +260 -0
- package/dist/chunk-UD6W67JM.js.map +1 -0
- package/dist/{chunk-WYU66IVZ.js → chunk-UE7L4EDU.js} +3 -3
- package/dist/{chunk-I6IJDTRP.js → chunk-VBRS7Z3O.js} +2 -2
- package/dist/{chunk-EGOWY342.js → chunk-WGCASEEQ.js} +2 -2
- package/dist/{chunk-QKFYJVDG.js → chunk-WHSNLCRO.js} +2 -2
- package/dist/{chunk-JXLEQIJ7.js → chunk-XQFHKPNB.js} +7 -7
- package/dist/{chunk-NE45P3OF.js → chunk-YA5M7OC4.js} +2 -2
- package/dist/{chunk-WL3PGPGU.js → chunk-YKWWG3CM.js} +2 -2
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-MMQVWKFY.js → chunk-YOC3EEZH.js} +12 -12
- package/dist/{chunk-ZEIJQ4I3.js → chunk-Z2OKRO3E.js} +2 -2
- package/dist/{chunk-5KBI2MOC.js → chunk-ZDGSHXJU.js} +1 -1
- package/dist/{chunk-R4JTUTU6.js → chunk-ZYZW46HZ.js} +3 -3
- package/dist/{cli-B8YyIrPR.d.ts → cli-CdJiBilM.d.ts} +5 -5
- package/dist/cli.d.ts +16 -16
- package/dist/cli.js +71 -69
- package/dist/coding/export-okf-codegraph.d.ts +1 -1
- package/dist/coding/export-okf-codegraph.js +13 -13
- package/dist/coding/pre-action-gate.d.ts +1 -1
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +12 -12
- 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 +2 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +11 -11
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +11 -11
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +4 -4
- package/dist/converge-config.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +2 -2
- package/dist/convergence-refresh.js +13 -13
- 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/corpus-watermark.d.ts +1 -1
- package/dist/corpus-watermark.js +13 -13
- package/dist/cross-lingual-recall.d.ts +1 -1
- package/dist/dashboard-runtime.js +2 -2
- package/dist/day-summary.d.ts +1 -1
- package/dist/deep-recall-http-glue.d.ts +14 -14
- package/dist/deep-recall-mcp-tools.d.ts +14 -14
- package/dist/deep-recall-policy-llm.d.ts +2 -2
- package/dist/deep-recall-seeds.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/dependency-propagation-config.d.ts +1 -1
- package/dist/{dependency-propagation-delivery-B-LvcDaq.d.ts → dependency-propagation-delivery-BgHy44WX.d.ts} +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/directory-sidecars.d.ts +87 -0
- package/dist/directory-sidecars.js +22 -0
- package/dist/directory-sidecars.js.map +1 -0
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-origin-fields.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +13 -13
- package/dist/entity-schema.d.ts +1 -1
- package/dist/episodic-context.d.ts +1 -1
- package/dist/eval-trajectory-contracts.d.ts +1 -1
- package/dist/eval-trajectory-store.d.ts +1 -1
- package/dist/eval-trajectory.d.ts +1 -1
- package/dist/event-order-recall.js +2 -2
- package/dist/explicit-capture.d.ts +11 -11
- package/dist/external-wiki-access.d.ts +14 -14
- package/dist/external-wiki-access.js +33 -31
- package/dist/external-wiki-collection-registration.d.ts +1 -1
- package/dist/external-wiki-collection.d.ts +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +14 -14
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-evidence-class.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +2 -2
- package/dist/extraction-judge-telemetry.d.ts +2 -2
- package/dist/extraction-judge-training.d.ts +2 -2
- package/dist/extraction-judge.d.ts +2 -2
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-normalization.d.ts +1 -1
- package/dist/extraction-prompt.d.ts +1 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction-span-config.d.ts +1 -1
- package/dist/extraction-span-materialize.d.ts +1 -1
- package/dist/extraction-span-wire.d.ts +1 -1
- package/dist/extraction.d.ts +2 -2
- package/dist/extraction.js +15 -15
- package/dist/fallback-llm-codex-subscription.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{generic-recall-paths-Bfc7zigS.d.ts → generic-recall-paths-BoueWXYr.d.ts} +1 -1
- package/dist/graph-append-error.d.ts +1 -1
- 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-jsonl.d.ts +1 -1
- package/dist/graph-path-reconstruction.d.ts +1 -1
- package/dist/graph-path-scoring.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/importers/test-utils.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/{index-C7IA1DxB.d.ts → index-cwbrXoxe.d.ts} +3 -3
- package/dist/index.d.ts +719 -719
- package/dist/index.js +132 -127
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +4 -4
- 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/{local-llm-DH4tWtck.d.ts → local-llm-B0vd_rp9.d.ts} +1 -1
- package/dist/local-llm-helpers.d.ts +2 -2
- package/dist/local-llm.d.ts +2 -2
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/location/index.d.ts +5 -5
- package/dist/location/index.js +13 -13
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +11 -11
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +11 -11
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.js +12 -12
- package/dist/{maintenance-n6yeTQhR.d.ts → maintenance-DJgQkoLh.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +14 -14
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-browse-http-glue.d.ts +14 -14
- package/dist/memory-browse-mcp-tools.d.ts +14 -14
- package/dist/memory-browse-operations.js +33 -31
- package/dist/memory-browse.d.ts +1 -1
- package/dist/memory-browse.js +7 -5
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-controller.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-snapshot.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +14 -14
- package/dist/memory-subject.d.ts +14 -14
- 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 +12 -12
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +11 -11
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +14 -14
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +3 -3
- package/dist/operator-toolkit.js +21 -21
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/orchestration/maintenance.js +19 -19
- package/dist/{orchestrator-Cirjo3ZB.d.ts → orchestrator-Kx5zwNUm.d.ts} +10 -10
- package/dist/orchestrator.d.ts +11 -11
- package/dist/orchestrator.js +99 -98
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-DK79FrgH.d.ts → pipeline-9SYxxign.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +2 -2
- package/dist/provenance.d.ts +1 -1
- package/dist/{public-http-VB85y0ZH.d.ts → public-http-Bvol6Jr4.d.ts} +1 -1
- package/dist/{qmd-BT0ErlRS.d.ts → qmd-DjwuG9Xa.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/qmd.js +4 -4
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-context-composition.d.ts +43 -3
- package/dist/recall-context-composition.js +5 -1
- 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-memory-map.d.ts +1 -1
- package/dist/recall-navigate-access.d.ts +1 -1
- package/dist/recall-navigate-access.js +2 -2
- package/dist/recall-navigate-disclosure.d.ts +1 -1
- package/dist/recall-navigate-disclosure.js +2 -2
- package/dist/recall-navigate.d.ts +1 -1
- package/dist/recall-navigate.js +2 -2
- package/dist/recall-navigation-http-glue.d.ts +14 -14
- package/dist/recall-navigation-mcp-tools.d.ts +14 -14
- package/dist/recall-navigation-operations.js +33 -31
- package/dist/recall-navigation-renderer.d.ts +1 -1
- package/dist/recall-navigation.d.ts +1 -1
- package/dist/recall-navigation.js +32 -30
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state-view-admission.d.ts +2 -2
- package/dist/recall-state-view-admission.js +8 -5
- package/dist/recall-state-view-anchors.d.ts +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/reconcile/cursor.d.ts +1 -1
- package/dist/reconcile/manifest.d.ts +1 -1
- package/dist/reconcile/plan.d.ts +1 -1
- 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/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -3
- 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/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +160 -160
- package/dist/{scope-profiles-BgPQx9iE.d.ts → scope-profiles-DPegZtRf.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +5 -5
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +9 -9
- 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-BrAWKVcm.d.ts → semantic-consolidation-DMzhKRYl.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +12 -12
- package/dist/semantic-rule-promotion.js +11 -11
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +11 -11
- package/dist/{service-CqHOTg87.d.ts → service-E99KuPvi.d.ts} +2 -2
- 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 +7 -7
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/source-agent-qualifier.js +14 -14
- package/dist/standup/build.js +11 -11
- package/dist/{storage-mD2YQ8_2.d.ts → storage-BS7ux8F7.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +10 -10
- package/dist/subject-config.d.ts +1 -1
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/support-passport/index.d.ts +17 -17
- package/dist/support-passport/index.js +35 -33
- package/dist/{tagging-CiAVA-ID.d.ts → tagging-BhOEcSXP.d.ts} +2 -2
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-timeline-recall.js +2 -2
- 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/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/export-okf.d.ts +1 -1
- package/dist/transfer/export-okf.js +11 -11
- package/dist/transfer/types.d.ts +78 -78
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DgDaoY61.d.ts → types-DQ2H8gAC.d.ts} +19 -0
- 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 +11 -11
- package/dist/who-knows-cli.d.ts +14 -14
- package/dist/who-knows-cli.js +12 -12
- package/dist/who-knows.d.ts +14 -14
- package/dist/who-knows.js +11 -11
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/config.ts +4 -0
- package/src/directory-sidecars.test.ts +268 -0
- package/src/directory-sidecars.ts +383 -0
- package/src/experience/session-experience-config.ts +51 -0
- package/src/experience/session-experience-lifecycle.test.ts +139 -0
- package/src/experience/session-experience.test.ts +233 -0
- package/src/experience/session-experience.ts +240 -0
- package/src/orchestration/generic-recall-paths.ts +5 -1
- package/src/orchestration/session-context.ts +50 -0
- package/src/recall-context-composition.test.ts +66 -0
- package/src/recall-context-composition.ts +102 -2
- package/src/types.ts +3 -0
- package/dist/chunk-7ZNJN5WZ.js.map +0 -1
- package/dist/chunk-AS4YKT75.js.map +0 -1
- package/dist/chunk-DE4ZGH2N.js.map +0 -1
- package/dist/chunk-OSTXIFZW.js.map +0 -1
- package/dist/chunk-WQW6KPVC.js.map +0 -1
- /package/dist/{chunk-MQT3WP3F.js.map → chunk-3MX4GWJO.js.map} +0 -0
- /package/dist/{chunk-PXJ6HE2W.js.map → chunk-3RABJOGD.js.map} +0 -0
- /package/dist/{chunk-V2XNMIAM.js.map → chunk-4WSR7MYV.js.map} +0 -0
- /package/dist/{chunk-6A6HSXWV.js.map → chunk-5WIGM42T.js.map} +0 -0
- /package/dist/{chunk-GGROFIYX.js.map → chunk-6DYCSSHN.js.map} +0 -0
- /package/dist/{chunk-55XWRL6F.js.map → chunk-6VLOKESS.js.map} +0 -0
- /package/dist/{chunk-OS4JVTHR.js.map → chunk-76RTJ533.js.map} +0 -0
- /package/dist/{chunk-4LPIVP77.js.map → chunk-7VTQETLT.js.map} +0 -0
- /package/dist/{chunk-X2NFFBTO.js.map → chunk-A7GQBJP5.js.map} +0 -0
- /package/dist/{chunk-G2SJRFGP.js.map → chunk-AIKUSXIS.js.map} +0 -0
- /package/dist/{chunk-MNG536IS.js.map → chunk-AJLQH4RO.js.map} +0 -0
- /package/dist/{chunk-XEHLYA5Z.js.map → chunk-AOMVOC2E.js.map} +0 -0
- /package/dist/{chunk-XXLH6KFO.js.map → chunk-BHXY5FXL.js.map} +0 -0
- /package/dist/{chunk-PA67GXYJ.js.map → chunk-C3YEB7CR.js.map} +0 -0
- /package/dist/{chunk-SK6HEQTW.js.map → chunk-DHTOYRDE.js.map} +0 -0
- /package/dist/{chunk-P3YNEMYY.js.map → chunk-E64HBW7M.js.map} +0 -0
- /package/dist/{chunk-NMA7RQPR.js.map → chunk-EBLKS57G.js.map} +0 -0
- /package/dist/{chunk-2QX65IEC.js.map → chunk-EHUUXLLA.js.map} +0 -0
- /package/dist/{chunk-LYMJ36UI.js.map → chunk-EST3J4LX.js.map} +0 -0
- /package/dist/{chunk-B6VDWDFN.js.map → chunk-EUP4AXIW.js.map} +0 -0
- /package/dist/{chunk-XZUHXKM3.js.map → chunk-FLZJMXFV.js.map} +0 -0
- /package/dist/{chunk-GXJSLNYO.js.map → chunk-FVRZDZ7J.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
- /package/dist/{chunk-2JFSQWFH.js.map → chunk-HIBNECBQ.js.map} +0 -0
- /package/dist/{chunk-QLCMOHRE.js.map → chunk-IIDXU2CY.js.map} +0 -0
- /package/dist/{chunk-2NKY7OC6.js.map → chunk-IMJPJY7V.js.map} +0 -0
- /package/dist/{chunk-JYXLW22X.js.map → chunk-JG4AP36X.js.map} +0 -0
- /package/dist/{chunk-3RKVSECD.js.map → chunk-JI5Q6EOY.js.map} +0 -0
- /package/dist/{chunk-XKHRNAK7.js.map → chunk-JIWZVOQM.js.map} +0 -0
- /package/dist/{chunk-7YUX52XZ.js.map → chunk-JVM3CEGL.js.map} +0 -0
- /package/dist/{chunk-QB4VXCFN.js.map → chunk-K26LFJE4.js.map} +0 -0
- /package/dist/{chunk-Y5VT7BIT.js.map → chunk-K4KE5MYE.js.map} +0 -0
- /package/dist/{chunk-TQSCR5DU.js.map → chunk-KG2OOAFF.js.map} +0 -0
- /package/dist/{chunk-VV5GCNKG.js.map → chunk-KHLNIDWB.js.map} +0 -0
- /package/dist/{chunk-LIAM7U2J.js.map → chunk-LE2UESGS.js.map} +0 -0
- /package/dist/{chunk-YXWMZAAN.js.map → chunk-LLFMZYFS.js.map} +0 -0
- /package/dist/{chunk-PPJLJD2D.js.map → chunk-LX32KV2C.js.map} +0 -0
- /package/dist/{chunk-44BILQ7U.js.map → chunk-N57PUWUE.js.map} +0 -0
- /package/dist/{chunk-V7IONNTJ.js.map → chunk-NDWBT2VV.js.map} +0 -0
- /package/dist/{chunk-DRLZB3CN.js.map → chunk-OTGIAJQ2.js.map} +0 -0
- /package/dist/{chunk-T3T5HTZZ.js.map → chunk-P36WML7L.js.map} +0 -0
- /package/dist/{chunk-SJ7ZNUXB.js.map → chunk-PAQ7YGJY.js.map} +0 -0
- /package/dist/{chunk-YOSLKZLZ.js.map → chunk-PSQRBCGR.js.map} +0 -0
- /package/dist/{chunk-FGRJJBFO.js.map → chunk-PZHN7R35.js.map} +0 -0
- /package/dist/{chunk-QMWKENNK.js.map → chunk-Q4AUIFUA.js.map} +0 -0
- /package/dist/{chunk-CD7VTZIG.js.map → chunk-QQA6VTOY.js.map} +0 -0
- /package/dist/{chunk-ROBGBT3D.js.map → chunk-RBHQA2UU.js.map} +0 -0
- /package/dist/{chunk-HG74GEVY.js.map → chunk-RS53SDCA.js.map} +0 -0
- /package/dist/{chunk-SS33RR4F.js.map → chunk-SKYYPVBW.js.map} +0 -0
- /package/dist/{chunk-EWBDJQ5Y.js.map → chunk-SYJZUVTN.js.map} +0 -0
- /package/dist/{chunk-IO7K7GES.js.map → chunk-T3F2HWIG.js.map} +0 -0
- /package/dist/{chunk-BMENHAJL.js.map → chunk-T3OLYUU6.js.map} +0 -0
- /package/dist/{chunk-QGAKUOKU.js.map → chunk-U3ZRHNNG.js.map} +0 -0
- /package/dist/{chunk-LIDJWAQV.js.map → chunk-UB4UVALS.js.map} +0 -0
- /package/dist/{chunk-WYU66IVZ.js.map → chunk-UE7L4EDU.js.map} +0 -0
- /package/dist/{chunk-I6IJDTRP.js.map → chunk-VBRS7Z3O.js.map} +0 -0
- /package/dist/{chunk-EGOWY342.js.map → chunk-WGCASEEQ.js.map} +0 -0
- /package/dist/{chunk-QKFYJVDG.js.map → chunk-WHSNLCRO.js.map} +0 -0
- /package/dist/{chunk-JXLEQIJ7.js.map → chunk-XQFHKPNB.js.map} +0 -0
- /package/dist/{chunk-NE45P3OF.js.map → chunk-YA5M7OC4.js.map} +0 -0
- /package/dist/{chunk-WL3PGPGU.js.map → chunk-YKWWG3CM.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-MMQVWKFY.js.map → chunk-YOC3EEZH.js.map} +0 -0
- /package/dist/{chunk-ZEIJQ4I3.js.map → chunk-Z2OKRO3E.js.map} +0 -0
- /package/dist/{chunk-5KBI2MOC.js.map → chunk-ZDGSHXJU.js.map} +0 -0
- /package/dist/{chunk-R4JTUTU6.js.map → chunk-ZYZW46HZ.js.map} +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-end agent-experience extraction (issue #2979).
|
|
3
|
+
*
|
|
4
|
+
* Deterministic Situation/Approach/Reflection episode extraction from a
|
|
5
|
+
* completed session transcript, plus the privacy/trust gate: with
|
|
6
|
+
* `sessionExperience.enabled` off the run performs ZERO storage calls
|
|
7
|
+
* (not even reads), and when on it writes at most one agent-subject,
|
|
8
|
+
* `pending_review` procedure memory per session key.
|
|
9
|
+
*/
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import test from "node:test";
|
|
15
|
+
|
|
16
|
+
import { parseConfig } from "../config.js";
|
|
17
|
+
import type { BufferTurn, MemoryFile, PluginConfig } from "../types.js";
|
|
18
|
+
import type { SealedMemoryEnvelope } from "../write-envelope.js";
|
|
19
|
+
import { StorageManager } from "../index.js";
|
|
20
|
+
import {
|
|
21
|
+
SESSION_EXPERIENCE_CONFIG_DEFAULTS,
|
|
22
|
+
parseSessionExperienceConfig,
|
|
23
|
+
} from "./session-experience-config.js";
|
|
24
|
+
import {
|
|
25
|
+
extractExperienceEpisode,
|
|
26
|
+
runSessionExperienceExtraction,
|
|
27
|
+
} from "./session-experience.js";
|
|
28
|
+
|
|
29
|
+
function turn(role: "user" | "assistant", content: string): BufferTurn {
|
|
30
|
+
return { role, content, timestamp: "2026-01-15T10:00:00.000Z" };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A representative successful coding session transcript. */
|
|
34
|
+
function successSession(): BufferTurn[] {
|
|
35
|
+
return [
|
|
36
|
+
turn("user", "Fix the failing integration test in the payments module. The test times out after 30 seconds."),
|
|
37
|
+
turn("assistant", "I will inspect the payments test file and reproduce the timeout locally first."),
|
|
38
|
+
turn("assistant", "The timeout came from an unawaited database call in the test setup. I added the missing await."),
|
|
39
|
+
turn("assistant", "Verified: the payments integration test passes now and the full suite is green."),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A representative failed session transcript (refusal/failure is signal). */
|
|
44
|
+
function failureSession(): BufferTurn[] {
|
|
45
|
+
return [
|
|
46
|
+
turn("user", "Migrate the billing export job to the new queue library before the release cut."),
|
|
47
|
+
turn("assistant", "I will port the export job onto the new queue client and run the smoke suite."),
|
|
48
|
+
turn("assistant", "The migration failed: the new queue library cannot serialize the billing payload format."),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Minimal structural stand-in for StorageManager; records every call. */
|
|
53
|
+
function recordingStorage(existing: MemoryFile[] = []) {
|
|
54
|
+
const calls: { readAllMemories: number; writeSealedMemory: SealedMemoryEnvelope[] } = {
|
|
55
|
+
readAllMemories: 0,
|
|
56
|
+
writeSealedMemory: [],
|
|
57
|
+
};
|
|
58
|
+
const storage = {
|
|
59
|
+
async readAllMemories() {
|
|
60
|
+
calls.readAllMemories += 1;
|
|
61
|
+
return existing;
|
|
62
|
+
},
|
|
63
|
+
async writeSealedMemory(envelope: SealedMemoryEnvelope) {
|
|
64
|
+
calls.writeSealedMemory.push(envelope);
|
|
65
|
+
return { id: "written-once" };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
return { storage, calls };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function enabledConfig(memoryDir: string): PluginConfig {
|
|
72
|
+
return parseConfig({ memoryDir, sessionExperience: { enabled: true } });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
test("extractExperienceEpisode derives the situation/approach/reflection classes from a session transcript", () => {
|
|
76
|
+
const episode = extractExperienceEpisode(successSession());
|
|
77
|
+
assert.ok(episode, "a representative session must yield an episode");
|
|
78
|
+
assert.match(episode.situation, /Fix the failing integration test/);
|
|
79
|
+
assert.match(episode.situation, /payments/i);
|
|
80
|
+
assert.match(episode.approach, /inspect the payments test file/);
|
|
81
|
+
assert.equal(episode.outcomeKind, "success");
|
|
82
|
+
assert.match(episode.reflection, /succeed|pass/i);
|
|
83
|
+
// Deterministic: identical transcript, identical episode.
|
|
84
|
+
assert.deepEqual(extractExperienceEpisode(successSession()), episode);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("extractExperienceEpisode records failure as signal, not a skip", () => {
|
|
88
|
+
const episode = extractExperienceEpisode(failureSession());
|
|
89
|
+
assert.ok(episode);
|
|
90
|
+
assert.equal(episode.outcomeKind, "failure");
|
|
91
|
+
assert.match(episode.reflection, /fail/i);
|
|
92
|
+
assert.match(episode.reflection, /cannot serialize the billing payload/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("extractExperienceEpisode returns null without a task or without agent work", () => {
|
|
96
|
+
assert.equal(extractExperienceEpisode([]), null);
|
|
97
|
+
assert.equal(extractExperienceEpisode([turn("assistant", "Working on it. Verified the fix.")]), null);
|
|
98
|
+
assert.equal(extractExperienceEpisode([turn("user", "Please fix the flaky payments test soon.")]), null);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("gate off performs zero storage calls — nothing is read, nothing is written", async () => {
|
|
102
|
+
const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-sx-off-"));
|
|
103
|
+
try {
|
|
104
|
+
const config = parseConfig({ memoryDir });
|
|
105
|
+
assert.equal(config.sessionExperience.enabled, false);
|
|
106
|
+
const { storage, calls } = recordingStorage();
|
|
107
|
+
const result = await runSessionExperienceExtraction({
|
|
108
|
+
turns: successSession(),
|
|
109
|
+
sessionKey: "session-off",
|
|
110
|
+
config,
|
|
111
|
+
storage,
|
|
112
|
+
});
|
|
113
|
+
assert.deepEqual(result, { written: false, skippedReason: "session_experience_disabled" });
|
|
114
|
+
assert.equal(calls.readAllMemories, 0, "gate off must not even read storage");
|
|
115
|
+
assert.equal(calls.writeSealedMemory.length, 0, "gate off must not compose or write anything");
|
|
116
|
+
} finally {
|
|
117
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("gate on writes exactly one pending_review agent-subject procedure memory per session", async () => {
|
|
122
|
+
const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-sx-on-"));
|
|
123
|
+
try {
|
|
124
|
+
const { storage, calls } = recordingStorage();
|
|
125
|
+
const result = await runSessionExperienceExtraction({
|
|
126
|
+
turns: successSession(),
|
|
127
|
+
sessionKey: "session-on",
|
|
128
|
+
config: enabledConfig(memoryDir),
|
|
129
|
+
storage,
|
|
130
|
+
});
|
|
131
|
+
assert.equal(result.written, true);
|
|
132
|
+
assert.equal(calls.writeSealedMemory.length, 1, "at most one experience per session");
|
|
133
|
+
const envelope = calls.writeSealedMemory[0];
|
|
134
|
+
assert.equal(envelope.category, "procedure");
|
|
135
|
+
assert.equal(envelope.subject, "agent");
|
|
136
|
+
assert.equal(envelope.source, "session-experience");
|
|
137
|
+
assert.ok(envelope.structuredAttributes?.experience_session_hash, "dedupe hash must be stamped");
|
|
138
|
+
assert.equal(envelope.structuredAttributes?.experience_outcome, "success");
|
|
139
|
+
assert.match(envelope.content, /Situation: Fix the failing integration test/);
|
|
140
|
+
assert.match(envelope.content, /Approach: /);
|
|
141
|
+
assert.match(envelope.content, /Reflection: /);
|
|
142
|
+
} finally {
|
|
143
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a repeated session_end for the same session key dedupes to zero new writes", async () => {
|
|
148
|
+
const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-sx-dup-"));
|
|
149
|
+
try {
|
|
150
|
+
const realStorage = new StorageManager(memoryDir);
|
|
151
|
+
await realStorage.ensureDirectories();
|
|
152
|
+
try {
|
|
153
|
+
const first = await runSessionExperienceExtraction({
|
|
154
|
+
turns: successSession(),
|
|
155
|
+
sessionKey: "session-dedupe",
|
|
156
|
+
config: enabledConfig(memoryDir),
|
|
157
|
+
storage: realStorage,
|
|
158
|
+
});
|
|
159
|
+
assert.equal(first.written, true);
|
|
160
|
+
const second = await runSessionExperienceExtraction({
|
|
161
|
+
turns: successSession(),
|
|
162
|
+
sessionKey: "session-dedupe",
|
|
163
|
+
config: enabledConfig(memoryDir),
|
|
164
|
+
storage: realStorage,
|
|
165
|
+
});
|
|
166
|
+
assert.deepEqual(second, { written: false, skippedReason: "duplicate_session" });
|
|
167
|
+
const experiences = (await realStorage.readAllMemories()).filter(
|
|
168
|
+
(m) => m.frontmatter.structuredAttributes?.experience_session_hash !== undefined,
|
|
169
|
+
);
|
|
170
|
+
assert.equal(experiences.length, 1, "replay must not duplicate the episode");
|
|
171
|
+
assert.equal(experiences[0]?.frontmatter.status, "pending_review", "trust-mode review by default");
|
|
172
|
+
assert.equal(experiences[0]?.frontmatter.subject, "agent");
|
|
173
|
+
assert.equal(experiences[0]?.frontmatter.category, "procedure");
|
|
174
|
+
} finally {
|
|
175
|
+
// StorageManager holds no explicit dispose; the temp dir removal below
|
|
176
|
+
// is the cleanup.
|
|
177
|
+
}
|
|
178
|
+
} finally {
|
|
179
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("deadline and abort are honored before any write", async () => {
|
|
184
|
+
const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-sx-dl-"));
|
|
185
|
+
try {
|
|
186
|
+
const { storage, calls } = recordingStorage();
|
|
187
|
+
const deadline = await runSessionExperienceExtraction({
|
|
188
|
+
turns: successSession(),
|
|
189
|
+
sessionKey: "session-deadline",
|
|
190
|
+
config: enabledConfig(memoryDir),
|
|
191
|
+
storage,
|
|
192
|
+
deadlineMs: Date.now() - 1,
|
|
193
|
+
});
|
|
194
|
+
assert.deepEqual(deadline, { written: false, skippedReason: "deadline_elapsed" });
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
controller.abort();
|
|
197
|
+
const aborted = await runSessionExperienceExtraction({
|
|
198
|
+
turns: successSession(),
|
|
199
|
+
sessionKey: "session-abort",
|
|
200
|
+
config: enabledConfig(memoryDir),
|
|
201
|
+
storage,
|
|
202
|
+
abortSignal: controller.signal,
|
|
203
|
+
});
|
|
204
|
+
assert.deepEqual(aborted, { written: false, skippedReason: "aborted" });
|
|
205
|
+
assert.equal(calls.writeSealedMemory.length, 0);
|
|
206
|
+
} finally {
|
|
207
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("sessionExperience config: default off, opt-in parse, unknown keys fail closed, conservative pins off", () => {
|
|
212
|
+
assert.deepEqual(SESSION_EXPERIENCE_CONFIG_DEFAULTS, { enabled: false });
|
|
213
|
+
assert.deepEqual(parseSessionExperienceConfig(undefined), { enabled: false });
|
|
214
|
+
assert.deepEqual(parseSessionExperienceConfig({}), { enabled: false });
|
|
215
|
+
assert.deepEqual(parseSessionExperienceConfig({ enabled: true }), { enabled: true });
|
|
216
|
+
assert.throws(() => parseSessionExperienceConfig("yes"), /sessionExperience must be an object/);
|
|
217
|
+
assert.throws(
|
|
218
|
+
() => parseSessionExperienceConfig({ enabled: true, maxEpisodes: 2 }),
|
|
219
|
+
/unknown key "maxEpisodes"/,
|
|
220
|
+
);
|
|
221
|
+
const memoryDir = "/tmp/remnic-sx-preset";
|
|
222
|
+
assert.equal(parseConfig({ memoryDir }).sessionExperience.enabled, false);
|
|
223
|
+
assert.equal(
|
|
224
|
+
parseConfig({ memoryDir, memoryOsPreset: "conservative" }).sessionExperience.enabled,
|
|
225
|
+
false,
|
|
226
|
+
"the conservative preset pins session-end experience extraction off",
|
|
227
|
+
);
|
|
228
|
+
assert.equal(
|
|
229
|
+
parseConfig({ memoryDir, memoryOsPreset: "balanced", sessionExperience: { enabled: true } })
|
|
230
|
+
.sessionExperience.enabled,
|
|
231
|
+
true,
|
|
232
|
+
);
|
|
233
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-end agent-experience extraction (issue #2979).
|
|
3
|
+
*
|
|
4
|
+
* At `session_end` the completed session's buffered turns are distilled —
|
|
5
|
+
* deterministically, no LLM — into at most ONE Situation/Approach/Reflect
|
|
6
|
+
* "experience episode" memory (agent-subject, category `procedure`,
|
|
7
|
+
* `pending_review`), mirroring OpenViking's experience-memory schema at the
|
|
8
|
+
* granularity Remnic's trust model allows: a machine-derived write that
|
|
9
|
+
* stays behind the existing sealed-envelope write path and never activates
|
|
10
|
+
* itself.
|
|
11
|
+
*
|
|
12
|
+
* Determinism: the episode is a pure function of the turn list — same
|
|
13
|
+
* transcript, same episode. Refusal/failure is signal, not a skip: a
|
|
14
|
+
* session whose terminal assistant turn records a failure gets a failure
|
|
15
|
+
* reflection.
|
|
16
|
+
*
|
|
17
|
+
* v1 heuristics (same tier as procedure-miner's `pseudoStepsFromCluster`):
|
|
18
|
+
* situation = first substantive user turn (task shape); approach = first
|
|
19
|
+
* sentences of the assistant turns; reflection = terminal outcome evidence.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
23
|
+
|
|
24
|
+
import { log } from "../logger.js";
|
|
25
|
+
import type { BufferTurn, MemoryFile, MemoryStatus, PluginConfig } from "../types.js";
|
|
26
|
+
import { composeSalvagedEnvelope } from "../salvage-envelope.js";
|
|
27
|
+
import type { SealedMemoryEnvelope } from "../write-envelope.js";
|
|
28
|
+
|
|
29
|
+
export type SessionExperienceOutcomeKind = "success" | "failure" | "inconclusive";
|
|
30
|
+
|
|
31
|
+
/** The OpenViking Situation/Approach/Reflect classes, Remnic spelling. */
|
|
32
|
+
export interface ExperienceEpisode {
|
|
33
|
+
/** Task shape: what the session was asked to do. */
|
|
34
|
+
situation: string;
|
|
35
|
+
/** What the agent tried, in order. */
|
|
36
|
+
approach: string;
|
|
37
|
+
/** Outcome and what to do differently. */
|
|
38
|
+
reflection: string;
|
|
39
|
+
outcomeKind: SessionExperienceOutcomeKind;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SessionExperienceSkippedReason =
|
|
43
|
+
| "session_experience_disabled"
|
|
44
|
+
| "aborted"
|
|
45
|
+
| "deadline_elapsed"
|
|
46
|
+
| "duplicate_session"
|
|
47
|
+
| "insufficient_signal";
|
|
48
|
+
|
|
49
|
+
export type SessionExperienceRunResult =
|
|
50
|
+
| { written: true; episode: ExperienceEpisode; memoryId: string }
|
|
51
|
+
| { written: false; skippedReason: SessionExperienceSkippedReason };
|
|
52
|
+
|
|
53
|
+
/** The subset of StorageManager the experience write needs (test-friendly). */
|
|
54
|
+
export interface SessionExperienceStorage {
|
|
55
|
+
readAllMemories(): Promise<MemoryFile[]>;
|
|
56
|
+
writeSealedMemory(
|
|
57
|
+
envelope: SealedMemoryEnvelope,
|
|
58
|
+
extras?: { status?: MemoryStatus },
|
|
59
|
+
): Promise<{ id?: string }>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SessionExperienceRunOptions {
|
|
63
|
+
turns: readonly BufferTurn[];
|
|
64
|
+
sessionKey: string;
|
|
65
|
+
config: PluginConfig;
|
|
66
|
+
storage: SessionExperienceStorage;
|
|
67
|
+
abortSignal?: AbortSignal;
|
|
68
|
+
/** Shared flush deadline (epoch ms); skipped when already elapsed. */
|
|
69
|
+
deadlineMs?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Deterministic derivation
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
const SITUATION_MAX_CHARS = 280;
|
|
77
|
+
const SNIPPET_MAX_CHARS = 160;
|
|
78
|
+
const APPROACH_MAX_SNIPPETS = 3;
|
|
79
|
+
const MIN_SITUATION_CHARS = 16;
|
|
80
|
+
|
|
81
|
+
/** Terminal-outcome markers, scanned over ASSISTANT turns only (last first). */
|
|
82
|
+
const FAILURE_MARKER =
|
|
83
|
+
/\b(?:failed|failing|failure|cannot|can't|unable to|blocked|refused|refusal)\b/i;
|
|
84
|
+
const SUCCESS_MARKER =
|
|
85
|
+
/\b(?:succeeded|verified|tests? pass(?:ed|ing)?|fixed|resolved|completed successfully|shipped)\b/i;
|
|
86
|
+
|
|
87
|
+
function collapse(text: string): string {
|
|
88
|
+
return text.replace(/\s+/g, " ").trim();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function capAtWordBoundary(text: string, maxChars: number): string {
|
|
92
|
+
if (text.length <= maxChars) return text;
|
|
93
|
+
const slice = text.slice(0, maxChars);
|
|
94
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
95
|
+
return `${(lastSpace > maxChars * 0.6 ? slice.slice(0, lastSpace) : slice).trim()}…`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function firstSentence(text: string): string {
|
|
99
|
+
const match = collapse(text).match(/^[^.!?]*[.!?]/);
|
|
100
|
+
return collapse(match ? match[0] : text);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sentenceContaining(text: string, marker: RegExp): string {
|
|
104
|
+
const flat = collapse(text);
|
|
105
|
+
for (const sentence of flat.split(/(?<=[.!?])\s+/)) {
|
|
106
|
+
if (marker.test(sentence)) return sentence;
|
|
107
|
+
}
|
|
108
|
+
return flat;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function classifyOutcome(assistantTurns: string[]): { outcomeKind: SessionExperienceOutcomeKind; evidence: string } {
|
|
112
|
+
for (let i = assistantTurns.length - 1; i >= 0; i -= 1) {
|
|
113
|
+
const turn = assistantTurns[i];
|
|
114
|
+
if (FAILURE_MARKER.test(turn)) {
|
|
115
|
+
return { outcomeKind: "failure", evidence: sentenceContaining(turn, FAILURE_MARKER) };
|
|
116
|
+
}
|
|
117
|
+
if (SUCCESS_MARKER.test(turn)) {
|
|
118
|
+
return { outcomeKind: "success", evidence: sentenceContaining(turn, SUCCESS_MARKER) };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { outcomeKind: "inconclusive", evidence: "" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function reflectionFor(outcomeKind: SessionExperienceOutcomeKind, evidence: string): string {
|
|
125
|
+
if (outcomeKind === "failure") {
|
|
126
|
+
return `The session failed: "${capAtWordBoundary(evidence, SNIPPET_MAX_CHARS)}". Change that step before retrying the same approach.`;
|
|
127
|
+
}
|
|
128
|
+
if (outcomeKind === "success") {
|
|
129
|
+
return `The session succeeded: "${capAtWordBoundary(evidence, SNIPPET_MAX_CHARS)}". The approach above worked for this situation.`;
|
|
130
|
+
}
|
|
131
|
+
return "No terminal outcome signal appears in the transcript; treat the approach above as unverified.";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Derive the single experience episode a completed session yields, or `null`
|
|
136
|
+
* when the transcript carries no learnable signal (no task, or no agent work).
|
|
137
|
+
*/
|
|
138
|
+
export function extractExperienceEpisode(turns: readonly BufferTurn[]): ExperienceEpisode | null {
|
|
139
|
+
const userTurns = turns.filter((turn) => turn.role === "user").map((turn) => collapse(turn.content)).filter(Boolean);
|
|
140
|
+
const situationSource = userTurns.find((content) => content.length >= MIN_SITUATION_CHARS);
|
|
141
|
+
if (situationSource === undefined) return null;
|
|
142
|
+
|
|
143
|
+
const assistantTurns = turns
|
|
144
|
+
.filter((turn) => turn.role === "assistant")
|
|
145
|
+
.map((turn) => collapse(turn.content))
|
|
146
|
+
.filter(Boolean);
|
|
147
|
+
if (assistantTurns.length === 0) return null;
|
|
148
|
+
|
|
149
|
+
const snippets: string[] = [];
|
|
150
|
+
for (const turn of assistantTurns) {
|
|
151
|
+
const sentence = firstSentence(turn);
|
|
152
|
+
if (sentence.length === 0 || snippets.includes(sentence)) continue;
|
|
153
|
+
snippets.push(capAtWordBoundary(sentence, SNIPPET_MAX_CHARS));
|
|
154
|
+
if (snippets.length >= APPROACH_MAX_SNIPPETS) break;
|
|
155
|
+
}
|
|
156
|
+
if (snippets.length === 0) return null;
|
|
157
|
+
|
|
158
|
+
const { outcomeKind, evidence } = classifyOutcome(assistantTurns);
|
|
159
|
+
return {
|
|
160
|
+
situation: capAtWordBoundary(situationSource, SITUATION_MAX_CHARS),
|
|
161
|
+
approach: snippets.join("; "),
|
|
162
|
+
reflection: reflectionFor(outcomeKind, evidence),
|
|
163
|
+
outcomeKind,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Gated write
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
const EXPERIENCE_SOURCE = "session-experience";
|
|
172
|
+
|
|
173
|
+
function sessionKeyHash(sessionKey: string): string {
|
|
174
|
+
return createHash("sha256").update(sessionKey).digest("hex");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function hasExistingExperience(
|
|
178
|
+
storage: SessionExperienceStorage,
|
|
179
|
+
hash: string,
|
|
180
|
+
): Promise<boolean> {
|
|
181
|
+
const memories = await storage.readAllMemories();
|
|
182
|
+
return memories.some((m) => m.frontmatter.structuredAttributes?.experience_session_hash === hash);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Run session-end experience extraction for one session. With the gate off
|
|
187
|
+
* this touches NOTHING — no reads, no envelope, no write. On write the
|
|
188
|
+
* episode lands as one `pending_review` agent-subject `procedure` memory
|
|
189
|
+
* (trust-mode review; promotion is the operator's call), deduped per
|
|
190
|
+
* session key so a replayed session_end cannot double-write.
|
|
191
|
+
*/
|
|
192
|
+
export async function runSessionExperienceExtraction(
|
|
193
|
+
options: SessionExperienceRunOptions,
|
|
194
|
+
): Promise<SessionExperienceRunResult> {
|
|
195
|
+
if (options.config.sessionExperience?.enabled !== true) {
|
|
196
|
+
return { written: false, skippedReason: "session_experience_disabled" };
|
|
197
|
+
}
|
|
198
|
+
if (options.abortSignal?.aborted === true) {
|
|
199
|
+
return { written: false, skippedReason: "aborted" };
|
|
200
|
+
}
|
|
201
|
+
if (typeof options.deadlineMs === "number" && Date.now() >= options.deadlineMs) {
|
|
202
|
+
return { written: false, skippedReason: "deadline_elapsed" };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const hash = sessionKeyHash(options.sessionKey);
|
|
206
|
+
if (await hasExistingExperience(options.storage, hash)) {
|
|
207
|
+
return { written: false, skippedReason: "duplicate_session" };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const episode = extractExperienceEpisode(options.turns);
|
|
211
|
+
if (episode === null) {
|
|
212
|
+
return { written: false, skippedReason: "insufficient_signal" };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Machine-derived from buffered turns — salvage-mode envelope, warn-logged
|
|
216
|
+
// drops (issue #1989 PR4 convention for machine-generated writes).
|
|
217
|
+
const envelope = composeSalvagedEnvelope(
|
|
218
|
+
EXPERIENCE_SOURCE,
|
|
219
|
+
{
|
|
220
|
+
content: `Situation: ${episode.situation}\nApproach: ${episode.approach}\nReflection: ${episode.reflection}`,
|
|
221
|
+
category: "procedure",
|
|
222
|
+
subject: "agent",
|
|
223
|
+
confidence: 0.6,
|
|
224
|
+
tags: ["session-experience", `experience-${episode.outcomeKind}`],
|
|
225
|
+
structuredAttributes: {
|
|
226
|
+
experience_situation: episode.situation,
|
|
227
|
+
experience_approach: episode.approach,
|
|
228
|
+
experience_reflection: episode.reflection,
|
|
229
|
+
experience_outcome: episode.outcomeKind,
|
|
230
|
+
experience_session_hash: hash,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{ source: EXPERIENCE_SOURCE },
|
|
234
|
+
);
|
|
235
|
+
const written = await options.storage.writeSealedMemory(envelope, { status: "pending_review" });
|
|
236
|
+
log.debug(
|
|
237
|
+
`${EXPERIENCE_SOURCE}: wrote episode for session hash ${hash.slice(0, 12)}… (outcome ${episode.outcomeKind})`,
|
|
238
|
+
);
|
|
239
|
+
return { written: true, episode, memoryId: typeof written.id === "string" ? written.id : "" };
|
|
240
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isMeetingRecordPath,
|
|
11
11
|
} from "./orchestrator-helpers.js";
|
|
12
12
|
import { isJournalDayPath } from "../activity/journal.js";
|
|
13
|
+
import { DIRECTORY_SIDECAR_BASENAME } from "../directory-sidecars.js";
|
|
13
14
|
import { OKF_RESERVED_BASENAMES } from "../okf/type-mapping.js";
|
|
14
15
|
|
|
15
16
|
|
|
@@ -244,7 +245,10 @@ export function isGenericRecallExcludedPath(
|
|
|
244
245
|
return (
|
|
245
246
|
isSearchExcludedPath(filePath, policy, source) ||
|
|
246
247
|
isTopLevelArchivePath(filePath, policy, source) ||
|
|
247
|
-
OKF_RESERVED_BASENAMES[path.basename(filePath)] === true
|
|
248
|
+
OKF_RESERVED_BASENAMES[path.basename(filePath)] === true ||
|
|
249
|
+
// Derived per-directory sidecars (#2977): recall-excluded like OKF index
|
|
250
|
+
// files, still searchable. Basename-only, the same accepted tradeoff.
|
|
251
|
+
path.basename(filePath) === DIRECTORY_SIDECAR_BASENAME
|
|
248
252
|
);
|
|
249
253
|
}
|
|
250
254
|
|
|
@@ -22,6 +22,7 @@ import { StorageManager } from "../index.js";
|
|
|
22
22
|
import { LocalLlmClient } from "../local-llm.js";
|
|
23
23
|
import { log } from "../logger.js";
|
|
24
24
|
import { canWriteNamespace, defaultNamespaceForPrincipal, recallNamespacesForPrincipal, resolvePrincipal } from "../namespaces/principal.js";
|
|
25
|
+
import { runSessionExperienceExtraction } from "../experience/session-experience.js";
|
|
25
26
|
import { ExtractionDeadlineError } from "./extraction-run.js";
|
|
26
27
|
import type { ExtractionRunResult } from "./extraction-run.js";
|
|
27
28
|
import type { EntitySynthesisCoordinator } from "./entity-synthesis-coordinator.js";
|
|
@@ -527,6 +528,9 @@ export class SessionContextCoordinator {
|
|
|
527
528
|
})
|
|
528
529
|
.catch(reject);
|
|
529
530
|
});
|
|
531
|
+
if (options.reason === "session_end" && this.deps.config.sessionExperience?.enabled === true) {
|
|
532
|
+
await this.extractSessionEndExperience(turnsForSession, sessionKey, options);
|
|
533
|
+
}
|
|
530
534
|
} finally {
|
|
531
535
|
if (
|
|
532
536
|
retainedTurnsForOtherSessions.length > 0 &&
|
|
@@ -553,6 +557,52 @@ export class SessionContextCoordinator {
|
|
|
553
557
|
}
|
|
554
558
|
}
|
|
555
559
|
|
|
560
|
+
/**
|
|
561
|
+
* Session-end experience extraction (issue #2979): derive at most one
|
|
562
|
+
* Situation/Approach/Reflect episode from the drained session's turns.
|
|
563
|
+
* Best-effort by contract — a failure here must never fail the flush
|
|
564
|
+
* (the fact extraction already committed). Honors the shared abort signal
|
|
565
|
+
* and flush deadline; the write itself is gated, deduped per session, and
|
|
566
|
+
* lands as `pending_review` inside `runSessionExperienceExtraction`.
|
|
567
|
+
*/
|
|
568
|
+
private async extractSessionEndExperience(
|
|
569
|
+
turns: BufferTurn[],
|
|
570
|
+
sessionKey: string,
|
|
571
|
+
options: SessionFlushOptions,
|
|
572
|
+
): Promise<void> {
|
|
573
|
+
try {
|
|
574
|
+
const explicitNamespace =
|
|
575
|
+
typeof options.writeNamespaceOverride === "string" && options.writeNamespaceOverride.trim().length > 0
|
|
576
|
+
? options.writeNamespaceOverride.trim()
|
|
577
|
+
: undefined;
|
|
578
|
+
// Same precedence as the extraction write path: explicit override →
|
|
579
|
+
// scope-profile plan → principal + coding overlay.
|
|
580
|
+
const namespace =
|
|
581
|
+
explicitNamespace ??
|
|
582
|
+
options.scopeProfileWritePlan?.writeNamespace ??
|
|
583
|
+
this.deps.applyCodingNamespaceOverlay(
|
|
584
|
+
sessionKey,
|
|
585
|
+
defaultNamespaceForPrincipal(this.deps.resolvePrincipal(sessionKey), this.deps.config),
|
|
586
|
+
);
|
|
587
|
+
const storage = await this.deps.orchestratorSelf.getStorageForNamespace(namespace);
|
|
588
|
+
const result = await runSessionExperienceExtraction({
|
|
589
|
+
turns,
|
|
590
|
+
sessionKey,
|
|
591
|
+
config: this.deps.config,
|
|
592
|
+
storage,
|
|
593
|
+
abortSignal: options.abortSignal,
|
|
594
|
+
deadlineMs: options.extractionDeadlineMs,
|
|
595
|
+
});
|
|
596
|
+
if (result.written) {
|
|
597
|
+
log.info(`session-experience: recorded ${result.episode.outcomeKind} episode for session end`);
|
|
598
|
+
} else {
|
|
599
|
+
log.debug(`session-experience skipped: ${result.skippedReason}`);
|
|
600
|
+
}
|
|
601
|
+
} catch (error) {
|
|
602
|
+
log.warn(`session-experience extraction failed (non-fatal): ${error}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
556
606
|
/** Lazily construct the CorrectionService for passive capture. Stateless
|
|
557
607
|
* across requests (per #1580 design); cached for the orchestrator's life. */
|
|
558
608
|
passiveCorrectionService(): CorrectionService {
|
|
@@ -2,6 +2,9 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
boundRecallContextComposition,
|
|
6
|
+
composeMissingMemoryContext,
|
|
7
|
+
composeRecallContext,
|
|
5
8
|
formatCuriosityFooter,
|
|
6
9
|
renderMemoryContextPrompt,
|
|
7
10
|
selectCuriosityQuestion,
|
|
@@ -88,3 +91,66 @@ test("renderMemoryContextPrompt caps context before its footer deterministically
|
|
|
88
91
|
assert.ok(rendered);
|
|
89
92
|
assert.equal(rendered.body, "ab\n\n---\n\nfooter");
|
|
90
93
|
});
|
|
94
|
+
|
|
95
|
+
test("#2972 healthy recall carries no degradation marker", () => {
|
|
96
|
+
const composition = boundRecallContextComposition({
|
|
97
|
+
context: "A remembered deployment decision.",
|
|
98
|
+
maxChars: 512,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
assert.equal("degradation" in composition, false);
|
|
102
|
+
assert.ok(!JSON.stringify(composition).includes("degradation"));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("#2972 budget-clipped context is marked degraded, not silently cut", () => {
|
|
106
|
+
const composition = boundRecallContextComposition({
|
|
107
|
+
context: "x".repeat(100),
|
|
108
|
+
maxChars: 20,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assert.equal(composition.degradation?.state, "degraded");
|
|
112
|
+
assert.equal(composition.degradation?.reason, "budget-clipped");
|
|
113
|
+
assert.equal(composition.degradation?.budget?.fullChars, 100);
|
|
114
|
+
assert.ok(
|
|
115
|
+
(composition.degradation?.budget?.deliveredChars ?? 0) <
|
|
116
|
+
(composition.degradation?.budget?.fullChars ?? 0),
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("#2972 a fitting compact context is preferred over clipping the tail", () => {
|
|
121
|
+
const composition = boundRecallContextComposition({
|
|
122
|
+
context: "full-form entry one\n\nfull-form entry two",
|
|
123
|
+
compactContext: "entry one\nentry two",
|
|
124
|
+
maxChars: 30,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
assert.equal(composition.context, "entry one\nentry two");
|
|
128
|
+
assert.equal(composition.degradation?.state, "degraded");
|
|
129
|
+
assert.equal(composition.degradation?.reason, "budget-compacted");
|
|
130
|
+
assert.equal(composition.degradation?.budget?.deliveredChars, 19);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("#2972 a compact form that is not shallower than the full context is ignored", () => {
|
|
134
|
+
const composition = boundRecallContextComposition({
|
|
135
|
+
context: "x".repeat(50),
|
|
136
|
+
compactContext: "y".repeat(60),
|
|
137
|
+
maxChars: 10,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
assert.equal(composition.degradation?.reason, "budget-clipped");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("#2972 backend failure yields an explicit missing note, never an empty string", () => {
|
|
144
|
+
const composition = composeMissingMemoryContext({ detail: "backend_unavailable" });
|
|
145
|
+
const body = composeRecallContext(composition);
|
|
146
|
+
|
|
147
|
+
assert.ok(body.length > 0);
|
|
148
|
+
assert.match(body, /memory context unavailable/i);
|
|
149
|
+
assert.equal(composition.degradation?.state, "missing");
|
|
150
|
+
assert.equal(composition.degradation?.reason, "backend-unavailable");
|
|
151
|
+
assert.equal(composition.degradation?.detail, "backend_unavailable");
|
|
152
|
+
|
|
153
|
+
const rendered = renderMemoryContextPrompt({ ...composition, maxChars: 512 });
|
|
154
|
+
assert.ok(rendered);
|
|
155
|
+
assert.match(rendered.body, /memory context unavailable/i);
|
|
156
|
+
});
|