@modusensus/dsh-mneme 0.14.0-beta.1 → 0.14.0-beta.2
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/LICENSE +21 -21
- package/README.md +830 -830
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +964 -964
- package/bin/dsh-mneme-choro.mjs +262 -262
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +407 -404
- package/docs/CONTINUITY.md +139 -139
- package/docs/DAEMON.md +57 -57
- package/docs/DIRECTORIES.md +114 -114
- package/docs/DISTILL.md +397 -371
- package/docs/DSH-GO.md +97 -97
- package/docs/ENTITIES.md +247 -247
- package/docs/FORGET.md +77 -77
- package/docs/KB.md +110 -110
- package/docs/LOCAL_MODEL.md +222 -222
- package/docs/SEMANTIC.md +256 -256
- package/docs/SHARE.md +552 -552
- package/docs/SLEEP.md +202 -202
- package/docs/STORAGE.md +122 -122
- package/docs/WORKFLOWS.md +366 -366
- package/lib/api-standalone.js +1007 -1007
- package/lib/api.js +3600 -3600
- package/lib/attachment-text.js +122 -122
- package/lib/bootstrap-http.js +105 -105
- package/lib/bootstrap.js +215 -215
- package/lib/candidates.js +454 -454
- package/lib/capabilities.js +819 -819
- package/lib/choro-command.js +111 -111
- package/lib/client.js +14420 -14322
- package/lib/commands.js +95 -95
- package/lib/config-keys.js +144 -144
- package/lib/config.js +1361 -1344
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +258 -258
- package/lib/date-hint.js +32 -0
- package/lib/diary/auto.js +190 -190
- package/lib/diary/compose.js +440 -440
- package/lib/diary/dir.js +59 -59
- package/lib/diary/export.js +97 -97
- package/lib/diary/index.js +26 -26
- package/lib/diary/library.js +189 -189
- package/lib/diary/qq-inbox.js +142 -142
- package/lib/diary/rollover.js +61 -61
- package/lib/diary/session-provider.js +122 -122
- package/lib/document-index.js +94 -94
- package/lib/document.js +322 -322
- package/lib/dream/clustering.js +123 -123
- package/lib/dream/decisions.js +945 -945
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +2027 -2027
- package/lib/dream/triage.js +139 -139
- package/lib/dream.js +1879 -1879
- package/lib/dsh-go/host.js +162 -162
- package/lib/dsh-go/panel.js +2091 -2091
- package/lib/dsh-go/text.js +342 -342
- package/lib/dsh-go/views/common.js +68 -68
- package/lib/dsh-go/views/entity.js +48 -48
- package/lib/dsh-go/views/memory.js +198 -198
- package/lib/dsh-go/views/root.js +30 -30
- package/lib/dsh-go/views/settings.js +398 -398
- package/lib/dsh-go/views/status.js +51 -51
- package/lib/embedding.js +342 -342
- package/lib/entities/extractor.js +285 -285
- package/lib/fence.js +24 -24
- package/lib/graph/anchoring.js +184 -184
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +119 -119
- package/lib/image-describe.js +158 -0
- package/lib/index.js +852 -852
- package/lib/inject.js +656 -651
- package/lib/kb/index.js +309 -309
- package/lib/kb/routes.js +219 -219
- package/lib/kb/tools.js +148 -148
- package/lib/lang.js +607 -595
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -162
- package/lib/memory-source.js +9 -9
- package/lib/mirror.js +330 -330
- package/lib/organize.js +456 -456
- package/lib/peak-hours.js +172 -172
- package/lib/pipeline-activities.js +178 -178
- package/lib/pipeline-attribution.js +165 -165
- package/lib/pipeline-command.js +279 -279
- package/lib/pipeline-confirm.js +142 -142
- package/lib/pipeline-exec.js +174 -174
- package/lib/pipeline-nudge-auto.js +129 -129
- package/lib/pipeline-nudge.js +1623 -1623
- package/lib/pipeline-observations.js +181 -181
- package/lib/pipeline-observe.js +133 -133
- package/lib/pipeline-runner.js +361 -361
- package/lib/pipeline-runs.js +442 -442
- package/lib/pipeline.js +716 -716
- package/lib/placement.js +13 -13
- package/lib/pre-inject-gate.js +354 -354
- package/lib/profile-extract.js +164 -164
- package/lib/profile-facts.js +356 -356
- package/lib/provenance-http.js +251 -251
- package/lib/quality-filter.js +162 -162
- package/lib/recall-stats.js +190 -190
- package/lib/reclaim-http.js +150 -150
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -223
- package/lib/safe-slice.js +18 -18
- package/lib/scope-state.js +277 -277
- package/lib/scope.js +187 -187
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +572 -572
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +228 -228
- package/lib/service.js +3268 -3268
- package/lib/session-gate.js +91 -91
- package/lib/session-usage.js +350 -350
- package/lib/settings.js +827 -817
- package/lib/share-http.js +281 -281
- package/lib/share-import.js +234 -234
- package/lib/share.js +335 -335
- package/lib/store.js +5895 -5895
- package/lib/summarize.js +1388 -1343
- package/lib/tools.js +1370 -1356
- package/lib/user-settings.js +230 -230
- package/lib/vector-index.js +180 -180
- package/lib/version-check.js +122 -122
- package/lib/wiki/claims.js +267 -267
- package/lib/wiki/cli-hint.js +90 -90
- package/lib/wiki/inject.js +164 -164
- package/lib/wiki/partition.js +200 -200
- package/lib/wiki/plan.js +214 -214
- package/lib/wiki/queries.js +640 -640
- package/lib/wiki/render.js +261 -261
- package/lib/wiki/scan.js +371 -371
- package/lib/wiki/t0.js +500 -500
- package/lib/wiki/t1-jobs.js +232 -232
- package/lib/wiki/t1.js +609 -609
- package/lib/write-admission.js +404 -404
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +1 -1
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +91 -91
- package/scripts/check-sync.js +44 -44
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/publish-gate.mjs +197 -197
- package/scripts/release-docs.mjs +210 -210
- package/scripts/release-notes.mjs +164 -164
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +48 -48
- package/src/api-standalone.js +1007 -1007
- package/src/api.js +3600 -3600
- package/src/attachment-text.js +122 -122
- package/src/bootstrap-http.js +105 -105
- package/src/bootstrap.js +215 -215
- package/src/candidates.js +454 -454
- package/src/capabilities.js +819 -819
- package/src/choro-command.js +111 -111
- package/src/commands.js +95 -95
- package/src/config-keys.js +144 -144
- package/src/config.js +1361 -1344
- package/src/content-hash.js +54 -54
- package/src/continuity.js +258 -258
- package/src/date-hint.js +32 -0
- package/src/diary/auto.js +190 -190
- package/src/diary/compose.js +440 -440
- package/src/diary/dir.js +59 -59
- package/src/diary/export.js +97 -97
- package/src/diary/index.js +26 -26
- package/src/diary/library.js +189 -189
- package/src/diary/qq-inbox.js +142 -142
- package/src/diary/rollover.js +61 -61
- package/src/diary/session-provider.js +122 -122
- package/src/document-index.js +94 -94
- package/src/document.js +322 -322
- package/src/dream/clustering.js +123 -123
- package/src/dream/decisions.js +945 -945
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +2027 -2027
- package/src/dream/triage.js +139 -139
- package/src/dream.js +1879 -1879
- package/src/dsh-go/host.js +162 -162
- package/src/dsh-go/panel.js +2091 -2091
- package/src/dsh-go/text.js +342 -342
- package/src/dsh-go/views/common.js +68 -68
- package/src/dsh-go/views/entity.js +48 -48
- package/src/dsh-go/views/memory.js +198 -198
- package/src/dsh-go/views/root.js +30 -30
- package/src/dsh-go/views/settings.js +398 -398
- package/src/dsh-go/views/status.js +51 -51
- package/src/embedding.js +342 -342
- package/src/entities/extractor.js +285 -285
- package/src/fence.js +24 -24
- package/src/graph/anchoring.js +184 -184
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +119 -119
- package/src/image-describe.js +158 -0
- package/src/index.js +852 -852
- package/src/inject.js +656 -651
- package/src/kb/index.js +309 -309
- package/src/kb/routes.js +219 -219
- package/src/kb/tools.js +148 -148
- package/src/lang.js +607 -595
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -162
- package/src/memory-source.js +9 -9
- package/src/mirror.js +330 -330
- package/src/organize.js +456 -456
- package/src/peak-hours.js +172 -172
- package/src/pipeline-activities.js +178 -178
- package/src/pipeline-attribution.js +165 -165
- package/src/pipeline-command.js +279 -279
- package/src/pipeline-confirm.js +142 -142
- package/src/pipeline-exec.js +174 -174
- package/src/pipeline-nudge-auto.js +129 -129
- package/src/pipeline-nudge.js +1623 -1623
- package/src/pipeline-observations.js +181 -181
- package/src/pipeline-observe.js +133 -133
- package/src/pipeline-runner.js +361 -361
- package/src/pipeline-runs.js +442 -442
- package/src/pipeline.js +716 -716
- package/src/placement.js +13 -13
- package/src/pre-inject-gate.js +354 -354
- package/src/profile-extract.js +164 -164
- package/src/profile-facts.js +356 -356
- package/src/provenance-http.js +251 -251
- package/src/quality-filter.js +162 -162
- package/src/recall-stats.js +190 -190
- package/src/reclaim-http.js +150 -150
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -223
- package/src/safe-slice.js +18 -18
- package/src/scope-state.js +277 -277
- package/src/scope.js +187 -187
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +572 -572
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +228 -228
- package/src/service.js +3268 -3268
- package/src/session-gate.js +91 -91
- package/src/session-usage.js +350 -350
- package/src/settings.js +827 -817
- package/src/share-http.js +281 -281
- package/src/share-import.js +234 -234
- package/src/share.js +335 -335
- package/src/store.js +5895 -5895
- package/src/summarize.js +1388 -1343
- package/src/tools.js +1370 -1356
- package/src/user-settings.js +230 -230
- package/src/vector-index.js +180 -180
- package/src/version-check.js +122 -122
- package/src/wiki/claims.js +267 -267
- package/src/wiki/cli-hint.js +90 -90
- package/src/wiki/inject.js +164 -164
- package/src/wiki/partition.js +200 -200
- package/src/wiki/plan.js +214 -214
- package/src/wiki/queries.js +640 -640
- package/src/wiki/render.js +261 -261
- package/src/wiki/scan.js +371 -371
- package/src/wiki/t0.js +500 -500
- package/src/wiki/t1-jobs.js +232 -232
- package/src/wiki/t1.js +609 -609
- package/src/write-admission.js +404 -404
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-hardening.test.js +173 -173
- package/test/api-nudge-route.test.js +259 -259
- package/test/api-routes-gaps.test.js +1092 -1092
- package/test/api.test.js +3007 -2998
- package/test/attachment-text.test.js +139 -139
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bm25-corpus-cache.test.js +145 -145
- package/test/bootstrap.test.js +228 -228
- package/test/candidates.test.js +345 -345
- package/test/capabilities.test.js +577 -577
- package/test/capacity-warning.test.js +262 -262
- package/test/changelog-prep.test.js +125 -125
- package/test/choro-command.test.js +218 -218
- package/test/cli-enums.test.js +37 -37
- package/test/cli-flag-value.test.js +86 -86
- package/test/cli-receive.test.js +390 -390
- package/test/cli-share.test.js +437 -437
- package/test/client-share.test.js +225 -225
- package/test/client.test.js +4595 -4425
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config-keys-routes.test.js +233 -233
- package/test/config.test.js +153 -153
- package/test/conflict-freeze.test.js +344 -344
- package/test/conflict-ledger-enforce.test.js +452 -452
- package/test/conflict-ledger.test.js +242 -242
- package/test/conflict-reviewed.test.js +79 -79
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +452 -452
- package/test/contrast.test.js +432 -432
- package/test/date-hint.test.js +156 -0
- package/test/dedupe-candidate-window.test.js +180 -180
- package/test/dependency-overrides.test.js +78 -78
- package/test/diary-library.test.js +187 -187
- package/test/diary.test.js +576 -576
- package/test/differentiate-feedback.test.js +248 -248
- package/test/distill-tx-embed-gap.test.js +110 -110
- package/test/docs-config-keys.test.js +65 -65
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +703 -703
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-baseline-clamp.test.js +174 -174
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-decision-evidence.test.js +314 -314
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-fence.test.js +207 -207
- package/test/dream-maxtokens-fallback.test.js +132 -132
- package/test/dream-merge-guardrail.test.js +219 -219
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1528 -1528
- package/test/dsh-go-host.test.js +134 -134
- package/test/dsh-go-panel-actions.test.js +854 -854
- package/test/dsh-go-panel.test.js +251 -251
- package/test/embed-ready-poll.test.js +94 -94
- package/test/embed-text-reasons.test.js +112 -112
- package/test/embedding-cache-f32.test.js +276 -276
- package/test/embedding-ready.test.js +226 -226
- package/test/entities.test.js +711 -711
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/failure-query.test.js +147 -147
- package/test/failure-retention.test.js +105 -105
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/forget-corrections.test.js +70 -70
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +333 -333
- package/test/graph-boosted-relations.test.js +189 -189
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-multipath.test.js +269 -269
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +86 -86
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +312 -312
- package/test/hot-memory.test.js +259 -259
- package/test/image-description.test.js +271 -0
- package/test/image-memory.test.js +170 -170
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +134 -109
- package/test/inject-pin-explicit.test.js +300 -300
- package/test/inject-pin-vector-crowd.test.js +225 -225
- package/test/inject-pools.test.js +195 -195
- package/test/inject-recall.test.js +172 -172
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +553 -553
- package/test/issue-617-step1-query-and-fusion.test.js +361 -361
- package/test/kb-api-wiring.test.js +128 -128
- package/test/kb-collections.test.js +272 -272
- package/test/kb-routes.test.js +379 -379
- package/test/lang.test.js +98 -98
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +116 -116
- package/test/llm-audit.test.js +815 -815
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -501
- package/test/mcp-stdio.test.js +385 -385
- package/test/mint-source.test.js +311 -311
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize-tool.test.js +232 -232
- package/test/organize.test.js +418 -418
- package/test/panel-chart-scale.test.js +90 -90
- package/test/panel-tokens.test.js +245 -245
- package/test/peak-hours-observability.test.js +70 -70
- package/test/peer-blockers.test.js +193 -193
- package/test/persona-addressing.test.js +83 -83
- package/test/persona-api.test.js +172 -172
- package/test/persona-by-session.test.js +568 -568
- package/test/persona-flag.test.js +36 -36
- package/test/persona-inject.test.js +145 -145
- package/test/persona-settings.test.js +198 -198
- package/test/pipeline-activities.test.js +155 -155
- package/test/pipeline-attribution.test.js +175 -175
- package/test/pipeline-command.test.js +243 -243
- package/test/pipeline-confirm.test.js +148 -148
- package/test/pipeline-exec.test.js +172 -172
- package/test/pipeline-nudge-auto.test.js +248 -248
- package/test/pipeline-nudge.test.js +1278 -1278
- package/test/pipeline-observations.test.js +157 -157
- package/test/pipeline-observe.test.js +127 -127
- package/test/pipeline-r1r2.test.js +278 -278
- package/test/pipeline-runner.test.js +468 -468
- package/test/pipeline-runs.test.js +767 -767
- package/test/pipeline.test.js +636 -636
- package/test/placement.test.js +36 -36
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -444
- package/test/profile-extract.test.js +269 -269
- package/test/profile-facts.test.js +492 -492
- package/test/prompt-stance.test.js +128 -128
- package/test/provenance-http.test.js +367 -367
- package/test/publish-gate.test.js +272 -272
- package/test/quality-backfill.test.js +290 -290
- package/test/quality-filter.test.js +295 -295
- package/test/readme-claims.test.js +113 -113
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-retention.test.js +128 -128
- package/test/recall-stats.test.js +274 -274
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -209
- package/test/reflection-update-gate.test.js +129 -129
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +475 -475
- package/test/reindex-scheduler.test.js +369 -369
- package/test/release-docs.test.js +156 -156
- package/test/release-notes.test.js +265 -265
- package/test/release-workflow.test.js +93 -93
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/route-policy.test.js +113 -113
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -319
- package/test/scope-candidates.test.js +491 -491
- package/test/scope-distill-stamp.test.js +134 -134
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-optional-service.test.js +110 -110
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-state.test.js +610 -610
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/security-doc.test.js +188 -188
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +251 -251
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +258 -258
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -410
- package/test/session-gate.test.js +264 -264
- package/test/session-usage.test.js +317 -317
- package/test/settings.test.js +273 -273
- package/test/share-foreign-rows.test.js +144 -144
- package/test/share-http.test.js +144 -144
- package/test/share-import.test.js +577 -577
- package/test/share-imported-guard.test.js +311 -311
- package/test/share-imported-identity.test.js +111 -111
- package/test/share-pack.test.js +511 -511
- package/test/share-receive-http.test.js +756 -756
- package/test/share-routes.test.js +337 -337
- package/test/share-store.test.js +133 -133
- package/test/share-validate.test.js +101 -101
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +441 -441
- package/test/sleep-catchup.test.js +315 -315
- package/test/sleep-failure-backoff.test.js +156 -156
- package/test/sleep-heat.test.js +174 -174
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +1455 -1455
- package/test/standalone-api.test.js +1344 -1344
- package/test/store-vector-cache.test.js +98 -98
- package/test/store-write-path-columns.test.js +191 -191
- package/test/store.test.js +744 -744
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -127
- package/test/summarize-parse-diagnostics.test.js +75 -75
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +2251 -2251
- package/test/supersede-state-tracking.test.js +394 -394
- package/test/test-count-sync.test.js +71 -71
- package/test/tool-state-ignore.test.js +65 -65
- package/test/tools-dto-schema-matrix.test.js +398 -398
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +585 -585
- package/test/updated-at-semantics.test.js +113 -113
- package/test/user-settings-preview.test.js +127 -127
- package/test/user-settings-segments.test.js +527 -527
- package/test/vector-index.test.js +334 -334
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/wiki-api.test.js +734 -734
- package/test/wiki-claims.test.js +194 -194
- package/test/wiki-cli-hint.test.js +118 -118
- package/test/wiki-inject.test.js +265 -265
- package/test/wiki-partition.test.js +112 -112
- package/test/wiki-plan.test.js +80 -80
- package/test/wiki-provenance.test.js +202 -202
- package/test/wiki-queries.test.js +398 -398
- package/test/wiki-scan.test.js +208 -208
- package/test/wiki-store.test.js +190 -190
- package/test/wiki-t0.test.js +456 -456
- package/test/wiki-t1-jobs.test.js +125 -125
- package/test/wiki-t1.test.js +460 -460
- package/test/wiki-tools.test.js +176 -176
- package/test/workflow-guards.test.js +155 -155
- package/test/write-admission.test.js +668 -668
package/docs/SLEEP.md
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
# dsh-mneme Sleep Mode 系统级睡眠设计文档
|
|
2
|
-
|
|
3
|
-
## 概述
|
|
4
|
-
从被动整理到主动维护:在系统空闲时段自动执行深度记忆压缩、冲突消解、模式发现与关系补全,实现知识库的长效健康治理。
|
|
5
|
-
|
|
6
|
-
## 动机与设计原则
|
|
7
|
-
- **非侵入性**:仅在用户空闲(Idle)时触发,不干扰正常读写与实时推理。
|
|
8
|
-
- **可中断**:用户一旦产生活动,立即中止当前睡眠周期,保障实时体验。
|
|
9
|
-
- **分层压缩**:按访问热度与时间衰减进行阶梯式降级,保留核心语义,释放存储压力。
|
|
10
|
-
- **模式发现**:利用 LLM 挖掘长期记忆中的隐性规律,生成结构化 Pattern 实体。
|
|
11
|
-
- **审计延续**:与 `autoDream` 共享 `dream_runs` 审计表,通过 `run_type` 区分,保障全链路可追溯。
|
|
12
|
-
|
|
13
|
-
## 架构图
|
|
14
|
-
```mermaid
|
|
15
|
-
flowchart TD
|
|
16
|
-
User[用户写入/读取] -->|noteWrite| Scheduler[SleepScheduler]
|
|
17
|
-
Scheduler -->|clearTimeout + 重置计时器| Timer[Idle Timer]
|
|
18
|
-
Timer -->|>= sleepIdleMinutes| Check[maybeSchedule]
|
|
19
|
-
|
|
20
|
-
Check -->|条件满足| Queue[service.enqueue]
|
|
21
|
-
Check -->|条件不满足| Wait[等待下一次 noteWrite]
|
|
22
|
-
|
|
23
|
-
subgraph Sleep Run [串行执行]
|
|
24
|
-
Q[Queue] --> P1[Phase 1: conflict_resolution]
|
|
25
|
-
P1 --> P2[Phase 2: archival_demotion]
|
|
26
|
-
P2 --> P3[Phase 3: pattern_discovery]
|
|
27
|
-
P3 --> P4[Phase 4: relation_completion]
|
|
28
|
-
P4 --> P5[Phase 5: affective_attribution]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
P5 -->|独立 try/catch 隔离| Receipt[buildReceipt]
|
|
32
|
-
Receipt -->|saveDreamRun run_type='sleep'| Audit[(dream_runs)]
|
|
33
|
-
|
|
34
|
-
User -->|活动触发| Abort[AbortController.abort]
|
|
35
|
-
Abort -->|中断当前 Run| Scheduler
|
|
36
|
-
|
|
37
|
-
AutoDream[autoDream 实时轻量触发] -.共享队列不重叠.-> Queue
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## 配置项说明
|
|
41
|
-
| 配置项 | 默认值 | 说明 |
|
|
42
|
-
|:---|:---|:---|
|
|
43
|
-
| `sleepModeEnabled` | `false` | 全局开关(Opt-in),开启后激活调度器 |
|
|
44
|
-
| `sleepIdleMinutes` | `5` | 连续无操作触发睡眠的阈值(分钟) |
|
|
45
|
-
| `sleepMinIntervalHours` | `8` | 两次 Sleep Run 的最小时间间隔(小时) |
|
|
46
|
-
| `sleepFailureBackoff` | `false` | 连续失败退避(#529,opt-in):有效间隔 = `sleepMinIntervalHours` × 2^连败数,成功清零,封顶 `max(sleepMinIntervalHours, 24h)`(配得比 24h 大就按配的算——退避只放大间隔,不压缩用户自己设的基数)。判据看整轮 `status === "failed"`——`noop`(通路正常、只是没活干)不算失败,否则在自己工作良好的库里也会越退越长 |
|
|
47
|
-
| `sleepConflictMaxPairs` | `12` | **单批**送审的冲突对上限(0 = 不限,回退旧行为) |
|
|
48
|
-
| `sleepConflictMaxPairsPerRun` | `60` | **单轮**最多送审多少对(0 = 不限)。与上一条分工:一个管批次大小、一个管整轮总量 |
|
|
49
|
-
| `sleepConflictStrictness` | `'normal'` | 冲突消解严格度:`'gentle'`(0.92) / `'normal'`(0.85) / `'aggressive'`(0.75) |
|
|
50
|
-
| `sleepArchiveDays` | `30` | 进入冷存储/摘要降级的天数阈值 |
|
|
51
|
-
| `sleepCompressDays` | `90` | 进入完全归档(Archive)的天数阈值 |
|
|
52
|
-
| `sleepPatternMinMemories` | `100` | 模式发现阶段扫描的最近记忆条数下限 |
|
|
53
|
-
| `sleepPatternLookbackDays` | `30` | 模式发现回溯的时间窗口(天) |
|
|
54
|
-
| `sleepMaxPatternPerRun` | `3` | 单次运行最多创建的模式数量上限 |
|
|
55
|
-
| `sleepAffectiveEnabled` | `false` | 情感归因相位(#619,opt-in):情境情绪写成 `affective_context` 关系,跨多次独立会话的稳定倾向才铸成 pattern。是 `sleepModeEnabled` 的子相,父开关关着整个 sleep 不跑 |
|
|
56
|
-
| `sleepAffectiveTraitMinSessions` | `3` | 判「稳定倾向」所需的**独立会话**数(2–10)。下界 2 是故意的:N=1 就是单次场合,正是必须禁止升成特质的那个情形 |
|
|
57
|
-
| `sleepAffectiveMaxRelationsPerRun` | `10` | 单轮最多写多少条 `affective_context` 关系(0–50,0 = 一条不写) |
|
|
58
|
-
| `sleepProvider` | `''` | 睡眠模式专用 LLM Provider(留空则复用默认) |
|
|
59
|
-
| `sleepModel` | `''` | 睡眠模式专用 LLM Model(留空则复用默认) |
|
|
60
|
-
| `sleepReasoningEffort` | (未配置) | 睡眠 LLM 推理强度透传:`off` / `low` / `medium` / `high` / `none`。未配置 = 自动取模型支持的最低档(#135:避免思考模型用自带默认档烧光预算返回空体);显式 `none` = 不传该字段,使用模型默认(与 `dreamReasoningEffort` 语义一致) |
|
|
61
|
-
| `sleepMaxTokens` | `8192` | 冲突消解与模式发现阶段的 LLM 输出预算上限(token,#257:原硬编码 2048——`sleepActionSet: full` 实测 24 对需约 7000,截断即整轮裁决失败)。流式计费按实际用量,调大不增加成本 |
|
|
62
|
-
|
|
63
|
-
## 五阶段详解
|
|
64
|
-
每个阶段独立包裹 `try/catch`,任一阶段失败仅跳过该阶段,不阻塞后续流程。四个相位读取的记忆行池都先剔掉 `source='imported'` 的外来行(`src/dream/sleep.js`)——外来行不经本方蒸馏与召回,时间分层与冲突判据在它身上系统性失真,我方对它的原文和归档面没有处置权,所以下文「输入」一栏里的「全量」一律读作「本方活跃行」,契约见 [SHARE.md](SHARE.md)。
|
|
65
|
-
|
|
66
|
-
### 1. `conflict_resolution`(冲突消解)
|
|
67
|
-
- **目标**:清理向量空间中的重复、矛盾或过时记忆。
|
|
68
|
-
- **输入**:全量/增量记忆向量索引、`sleepConflictStrictness` 阈值。
|
|
69
|
-
- **算法**:
|
|
70
|
-
1. 向量检索 `findPotentialConflicts` 获取高相似度对;`sleepActionSet: "full"` 时再补一遍跨类型对。
|
|
71
|
-
2. **已裁决对先压掉**:`differentiated_pairs` 账本里记着「这一对判过"互补、两条都留"」,两侧正文的 `content_hash` 与记账时一致就不再送模型(任一侧被改写则重新进候选)。这一轮被压掉几对记在 `phase.suppressed`,与「一对都没检测到」(`no conflicts found`)分开报。
|
|
72
|
-
3. **台账预筛**(#529):查 `conflict_reviewed`,把「已经判过、且两侧 `updated_at` 都不晚于 `judged_at`」的对从候选池里剔掉——它们上一轮已有结论,两侧都没动就没必要再问一遍。任一字段更新即重新入池(与 `conflict_pending` 复核项 4 同一条宽松代理)。两条边界:跨 scope 对**不筛**(它们本来就要停车进人工队列,不能被机器台账吞掉),`conflictFreezeEnabled` 档**不筛**(那一档只停车不送 LLM)。
|
|
73
|
-
4. **去重**:每条记忆至多参与一对,相似度高者优先。**必须排在 3 之后**——先占位再去掉,一个已审的对会把它那一端记忆的配对名额占住,而它真正的对手(另一条没审过的记忆)因此进不了这一轮。被台账挡下的对数记在 `phase.ledgerSkipped`。
|
|
74
|
-
5. **每轮总量上限**(`sleepConflictMaxPairsPerRun`,默认 60):没进这一轮的对留在下一轮,既不丢也不记账。
|
|
75
|
-
6. **分批**(`sleepConflictMaxPairs`,默认 12):每批单独一次 LLM 往返、单独 `validateDecisions` + `applyDecisions`。某批撞上下文或输出坏掉只让**这一批**失败并计入 `degraded`,其余批照常落地;只有一批都没成功才整轮 `failed`。
|
|
76
|
-
7. LLM 仲裁:基于严格度阈值生成决策(默认档 `keep/merge/conflict/create/archive/update`,full 档另开 `supersede/differentiate`)。
|
|
77
|
-
8. `validateDecisions`:严格校验决策合法性,缺省策略为 `keep`。**`reflectionUpdateMaxPerRun` 是整轮上限,不是每批上限**——额度由分批循环持有,每批只领剩余额度,校验通过后按本批通过校验的 `update` 数扣减;额度耗尽的那批若还含 `update` 会被校验器整单拒绝(记 `failed`),其余批照常继续。
|
|
78
|
-
9. `applyDecisions`:批量执行状态变更。
|
|
79
|
-
10. **台账记账**:本批**全部送审对**都写一行 `conflict_reviewed`(不只记被裁决成冲突的那几对,判 `keep` 的正是下一轮重复送审的元凶),`outcome` 取两端里非 `keep` 的那一侧。记账是幂等 upsert,按 `(memory_a, memory_b)` 排序归一化。两个防"假已审"的细节:`judged_at` 取**发送本批之前**的时刻(不是落地之后——LLM 往返期间若一侧被外部改动,它的 `updated_at` 会晚于 `judged_at`,下一轮自然重新入池;按落地时刻封盘等于用陈旧结论锁死这一对),且取 `max(墙钟, 本批两侧最新的 updated_at)`——`updated_at` 用的是 store 的**单调时钟**(`src/store.js` 的 `nowIso` 在同一毫秒内的连续写入上逐次 +1ms),它可能领先墙钟几毫秒,两列不同源的话 `alreadyJudged` 会把「刚判过」读成「判过之后又被改过」,判 `keep` 的死循环原样复活;本批 `failures` / `conflicts`(CAS 冲突)/ `skipped` 里出现过的 id,其所在对**不记账**——没落地的裁决记成已审,会让这一对从此不再送审,沉默地永久压制一个真实冲突。
|
|
80
|
-
- **`differentiate` 不改正文**(#533):`#126` 曾把同一段差异注记**同时**追加到两侧,指望注记进 embedding 后下一轮不再判重复。离线复现证伪了这个假设——两侧共享同一段后缀会让两条向量**同向靠拢**(余弦 0.876 → 0.960 只用 5 轮,注记截到 0 字照样往上爬),于是同一对每轮又被判成重复、又追加一段(实测单条堆到 19 段,正文 150 字膨胀到 2200+)。现在只落 `differentiated_pairs` 账本(配对 + 两侧当时的 `content_hash` + 模型给的 `distinctions`),正文一个字节都不动。存量库里仍带旧注记块的行按「已裁决」处理,升级不清账。
|
|
81
|
-
- **输出**:冲突消解报告(决策明细 + `pairs` / `batches` / `ledgerSkipped` / `perRunSkipped`)。**失败轮同样带 `pairs` 与 `batches`**——此前只有成功路径带,85 条失败审计行连"送了多少对"都查不到。批间被用户活动打断时报 `aborted`(不是 `noop`:`noop` 的语义是"看过、没冲突",而没送出去的批根本没看过),整轮 `deriveStatus` 也沿用同一个词,不再折回 `noop`。
|
|
82
|
-
- **验收**:无合法记忆被误删;仲裁结果符合配置的严格度阈值;决策可回滚。
|
|
83
|
-
- **为什么要有预算(#529 的现场)**:整池相似对一次拼进一个 prompt,实测单请求 **163,054** 输入 token(服务端 `--ctx-size` 163,840,占 99.5%),撞墙后按冷却重排,送进去的还是同一份输入,于是连续 **23 轮** `llm failed`;而 `keep` 当时是彻底的 no-op(不留痕),同一对每个冷却周期都被重新问一遍。三处一起改之后单请求降到 806–2,011 token、连续失败 23 轮 → 0 轮。
|
|
84
|
-
- **台账为什么不复用 `conflict_pending`**:那张表是**人工**待裁决队列,面板徽标数就是 `countConflictPending()`(`resolved_at IS NULL` 的行数)。机器每判一对写一行会把这个数字淹掉,人就再也看不出"有哪些待我裁决"。两张表分工:`conflict_pending` = 人要看,`conflict_reviewed` = 机器记账,零界面。
|
|
85
|
-
|
|
86
|
-
### 2. `archival_demotion`(归档降级)
|
|
87
|
-
- **目标**:按时间衰减对记忆进行分层压缩,释放热存储压力。
|
|
88
|
-
- **输入**:`last_accessed_at` 排序的记忆列表、`sleepArchiveDays`、`sleepCompressDays`。
|
|
89
|
-
- **算法**(纯规则、无 LLM,确定性执行):
|
|
90
|
-
- `>= sleepCompressDays`:调用 `setArchived` 完全归档。
|
|
91
|
-
- `>= sleepArchiveDays && < sleepCompressDays`:调用 `demoteToSummary(m.id, 摘要, {minRefTimeMs: archiveCut})` 转为摘要态——原内容存入 `_full_content`,摘要由前 120 字截断生成(LLM 未配置时的 fallback)。
|
|
92
|
-
- `< sleepArchiveDays`:保持原状不动。
|
|
93
|
-
- **minRefTimeMs 关键点**:传入快照时刻 `archiveCut`,`demoteToSummary` 在事务内复查 `last_accessed_at`——快照后被召回 touch 的记忆视为"重新活跃",跳过不降级。
|
|
94
|
-
- **输出**:降级操作清单与状态快照。
|
|
95
|
-
- **验收**:严格遵循分层阈值;`minRefTimeMs` 机制生效,防止快照后 `touch` 导致的误降级;摘要保留 `_full_content` 可无损恢复。
|
|
96
|
-
|
|
97
|
-
### 3. `pattern_discovery`(模式发现)
|
|
98
|
-
- **目标**:从近期记忆中提炼可复用的隐性规律或知识模式。
|
|
99
|
-
- **输入**:最近 `sleepPatternLookbackDays` 内的 `sleepPatternMinMemories` 条记忆。
|
|
100
|
-
- **算法**:
|
|
101
|
-
1. LLM 扫描分析,提取候选模式。
|
|
102
|
-
2. **证据校验**:强制 `evidence` 列表与数据库真实 ID 求交集(`intersect`),过滤伪造/幻觉引用。
|
|
103
|
-
3. 输出 `type=pattern` 创建指令,受 `sleepMaxPatternPerRun` 限制。
|
|
104
|
-
- **输出**:新创建的 Pattern 实体及关联证据链。
|
|
105
|
-
- **验收**:无孤立证据;Pattern 创建数不超上限;证据 100% 可溯源。
|
|
106
|
-
|
|
107
|
-
### 4. `relation_completion`(关系补全)
|
|
108
|
-
- **目标**:修复知识图谱中的断链与孤立节点。
|
|
109
|
-
- **输入**:`listEntities` 获取的实体列表、关系图谱。
|
|
110
|
-
- **算法**:
|
|
111
|
-
1. 筛选 `getRelations` 返回为空的孤立实体。
|
|
112
|
-
2. 基于上下文推断关系:
|
|
113
|
-
- 语义/实体共现 → `related_to`
|
|
114
|
-
- 同属项目/模块 → `part_of`
|
|
115
|
-
- 技术栈/流程依赖 → `depends_on`
|
|
116
|
-
3. 批量写入关系边。
|
|
117
|
-
- **输出**:新增关系边列表。
|
|
118
|
-
- **验收**:关系推断符合领域常识;不引入循环依赖;图谱连通性提升。
|
|
119
|
-
|
|
120
|
-
### 5. `affective_attribution`(情感归因,#619)
|
|
121
|
-
- **目标**:把「谁在什么场合因为什么而有情绪」写成图上的一条边,并且**不**让一次性的情绪升格成「他就是这种人」。
|
|
122
|
-
- **输入**:实体列表(`listEntities`,至少 2 个),扫描池口径与模式发现一致(`sleepPatternMinMemories`、剔除 document/summary/pattern、剔除 imported、剔暂停项);`sleepAffectiveTraitMinSessions`、`sleepAffectiveMaxRelationsPerRun`。
|
|
123
|
-
- **算法**:
|
|
124
|
-
1. 开关 `sleepAffectiveEnabled !== true` → `skipped: disabled`;无 LLM 路由 → `skipped: no llm route`;实体不足 2 个 → `skipped: too few entities`。
|
|
125
|
-
2. LLM 读实体名清单 + 记忆列表,每条候选输出 `person` / `target` / `emotion` / `attribution`(`occasion` 或 `trait`)/ `evidence`(记忆 id)/ `why`。
|
|
126
|
-
3. **护栏在代码里,不在 prompt 里**:`person`/`target` 必须解析到已存在的实体(否则丢进 `dropped: unknown entity`);`evidence` 必须是本轮扫描池里的真实 id(否则 `no real evidence`);`emotion` 非空。
|
|
127
|
-
4. **升特质要过 provenance**:模型说 `trait`,就用 `service.provenanceFor(memoryId)` 收集这批 evidence 覆盖的 `session_id`,去重后数量 `< sleepAffectiveTraitMinSessions` 的一律**降级为 `occasion`**(记 `downgradedFrom: "trait"`,不丢弃)。被降级的条数就是这张探针要抓的误归因数——静默丢掉它就等于把测到的东西藏起来。
|
|
128
|
-
5. `occasion` 落边:先按**会话**判是不是同一个场合,再决定合并还是新写。`entity_relations` 没有 UNIQUE 约束,`saveRelation` 每次都是新插一条,而探针会拿同一批段反复回放——不判重就会把同一次场合叠成多条边、把 `sessionCount` 抬得比真实场合数高。但**判重不能按 evidence 有没有交集**:模型给 evidence 的顺序不稳定(`parseJsonArray` 也不排序),`[m1,m2]` 与 `[m2,m1]` 是同一场合的两种写法;同一会话里的两条不同记忆各说一句「他很焦虑」,证据天然不相交,可它们是同一个场合。所以键落在会话层:同一 `(person, target, emotion)` 且两边**会话集**有交集 ⇒ 同一场合。
|
|
129
|
-
- 命中 ⇒ **合并而不是丢弃**:把 evidence 并集、按并集重算的 `sessionCount` 写回既有边(`updateRelationMetadata`),`lastMergedRunId` 记下是哪一轮补的,并计入返回值 `merged`。只丢弃会让边上的场合数冻结在第一次写入那一刻,将来拿它算归因覆盖率会一直读到 1。
|
|
130
|
-
- 未命中且未超 `sleepAffectiveMaxRelationsPerRun` ⇒ `saveRelation({relation_type: "affective_context", source: "llm", memory_id: evidence[0], metadata: {emotion, attribution, why, evidence, sessionCount, model, runId}})`;超出的进 `dropped: relation cap`。合并发生在 cap 之前——合并既有边不新增行,不该占名额。
|
|
131
|
-
6. `trait` 走**标准决策通道**——铸成 `type=pattern` 记忆,经 `validateDecisions`(`allowedActions: ["create"]`、`maxCreatePerRun` 取 `sleepMaxPatternPerRun`)与 `applyDecisions`(`mintSource: "sleep"`,带 #512 铸造台账)。不新增写路径,因此继承所有既有上限与审计。**标题必须含 `target`**:`applyCreate` 走 `service.saveWithDedupe`,按 `(type, title)` 找同题行;标题不含对象时,「对答辩长期焦虑」与「对面试长期焦虑」是同一条,第二条会被静默合并——不报错、不进 `failures`,而 `applied` 仍如实显示写了,等于一条真实结论被吞掉而返回值说它已落库。**pattern 走 `_overwrite` 原地刷新、不追加**:`applyCreate` 对 `type === "pattern"` 传 `_overwrite: true`,命中同题行时正文被整体替换(旧文进 `content_history`,`source: "overwrite"`)。不这样的话,重放同一批证据会把同一段结论一遍遍 `appendContent` 粘上去(实测 161 → 355 → 549 字,外加一串重复的 `[证据: …]` 尾巴),而 pattern 正是注入面消费的东西。只对 pattern 放开——其他类型被 `create` 撞上同题行时仍按合并语义走,整段覆盖既有行比追加更危险。
|
|
132
|
-
7. `applyCreate` 命中同题行时 `saveWithDedupe` 返回 `action: "merged"`:它确实刷新了那一行,但**没有铸出新条目**。这种结局不计入 `applied`,单独计 `merged` 并返回 `refreshedTraits`;决定 `status` 时它既不是 `ok`(没有新铸)也不是 `noop`(确实动过库),单独报 `refreshed`。`deriveStatus` 把 `refreshed` 算作「有产出」,否则整轮会被记成 noop,审计行又变回「跑了、什么都没干」。
|
|
133
|
-
8. 已存在的 `occasion` 边在同类情绪被确认为 `trait` 时**不改写、只标记**:边记录的是「这次场合观察到过」,这句话在升格后仍然为真,所以 `sessionCount` 保持原值;但会给它写上 `promotedToTrait: true` 与 `traitSessionCount`。不标的话,同一件事实在图上写着「一次性」、在 pattern 里写着「长期」,读归因覆盖率的人拿到两份互相矛盾的记录,而且不会自愈——边不参与 trait 判定(trait 只看当轮 evidence)。
|
|
134
|
-
- **输出**:`{status, scanned, relations, merged, mergedList, edgeWrites, applied, refreshedTraits, failures, conflicts, traits, downgraded, dropped}`。`downgraded` 与 `dropped` 必须都是可见数字。`edgeWrites`、`merged`、`refreshedTraits` 各自单独报,因为它们都不是 `applied` 决策:第一个是「新写了一条边」,第二个是「认出了同一场合、把证据补进既有边」,第三个是「trait 标题撞上既有 pattern、原地刷新了它」。混进 `applied` 会让两个数字的含义随相位漂移,也会让「一条真实结论被同题行吃掉」看起来像成功。但 **receipt 里三者都算**——`buildReceipt` 只回答「这一轮到底改动过库没有」,只累加 `applied` 会让「写了边的一轮」和「只做了合并的一轮」都读成 `:0`,也就是「跑了但什么都没做」,那正是探针绝不能看到的状态。receipt 保持八段不变(`parseReceipt` 与 `organize.js` 共用这条契约),明细在各相位的 `phases` JSON 里,不在 receipt 里。
|
|
135
|
-
- **每条返回路径都带全字段**:`aborted` 与 `failed` 两个分支同样有 `merged` 与 `mergedList`。少一个不会报错,消费方读到 `undefined` 会静默当作「没有合并」——审计面上最难查的就是这种。
|
|
136
|
-
- **中断与失败**:中途 `signal.aborted` ⇒ `status: "aborted"`(**即使一条都没写**——循环断在任意下标,没扫完就不能报 `noop` 说「池子是空的」);`validateDecisions` 拒绝 trait 决策 ⇒ `status: "failed"`,不是静默的 `noop`。
|
|
137
|
-
- **验收**:一次场合的情绪只能落 `affective_context` 边、不能铸 pattern;同一类型跨 N 个独立会话才允许铸 pattern;判为 `related_to` 的关系计数在本相位后不增长;同一批段重放不产生第二条相同边(而是合并进既有边);重放时把 evidence 数组换个顺序也不产生第二条边;同一会话里两条不同记忆说的是同一种情绪时合并成一条边、`sessionCount` 仍是 1;后面又攒到新会话时既有边被扩充、`sessionCount` 跟着涨而不是冻结;同一条记忆里的两种不同情绪必须是两条边;只做了合并的一轮,receipt 的写入计数不为 0;同一个人对两个不同对象各够 trait 阈值时铸出**两条** pattern(不许一条吞掉另一条);重放同一批证据三轮,pattern 正文长度与 `[证据]` 尾巴个数都不变;只做了原地刷新的那一轮报 `refreshed` 而不是 `ok` + `applied: 0`;`occasion` 边在升格 trait 后被标上 `promotedToTrait` 而 `sessionCount` 不变。
|
|
138
|
-
- **为什么不能接在 `relation_completion` 里**:那一相只扫**孤立**实体(`getRelations(e.id).length === 0`),且 `inferRelationType` 三种类型硬编码、末行 `return "related_to"` 兜底——把情感塞进去只会得到一批 `related_to`,图上看不出情感轴。
|
|
139
|
-
|
|
140
|
-
## 分层压缩策略表
|
|
141
|
-
| 记忆层级 | 判定条件 (`last_accessed_at`) | 处理策略 | 存储形态 |
|
|
142
|
-
|:---|:---|:---|:---|
|
|
143
|
-
| **活跃 (Active)** | `< sleepArchiveDays` | 保持原状,正常搜索可见 | 全文索引 + 向量 |
|
|
144
|
-
| **冷 (Cold)** | `>= sleepArchiveDays` 且 `< sleepCompressDays` | `demoteToSummary` 压成摘要,原文进 `_full_content` | 摘要 + `_full_content` 分离 |
|
|
145
|
-
| **睡眠 (Archived)** | `>= sleepCompressDays` | `setArchived` 移出热查询域 | 仅保留元数据与归档标记 |
|
|
146
|
-
|
|
147
|
-
## 调度器与可中断机制
|
|
148
|
-
- **`noteWrite()` 心跳重置**:每次用户写入/读取记忆时触发。必须先 `clearTimeout` 清除旧计时器,再重新设置 Idle Timer,彻底杜绝 Stale Timer 导致的误触发。
|
|
149
|
-
- **`maybeSchedule()` 准入控制**:串行检查 `sleepModeEnabled` → 空闲时长 `>= sleepIdleMinutes` → 距上次运行 `>= 有效最小间隔` → 状态非 `running/disposed`。任一不满足则放弃调度。
|
|
150
|
-
- **有效最小间隔**(#529):`sleepFailureBackoff` 关(默认)时逐字节等于 `sleepMinIntervalHours`;开启时按连续失败次数指数推迟,封顶 `max(sleepMinIntervalHours, 24h)`——不是硬 24h:基数允许配到 168 小时,那时封顶跟着基数走,退避只能放宽间隔、不会把用户设的基数压小。`armNextWindow()` 排下一次闹钟用的是同一个函数——两处口径必须一致,否则退避期闹钟会在基数到点响、被 `shouldRun` 否掉、再重排一次,白转一轮。
|
|
151
|
-
- 退避**不是**按"同一份输入又失败"判的:`runSleep` 里那个 `snapshotHash` 是跑完之后对 `phases` 结果算的摘要哈希,不是输入快照,拿它当判据会把"同样的输入"认成"不同的结果"。改用连续失败计数,与 `autoDream` 的失败退避(#292)同构。
|
|
152
|
-
- **`service.enqueue` 串行队列**:所有 Sleep Run 必须进入全局串行队列执行,与 `autoDream` 严格互斥,避免并发写入冲突与资源争抢。
|
|
153
|
-
- **`AbortController` 可中断**:调度器持有 AbortController 实例。一旦检测到用户活动(`noteWrite` 或显式交互),立即调用 `abort()` 中断当前 `runSleep` 的执行上下文,保证实时性优先。
|
|
154
|
-
|
|
155
|
-
## 数据迁移与存储层变更
|
|
156
|
-
- **幂等迁移**:
|
|
157
|
-
- `memories` 表新增列:`last_accessed_at` (DATETIME)、`_full_content` (TEXT/BLOB)。
|
|
158
|
-
- `dream_runs` 表新增列:`run_type` (VARCHAR, 默认 'auto',用于区分 'sleep')。
|
|
159
|
-
- 迁移脚本需保证幂等(`IF NOT EXISTS` / 检查列是否存在)。
|
|
160
|
-
- **Store 新增方法**:
|
|
161
|
-
- `demoteToSummary(id, summary, opts)`:执行降级并记录 `minRefTimeMs`。
|
|
162
|
-
- `restoreContent(id)`:按需从归档/摘要恢复原文。
|
|
163
|
-
- `touchLastAccess(id)`:更新 `last_accessed_at` 至当前时间。
|
|
164
|
-
- `getUnrecalledSince(days)`:按时间窗口拉取未召回记忆。
|
|
165
|
-
- `listEntities()`:获取实体列表用于关系分析。
|
|
166
|
-
- **Service 扩展**:
|
|
167
|
-
- `enqueue(task)`:串行任务队列入口。
|
|
168
|
-
- `setSleepHook(fn)`:注入 `noteWrite` 钩子。
|
|
169
|
-
- `touchRecalled(id)`:在召回路径中自动更新访问时间。
|
|
170
|
-
|
|
171
|
-
## 与 autoDream 的协作关系
|
|
172
|
-
| 维度 | autoDream (实时) | Sleep Mode (系统级) |
|
|
173
|
-
|:---|:---|:---|
|
|
174
|
-
| **触发时机** | 写入阈值/实时事件 | 系统空闲 + 定时周期 |
|
|
175
|
-
| **执行深度** | 轻量级、局部关联补全 | 深度扫描、全局压缩与模式提炼 |
|
|
176
|
-
| **资源占用** | 低延迟、短时 | 允许较高延迟、长时运行 |
|
|
177
|
-
| **队列关系** | 共享 `service.enqueue` 串行队列 | 共享 `service.enqueue` 串行队列(严格互斥不重叠) |
|
|
178
|
-
| **审计记录** | 共用 `dream_runs`,`run_type='auto'` | 共用 `dream_runs`,`run_type='sleep'` |
|
|
179
|
-
|
|
180
|
-
## 实现要点与避坑(v0.4.1 教训)
|
|
181
|
-
1. **Stale Timer 防护**:`noteWrite()` 中必须严格遵循 `clearTimeout(oldTimer) -> setNewTimer()` 顺序,否则快速连续写入会累积多个 Timer 导致频繁误触发。
|
|
182
|
-
2. **`minRefTimeMs` 防误降级**:调用 `demoteToSummary` 时必须传入快照时刻 `{minRefTimeMs: archiveCut}`。否则在 Sleep Run 执行期间被用户 `touch` 的记忆会因时间窗口漂移被错误降级。
|
|
183
|
-
3. **证据强过滤**:Pattern Discovery 阶段 LLM 返回的 `evidence` 必须与 DB 真实 ID 列表求交集。未通过 `intersect` 校验的证据一律丢弃,严防幻觉伪造关联。
|
|
184
|
-
4. **严格串行化**:所有 Sleep Run **必须**走 `service.enqueue`。禁止直接调用异步函数启动,确保与 `autoDream` 绝对不重叠,避免写入锁冲突。
|
|
185
|
-
5. **Fail-Safe 阶段隔离**:各阶段必须各自独立 `try/catch`。LLM 服务抖动或网络异常时,仅跳过当前阶段(如跳过 Pattern 发现),后续阶段(如 Demotion、Relation、Affective)必须继续执行并记录 Receipt。
|
|
186
|
-
6. **无 LLM 降级路由**:当 `resolveSleepRoute` 返回 `undefined`(如 Provider 未配置或模型不可用)时,系统应自动跳过所有依赖 LLM 的阶段,但 `archival_demotion`(纯规则)仍需照常执行,保障基础压缩能力不丢失。
|
|
187
|
-
|
|
188
|
-
## 测试清单
|
|
189
|
-
- [ ] **调度器**:验证 `sleepIdleMinutes` 触发准确性;验证 `clearTimeout` 防 Stale;验证 `sleepMinIntervalHours` 冷却生效。
|
|
190
|
-
- [ ] **中断机制**:运行中触发 `noteWrite`,验证 `abort()` 立即终止 Run 且状态回滚至安全点。
|
|
191
|
-
- [ ] **阶段隔离**:Mock LLM 500 错误,验证仅对应阶段跳过,Receipt 仍生成且 `run_type='sleep'`。
|
|
192
|
-
- [ ] **降级策略**:构造不同 `last_accessed_at` 的记忆,验证 `minRefTimeMs` 防误降级逻辑及分层归档正确性。
|
|
193
|
-
- [ ] **模式发现**:注入含虚假 ID 的 LLM 响应,验证 `intersect` 过滤生效且创建数 `<= sleepMaxPatternPerRun`。
|
|
194
|
-
- [ ] **情感归因(#619)**:单会话证据判 `trait` → 必须降级为 `occasion` 且 `downgraded` 计数 +1;跨 N 个独立会话才铸 pattern;`related_to` 计数在本相位后不增长;未知实体/伪造 evidence 进 `dropped` 而不是静默消失。
|
|
195
|
-
- [ ] **迁移幂等**:重复执行迁移脚本,验证无报错、无重复列/数据。
|
|
196
|
-
- [ ] **队列互斥**:并发触发 `autoDream` 与 Sleep,验证 `service.enqueue` 串行执行无重叠。
|
|
197
|
-
|
|
198
|
-
## 启用指南(Opt-in)
|
|
199
|
-
1. **配置开启**:在 `config.js` 或环境变量中设置 `sleepModeEnabled: true`。
|
|
200
|
-
2. **资源评估**:建议生产环境配置独立的 `sleepProvider` 与 `sleepModel`,避免与实时推理争抢额度/并发。
|
|
201
|
-
3. **初始运行**:首次启用后,系统将在首次空闲 `>= sleepIdleMinutes` 时触发全量扫描。可通过查看 `dream_runs` 表中 `run_type='sleep'` 的记录监控执行结果。
|
|
202
|
-
4. **调优建议**:初期可设置 `sleepConflictStrictness: 'gentle'`,观察 `conflict_resolution` 决策准确率后再逐步收紧至 `'normal'` 或 `'aggressive'`。
|
|
1
|
+
# dsh-mneme Sleep Mode 系统级睡眠设计文档
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
从被动整理到主动维护:在系统空闲时段自动执行深度记忆压缩、冲突消解、模式发现与关系补全,实现知识库的长效健康治理。
|
|
5
|
+
|
|
6
|
+
## 动机与设计原则
|
|
7
|
+
- **非侵入性**:仅在用户空闲(Idle)时触发,不干扰正常读写与实时推理。
|
|
8
|
+
- **可中断**:用户一旦产生活动,立即中止当前睡眠周期,保障实时体验。
|
|
9
|
+
- **分层压缩**:按访问热度与时间衰减进行阶梯式降级,保留核心语义,释放存储压力。
|
|
10
|
+
- **模式发现**:利用 LLM 挖掘长期记忆中的隐性规律,生成结构化 Pattern 实体。
|
|
11
|
+
- **审计延续**:与 `autoDream` 共享 `dream_runs` 审计表,通过 `run_type` 区分,保障全链路可追溯。
|
|
12
|
+
|
|
13
|
+
## 架构图
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TD
|
|
16
|
+
User[用户写入/读取] -->|noteWrite| Scheduler[SleepScheduler]
|
|
17
|
+
Scheduler -->|clearTimeout + 重置计时器| Timer[Idle Timer]
|
|
18
|
+
Timer -->|>= sleepIdleMinutes| Check[maybeSchedule]
|
|
19
|
+
|
|
20
|
+
Check -->|条件满足| Queue[service.enqueue]
|
|
21
|
+
Check -->|条件不满足| Wait[等待下一次 noteWrite]
|
|
22
|
+
|
|
23
|
+
subgraph Sleep Run [串行执行]
|
|
24
|
+
Q[Queue] --> P1[Phase 1: conflict_resolution]
|
|
25
|
+
P1 --> P2[Phase 2: archival_demotion]
|
|
26
|
+
P2 --> P3[Phase 3: pattern_discovery]
|
|
27
|
+
P3 --> P4[Phase 4: relation_completion]
|
|
28
|
+
P4 --> P5[Phase 5: affective_attribution]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
P5 -->|独立 try/catch 隔离| Receipt[buildReceipt]
|
|
32
|
+
Receipt -->|saveDreamRun run_type='sleep'| Audit[(dream_runs)]
|
|
33
|
+
|
|
34
|
+
User -->|活动触发| Abort[AbortController.abort]
|
|
35
|
+
Abort -->|中断当前 Run| Scheduler
|
|
36
|
+
|
|
37
|
+
AutoDream[autoDream 实时轻量触发] -.共享队列不重叠.-> Queue
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 配置项说明
|
|
41
|
+
| 配置项 | 默认值 | 说明 |
|
|
42
|
+
|:---|:---|:---|
|
|
43
|
+
| `sleepModeEnabled` | `false` | 全局开关(Opt-in),开启后激活调度器 |
|
|
44
|
+
| `sleepIdleMinutes` | `5` | 连续无操作触发睡眠的阈值(分钟) |
|
|
45
|
+
| `sleepMinIntervalHours` | `8` | 两次 Sleep Run 的最小时间间隔(小时) |
|
|
46
|
+
| `sleepFailureBackoff` | `false` | 连续失败退避(#529,opt-in):有效间隔 = `sleepMinIntervalHours` × 2^连败数,成功清零,封顶 `max(sleepMinIntervalHours, 24h)`(配得比 24h 大就按配的算——退避只放大间隔,不压缩用户自己设的基数)。判据看整轮 `status === "failed"`——`noop`(通路正常、只是没活干)不算失败,否则在自己工作良好的库里也会越退越长 |
|
|
47
|
+
| `sleepConflictMaxPairs` | `12` | **单批**送审的冲突对上限(0 = 不限,回退旧行为) |
|
|
48
|
+
| `sleepConflictMaxPairsPerRun` | `60` | **单轮**最多送审多少对(0 = 不限)。与上一条分工:一个管批次大小、一个管整轮总量 |
|
|
49
|
+
| `sleepConflictStrictness` | `'normal'` | 冲突消解严格度:`'gentle'`(0.92) / `'normal'`(0.85) / `'aggressive'`(0.75) |
|
|
50
|
+
| `sleepArchiveDays` | `30` | 进入冷存储/摘要降级的天数阈值 |
|
|
51
|
+
| `sleepCompressDays` | `90` | 进入完全归档(Archive)的天数阈值 |
|
|
52
|
+
| `sleepPatternMinMemories` | `100` | 模式发现阶段扫描的最近记忆条数下限 |
|
|
53
|
+
| `sleepPatternLookbackDays` | `30` | 模式发现回溯的时间窗口(天) |
|
|
54
|
+
| `sleepMaxPatternPerRun` | `3` | 单次运行最多创建的模式数量上限 |
|
|
55
|
+
| `sleepAffectiveEnabled` | `false` | 情感归因相位(#619,opt-in):情境情绪写成 `affective_context` 关系,跨多次独立会话的稳定倾向才铸成 pattern。是 `sleepModeEnabled` 的子相,父开关关着整个 sleep 不跑 |
|
|
56
|
+
| `sleepAffectiveTraitMinSessions` | `3` | 判「稳定倾向」所需的**独立会话**数(2–10)。下界 2 是故意的:N=1 就是单次场合,正是必须禁止升成特质的那个情形 |
|
|
57
|
+
| `sleepAffectiveMaxRelationsPerRun` | `10` | 单轮最多写多少条 `affective_context` 关系(0–50,0 = 一条不写) |
|
|
58
|
+
| `sleepProvider` | `''` | 睡眠模式专用 LLM Provider(留空则复用默认) |
|
|
59
|
+
| `sleepModel` | `''` | 睡眠模式专用 LLM Model(留空则复用默认) |
|
|
60
|
+
| `sleepReasoningEffort` | (未配置) | 睡眠 LLM 推理强度透传:`off` / `low` / `medium` / `high` / `none`。未配置 = 自动取模型支持的最低档(#135:避免思考模型用自带默认档烧光预算返回空体);显式 `none` = 不传该字段,使用模型默认(与 `dreamReasoningEffort` 语义一致) |
|
|
61
|
+
| `sleepMaxTokens` | `8192` | 冲突消解与模式发现阶段的 LLM 输出预算上限(token,#257:原硬编码 2048——`sleepActionSet: full` 实测 24 对需约 7000,截断即整轮裁决失败)。流式计费按实际用量,调大不增加成本 |
|
|
62
|
+
|
|
63
|
+
## 五阶段详解
|
|
64
|
+
每个阶段独立包裹 `try/catch`,任一阶段失败仅跳过该阶段,不阻塞后续流程。四个相位读取的记忆行池都先剔掉 `source='imported'` 的外来行(`src/dream/sleep.js`)——外来行不经本方蒸馏与召回,时间分层与冲突判据在它身上系统性失真,我方对它的原文和归档面没有处置权,所以下文「输入」一栏里的「全量」一律读作「本方活跃行」,契约见 [SHARE.md](SHARE.md)。
|
|
65
|
+
|
|
66
|
+
### 1. `conflict_resolution`(冲突消解)
|
|
67
|
+
- **目标**:清理向量空间中的重复、矛盾或过时记忆。
|
|
68
|
+
- **输入**:全量/增量记忆向量索引、`sleepConflictStrictness` 阈值。
|
|
69
|
+
- **算法**:
|
|
70
|
+
1. 向量检索 `findPotentialConflicts` 获取高相似度对;`sleepActionSet: "full"` 时再补一遍跨类型对。
|
|
71
|
+
2. **已裁决对先压掉**:`differentiated_pairs` 账本里记着「这一对判过"互补、两条都留"」,两侧正文的 `content_hash` 与记账时一致就不再送模型(任一侧被改写则重新进候选)。这一轮被压掉几对记在 `phase.suppressed`,与「一对都没检测到」(`no conflicts found`)分开报。
|
|
72
|
+
3. **台账预筛**(#529):查 `conflict_reviewed`,把「已经判过、且两侧 `updated_at` 都不晚于 `judged_at`」的对从候选池里剔掉——它们上一轮已有结论,两侧都没动就没必要再问一遍。任一字段更新即重新入池(与 `conflict_pending` 复核项 4 同一条宽松代理)。两条边界:跨 scope 对**不筛**(它们本来就要停车进人工队列,不能被机器台账吞掉),`conflictFreezeEnabled` 档**不筛**(那一档只停车不送 LLM)。
|
|
73
|
+
4. **去重**:每条记忆至多参与一对,相似度高者优先。**必须排在 3 之后**——先占位再去掉,一个已审的对会把它那一端记忆的配对名额占住,而它真正的对手(另一条没审过的记忆)因此进不了这一轮。被台账挡下的对数记在 `phase.ledgerSkipped`。
|
|
74
|
+
5. **每轮总量上限**(`sleepConflictMaxPairsPerRun`,默认 60):没进这一轮的对留在下一轮,既不丢也不记账。
|
|
75
|
+
6. **分批**(`sleepConflictMaxPairs`,默认 12):每批单独一次 LLM 往返、单独 `validateDecisions` + `applyDecisions`。某批撞上下文或输出坏掉只让**这一批**失败并计入 `degraded`,其余批照常落地;只有一批都没成功才整轮 `failed`。
|
|
76
|
+
7. LLM 仲裁:基于严格度阈值生成决策(默认档 `keep/merge/conflict/create/archive/update`,full 档另开 `supersede/differentiate`)。
|
|
77
|
+
8. `validateDecisions`:严格校验决策合法性,缺省策略为 `keep`。**`reflectionUpdateMaxPerRun` 是整轮上限,不是每批上限**——额度由分批循环持有,每批只领剩余额度,校验通过后按本批通过校验的 `update` 数扣减;额度耗尽的那批若还含 `update` 会被校验器整单拒绝(记 `failed`),其余批照常继续。
|
|
78
|
+
9. `applyDecisions`:批量执行状态变更。
|
|
79
|
+
10. **台账记账**:本批**全部送审对**都写一行 `conflict_reviewed`(不只记被裁决成冲突的那几对,判 `keep` 的正是下一轮重复送审的元凶),`outcome` 取两端里非 `keep` 的那一侧。记账是幂等 upsert,按 `(memory_a, memory_b)` 排序归一化。两个防"假已审"的细节:`judged_at` 取**发送本批之前**的时刻(不是落地之后——LLM 往返期间若一侧被外部改动,它的 `updated_at` 会晚于 `judged_at`,下一轮自然重新入池;按落地时刻封盘等于用陈旧结论锁死这一对),且取 `max(墙钟, 本批两侧最新的 updated_at)`——`updated_at` 用的是 store 的**单调时钟**(`src/store.js` 的 `nowIso` 在同一毫秒内的连续写入上逐次 +1ms),它可能领先墙钟几毫秒,两列不同源的话 `alreadyJudged` 会把「刚判过」读成「判过之后又被改过」,判 `keep` 的死循环原样复活;本批 `failures` / `conflicts`(CAS 冲突)/ `skipped` 里出现过的 id,其所在对**不记账**——没落地的裁决记成已审,会让这一对从此不再送审,沉默地永久压制一个真实冲突。
|
|
80
|
+
- **`differentiate` 不改正文**(#533):`#126` 曾把同一段差异注记**同时**追加到两侧,指望注记进 embedding 后下一轮不再判重复。离线复现证伪了这个假设——两侧共享同一段后缀会让两条向量**同向靠拢**(余弦 0.876 → 0.960 只用 5 轮,注记截到 0 字照样往上爬),于是同一对每轮又被判成重复、又追加一段(实测单条堆到 19 段,正文 150 字膨胀到 2200+)。现在只落 `differentiated_pairs` 账本(配对 + 两侧当时的 `content_hash` + 模型给的 `distinctions`),正文一个字节都不动。存量库里仍带旧注记块的行按「已裁决」处理,升级不清账。
|
|
81
|
+
- **输出**:冲突消解报告(决策明细 + `pairs` / `batches` / `ledgerSkipped` / `perRunSkipped`)。**失败轮同样带 `pairs` 与 `batches`**——此前只有成功路径带,85 条失败审计行连"送了多少对"都查不到。批间被用户活动打断时报 `aborted`(不是 `noop`:`noop` 的语义是"看过、没冲突",而没送出去的批根本没看过),整轮 `deriveStatus` 也沿用同一个词,不再折回 `noop`。
|
|
82
|
+
- **验收**:无合法记忆被误删;仲裁结果符合配置的严格度阈值;决策可回滚。
|
|
83
|
+
- **为什么要有预算(#529 的现场)**:整池相似对一次拼进一个 prompt,实测单请求 **163,054** 输入 token(服务端 `--ctx-size` 163,840,占 99.5%),撞墙后按冷却重排,送进去的还是同一份输入,于是连续 **23 轮** `llm failed`;而 `keep` 当时是彻底的 no-op(不留痕),同一对每个冷却周期都被重新问一遍。三处一起改之后单请求降到 806–2,011 token、连续失败 23 轮 → 0 轮。
|
|
84
|
+
- **台账为什么不复用 `conflict_pending`**:那张表是**人工**待裁决队列,面板徽标数就是 `countConflictPending()`(`resolved_at IS NULL` 的行数)。机器每判一对写一行会把这个数字淹掉,人就再也看不出"有哪些待我裁决"。两张表分工:`conflict_pending` = 人要看,`conflict_reviewed` = 机器记账,零界面。
|
|
85
|
+
|
|
86
|
+
### 2. `archival_demotion`(归档降级)
|
|
87
|
+
- **目标**:按时间衰减对记忆进行分层压缩,释放热存储压力。
|
|
88
|
+
- **输入**:`last_accessed_at` 排序的记忆列表、`sleepArchiveDays`、`sleepCompressDays`。
|
|
89
|
+
- **算法**(纯规则、无 LLM,确定性执行):
|
|
90
|
+
- `>= sleepCompressDays`:调用 `setArchived` 完全归档。
|
|
91
|
+
- `>= sleepArchiveDays && < sleepCompressDays`:调用 `demoteToSummary(m.id, 摘要, {minRefTimeMs: archiveCut})` 转为摘要态——原内容存入 `_full_content`,摘要由前 120 字截断生成(LLM 未配置时的 fallback)。
|
|
92
|
+
- `< sleepArchiveDays`:保持原状不动。
|
|
93
|
+
- **minRefTimeMs 关键点**:传入快照时刻 `archiveCut`,`demoteToSummary` 在事务内复查 `last_accessed_at`——快照后被召回 touch 的记忆视为"重新活跃",跳过不降级。
|
|
94
|
+
- **输出**:降级操作清单与状态快照。
|
|
95
|
+
- **验收**:严格遵循分层阈值;`minRefTimeMs` 机制生效,防止快照后 `touch` 导致的误降级;摘要保留 `_full_content` 可无损恢复。
|
|
96
|
+
|
|
97
|
+
### 3. `pattern_discovery`(模式发现)
|
|
98
|
+
- **目标**:从近期记忆中提炼可复用的隐性规律或知识模式。
|
|
99
|
+
- **输入**:最近 `sleepPatternLookbackDays` 内的 `sleepPatternMinMemories` 条记忆。
|
|
100
|
+
- **算法**:
|
|
101
|
+
1. LLM 扫描分析,提取候选模式。
|
|
102
|
+
2. **证据校验**:强制 `evidence` 列表与数据库真实 ID 求交集(`intersect`),过滤伪造/幻觉引用。
|
|
103
|
+
3. 输出 `type=pattern` 创建指令,受 `sleepMaxPatternPerRun` 限制。
|
|
104
|
+
- **输出**:新创建的 Pattern 实体及关联证据链。
|
|
105
|
+
- **验收**:无孤立证据;Pattern 创建数不超上限;证据 100% 可溯源。
|
|
106
|
+
|
|
107
|
+
### 4. `relation_completion`(关系补全)
|
|
108
|
+
- **目标**:修复知识图谱中的断链与孤立节点。
|
|
109
|
+
- **输入**:`listEntities` 获取的实体列表、关系图谱。
|
|
110
|
+
- **算法**:
|
|
111
|
+
1. 筛选 `getRelations` 返回为空的孤立实体。
|
|
112
|
+
2. 基于上下文推断关系:
|
|
113
|
+
- 语义/实体共现 → `related_to`
|
|
114
|
+
- 同属项目/模块 → `part_of`
|
|
115
|
+
- 技术栈/流程依赖 → `depends_on`
|
|
116
|
+
3. 批量写入关系边。
|
|
117
|
+
- **输出**:新增关系边列表。
|
|
118
|
+
- **验收**:关系推断符合领域常识;不引入循环依赖;图谱连通性提升。
|
|
119
|
+
|
|
120
|
+
### 5. `affective_attribution`(情感归因,#619)
|
|
121
|
+
- **目标**:把「谁在什么场合因为什么而有情绪」写成图上的一条边,并且**不**让一次性的情绪升格成「他就是这种人」。
|
|
122
|
+
- **输入**:实体列表(`listEntities`,至少 2 个),扫描池口径与模式发现一致(`sleepPatternMinMemories`、剔除 document/summary/pattern、剔除 imported、剔暂停项);`sleepAffectiveTraitMinSessions`、`sleepAffectiveMaxRelationsPerRun`。
|
|
123
|
+
- **算法**:
|
|
124
|
+
1. 开关 `sleepAffectiveEnabled !== true` → `skipped: disabled`;无 LLM 路由 → `skipped: no llm route`;实体不足 2 个 → `skipped: too few entities`。
|
|
125
|
+
2. LLM 读实体名清单 + 记忆列表,每条候选输出 `person` / `target` / `emotion` / `attribution`(`occasion` 或 `trait`)/ `evidence`(记忆 id)/ `why`。
|
|
126
|
+
3. **护栏在代码里,不在 prompt 里**:`person`/`target` 必须解析到已存在的实体(否则丢进 `dropped: unknown entity`);`evidence` 必须是本轮扫描池里的真实 id(否则 `no real evidence`);`emotion` 非空。
|
|
127
|
+
4. **升特质要过 provenance**:模型说 `trait`,就用 `service.provenanceFor(memoryId)` 收集这批 evidence 覆盖的 `session_id`,去重后数量 `< sleepAffectiveTraitMinSessions` 的一律**降级为 `occasion`**(记 `downgradedFrom: "trait"`,不丢弃)。被降级的条数就是这张探针要抓的误归因数——静默丢掉它就等于把测到的东西藏起来。
|
|
128
|
+
5. `occasion` 落边:先按**会话**判是不是同一个场合,再决定合并还是新写。`entity_relations` 没有 UNIQUE 约束,`saveRelation` 每次都是新插一条,而探针会拿同一批段反复回放——不判重就会把同一次场合叠成多条边、把 `sessionCount` 抬得比真实场合数高。但**判重不能按 evidence 有没有交集**:模型给 evidence 的顺序不稳定(`parseJsonArray` 也不排序),`[m1,m2]` 与 `[m2,m1]` 是同一场合的两种写法;同一会话里的两条不同记忆各说一句「他很焦虑」,证据天然不相交,可它们是同一个场合。所以键落在会话层:同一 `(person, target, emotion)` 且两边**会话集**有交集 ⇒ 同一场合。
|
|
129
|
+
- 命中 ⇒ **合并而不是丢弃**:把 evidence 并集、按并集重算的 `sessionCount` 写回既有边(`updateRelationMetadata`),`lastMergedRunId` 记下是哪一轮补的,并计入返回值 `merged`。只丢弃会让边上的场合数冻结在第一次写入那一刻,将来拿它算归因覆盖率会一直读到 1。
|
|
130
|
+
- 未命中且未超 `sleepAffectiveMaxRelationsPerRun` ⇒ `saveRelation({relation_type: "affective_context", source: "llm", memory_id: evidence[0], metadata: {emotion, attribution, why, evidence, sessionCount, model, runId}})`;超出的进 `dropped: relation cap`。合并发生在 cap 之前——合并既有边不新增行,不该占名额。
|
|
131
|
+
6. `trait` 走**标准决策通道**——铸成 `type=pattern` 记忆,经 `validateDecisions`(`allowedActions: ["create"]`、`maxCreatePerRun` 取 `sleepMaxPatternPerRun`)与 `applyDecisions`(`mintSource: "sleep"`,带 #512 铸造台账)。不新增写路径,因此继承所有既有上限与审计。**标题必须含 `target`**:`applyCreate` 走 `service.saveWithDedupe`,按 `(type, title)` 找同题行;标题不含对象时,「对答辩长期焦虑」与「对面试长期焦虑」是同一条,第二条会被静默合并——不报错、不进 `failures`,而 `applied` 仍如实显示写了,等于一条真实结论被吞掉而返回值说它已落库。**pattern 走 `_overwrite` 原地刷新、不追加**:`applyCreate` 对 `type === "pattern"` 传 `_overwrite: true`,命中同题行时正文被整体替换(旧文进 `content_history`,`source: "overwrite"`)。不这样的话,重放同一批证据会把同一段结论一遍遍 `appendContent` 粘上去(实测 161 → 355 → 549 字,外加一串重复的 `[证据: …]` 尾巴),而 pattern 正是注入面消费的东西。只对 pattern 放开——其他类型被 `create` 撞上同题行时仍按合并语义走,整段覆盖既有行比追加更危险。
|
|
132
|
+
7. `applyCreate` 命中同题行时 `saveWithDedupe` 返回 `action: "merged"`:它确实刷新了那一行,但**没有铸出新条目**。这种结局不计入 `applied`,单独计 `merged` 并返回 `refreshedTraits`;决定 `status` 时它既不是 `ok`(没有新铸)也不是 `noop`(确实动过库),单独报 `refreshed`。`deriveStatus` 把 `refreshed` 算作「有产出」,否则整轮会被记成 noop,审计行又变回「跑了、什么都没干」。
|
|
133
|
+
8. 已存在的 `occasion` 边在同类情绪被确认为 `trait` 时**不改写、只标记**:边记录的是「这次场合观察到过」,这句话在升格后仍然为真,所以 `sessionCount` 保持原值;但会给它写上 `promotedToTrait: true` 与 `traitSessionCount`。不标的话,同一件事实在图上写着「一次性」、在 pattern 里写着「长期」,读归因覆盖率的人拿到两份互相矛盾的记录,而且不会自愈——边不参与 trait 判定(trait 只看当轮 evidence)。
|
|
134
|
+
- **输出**:`{status, scanned, relations, merged, mergedList, edgeWrites, applied, refreshedTraits, failures, conflicts, traits, downgraded, dropped}`。`downgraded` 与 `dropped` 必须都是可见数字。`edgeWrites`、`merged`、`refreshedTraits` 各自单独报,因为它们都不是 `applied` 决策:第一个是「新写了一条边」,第二个是「认出了同一场合、把证据补进既有边」,第三个是「trait 标题撞上既有 pattern、原地刷新了它」。混进 `applied` 会让两个数字的含义随相位漂移,也会让「一条真实结论被同题行吃掉」看起来像成功。但 **receipt 里三者都算**——`buildReceipt` 只回答「这一轮到底改动过库没有」,只累加 `applied` 会让「写了边的一轮」和「只做了合并的一轮」都读成 `:0`,也就是「跑了但什么都没做」,那正是探针绝不能看到的状态。receipt 保持八段不变(`parseReceipt` 与 `organize.js` 共用这条契约),明细在各相位的 `phases` JSON 里,不在 receipt 里。
|
|
135
|
+
- **每条返回路径都带全字段**:`aborted` 与 `failed` 两个分支同样有 `merged` 与 `mergedList`。少一个不会报错,消费方读到 `undefined` 会静默当作「没有合并」——审计面上最难查的就是这种。
|
|
136
|
+
- **中断与失败**:中途 `signal.aborted` ⇒ `status: "aborted"`(**即使一条都没写**——循环断在任意下标,没扫完就不能报 `noop` 说「池子是空的」);`validateDecisions` 拒绝 trait 决策 ⇒ `status: "failed"`,不是静默的 `noop`。
|
|
137
|
+
- **验收**:一次场合的情绪只能落 `affective_context` 边、不能铸 pattern;同一类型跨 N 个独立会话才允许铸 pattern;判为 `related_to` 的关系计数在本相位后不增长;同一批段重放不产生第二条相同边(而是合并进既有边);重放时把 evidence 数组换个顺序也不产生第二条边;同一会话里两条不同记忆说的是同一种情绪时合并成一条边、`sessionCount` 仍是 1;后面又攒到新会话时既有边被扩充、`sessionCount` 跟着涨而不是冻结;同一条记忆里的两种不同情绪必须是两条边;只做了合并的一轮,receipt 的写入计数不为 0;同一个人对两个不同对象各够 trait 阈值时铸出**两条** pattern(不许一条吞掉另一条);重放同一批证据三轮,pattern 正文长度与 `[证据]` 尾巴个数都不变;只做了原地刷新的那一轮报 `refreshed` 而不是 `ok` + `applied: 0`;`occasion` 边在升格 trait 后被标上 `promotedToTrait` 而 `sessionCount` 不变。
|
|
138
|
+
- **为什么不能接在 `relation_completion` 里**:那一相只扫**孤立**实体(`getRelations(e.id).length === 0`),且 `inferRelationType` 三种类型硬编码、末行 `return "related_to"` 兜底——把情感塞进去只会得到一批 `related_to`,图上看不出情感轴。
|
|
139
|
+
|
|
140
|
+
## 分层压缩策略表
|
|
141
|
+
| 记忆层级 | 判定条件 (`last_accessed_at`) | 处理策略 | 存储形态 |
|
|
142
|
+
|:---|:---|:---|:---|
|
|
143
|
+
| **活跃 (Active)** | `< sleepArchiveDays` | 保持原状,正常搜索可见 | 全文索引 + 向量 |
|
|
144
|
+
| **冷 (Cold)** | `>= sleepArchiveDays` 且 `< sleepCompressDays` | `demoteToSummary` 压成摘要,原文进 `_full_content` | 摘要 + `_full_content` 分离 |
|
|
145
|
+
| **睡眠 (Archived)** | `>= sleepCompressDays` | `setArchived` 移出热查询域 | 仅保留元数据与归档标记 |
|
|
146
|
+
|
|
147
|
+
## 调度器与可中断机制
|
|
148
|
+
- **`noteWrite()` 心跳重置**:每次用户写入/读取记忆时触发。必须先 `clearTimeout` 清除旧计时器,再重新设置 Idle Timer,彻底杜绝 Stale Timer 导致的误触发。
|
|
149
|
+
- **`maybeSchedule()` 准入控制**:串行检查 `sleepModeEnabled` → 空闲时长 `>= sleepIdleMinutes` → 距上次运行 `>= 有效最小间隔` → 状态非 `running/disposed`。任一不满足则放弃调度。
|
|
150
|
+
- **有效最小间隔**(#529):`sleepFailureBackoff` 关(默认)时逐字节等于 `sleepMinIntervalHours`;开启时按连续失败次数指数推迟,封顶 `max(sleepMinIntervalHours, 24h)`——不是硬 24h:基数允许配到 168 小时,那时封顶跟着基数走,退避只能放宽间隔、不会把用户设的基数压小。`armNextWindow()` 排下一次闹钟用的是同一个函数——两处口径必须一致,否则退避期闹钟会在基数到点响、被 `shouldRun` 否掉、再重排一次,白转一轮。
|
|
151
|
+
- 退避**不是**按"同一份输入又失败"判的:`runSleep` 里那个 `snapshotHash` 是跑完之后对 `phases` 结果算的摘要哈希,不是输入快照,拿它当判据会把"同样的输入"认成"不同的结果"。改用连续失败计数,与 `autoDream` 的失败退避(#292)同构。
|
|
152
|
+
- **`service.enqueue` 串行队列**:所有 Sleep Run 必须进入全局串行队列执行,与 `autoDream` 严格互斥,避免并发写入冲突与资源争抢。
|
|
153
|
+
- **`AbortController` 可中断**:调度器持有 AbortController 实例。一旦检测到用户活动(`noteWrite` 或显式交互),立即调用 `abort()` 中断当前 `runSleep` 的执行上下文,保证实时性优先。
|
|
154
|
+
|
|
155
|
+
## 数据迁移与存储层变更
|
|
156
|
+
- **幂等迁移**:
|
|
157
|
+
- `memories` 表新增列:`last_accessed_at` (DATETIME)、`_full_content` (TEXT/BLOB)。
|
|
158
|
+
- `dream_runs` 表新增列:`run_type` (VARCHAR, 默认 'auto',用于区分 'sleep')。
|
|
159
|
+
- 迁移脚本需保证幂等(`IF NOT EXISTS` / 检查列是否存在)。
|
|
160
|
+
- **Store 新增方法**:
|
|
161
|
+
- `demoteToSummary(id, summary, opts)`:执行降级并记录 `minRefTimeMs`。
|
|
162
|
+
- `restoreContent(id)`:按需从归档/摘要恢复原文。
|
|
163
|
+
- `touchLastAccess(id)`:更新 `last_accessed_at` 至当前时间。
|
|
164
|
+
- `getUnrecalledSince(days)`:按时间窗口拉取未召回记忆。
|
|
165
|
+
- `listEntities()`:获取实体列表用于关系分析。
|
|
166
|
+
- **Service 扩展**:
|
|
167
|
+
- `enqueue(task)`:串行任务队列入口。
|
|
168
|
+
- `setSleepHook(fn)`:注入 `noteWrite` 钩子。
|
|
169
|
+
- `touchRecalled(id)`:在召回路径中自动更新访问时间。
|
|
170
|
+
|
|
171
|
+
## 与 autoDream 的协作关系
|
|
172
|
+
| 维度 | autoDream (实时) | Sleep Mode (系统级) |
|
|
173
|
+
|:---|:---|:---|
|
|
174
|
+
| **触发时机** | 写入阈值/实时事件 | 系统空闲 + 定时周期 |
|
|
175
|
+
| **执行深度** | 轻量级、局部关联补全 | 深度扫描、全局压缩与模式提炼 |
|
|
176
|
+
| **资源占用** | 低延迟、短时 | 允许较高延迟、长时运行 |
|
|
177
|
+
| **队列关系** | 共享 `service.enqueue` 串行队列 | 共享 `service.enqueue` 串行队列(严格互斥不重叠) |
|
|
178
|
+
| **审计记录** | 共用 `dream_runs`,`run_type='auto'` | 共用 `dream_runs`,`run_type='sleep'` |
|
|
179
|
+
|
|
180
|
+
## 实现要点与避坑(v0.4.1 教训)
|
|
181
|
+
1. **Stale Timer 防护**:`noteWrite()` 中必须严格遵循 `clearTimeout(oldTimer) -> setNewTimer()` 顺序,否则快速连续写入会累积多个 Timer 导致频繁误触发。
|
|
182
|
+
2. **`minRefTimeMs` 防误降级**:调用 `demoteToSummary` 时必须传入快照时刻 `{minRefTimeMs: archiveCut}`。否则在 Sleep Run 执行期间被用户 `touch` 的记忆会因时间窗口漂移被错误降级。
|
|
183
|
+
3. **证据强过滤**:Pattern Discovery 阶段 LLM 返回的 `evidence` 必须与 DB 真实 ID 列表求交集。未通过 `intersect` 校验的证据一律丢弃,严防幻觉伪造关联。
|
|
184
|
+
4. **严格串行化**:所有 Sleep Run **必须**走 `service.enqueue`。禁止直接调用异步函数启动,确保与 `autoDream` 绝对不重叠,避免写入锁冲突。
|
|
185
|
+
5. **Fail-Safe 阶段隔离**:各阶段必须各自独立 `try/catch`。LLM 服务抖动或网络异常时,仅跳过当前阶段(如跳过 Pattern 发现),后续阶段(如 Demotion、Relation、Affective)必须继续执行并记录 Receipt。
|
|
186
|
+
6. **无 LLM 降级路由**:当 `resolveSleepRoute` 返回 `undefined`(如 Provider 未配置或模型不可用)时,系统应自动跳过所有依赖 LLM 的阶段,但 `archival_demotion`(纯规则)仍需照常执行,保障基础压缩能力不丢失。
|
|
187
|
+
|
|
188
|
+
## 测试清单
|
|
189
|
+
- [ ] **调度器**:验证 `sleepIdleMinutes` 触发准确性;验证 `clearTimeout` 防 Stale;验证 `sleepMinIntervalHours` 冷却生效。
|
|
190
|
+
- [ ] **中断机制**:运行中触发 `noteWrite`,验证 `abort()` 立即终止 Run 且状态回滚至安全点。
|
|
191
|
+
- [ ] **阶段隔离**:Mock LLM 500 错误,验证仅对应阶段跳过,Receipt 仍生成且 `run_type='sleep'`。
|
|
192
|
+
- [ ] **降级策略**:构造不同 `last_accessed_at` 的记忆,验证 `minRefTimeMs` 防误降级逻辑及分层归档正确性。
|
|
193
|
+
- [ ] **模式发现**:注入含虚假 ID 的 LLM 响应,验证 `intersect` 过滤生效且创建数 `<= sleepMaxPatternPerRun`。
|
|
194
|
+
- [ ] **情感归因(#619)**:单会话证据判 `trait` → 必须降级为 `occasion` 且 `downgraded` 计数 +1;跨 N 个独立会话才铸 pattern;`related_to` 计数在本相位后不增长;未知实体/伪造 evidence 进 `dropped` 而不是静默消失。
|
|
195
|
+
- [ ] **迁移幂等**:重复执行迁移脚本,验证无报错、无重复列/数据。
|
|
196
|
+
- [ ] **队列互斥**:并发触发 `autoDream` 与 Sleep,验证 `service.enqueue` 串行执行无重叠。
|
|
197
|
+
|
|
198
|
+
## 启用指南(Opt-in)
|
|
199
|
+
1. **配置开启**:在 `config.js` 或环境变量中设置 `sleepModeEnabled: true`。
|
|
200
|
+
2. **资源评估**:建议生产环境配置独立的 `sleepProvider` 与 `sleepModel`,避免与实时推理争抢额度/并发。
|
|
201
|
+
3. **初始运行**:首次启用后,系统将在首次空闲 `>= sleepIdleMinutes` 时触发全量扫描。可通过查看 `dream_runs` 表中 `run_type='sleep'` 的记录监控执行结果。
|
|
202
|
+
4. **调优建议**:初期可设置 `sleepConflictStrictness: 'gentle'`,观察 `conflict_resolution` 决策准确率后再逐步收紧至 `'normal'` 或 `'aggressive'`。
|
|
203
203
|
5. **关闭恢复**:设置 `sleepModeEnabled: false` 后,调度器将在 `dispose()` 阶段清理 Timer 并释放 Hook,已归档数据可通过 `restoreContent` 按需恢复。
|