@remnic/core 9.69.48 → 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 +31 -29
- package/dist/access-authorization-probe.d.ts +14 -14
- package/dist/access-authorization-probe.js +32 -30
- package/dist/access-boundary.d.ts +14 -14
- package/dist/access-boundary.js +31 -29
- package/dist/access-cli.js +98 -97
- 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 +31 -29
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-briefing.d.ts +14 -14
- package/dist/access-http-briefing.js +32 -30
- 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 +33 -31
- package/dist/access-http.d.ts +15 -15
- package/dist/access-http.js +51 -49
- package/dist/access-identity-continuity-surface.d.ts +11 -11
- package/dist/access-identity-continuity-surface.js +31 -29
- package/dist/access-lcm-surface.d.ts +14 -14
- package/dist/access-lcm-surface.js +31 -29
- package/dist/access-mcp-shared-context-tools.d.ts +14 -14
- package/dist/access-mcp.d.ts +14 -14
- package/dist/access-mcp.js +41 -39
- 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 +31 -29
- 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 +31 -29
- package/dist/access-offline-manifest.d.ts +11 -11
- package/dist/access-operations-batch.js +33 -31
- package/dist/access-operations.d.ts +14 -14
- package/dist/access-operations.js +40 -38
- package/dist/access-recall-concurrency.d.ts +14 -14
- package/dist/access-recall-concurrency.js +31 -29
- package/dist/access-recall-response.d.ts +14 -14
- package/dist/access-recall-response.js +31 -29
- package/dist/access-recall-surface.d.ts +14 -14
- package/dist/access-recall-surface.js +31 -29
- package/dist/access-schema.d.ts +1 -1
- package/dist/{access-service-DUkTWyZS.d.ts → access-service-70VHaltr.d.ts} +9 -9
- package/dist/access-service-helpers.d.ts +14 -14
- package/dist/access-service.d.ts +14 -14
- package/dist/access-service.js +31 -29
- package/dist/access-surface-catalog.d.ts +14 -14
- package/dist/access-surface-catalog.js +33 -31
- 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-NNFB4CU4.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-JIURESYF.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-2B2RHLHI.js → chunk-EBLKS57G.js} +2 -2
- package/dist/{chunk-MUNR43V3.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-XYGINDIJ.js → chunk-FVRZDZ7J.js} +2 -2
- package/dist/{chunk-645WOU5I.js → chunk-H7BZBSP6.js} +4 -4
- package/dist/{chunk-TW4MXM7H.js → chunk-HIBNECBQ.js} +5 -5
- package/dist/{chunk-QLCMOHRE.js → chunk-IIDXU2CY.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-MKJFPIVG.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-B4WI7OF2.js → chunk-PAQ7YGJY.js} +2 -2
- package/dist/{chunk-BIXELUIR.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-R32AEEU3.js → chunk-RBHQA2UU.js} +49 -49
- 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-KVIMQAER.js → chunk-T3OLYUU6.js} +2 -2
- package/dist/{chunk-KPYMORPQ.js → chunk-TUSY4KRE.js} +206 -43
- 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-BNX2TUSF.js → chunk-WHSNLCRO.js} +2 -2
- package/dist/{chunk-7DU2F7LM.js → chunk-XQFHKPNB.js} +6 -6
- package/dist/{chunk-MKLDZNBD.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-32ZBIGID.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-BMBAA2M5.js → chunk-ZYZW46HZ.js} +3 -3
- package/dist/{cli-Bbndoc6k.d.ts → cli-CdJiBilM.d.ts} +5 -5
- package/dist/cli.d.ts +16 -16
- package/dist/cli.js +70 -68
- 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 +12 -12
- 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 +32 -30
- 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 +42 -42
- package/dist/index.js +127 -126
- 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 +32 -30
- 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 +98 -97
- 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-DaHHhWRR.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-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 +32 -30
- package/dist/recall-navigation-renderer.d.ts +1 -1
- package/dist/recall-navigation.d.ts +1 -1
- package/dist/recall-navigation.js +31 -29
- 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/{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 +1 -1
- 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 +34 -32
- 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-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/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/types.ts +3 -0
- package/dist/chunk-DE4ZGH2N.js.map +0 -1
- package/dist/chunk-KPYMORPQ.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-NNFB4CU4.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-JIURESYF.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-2B2RHLHI.js.map → chunk-EBLKS57G.js.map} +0 -0
- /package/dist/{chunk-MUNR43V3.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-XYGINDIJ.js.map → chunk-FVRZDZ7J.js.map} +0 -0
- /package/dist/{chunk-645WOU5I.js.map → chunk-H7BZBSP6.js.map} +0 -0
- /package/dist/{chunk-TW4MXM7H.js.map → chunk-HIBNECBQ.js.map} +0 -0
- /package/dist/{chunk-QLCMOHRE.js.map → chunk-IIDXU2CY.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-MKJFPIVG.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-B4WI7OF2.js.map → chunk-PAQ7YGJY.js.map} +0 -0
- /package/dist/{chunk-BIXELUIR.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-R32AEEU3.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-KVIMQAER.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-BNX2TUSF.js.map → chunk-WHSNLCRO.js.map} +0 -0
- /package/dist/{chunk-7DU2F7LM.js.map → chunk-XQFHKPNB.js.map} +0 -0
- /package/dist/{chunk-MKLDZNBD.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-32ZBIGID.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-BMBAA2M5.js.map → chunk-ZYZW46HZ.js.map} +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-end experience extraction config (issue #2979) — the
|
|
3
|
+
* `sessionExperience` block.
|
|
4
|
+
*
|
|
5
|
+
* Parsed like `recallNavigation`: shape-validated, fail-closed on unknown
|
|
6
|
+
* keys, no silent clamping. Defaults OFF (a machine-generated memory write
|
|
7
|
+
* from the completed session must be an explicit operator opt-in); the
|
|
8
|
+
* `conservative` preset pins `enabled: false`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { coerceBool } from "../connectors/coerce.js";
|
|
12
|
+
|
|
13
|
+
export interface SessionExperienceConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Master switch for session-end Situation/Approach/Reflection episode
|
|
16
|
+
* extraction. Default false.
|
|
17
|
+
*/
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const SESSION_EXPERIENCE_CONFIG_DEFAULTS: SessionExperienceConfig = {
|
|
22
|
+
enabled: false,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function parseFlag(src: Record<string, unknown>, key: string, fallback: boolean): boolean {
|
|
26
|
+
const value = src[key];
|
|
27
|
+
if (value === undefined || value === null) return fallback;
|
|
28
|
+
const coerced = coerceBool(value);
|
|
29
|
+
if (coerced === undefined) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`sessionExperience.${key} must be a boolean (or "true"/"false"/"1"/"0"); got ${JSON.stringify(value)}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return coerced;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseSessionExperienceConfig(raw: unknown): SessionExperienceConfig {
|
|
38
|
+
if (raw === undefined || raw === null) return { ...SESSION_EXPERIENCE_CONFIG_DEFAULTS };
|
|
39
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
40
|
+
throw new Error(`sessionExperience must be an object; got ${JSON.stringify(raw)}`);
|
|
41
|
+
}
|
|
42
|
+
const src = raw as Record<string, unknown>;
|
|
43
|
+
const { enabled: _enabled, ...unknown } = src;
|
|
44
|
+
const unknownKey = Object.keys(unknown)[0];
|
|
45
|
+
if (unknownKey !== undefined) {
|
|
46
|
+
throw new Error(`sessionExperience contains unknown key ${JSON.stringify(unknownKey)}`);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
enabled: parseFlag(src, "enabled", SESSION_EXPERIENCE_CONFIG_DEFAULTS.enabled),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-end experience extraction through the real flush lifecycle
|
|
3
|
+
* (issue #2979): `flushSession(reason: "session_end")` drains the buffer
|
|
4
|
+
* and then derives at most one agent-experience episode; `before_reset`
|
|
5
|
+
* must not write one; gate off must write nothing anywhere.
|
|
6
|
+
*/
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
|
|
11
|
+
import { Orchestrator } from "../orchestrator.js";
|
|
12
|
+
import {
|
|
13
|
+
cleanupDir,
|
|
14
|
+
makeHarmonicLifecycleConfig,
|
|
15
|
+
markdownFilesUnder,
|
|
16
|
+
mkTempMemoryDir,
|
|
17
|
+
singleFactResult,
|
|
18
|
+
stubExtraction,
|
|
19
|
+
} from "../testing/orchestrator-lite.js";
|
|
20
|
+
|
|
21
|
+
async function sessionEndMemories(memoryDir: string): Promise<string[]> {
|
|
22
|
+
const matches: string[] = [];
|
|
23
|
+
for (const file of await markdownFilesUnder(`${memoryDir}/procedures`)) {
|
|
24
|
+
if ((await readFile(file, "utf8")).includes("Reflection: ")) matches.push(file);
|
|
25
|
+
}
|
|
26
|
+
return matches;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function withOrchestrator(
|
|
30
|
+
label: string,
|
|
31
|
+
overrides: Record<string, unknown>,
|
|
32
|
+
run: (orchestrator: Orchestrator, memoryDir: string) => Promise<void>,
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const memoryDir = await mkTempMemoryDir(label);
|
|
35
|
+
let orchestrator: Orchestrator | undefined;
|
|
36
|
+
try {
|
|
37
|
+
orchestrator = new Orchestrator(makeHarmonicLifecycleConfig(memoryDir, overrides));
|
|
38
|
+
stubExtraction(orchestrator, (turns) => singleFactResult("The harmonic probe fact persists."));
|
|
39
|
+
await run(orchestrator, memoryDir);
|
|
40
|
+
} finally {
|
|
41
|
+
await orchestrator?.destroy().catch(() => undefined);
|
|
42
|
+
await cleanupDir(memoryDir);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SESSION = "session-end-experience";
|
|
47
|
+
|
|
48
|
+
const SUCCESS_TURNS: Array<[role: "user" | "assistant", content: string]> = [
|
|
49
|
+
["user", "Fix the flaky payments integration test that times out after thirty seconds."],
|
|
50
|
+
["assistant", "I will reproduce the timeout and inspect the payments test setup."],
|
|
51
|
+
["assistant", "Added the missing await to the setup call. Verified: the payments test passes now."],
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
test("session_end flush writes exactly one pending_review agent-experience episode", async () => {
|
|
55
|
+
await withOrchestrator(
|
|
56
|
+
"sx-e2e-on",
|
|
57
|
+
{ sessionExperience: { enabled: true } },
|
|
58
|
+
async (orchestrator, memoryDir) => {
|
|
59
|
+
for (const [role, content] of SUCCESS_TURNS) {
|
|
60
|
+
await orchestrator.processTurn(role, content, SESSION);
|
|
61
|
+
}
|
|
62
|
+
await orchestrator.flushSession(SESSION, { reason: "session_end" });
|
|
63
|
+
const experiences = await sessionEndMemories(memoryDir);
|
|
64
|
+
assert.equal(experiences.length, 1, "one session = at most one experience episode");
|
|
65
|
+
const body = await readFile(experiences[0], "utf8");
|
|
66
|
+
assert.match(body, /Situation: Fix the flaky payments integration test/);
|
|
67
|
+
assert.match(body, /Approach: /);
|
|
68
|
+
assert.match(body, /Reflection: /);
|
|
69
|
+
assert.match(body, /status: pending_review/);
|
|
70
|
+
assert.match(body, /subject: agent/);
|
|
71
|
+
assert.match(body, /category: procedure/);
|
|
72
|
+
assert.match(body, /experience_session_hash/);
|
|
73
|
+
// A second session_end drain sees an empty buffer and must not duplicate.
|
|
74
|
+
await orchestrator.flushSession(SESSION, { reason: "session_end" });
|
|
75
|
+
assert.equal((await sessionEndMemories(memoryDir)).length, 1, "replay must not duplicate");
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("a failed session_end records the failure reflection", async () => {
|
|
81
|
+
await withOrchestrator(
|
|
82
|
+
"sx-e2e-fail",
|
|
83
|
+
{ sessionExperience: { enabled: true } },
|
|
84
|
+
async (orchestrator, memoryDir) => {
|
|
85
|
+
await orchestrator.processTurn(
|
|
86
|
+
"user",
|
|
87
|
+
"Migrate the billing export job to the new queue library before the release cut.",
|
|
88
|
+
SESSION,
|
|
89
|
+
);
|
|
90
|
+
await orchestrator.processTurn(
|
|
91
|
+
"assistant",
|
|
92
|
+
"The migration failed: the new queue library cannot serialize the billing payload format.",
|
|
93
|
+
SESSION,
|
|
94
|
+
);
|
|
95
|
+
await orchestrator.flushSession(SESSION, { reason: "session_end" });
|
|
96
|
+
const experiences = await sessionEndMemories(memoryDir);
|
|
97
|
+
assert.equal(experiences.length, 1);
|
|
98
|
+
const body = await readFile(experiences[0], "utf8");
|
|
99
|
+
assert.match(body, /experience_outcome: failure|experience_outcome=failure/);
|
|
100
|
+
assert.match(body, /fail/i);
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("gate off (default): a session_end flush writes no experience anywhere", async () => {
|
|
106
|
+
await withOrchestrator("sx-e2e-off", {}, async (orchestrator, memoryDir) => {
|
|
107
|
+
for (const [role, content] of SUCCESS_TURNS) {
|
|
108
|
+
await orchestrator.processTurn(role, content, SESSION);
|
|
109
|
+
}
|
|
110
|
+
await orchestrator.flushSession(SESSION, { reason: "session_end" });
|
|
111
|
+
assert.equal((await sessionEndMemories(memoryDir)).length, 0, "gate off must write nothing");
|
|
112
|
+
for (const file of await markdownFilesUnder(memoryDir)) {
|
|
113
|
+
const body = await readFile(file, "utf8");
|
|
114
|
+
assert.doesNotMatch(
|
|
115
|
+
body,
|
|
116
|
+
/Reflection: /,
|
|
117
|
+
`no experience body may appear anywhere while gated off (saw ${file})`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("before_reset drains the buffer but writes no experience episode", async () => {
|
|
124
|
+
await withOrchestrator(
|
|
125
|
+
"sx-e2e-reset",
|
|
126
|
+
{ sessionExperience: { enabled: true } },
|
|
127
|
+
async (orchestrator, memoryDir) => {
|
|
128
|
+
for (const [role, content] of SUCCESS_TURNS) {
|
|
129
|
+
await orchestrator.processTurn(role, content, SESSION);
|
|
130
|
+
}
|
|
131
|
+
await orchestrator.flushSession(SESSION, { reason: "before_reset" });
|
|
132
|
+
assert.equal(
|
|
133
|
+
(await sessionEndMemories(memoryDir)).length,
|
|
134
|
+
0,
|
|
135
|
+
"experiences are session_end-only in v1",
|
|
136
|
+
);
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
});
|
|
@@ -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
|
|