@modusensus/dsh-mneme 0.7.0 → 0.7.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.
Files changed (145) hide show
  1. package/README.md +415 -149
  2. package/{dsh-mneme/src → lib}/api.js +6 -3
  3. package/{dsh-mneme/lib → lib}/client.js +55 -8
  4. package/{dsh-mneme/src → lib}/config.js +5 -0
  5. package/{dsh-mneme/src → lib}/dream.js +7 -2
  6. package/{dsh-mneme/lib → lib}/index.js +7 -6
  7. package/{dsh-mneme/lib → lib}/inject.js +31 -2
  8. package/{dsh-mneme/src → lib}/service.js +37 -3
  9. package/{dsh-mneme/src → lib}/settings.js +15 -6
  10. package/{dsh-mneme/src → lib}/store.js +5 -0
  11. package/package.json +40 -18
  12. package/{dsh-mneme/lib → src}/api.js +6 -3
  13. package/{dsh-mneme/lib → src}/config.js +5 -0
  14. package/{dsh-mneme/lib → src}/dream.js +7 -2
  15. package/{dsh-mneme/src → src}/index.js +7 -6
  16. package/{dsh-mneme/src → src}/inject.js +31 -2
  17. package/{dsh-mneme/lib → src}/service.js +37 -3
  18. package/{dsh-mneme/lib → src}/settings.js +15 -6
  19. package/{dsh-mneme/lib → src}/store.js +5 -0
  20. package/{dsh-mneme/test → test}/api.test.js +10 -3
  21. package/{dsh-mneme/test → test}/client.test.js +37 -0
  22. package/{dsh-mneme/test → test}/config.test.js +7 -0
  23. package/{dsh-mneme/test → test}/inject.test.js +28 -0
  24. package/{dsh-mneme/test → test}/settings.test.js +6 -2
  25. package/{dsh-mneme/test → test}/tag.test.js +114 -0
  26. package/.github/workflows/test.yml +0 -32
  27. package/CHANGELOG.md +0 -89
  28. package/CONTRIBUTING.md +0 -245
  29. package/SECURITY.md +0 -544
  30. package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
  31. package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
  32. package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
  33. package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
  34. package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
  35. package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
  36. package/dsh-mneme/CHANGELOG.md +0 -286
  37. package/dsh-mneme/LICENSE +0 -21
  38. package/dsh-mneme/README.md +0 -482
  39. package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
  40. package/dsh-mneme/docs/ENTITIES.md +0 -245
  41. package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
  42. package/dsh-mneme/docs/MIGRATION.md +0 -127
  43. package/dsh-mneme/docs/SEMANTIC.md +0 -256
  44. package/dsh-mneme/docs/SLEEP.md +0 -163
  45. package/dsh-mneme/package-lock.json +0 -1936
  46. package/dsh-mneme/package.json +0 -80
  47. package/v0.7-plan-2026-08-21.md +0 -75
  48. package//346/250/252/345/271/205.png +0 -0
  49. /package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +0 -0
  50. /package/{dsh-mneme/lib → lib}/commands.js +0 -0
  51. /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
  52. /package/{dsh-mneme/lib → lib}/dream/decisions.js +0 -0
  53. /package/{dsh-mneme/lib → lib}/dream/sleep.js +0 -0
  54. /package/{dsh-mneme/lib → lib}/dream/tag-extractor.js +0 -0
  55. /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
  56. /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
  57. /package/{dsh-mneme/lib → lib}/heat.js +0 -0
  58. /package/{dsh-mneme/lib → lib}/hot-memory.js +0 -0
  59. /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
  60. /package/{dsh-mneme/lib → lib}/mirror.js +0 -0
  61. /package/{dsh-mneme/lib → lib}/parser/tag.js +0 -0
  62. /package/{dsh-mneme/lib → lib}/parser/wiki-link.js +0 -0
  63. /package/{dsh-mneme/lib → lib}/quality-filter.js +0 -0
  64. /package/{dsh-mneme/lib → lib}/reranker.js +0 -0
  65. /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
  66. /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
  67. /package/{dsh-mneme/lib → lib}/search/tag-boost.js +0 -0
  68. /package/{dsh-mneme/lib → lib}/summarize.js +0 -0
  69. /package/{dsh-mneme/lib → lib}/tools.js +0 -0
  70. /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
  71. /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
  72. /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
  73. /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
  74. /package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +0 -0
  75. /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
  76. /package/{dsh-mneme/scripts → scripts}/sync-lib.js +0 -0
  77. /package/{dsh-mneme/src → src}/commands.js +0 -0
  78. /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
  79. /package/{dsh-mneme/src → src}/dream/decisions.js +0 -0
  80. /package/{dsh-mneme/src → src}/dream/sleep.js +0 -0
  81. /package/{dsh-mneme/src → src}/dream/tag-extractor.js +0 -0
  82. /package/{dsh-mneme/src → src}/embedding.js +0 -0
  83. /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
  84. /package/{dsh-mneme/src → src}/heat.js +0 -0
  85. /package/{dsh-mneme/src → src}/hot-memory.js +0 -0
  86. /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
  87. /package/{dsh-mneme/src → src}/mirror.js +0 -0
  88. /package/{dsh-mneme/src → src}/parser/tag.js +0 -0
  89. /package/{dsh-mneme/src → src}/parser/wiki-link.js +0 -0
  90. /package/{dsh-mneme/src → src}/quality-filter.js +0 -0
  91. /package/{dsh-mneme/src → src}/reranker.js +0 -0
  92. /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
  93. /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
  94. /package/{dsh-mneme/src → src}/search/tag-boost.js +0 -0
  95. /package/{dsh-mneme/src → src}/summarize.js +0 -0
  96. /package/{dsh-mneme/src → src}/tools.js +0 -0
  97. /package/{dsh-mneme/src → src}/vector-index.js +0 -0
  98. /package/{dsh-mneme/test → test}/audit.test.js +0 -0
  99. /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
  100. /package/{dsh-mneme/test → test}/boundary-v0625.test.js +0 -0
  101. /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
  102. /package/{dsh-mneme/test → test}/commands.test.js +0 -0
  103. /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
  104. /package/{dsh-mneme/test → test}/directory.test.js +0 -0
  105. /package/{dsh-mneme/test → test}/dream.test.js +0 -0
  106. /package/{dsh-mneme/test → test}/entities.test.js +0 -0
  107. /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
  108. /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
  109. /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
  110. /package/{dsh-mneme/test → test}/graph-api.test.js +0 -0
  111. /package/{dsh-mneme/test → test}/heat.test.js +0 -0
  112. /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
  113. /package/{dsh-mneme/test → test}/hot-memory.test.js +0 -0
  114. /package/{dsh-mneme/test → test}/llm-audit.test.js +0 -0
  115. /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
  116. /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
  117. /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
  118. /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
  119. /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
  120. /package/{dsh-mneme/test → test}/normalize-decisions.test.js +0 -0
  121. /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
  122. /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
  123. /package/{dsh-mneme/test → test}/provenance.test.js +0 -0
  124. /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
  125. /package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -0
  126. /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
  127. /package/{dsh-mneme/test → test}/recall-layer.test.js +0 -0
  128. /package/{dsh-mneme/test → test}/recall-runs.test.js +0 -0
  129. /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
  130. /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
  131. /package/{dsh-mneme/test → test}/reranker.test.js +0 -0
  132. /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
  133. /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
  134. /package/{dsh-mneme/test → test}/service-search.test.js +0 -0
  135. /package/{dsh-mneme/test → test}/service.test.js +0 -0
  136. /package/{dsh-mneme/test → test}/sleep-heat.test.js +0 -0
  137. /package/{dsh-mneme/test → test}/sleep.test.js +0 -0
  138. /package/{dsh-mneme/test → test}/store.test.js +0 -0
  139. /package/{dsh-mneme/test → test}/stress.test.js +0 -0
  140. /package/{dsh-mneme/test → test}/summarize.test.js +0 -0
  141. /package/{dsh-mneme/test → test}/tag-boost.test.js +0 -0
  142. /package/{dsh-mneme/test → test}/tools.test.js +0 -0
  143. /package/{dsh-mneme/test → test}/updated-at-semantics.test.js +0 -0
  144. /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
  145. /package/{dsh-mneme/test → test}/wiki-link.test.js +0 -0
@@ -1,127 +0,0 @@
1
- # dsh-mneme v0.3.x → v0.4.0(系统级睡眠 Sleep Mode)升级说明
2
-
3
- - **日期**:2026-08-17
4
- - **适用范围**:从 v0.3.9(审计加固)升级到 v0.4.0(Sleep Mode)
5
- - **目标**:**零配置迁移、零数据损失**。升级后默认不启用 Sleep(opt-in),原行为完全不变。
6
-
7
- ## 1. 数据兼容(自动迁移,无需手工操作)
8
-
9
- ### 1.1 SQLite 记忆库
10
-
11
- - **memories 表新增两列**(幂等迁移,`PRAGMA table_info` 检查 + `ALTER TABLE ... ADD COLUMN`):
12
- - `last_accessed_at TEXT` — 最后召回时间戳(搜索/注入路径自动 touch)
13
- - `_full_content TEXT` — 冷记忆降级时的原文存档(压缩后内容可在原处恢复)
14
- - **dream_runs 表新增一列**:`run_type TEXT NOT NULL DEFAULT 'auto'` — `auto`(autoDream)/ `sleep`(睡眠周期)审计区分
15
- - 已有数据不受影响;降级/归档只在 `sleepModeEnabled: true` 后按阈值触发
16
-
17
- ### 1.2 镜像与审计
18
-
19
- - Markdown 镜像格式不变;`demoteToSummary`/`restoreContent` 走正常写钩子(镜像重渲染),`touchLastAccess` 是读戳(不触发写钩子,避免脏镜像)
20
- - 既有 `dream_runs` 审计记录保留;睡眠周期新增 `run_type='sleep'` 记录,与 autoDream 共用审计表
21
-
22
- ## 2. 配置变更(全部 opt-in)
23
-
24
- ### 2.1 新增配置项
25
-
26
- | 键 | 默认值 | 说明 |
27
- |----|--------|------|
28
- | `sleepModeEnabled` | `false` | 总开关,开启后才激活睡眠调度器 |
29
- | `sleepIdleMinutes` | `5` | 连续空闲多少分钟触发睡眠周期 |
30
- | `sleepMinIntervalHours` | `8` | 两次睡眠周期的最小间隔(小时) |
31
- | `sleepConflictStrictness` | `'normal'` | 冲突消解严格度:`gentle`(0.92) / `normal`(0.85) / `aggressive`(0.75) |
32
- | `sleepArchiveDays` | `30` | 多少天未召回 → 压成摘要(`_full_content` 存档) |
33
- | `sleepCompressDays` | `90` | 多少天未召回 → 完全归档 |
34
- | `sleepPatternMinMemories` | `100` | 模式发现扫描的最近记忆条数 |
35
- | `sleepPatternLookbackDays` | `30` | 模式发现回溯天数 |
36
- | `sleepMaxPatternPerRun` | `3` | 单次睡眠周期最多产出的模式数 |
37
- | `sleepProvider` / `sleepModel` | `''` | 睡眠专用 LLM 路由(留空复用默认) |
38
-
39
- ### 2.2 行为变更说明
40
-
41
- - **零配置升级**:不设任何 sleep 配置 → 与 v0.3.9 行为完全一致,无新增后台任务
42
- - **首次启用**:设 `sleepModeEnabled: true` 后,系统空闲 `sleepIdleMinutes` 分钟触发首次深度维护
43
- - **推荐起步**:`sleepConflictStrictness: 'gentle'` 观察裁决质量后再收紧
44
-
45
- ---
46
-
47
- # dsh-mneme v0.1 → v0.2(语义增强)升级说明
48
-
49
- - **日期**:2026-08-15
50
- - **适用范围**:从 npm `@modusensus/dsh-mneme` v0.1.x(LIKE 搜索 + 可选 OpenAI 兼容向量)升级到 v0.2(本地语义引擎)
51
- - **目标**:**零配置迁移、零数据损失**。升级后不设置任何新配置项,行为与 v0.1 完全一致;想要本地语义能力只需新增几行配置。
52
-
53
- ## 1. 数据兼容(自动迁移,无需手工操作)
54
-
55
- ### 1.1 SQLite 记忆库
56
-
57
- - **不重建、不导出**:沿用同一 `~/.dsh/memory/memory.db`,现有 `memories` 表直接复用
58
- - **schema 迁移幂等**:v0.1 已通过 `PRAGMA table_info` 检查 + `ALTER TABLE ... ADD COLUMN` 幂等补齐 `embedding` / `archived` 等列;v0.2 沿用同一机制,重复启动/旧版本回退都不会重复加列或破坏数据
59
- - **现有向量兼容**:`memories.embedding` 存 JSON 向量(TEXT 列),v0.2 直接读取,已嵌入的记忆**无需重新嵌入**
60
- - **用户设置保留**:v0.1 在 `user_settings` 表配置的向量端点(baseUrl/apiKey/model)保留,作为降级路径继续可用
61
-
62
- ### 1.2 Markdown 镜像
63
-
64
- - 五个 `.md` 镜像文件(`preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md`)格式不变,双向同步、人工优先的规则不变
65
- - 升级后首次启动仍执行"读取人工编辑 → 合并回库",无竞态(先全量读取再统一合并)
66
-
67
- ### 1.3 dream_runs 审计表
68
-
69
- - 既有审计记录保留;新格式与 v0.1 兼容(snapshot digest + 决策清单 + receipt),无需迁移
70
-
71
- ## 2. 配置变更
72
-
73
- ### 2.1 新增配置项
74
-
75
- | 键 | 默认值 | 说明 |
76
- |----|--------|------|
77
- | `embedProvider` | `openai` | 嵌入后端:`local`(ONNX)\| `ollama` \| `openai` |
78
- | `embedModel` | 空 | 模型名:local=HF 模型 id(`Xenova/bge-small-zh-v1.5`),ollama=Ollama 模型,openai=API 模型 |
79
- | `embedDimension` | 空 | 向量维度;不填则按后端推断(local 默认 512,ollama/openai 首次响应推断) |
80
- | `embedDevice` | `cpu` | 仅 local 生效:`cpu` \| `wasm` \| `gpu` |
81
- | `embedBatchSize` | `8` | 分批嵌入条数(local/openai 生效) |
82
- | `embedCacheDir` | `""` | 仅 local 生效:模型缓存目录,空=HF 默认缓存 |
83
- | `embedBaseUrl` | `""` | ollama 默认 `http://localhost:11434`;openai 必填 |
84
- | `embedApiKey` | `""` | 仅 openai 生效 |
85
- | `rerankEnabled` | `false` | 是否启用 Rerank 精排(Phase 2) |
86
- | `rerankModel` | `Xenova/bge-reranker-base` | Rerank 模型 |
87
-
88
- ### 2.2 默认值 = 保持 v0.1 行为
89
-
90
- - `embedProvider` 默认 `openai`,且 `embedBaseUrl` / `embedApiKey` / `embedModel` 为空时,**行为与 v0.1 完全一致**:使用 `user_settings` 表里通过 Web/API 配置的 `vector-config`(若配置过),否则 LIKE 关键词搜索
91
- - 也就是说:**什么都不改,升级后一切照旧**;想让语义搜索离线化,把 `embedProvider` 改成 `local` 或 `ollama` 即可
92
-
93
- ### 2.3 行为变化
94
-
95
- | 场景 | v0.1 | v0.2 |
96
- |------|------|------|
97
- | 未配置任何向量 | LIKE 子串搜索 | LIKE 子串搜索(不变) |
98
- | 配置 OpenAI 兼容端点 | 调 `/embeddings`,失败返回 null 静默 | 同,但 `OpenAIEmbedder` 批量嵌入、抛错由降级链处理 |
99
- | 本地模型 | 不支持 | `embedProvider: local` / `ollama` |
100
- | 向量初筛结果 | 直接返回 | 可经 `rerankEnabled` 精排后返回 |
101
- | 搜索模式 | `auto` / `keyword` / `vector` | 不变,`auto` 新增混合召回 |
102
-
103
- ## 3. 向后兼容保证
104
-
105
- 1. **数据兼容**:SQLite、Markdown 镜像、审计表全部沿用,升级与回退都不丢数据
106
- 2. **配置兼容**:v0.1 的全部配置键(`memoryDir`、`autoInject`、`autoDream` 等)不变;新增键全部带默认值
107
- 3. **API 兼容**:`memory_*` 工具、Web 面板路由、`vector-config` 端点不变;`memory_search` 返回结构不变(额外多出可选 `score`)
108
- 4. **降级兼容**:本地 Embedder 加载失败 / 下载失败 / 推理异常,自动降级到 Ollama → OpenAI → LIKE 关键词,**任何失败都不阻断记忆读写**
109
- 5. **索引一致**:`modelHash` 与向量维度不匹配时拒绝混算,提示重建索引(不静默产生错误结果)
110
- 6. **回滚安全**:卸载 v0.2 装回 v0.1.x,库文件仍可正常读写(v0.1 能识别 `embedding` 列)
111
-
112
- ## 4. 升级步骤
113
-
114
- ```bash
115
- # 1. 升级插件
116
- dsh plugin --profile web update @modusensus/dsh-mneme
117
-
118
- # 2. (可选)启用本地语义:编辑 ~/.dsh/profiles/web/cordis.patch.yml
119
- # - id: dsh-mneme
120
- # config:
121
- # embedProvider: local
122
- # embedModel: Xenova/bge-small-zh-v1.5
123
-
124
- # 3. 重启并验证
125
- dsh web
126
- # 首次本地使用自动下载模型;确认日志出现 "local embedder ready"
127
- ```
@@ -1,256 +0,0 @@
1
- # dsh-mneme 语义增强架构 — 设计文档
2
-
3
- - **日期**:2026-08-15
4
- - **状态**:进行中(分支 `feat/local-semantic`)
5
- - **范围**:为 dsh-mneme 增加"完全离线的语义记忆引擎",覆盖本地 Embedding、Rerank 精排、autoDream 向量聚类三个阶段
6
- - **相关文档**:[本地模型部署指南](LOCAL_MODEL.md) · [从 v0.1 升级说明](MIGRATION.md)
7
-
8
- ## 1. 背景与目标
9
-
10
- dsh-mneme v0.1 已具备**可选的向量搜索**:通过 `/api/dsh-mneme/vector-config` 配置 OpenAI 兼容 `/embeddings` 端点,命中"字面不同但语义相近"的记忆;未配置或失败时降级为 LIKE 子串搜索。
11
-
12
- v0.2 的目标是把这条**可选、依赖外部 API** 的向量链路升级为**默认可用、完全离线**的语义记忆引擎:
13
-
14
- 1. **本地 Embedding**:ONNX(`Xenova/bge-small-zh-v1.5`)或 Ollama,记忆向量不再依赖外网 API
15
- 2. **Rerank 精排**:向量初筛后交叉编码精排,把 Top-K 准确率再抬一档
16
- 3. **autoDream 语义增强**:对记忆向量做聚类,让自动巩固(去重/合并/冲突裁决)从"主题相近"升级为"语义相近"
17
-
18
- ## 2. 总体架构
19
-
20
- ```
21
- ┌──────────────────────────────────────────────────────────────┐
22
- │ 语义引擎(v0.2,完全离线可选) │
23
- ├──────────────────────────────────────────────────────────────┤
24
- │ ① Embedder 层(三选一,统一接口,可自动降级) │
25
- │ LocalEmbedder(ONNX) │ OllamaEmbedder │ OpenAIEmbedder │
26
- │ init() / embed(texts) / embedSingle() / dimension / hash │
27
- ├──────────────────────────────────────────────────────────────┤
28
- │ ② VectorIndex 层 │
29
- │ SQLite memories.embedding 列(JSON 向量) │
30
- │ + 余弦相似度检索(score 0..1)+ 缺失向量增量补建 reindex │
31
- ├──────────────────────────────────────────────────────────────┤
32
- │ ③ Rerank 层(可选) │
33
- │ LocalReranker(Xenova/bge-reranker-base,交叉编码) │
34
- │ 召回候选 → 精排 → Top-K │
35
- ├──────────────────────────────────────────────────────────────┤
36
- │ ④ autoDream 聚类(可选) │
37
- │ clusterMemories(向量 K-Means) / findPotentialConflicts │
38
- │ 语义分组 + 疑似矛盾检测 → 巩固决策更精准 │
39
- └──────────────────────────────────────────────────────────────┘
40
- ```
41
-
42
- - **① Embedder**:`src/local-embedder.js`,三后端统一接口,`createEmbedderByProvider(provider, opts)` 按名创建
43
- - **② VectorIndex**:复用 v0.1 的 `memories.embedding` 列与 `store.js` 的余弦检索,无新存储层
44
- - **③ Rerank**:`src/reranker.js`(Phase 2),可选开启
45
- - **④ 聚类**:`src/dream/clustering.js`(Phase 3),autoDream 调度器集成
46
-
47
- ### 目录结构(新增/变化部分)
48
-
49
- ```
50
- src/
51
- ├── local-embedder.js # [新增] Local/Ollama/OpenAI 三 Embedder + createEmbedderByProvider
52
- ├── reranker.js # [新增·Phase2] LocalReranker 交叉编码精排
53
- ├── dream/
54
- │ └── clustering.js # [新增·Phase3] clusterMemories / findPotentialConflicts / kMeans
55
- └── embedding.js # [保留] v0.1 OpenAI 兼容客户端(作为降级路径之一复用)
56
- scripts/
57
- ├── benchmark-embed.js # [新增] Embedding 吞吐量基准
58
- └── benchmark-rerank.js # [新增] Rerank 延迟基准
59
- docs/
60
- ├── SEMANTIC.md # 本文档
61
- ├── LOCAL_MODEL.md # 本地模型部署指南
62
- └── MIGRATION.md # 从 v0.1 升级说明
63
- ```
64
-
65
- ## 3. 搜索流水线
66
-
67
- `memory_search` 在 v0.2 的完整链路(对应 `src/tools.js` 的 `mode: auto | keyword | vector`):
68
-
69
- ```
70
- query
71
-
72
- ┌───────────┴───────────┐
73
- ▼ ▼
74
- keyword 召回 vector 召回
75
- (LIKE 子串 title/ (Embedder.embed(query)
76
- content/tags) → 余弦相似度 top-N
77
- → 缺失向量自动补建)
78
- │ │
79
- └───── 混合融合 ─────────┘
80
- (auto: 关键词命中优先 + 向量补足;分数归一化合并)
81
-
82
-
83
- [Rerank 开启?]
84
- ├─ 是 → LocalReranker.rerank(query, candidates, topK)
85
- │ 交叉编码精排,取 topK
86
- └─ 否 → 按 importance/score 直接截取 topK
87
-
88
-
89
- memory_search 结果
90
- ```
91
-
92
- - **召回阶段(候选)**:混合召回,宁多勿漏,候选集通常取向量 top-N(默认远超 Top-K,供精排筛选)
93
- - **精排阶段(Rerank)**:对候选逐条打分(query 与候选的交互编码),取 Top-K
94
- - **结果阶段**:返回条目 + `score`(召回分/精排分)+ `source` 时间戳,与 v0.1 返回结构保持一致
95
-
96
- ## 4. 模块接口
97
-
98
- ### 4.1 Embedder 统一接口(`src/local-embedder.js`)
99
-
100
- 三个后端共享同一接口,方法失败**抛错**,由上层编排降级链(与 v0.1 `embedding.js` 的"失败返回 null"不同)。
101
-
102
- | 成员 | 说明 |
103
- |------|------|
104
- | `init()` | 加载模型 / 探测服务,失败抛错 |
105
- | `embed(texts: string[])` | 批量嵌入,返回 `number[][]`(本地模型 mean pooling + L2 归一化) |
106
- | `embedSingle(text)` | 单条嵌入 → `number[]` |
107
- | `dimension` | 向量维度(getter) |
108
- | `modelHash` | 模型指纹(`<model>#<hash>`),用于向量索引一致性校验,模型换名/换维度时提示重建索引 |
109
- | `dispose()` | 释放模型资源 |
110
-
111
- **LocalEmbedder(ONNX)**
112
-
113
- ```js
114
- new LocalEmbedder({
115
- model: "Xenova/bge-small-zh-v1.5", // 默认;中文优化,全离线
116
- dimension: 512, // 默认与模型匹配
117
- device: "cpu", // cpu | wasm | gpu(onnxruntime 后端)
118
- batchSize: 8, // 分批嵌入,控制峰值内存
119
- cacheDir: "", // 自定义模型缓存目录,空则用 HF 默认缓存
120
- useDtype: "q8", // 量化精度 q8/fp32/fp16
121
- logger: null
122
- })
123
- ```
124
-
125
- **OllamaEmbedder**
126
-
127
- ```js
128
- new OllamaEmbedder({
129
- baseUrl: "http://localhost:11434", // 默认
130
- model: "nomic-embed-text", // 默认;维度从首次响应自动推断
131
- logger: null
132
- })
133
- ```
134
-
135
- **OpenAIEmbedder(兼容 v0.1 行为)**
136
-
137
- ```js
138
- new OpenAIEmbedder({
139
- baseUrl: "", // 例如 https://api.openai.com/v1,也支持 SiliconFlow/智谱/Ollama 代理
140
- apiKey: "",
141
- model: "",
142
- timeoutMs: 15000
143
- })
144
- ```
145
-
146
- **工厂**
147
-
148
- ```js
149
- import { createEmbedderByProvider } from "../src/local-embedder.js";
150
- const embedder = createEmbedderByProvider("local", { device: "cpu" }); // local | ollama | openai
151
- await embedder.init();
152
- const vecs = await embedder.embed(["你好", "记忆库"]);
153
- ```
154
-
155
- ### 4.2 Reranker(`src/reranker.js`,Phase 2)
156
-
157
- | 成员 | 说明 |
158
- |------|------|
159
- | `constructor({ model, device, batchSize, cacheDir, logger })` | `model` 默认 `Xenova/bge-reranker-base` |
160
- | `init()` | 加载交叉编码模型 |
161
- | `rerank(query, candidates, topK?)` | 对候选逐条打分,返回按相关性降序的候选(附 `score`),`topK` 缺省返回全量排序 |
162
-
163
- 示例:
164
-
165
- ```js
166
- import { LocalReranker } from "../src/reranker.js";
167
- const reranker = new LocalReranker({ model: "Xenova/bge-reranker-base" });
168
- await reranker.init();
169
- const top = await reranker.rerank("怎么部署本地模型", candidates, 5);
170
- ```
171
-
172
- ### 4.3 autoDream 聚类(`src/dream/clustering.js`,Phase 3,已实现)
173
-
174
- | 成员 | 说明 |
175
- |------|------|
176
- | `cosineSimilarity(a, b)` | 余弦相似度,零向量/长度不齐返回 0,数值稳定 |
177
- | `kMeans(vectors, k, opts)` | K-Means(k-means++ 播种 + 空簇修复),返回每簇索引数组 |
178
- | `clusterMemories(memories, vectors, k)` | 按向量把记忆分组,返回簇(保留原对象引用) |
179
- | `findPotentialConflicts(memories, vectors, threshold = 0.85)` | 同类型、高相似(可能矛盾)的记忆对 |
180
-
181
- autoDream 调度器在触发巩固时:对候选记忆做 `clusterMemories` 分组 → 每组送 LLM 决策(merge/archive/conflict),并用 `findPotentialConflicts` 预先标出疑似矛盾对,减少漏判。
182
-
183
- ## 5. 降级策略
184
-
185
- ```
186
- 首选 回退 1 回退 2 兜底
187
- LocalEmbedder(ONNX) ──失败──► OllamaEmbedder ──失败──► OpenAIEmbedder ──失败/未配置──► 关键词 LIKE
188
- ```
189
-
190
- - Embedder 方法**抛错**,由上层 `createEmbedderByProvider` 编排的降级链逐级捕获切换;全链路失败则回退到 v0.1 的 LIKE 子串关键词搜索(本库无 FTS5,关键词召回即 LIKE 子串扫描)
191
- - **原则**:语义链路任何一环失败都**静默降级、不阻断记忆读写**——写入照常落库,只是当次不补向量
192
- - **降级是每请求动态的**:同一会话内 ONNX 偶发失败,下一次请求自动重试首选路径,不固化到降级档
193
- - **索引一致性**:Embedder 的 `modelHash` 变化(换模型/换维度)时,旧向量与新向量不可混算余弦,需触发「重建索引」(复用 v0.1 的 `reindexMissing` 补建缺失向量)
194
-
195
- ## 6. 模型清单
196
-
197
- | 用途 | 模型 | 后端 | 维度 | 说明 |
198
- |------|------|------|------|------|
199
- | Embedding | `Xenova/bge-small-zh-v1.5` | ONNX(默认) | 512 | 中文优化,全离线,体积小 |
200
- | Embedding | `bge-m3` | Ollama | 1024 | 多语言(中/英/日等),质量高 |
201
- | Embedding | `nomic-embed-text` | Ollama | 768 | 英文为主,通用 |
202
- | Embedding | `text-embedding-3-small` | OpenAI 兼容 | 1536 | v0.1 云端默认,升级后仍可用 |
203
- | Rerank | `Xenova/bge-reranker-base` | ONNX(Phase 2) | — | 交叉编码,首候选精排 |
204
- | Rerank | `bge-reranker-v2-m3` | 云/本地 | — | 更强精排,多语言 |
205
-
206
- > 维度随模型而定:换用不同维度的模型后,已存向量与模型指纹不匹配,需重建索引。
207
-
208
- ## 7. 配置新增项
209
-
210
- 见 [MIGRATION.md](MIGRATION.md) 完整表格。核心:`embedProvider`(`local` / `ollama` / `openai`,默认 `openai` 保持 v0.1 行为)、`embedModel`、`embedDimension`、`embedDevice`、`embedBatchSize`、`embedCacheDir`、`rerankEnabled`、`rerankModel`。
211
-
212
- ## 8. 测试与基准
213
-
214
- - 单元:`src/local-embedder.js` 三后端接口一致性、`clustering.js` 聚类正确性(node:test)
215
- - 降级链:mock 各后端抛错,断言逐级回退到 LIKE
216
- - 基准:`scripts/benchmark-embed.js`(嵌入延迟/吞吐)、`scripts/benchmark-rerank.js`(Rerank 延迟 vs 候选规模)
217
-
218
- ## 9. 反思更新(v0.2.1 `update` 决策 + 失败追踪)
219
-
220
- ### 9.1 `update` 决策类型
221
-
222
- autoDream 整理时,LLM 可输出第 5 种决策 `update`,直接修正单条记忆的过时/错误内容:
223
-
224
- - **约束**:`ids` 只能含一个 id;必须产生实际字段变化;不能更新 `summary`;新建 < 24h 的记忆不可 update
225
- - **频率限制**:每次 autoDream 最多 `reflectionUpdateMaxPerRun`(默认 2)个 update,超限整单拒绝
226
- - **幂等**:字段已与目标一致时跳过(可重放无副作用)
227
- - **审计**:`dream_runs` 记录 update 的 `_before` 快照,可回溯变更前内容
228
- - **向量同步**:update 后删除旧向量、按新内容重嵌入,保持索引一致
229
-
230
- ### 9.2 失败追踪(failure_memories)
231
-
232
- `failure_memories` 表记录记忆纠正/失败事件,为后续反思进化积累数据:
233
-
234
- ```sql
235
- CREATE TABLE failure_memories (
236
- id TEXT PRIMARY KEY,
237
- query TEXT, -- 用户原始查询/意图(可选)
238
- expected TEXT, -- 期望结果
239
- actual TEXT, -- 实际结果
240
- failure_type TEXT, -- outdated / miss / wrong / user_correction
241
- memory_id TEXT, -- 关联的记忆 id
242
- created_at TEXT NOT NULL
243
- );
244
- ```
245
-
246
- - **触发**:用户调用 `memory_update` 且内容变化时,若 `reflectionFailureTracking` 开启则记录 `user_correction` 行(actual=旧值、expected=新值)
247
- - **查询**:`store.listFailures`(过滤/分页)、`store.getFailureStats`(按类型计数)
248
-
249
- ### 9.3 配置项
250
-
251
- ```javascript
252
- reflectionUpdateEnabled: true, // update 决策总开关
253
- reflectionFailureTracking: true, // 失败追踪总开关
254
- reflectionUpdateMaxPerRun: 2, // 每次整理最多 update 数
255
- reflectionUpdateMinAgeHours: 24 // 新建记忆保护期(小时)
256
- ```
@@ -1,163 +0,0 @@
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
- end
29
-
30
- P4 -->|独立 try/catch 隔离| Receipt[buildReceipt]
31
- Receipt -->|saveDreamRun run_type='sleep'| Audit[(dream_runs)]
32
-
33
- User -->|活动触发| Abort[AbortController.abort]
34
- Abort -->|中断当前 Run| Scheduler
35
-
36
- AutoDream[autoDream 实时轻量触发] -.共享队列不重叠.-> Queue
37
- ```
38
-
39
- ## 配置项说明
40
- | 配置项 | 默认值 | 说明 |
41
- |:---|:---|:---|
42
- | `sleepModeEnabled` | `false` | 全局开关(Opt-in),开启后激活调度器 |
43
- | `sleepIdleMinutes` | `5` | 连续无操作触发睡眠的阈值(分钟) |
44
- | `sleepMinIntervalHours` | `8` | 两次 Sleep Run 的最小时间间隔(小时) |
45
- | `sleepConflictStrictness` | `'normal'` | 冲突消解严格度:`'gentle'`(0.92) / `'normal'`(0.85) / `'aggressive'`(0.75) |
46
- | `sleepArchiveDays` | `30` | 进入冷存储/摘要降级的天数阈值 |
47
- | `sleepCompressDays` | `90` | 进入完全归档(Archive)的天数阈值 |
48
- | `sleepPatternMinMemories` | `100` | 模式发现阶段扫描的最近记忆条数下限 |
49
- | `sleepPatternLookbackDays` | `30` | 模式发现回溯的时间窗口(天) |
50
- | `sleepMaxPatternPerRun` | `3` | 单次运行最多创建的模式数量上限 |
51
- | `sleepProvider` | `''` | 睡眠模式专用 LLM Provider(显式优先,留空回退 `dreamProvider` → agent 默认) |
52
- | `sleepModel` | `''` | 睡眠模式专用 LLM Model(同上回退顺序) |
53
- | `sleepReasoningEffort` | `'none'` | 睡眠 LLM 推理强度透传:`low` / `medium` / `high` / `none`。`none`(默认)= 不传该字段,使用模型默认;思考型模型预算被推理耗尽时可设 `low`(与 `dreamReasoningEffort` 语义一致) |
54
-
55
- ## 四阶段详解
56
- 每个阶段独立包裹 `try/catch`,任一阶段失败仅跳过该阶段,不阻塞后续流程。
57
-
58
- ### 1. `conflict_resolution`(冲突消解)
59
- - **目标**:清理向量空间中的重复、矛盾或过时记忆。
60
- - **输入**:全量/增量记忆向量索引、`sleepConflictStrictness` 阈值。
61
- - **算法**:
62
- 1. 向量检索 `findPotentialConflicts` 获取高相似度对。
63
- 2. LLM 仲裁:基于严格度阈值生成 `keep/merge/discard` 决策。
64
- 3. `validateDecisions`:严格校验决策合法性,缺省策略为 `keep`。
65
- 4. `applyDecisions`:批量执行状态变更。
66
- - **输出**:冲突消解报告(决策明细)。
67
- - **验收**:无合法记忆被误删;仲裁结果符合配置的严格度阈值;决策可回滚。
68
-
69
- ### 2. `archival_demotion`(归档降级)
70
- - **目标**:按时间衰减对记忆进行分层压缩,释放热存储压力。
71
- - **输入**:`last_accessed_at` 排序的记忆列表、`sleepArchiveDays`、`sleepCompressDays`。
72
- - **算法**(纯规则、无 LLM,确定性执行):
73
- - `>= sleepCompressDays`:调用 `setArchived` 完全归档。
74
- - `>= sleepArchiveDays && < sleepCompressDays`:调用 `demoteToSummary(m.id, 摘要, {minRefTimeMs: archiveCut})` 转为摘要态——原内容存入 `_full_content`,摘要由前 120 字截断生成(LLM 未配置时的 fallback)。
75
- - `< sleepArchiveDays`:保持原状不动。
76
- - **minRefTimeMs 关键点**:传入快照时刻 `archiveCut`,`demoteToSummary` 在事务内复查 `last_accessed_at`——快照后被召回 touch 的记忆视为"重新活跃",跳过不降级。
77
- - **输出**:降级操作清单与状态快照。
78
- - **验收**:严格遵循分层阈值;`minRefTimeMs` 机制生效,防止快照后 `touch` 导致的误降级;摘要保留 `_full_content` 可无损恢复。
79
-
80
- ### 3. `pattern_discovery`(模式发现)
81
- - **目标**:从近期记忆中提炼可复用的隐性规律或知识模式。
82
- - **输入**:最近 `sleepPatternLookbackDays` 内的 `sleepPatternMinMemories` 条记忆。
83
- - **算法**:
84
- 1. LLM 扫描分析,提取候选模式。
85
- 2. **证据校验**:强制 `evidence` 列表与数据库真实 ID 求交集(`intersect`),过滤伪造/幻觉引用。
86
- 3. 输出 `type=pattern` 创建指令,受 `sleepMaxPatternPerRun` 限制。
87
- - **输出**:新创建的 Pattern 实体及关联证据链。
88
- - **验收**:无孤立证据;Pattern 创建数不超上限;证据 100% 可溯源。
89
-
90
- ### 4. `relation_completion`(关系补全)
91
- - **目标**:修复知识图谱中的断链与孤立节点。
92
- - **输入**:`listEntities` 获取的实体列表、关系图谱。
93
- - **算法**:
94
- 1. 筛选 `getRelations` 返回为空的孤立实体。
95
- 2. 基于上下文推断关系:
96
- - 语义/实体共现 → `related_to`
97
- - 同属项目/模块 → `part_of`
98
- - 技术栈/流程依赖 → `depends_on`
99
- 3. 批量写入关系边。
100
- - **输出**:新增关系边列表。
101
- - **验收**:关系推断符合领域常识;不引入循环依赖;图谱连通性提升。
102
-
103
- ## 分层压缩策略表
104
- | 记忆层级 | 判定条件 (`last_accessed_at`) | 处理策略 | 存储形态 |
105
- |:---|:---|:---|:---|
106
- | **活跃 (Active)** | `< sleepArchiveDays` | 保持原状,正常搜索可见 | 全文索引 + 向量 |
107
- | **冷 (Cold)** | `>= sleepArchiveDays` 且 `< sleepCompressDays` | `demoteToSummary` 压成摘要,原文进 `_full_content` | 摘要 + `_full_content` 分离 |
108
- | **睡眠 (Archived)** | `>= sleepCompressDays` | `setArchived` 移出热查询域 | 仅保留元数据与归档标记 |
109
-
110
- ## 调度器与可中断机制
111
- - **`noteWrite()` 心跳重置**:每次用户写入/读取记忆时触发。必须先 `clearTimeout` 清除旧计时器,再重新设置 Idle Timer,彻底杜绝 Stale Timer 导致的误触发。
112
- - **`maybeSchedule()` 准入控制**:串行检查 `sleepModeEnabled` → 空闲时长 `>= sleepIdleMinutes` → 距上次运行 `>= sleepMinIntervalHours` → 状态非 `running/disposed`。任一不满足则放弃调度。
113
- - **`service.enqueue` 串行队列**:所有 Sleep Run 必须进入全局串行队列执行,与 `autoDream` 严格互斥,避免并发写入冲突与资源争抢。
114
- - **`AbortController` 可中断**:调度器持有 AbortController 实例。一旦检测到用户活动(`noteWrite` 或显式交互),立即调用 `abort()` 中断当前 `runSleep` 的执行上下文,保证实时性优先。
115
-
116
- ## 数据迁移与存储层变更
117
- - **幂等迁移**:
118
- - `memories` 表新增列:`last_accessed_at` (DATETIME)、`_full_content` (TEXT/BLOB)。
119
- - `dream_runs` 表新增列:`run_type` (VARCHAR, 默认 'auto',用于区分 'sleep')。
120
- - 迁移脚本需保证幂等(`IF NOT EXISTS` / 检查列是否存在)。
121
- - **Store 新增方法**:
122
- - `demoteToSummary(id, summary, opts)`:执行降级并记录 `minRefTimeMs`。
123
- - `restoreContent(id)`:按需从归档/摘要恢复原文。
124
- - `touchLastAccess(id)`:更新 `last_accessed_at` 至当前时间。
125
- - `getUnrecalledSince(days)`:按时间窗口拉取未召回记忆。
126
- - `listEntities()`:获取实体列表用于关系分析。
127
- - **Service 扩展**:
128
- - `enqueue(task)`:串行任务队列入口。
129
- - `setSleepHook(fn)`:注入 `noteWrite` 钩子。
130
- - `touchRecalled(id)`:在召回路径中自动更新访问时间。
131
-
132
- ## 与 autoDream 的协作关系
133
- | 维度 | autoDream (实时) | Sleep Mode (系统级) |
134
- |:---|:---|:---|
135
- | **触发时机** | 写入阈值/实时事件 | 系统空闲 + 定时周期 |
136
- | **执行深度** | 轻量级、局部关联补全 | 深度扫描、全局压缩与模式提炼 |
137
- | **资源占用** | 低延迟、短时 | 允许较高延迟、长时运行 |
138
- | **队列关系** | 共享 `service.enqueue` 串行队列 | 共享 `service.enqueue` 串行队列(严格互斥不重叠) |
139
- | **审计记录** | 共用 `dream_runs`,`run_type='auto'` | 共用 `dream_runs`,`run_type='sleep'` |
140
-
141
- ## 实现要点与避坑(v0.4.1 教训)
142
- 1. **Stale Timer 防护**:`noteWrite()` 中必须严格遵循 `clearTimeout(oldTimer) -> setNewTimer()` 顺序,否则快速连续写入会累积多个 Timer 导致频繁误触发。
143
- 2. **`minRefTimeMs` 防误降级**:调用 `demoteToSummary` 时必须传入快照时刻 `{minRefTimeMs: archiveCut}`。否则在 Sleep Run 执行期间被用户 `touch` 的记忆会因时间窗口漂移被错误降级。
144
- 3. **证据强过滤**:Pattern Discovery 阶段 LLM 返回的 `evidence` 必须与 DB 真实 ID 列表求交集。未通过 `intersect` 校验的证据一律丢弃,严防幻觉伪造关联。
145
- 4. **严格串行化**:所有 Sleep Run **必须**走 `service.enqueue`。禁止直接调用异步函数启动,确保与 `autoDream` 绝对不重叠,避免写入锁冲突。
146
- 5. **Fail-Safe 阶段隔离**:四阶段必须各自独立 `try/catch`。LLM 服务抖动或网络异常时,仅跳过当前阶段(如跳过 Pattern 发现),后续阶段(如 Demotion、Relation)必须继续执行并记录 Receipt。
147
- 6. **无 LLM 降级路由**:当 `resolveSleepRoute` 返回 `undefined`(如 Provider 未配置或模型不可用)时,系统应自动跳过所有依赖 LLM 的阶段,但 `archival_demotion`(纯规则)仍需照常执行,保障基础压缩能力不丢失。
148
-
149
- ## 测试清单
150
- - [ ] **调度器**:验证 `sleepIdleMinutes` 触发准确性;验证 `clearTimeout` 防 Stale;验证 `sleepMinIntervalHours` 冷却生效。
151
- - [ ] **中断机制**:运行中触发 `noteWrite`,验证 `abort()` 立即终止 Run 且状态回滚至安全点。
152
- - [ ] **阶段隔离**:Mock LLM 500 错误,验证仅对应阶段跳过,Receipt 仍生成且 `run_type='sleep'`。
153
- - [ ] **降级策略**:构造不同 `last_accessed_at` 的记忆,验证 `minRefTimeMs` 防误降级逻辑及分层归档正确性。
154
- - [ ] **模式发现**:注入含虚假 ID 的 LLM 响应,验证 `intersect` 过滤生效且创建数 `<= sleepMaxPatternPerRun`。
155
- - [ ] **迁移幂等**:重复执行迁移脚本,验证无报错、无重复列/数据。
156
- - [ ] **队列互斥**:并发触发 `autoDream` 与 Sleep,验证 `service.enqueue` 串行执行无重叠。
157
-
158
- ## 启用指南(Opt-in)
159
- 1. **配置开启**:在 `config.js` 或环境变量中设置 `sleepModeEnabled: true`。
160
- 2. **资源评估**:建议生产环境配置独立的 `sleepProvider` 与 `sleepModel`,避免与实时推理争抢额度/并发。
161
- 3. **初始运行**:首次启用后,系统将在首次空闲 `>= sleepIdleMinutes` 时触发全量扫描。可通过查看 `dream_runs` 表中 `run_type='sleep'` 的记录监控执行结果。
162
- 4. **调优建议**:初期可设置 `sleepConflictStrictness: 'gentle'`,观察 `conflict_resolution` 决策准确率后再逐步收紧至 `'normal'` 或 `'aggressive'`。
163
- 5. **关闭恢复**:设置 `sleepModeEnabled: false` 后,调度器将在 `dispose()` 阶段清理 Timer 并释放 Hook,已归档数据可通过 `restoreContent` 按需恢复。