@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.
- package/README.md +415 -149
- package/{dsh-mneme/src → lib}/api.js +6 -3
- package/{dsh-mneme/lib → lib}/client.js +55 -8
- package/{dsh-mneme/src → lib}/config.js +5 -0
- package/{dsh-mneme/src → lib}/dream.js +7 -2
- package/{dsh-mneme/lib → lib}/index.js +7 -6
- package/{dsh-mneme/lib → lib}/inject.js +31 -2
- package/{dsh-mneme/src → lib}/service.js +37 -3
- package/{dsh-mneme/src → lib}/settings.js +15 -6
- package/{dsh-mneme/src → lib}/store.js +5 -0
- package/package.json +40 -18
- package/{dsh-mneme/lib → src}/api.js +6 -3
- package/{dsh-mneme/lib → src}/config.js +5 -0
- package/{dsh-mneme/lib → src}/dream.js +7 -2
- package/{dsh-mneme/src → src}/index.js +7 -6
- package/{dsh-mneme/src → src}/inject.js +31 -2
- package/{dsh-mneme/lib → src}/service.js +37 -3
- package/{dsh-mneme/lib → src}/settings.js +15 -6
- package/{dsh-mneme/lib → src}/store.js +5 -0
- package/{dsh-mneme/test → test}/api.test.js +10 -3
- package/{dsh-mneme/test → test}/client.test.js +37 -0
- package/{dsh-mneme/test → test}/config.test.js +7 -0
- package/{dsh-mneme/test → test}/inject.test.js +28 -0
- package/{dsh-mneme/test → test}/settings.test.js +6 -2
- package/{dsh-mneme/test → test}/tag.test.js +114 -0
- package/.github/workflows/test.yml +0 -32
- package/CHANGELOG.md +0 -89
- package/CONTRIBUTING.md +0 -245
- package/SECURITY.md +0 -544
- package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
- package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
- package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
- package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
- package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
- package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
- package/dsh-mneme/CHANGELOG.md +0 -286
- package/dsh-mneme/LICENSE +0 -21
- package/dsh-mneme/README.md +0 -482
- package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
- package/dsh-mneme/docs/ENTITIES.md +0 -245
- package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
- package/dsh-mneme/docs/MIGRATION.md +0 -127
- package/dsh-mneme/docs/SEMANTIC.md +0 -256
- package/dsh-mneme/docs/SLEEP.md +0 -163
- package/dsh-mneme/package-lock.json +0 -1936
- package/dsh-mneme/package.json +0 -80
- package/v0.7-plan-2026-08-21.md +0 -75
- package//346/250/252/345/271/205.png +0 -0
- /package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +0 -0
- /package/{dsh-mneme/lib → lib}/commands.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/decisions.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/sleep.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/tag-extractor.js +0 -0
- /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
- /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
- /package/{dsh-mneme/lib → lib}/heat.js +0 -0
- /package/{dsh-mneme/lib → lib}/hot-memory.js +0 -0
- /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
- /package/{dsh-mneme/lib → lib}/mirror.js +0 -0
- /package/{dsh-mneme/lib → lib}/parser/tag.js +0 -0
- /package/{dsh-mneme/lib → lib}/parser/wiki-link.js +0 -0
- /package/{dsh-mneme/lib → lib}/quality-filter.js +0 -0
- /package/{dsh-mneme/lib → lib}/reranker.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/tag-boost.js +0 -0
- /package/{dsh-mneme/lib → lib}/summarize.js +0 -0
- /package/{dsh-mneme/lib → lib}/tools.js +0 -0
- /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/sync-lib.js +0 -0
- /package/{dsh-mneme/src → src}/commands.js +0 -0
- /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
- /package/{dsh-mneme/src → src}/dream/decisions.js +0 -0
- /package/{dsh-mneme/src → src}/dream/sleep.js +0 -0
- /package/{dsh-mneme/src → src}/dream/tag-extractor.js +0 -0
- /package/{dsh-mneme/src → src}/embedding.js +0 -0
- /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
- /package/{dsh-mneme/src → src}/heat.js +0 -0
- /package/{dsh-mneme/src → src}/hot-memory.js +0 -0
- /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
- /package/{dsh-mneme/src → src}/mirror.js +0 -0
- /package/{dsh-mneme/src → src}/parser/tag.js +0 -0
- /package/{dsh-mneme/src → src}/parser/wiki-link.js +0 -0
- /package/{dsh-mneme/src → src}/quality-filter.js +0 -0
- /package/{dsh-mneme/src → src}/reranker.js +0 -0
- /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
- /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
- /package/{dsh-mneme/src → src}/search/tag-boost.js +0 -0
- /package/{dsh-mneme/src → src}/summarize.js +0 -0
- /package/{dsh-mneme/src → src}/tools.js +0 -0
- /package/{dsh-mneme/src → src}/vector-index.js +0 -0
- /package/{dsh-mneme/test → test}/audit.test.js +0 -0
- /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
- /package/{dsh-mneme/test → test}/boundary-v0625.test.js +0 -0
- /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
- /package/{dsh-mneme/test → test}/commands.test.js +0 -0
- /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
- /package/{dsh-mneme/test → test}/directory.test.js +0 -0
- /package/{dsh-mneme/test → test}/dream.test.js +0 -0
- /package/{dsh-mneme/test → test}/entities.test.js +0 -0
- /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
- /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
- /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
- /package/{dsh-mneme/test → test}/graph-api.test.js +0 -0
- /package/{dsh-mneme/test → test}/heat.test.js +0 -0
- /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
- /package/{dsh-mneme/test → test}/hot-memory.test.js +0 -0
- /package/{dsh-mneme/test → test}/llm-audit.test.js +0 -0
- /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
- /package/{dsh-mneme/test → test}/normalize-decisions.test.js +0 -0
- /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
- /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
- /package/{dsh-mneme/test → test}/provenance.test.js +0 -0
- /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
- /package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-layer.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-runs.test.js +0 -0
- /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
- /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
- /package/{dsh-mneme/test → test}/reranker.test.js +0 -0
- /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
- /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
- /package/{dsh-mneme/test → test}/service-search.test.js +0 -0
- /package/{dsh-mneme/test → test}/service.test.js +0 -0
- /package/{dsh-mneme/test → test}/sleep-heat.test.js +0 -0
- /package/{dsh-mneme/test → test}/sleep.test.js +0 -0
- /package/{dsh-mneme/test → test}/store.test.js +0 -0
- /package/{dsh-mneme/test → test}/stress.test.js +0 -0
- /package/{dsh-mneme/test → test}/summarize.test.js +0 -0
- /package/{dsh-mneme/test → test}/tag-boost.test.js +0 -0
- /package/{dsh-mneme/test → test}/tools.test.js +0 -0
- /package/{dsh-mneme/test → test}/updated-at-semantics.test.js +0 -0
- /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
- /package/{dsh-mneme/test → test}/wiki-link.test.js +0 -0
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
# dsh-mneme:给 DeepSeek Harness 写一个会「做梦」的记忆插件
|
|
2
|
-
|
|
3
|
-
> 一个跨会话记忆插件从 0 到发布 6 个版本的全过程:架构、踩坑、测试与发布流水线。
|
|
4
|
-
|
|
5
|
-
- 日期:2026-08-14
|
|
6
|
-
- 版本线:v0.1.0 → v0.1.6
|
|
7
|
-
- 仓库:github.com/modusensus/dsh-mneme
|
|
8
|
-
- 技术栈:Cordis 4 · Node 24+ · `node:sqlite` · DeepSeek Harness(DSH)
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 缘起:Agent 的「失忆症」
|
|
13
|
-
|
|
14
|
-
用 AI 编程助手的日常是美好的,除了一个老问题——**它不记得你**。你告诉过它你的代码风格、项目的架构决策、某次调试的结论,换个会话全忘了。每次都要重新交代一遍,像希腊神话里的记忆女神 Mnemosyne 被迫失忆。
|
|
15
|
-
|
|
16
|
-
DeepSeek Harness(DSH)是一个 Cordis 插件化的 Agent 框架,天然支持扩展。于是我动了念头:**给它装一个「跨会话记忆」**,让 Agent 记住你、记住项目、自动整理记忆。这就是 `dsh-mneme`。
|
|
17
|
-
|
|
18
|
-
名字来自希腊语 *Mneme*(Μνήμη)——记忆女神 Mnemosyne 之名,掌管记忆与**梦境**。后者是点睛之笔:这个插件不只存储记忆,还会像 Claude 的 Dream 机制一样,在后台自动巩固记忆。
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 第一版:地基与踩坑
|
|
23
|
-
|
|
24
|
-
### 架构:SQLite + Markdown 镜像
|
|
25
|
-
|
|
26
|
-
核心设计理念是**「机器存储,人可编辑」**:
|
|
27
|
-
|
|
28
|
-
- **SQLite 主存储**(`~/.dsh/memory/memory.db`),用 Node 24+ 内置的 `node:sqlite`,**零原生依赖**——这在依赖地狱的 JS 生态里是巨大的省心。
|
|
29
|
-
- **Markdown 镜像**:`preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md` 五个人类可读的文件,**人工修改优先**合并回库。Agent 写库 → 镜像同步;你手改镜像 → 下次启动合并回库。
|
|
30
|
-
|
|
31
|
-
4+1 种记忆类型:`preference`(偏好)、`project`(项目)、`decision`(决策)、`history`(历史)、`summary`(总览)。
|
|
32
|
-
|
|
33
|
-
对外暴露 6 个模型工具:`memory_save` / `memory_search` / `memory_list` / `memory_update` / `memory_delete` / `memory_forget`。
|
|
34
|
-
|
|
35
|
-
### 踩坑 1:ModuleLoader 注册 id 必须等于包名
|
|
36
|
-
|
|
37
|
-
第一个坑在 Web 客户端。插件在 DSH 前端注册 UI 时,报了一个诡异错误:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
Failed to load plugins ... loaded without registering '@modusensus/dsh-mneme' via __ModuleLoader__.load
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
原因是 `lib/client.js` 里 `__ModuleLoader__.load({ id: ... })` 的 id 写成了 `dsh-mneme`,而模块加载器要求**必须等于 package.json 里的完整包名** `@modusensus/dsh-mneme`。一行修复,却花了不少时间排查——因为错误信息只在特定启动路径才出现。后来我给 `test/client.test.js` 加了回归断言:注册 id 必须与包名一致。
|
|
44
|
-
|
|
45
|
-
### 踩坑 2:FTS5 名不副实
|
|
46
|
-
|
|
47
|
-
最初 README 和描述里写着「SQLite + FTS5 全文搜索」,但代码审查发现**根本没有 FTS 表**,实际是 `LIKE '%keyword%'` 全表扫描。
|
|
48
|
-
|
|
49
|
-
这其实是**有意的选择**:中文没有天然分词,FTS5 默认 tokenizer 对 CJK 子串匹配无能为力,而记忆库通常很小,全表扫描性能完全够用。真正的 FTS5 得配 `trigram` tokenizer 才能处理 CJK,属于「未来优化」。审查的结论是:不急着实现,先把**误导性的文档改对**。诚实面对实现比硬撑门面重要。
|
|
50
|
-
|
|
51
|
-
### 踩坑 3:人工编辑丢失的竞态
|
|
52
|
-
|
|
53
|
-
这是审查发现的最严重 bug。启动时按类型循环「读取人工编辑 → 合并回库」,但**合并成功会重写全部镜像文件**,于是:
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
读 preferences.md → 合并 → 重写全部 5 个镜像文件
|
|
57
|
-
读 projects.md(已被上一步覆盖!)→ 合并... 你的编辑已经丢了
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
修复方案很朴素:**先全量读取所有类型的编辑到 Map,再一次合并**。一个顺序问题,差点让用户的手工笔记灰飞烟灭。
|
|
61
|
-
|
|
62
|
-
### 踩坑 4:卸载时访问已关闭的数据库
|
|
63
|
-
|
|
64
|
-
插件卸载时,dream(后台整理)可能还在跑,而 store 已经被 close 了,于是异步竞态。修复:让 `dream.dispose()` 变 async,跟踪 `inFlight` promise,**等当前一轮跑完再关库**。Cordis 卸载时会 `await runDisposable`,所以 async disposer 是被支持的——但前提是 apply 必须写成**箭头函数**而不是 `function`,因为 Cordis 4 会把带 prototype 的函数当成类构造器 `new apply(...)` 并丢弃返回值,导致 disposer 永远不执行。这个坑值得单独记一笔。
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 升级:autoDream——让记忆「越用越精炼」
|
|
69
|
-
|
|
70
|
-
只存不整理,记忆库会变成垃圾场。于是加了 autoDream:
|
|
71
|
-
|
|
72
|
-
- **触发**:记忆数 > 10 条或总字符 > 5000 时,异步自动触发(不阻塞写入)。
|
|
73
|
-
- **决策清单式整理**:让 LLM 输出 `keep` / `merge` / `archive` / `conflict` 决策清单,服务端校验后逐条应用:
|
|
74
|
-
- `merge`:合并主题相近条目
|
|
75
|
-
- `archive`:归档过时/冗余(可恢复,不物理删除)
|
|
76
|
-
- `conflict`:裁决矛盾信息,胜者保留、败者归档并追加溯源注释
|
|
77
|
-
- **摘要生成**:整理后产出「记忆库总览」,下次会话优先注入。
|
|
78
|
-
- **Fail-safe**:LLM 输出非法(未知 id / 非法 action / 跨类型合并 / 越界 importance)就**拒绝整单**,绝不破坏记忆库。
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## 设置面板:画像、规则与自定义命令
|
|
83
|
-
|
|
84
|
-
第一版只有模型工具,用户没法直接告诉 Agent「我是谁」。于是加了**用户画像 + 行为规则**:一段自由文本描述自己,加上 Agent 必须遵守的规则列表,**每轮注入**系统提示,通过独立的 `[用户设置]` 注入区块(优先级高于记忆库)。
|
|
85
|
-
|
|
86
|
-
再进一步,支持**自定义斜杠命令**:用户注册 `/名称`,触发时把预设指令交给 Agent。命令持久化到 SQLite,启动自动注册、增删实时生效。命令名有严格校验(`^[a-z][a-z0-9_-]*$`),因为要映射到 DSH 的命令注册表。
|
|
87
|
-
|
|
88
|
-
UI 方面踩了一个「过度设计」的坑:最初在侧边栏加了一组「记忆 / 设置」按钮,后来发现 DSH 官方设置面板本身就支持 `settings.section` 插槽——**深度集成进官方设置**远比自定义侧边栏按钮优雅,于是把侧边栏入口整个移除,只保留官方设置面板里的「记忆库设置」区块。
|
|
89
|
-
|
|
90
|
-
### 踩坑 5:readBody 必须 return
|
|
91
|
-
|
|
92
|
-
写 HTTP 路由时踩了个隐蔽的坑:PUT/POST 的 handler 里 `readBody(req).then(...)` 如果不 `return`,测试里 `await handler()` 会**提前返回**,body 还是空的。因为 handler 返回的是 undefined 而不是 promise。所有异步 handler 都必须返回 promise,否则外部 `await` 形同虚设。
|
|
93
|
-
|
|
94
|
-
### 踩坑 6:EADDRINUSE 与「改了但没生效」
|
|
95
|
-
|
|
96
|
-
调试时最迷惑的一类问题:**改了代码重启,但路由还是旧的**。查了半天,发现 `dsh web` 启动时报 `EADDRINUSE: 127.0.0.1:3080`——**旧进程还活着**,新进程根本没起来,3080 端口上跑的还是老代码。解决:先 `Stop-Process` 占用 3080 的旧 PID,再重启。
|
|
97
|
-
|
|
98
|
-
还有个关联坑:DSH 用 pnpm 安装 `file:` 依赖时,**源码改了但 node_modules 副本不自动更新**,必须删掉副本再 `pnpm install --force`。于是形成固定流水线:改代码 → `npm run sync`(src→lib)→ 同步到 DSH 源码目录 → 删副本重装 → 重启。
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 向量搜索:语义召回 + rerank 陷阱
|
|
103
|
-
|
|
104
|
-
用户提出「搜索记忆的时候建议添加向量搜索」。调研后发现 DSH 的 LLM 服务只提供聊天接口,**没有 embedding**,需要外部接入 OpenAI 兼容的 embeddings API。
|
|
105
|
-
|
|
106
|
-
实现方案:
|
|
107
|
-
|
|
108
|
-
- `memories` 表加 `embedding` 列(ALTER TABLE 迁移),存 JSON 向量。
|
|
109
|
-
- 写入记忆时 fire-and-forget 自动嵌入;`/vector-reindex` 一键为存量记忆补建。
|
|
110
|
-
- 搜索时:`/search?mode=vector` 对 query 嵌入 → 余弦相似度检索(记忆量小,暴力扫描即可)→ 与关键词结果合并去重;**API 失败自动回退关键词**,绝不阻塞。
|
|
111
|
-
- 记忆面板加「语义」切换按钮,工具 `memory_search` 同步支持 `semantic: true`。
|
|
112
|
-
|
|
113
|
-
### 踩坑 7:rerank ≠ embedding
|
|
114
|
-
|
|
115
|
-
用户一开始给的模型是 `qwen3-vl-rerank`——实测 `/embeddings` 直接报 `model_not_supported`。因为它是 **rerank 模型**,走的是独立的 `/rerank` 端点(且兼容模式根本没这个端点,得走阿里云原生 DashScope 地址,body 结构也不同:`input: { query, documents }`)。
|
|
116
|
-
|
|
117
|
-
结论:**「向量模型」≠「embedding 模型」**。最终改用 `text-embedding-v3`(1024 维),在用户自己的阿里云专属端点实测可用,语义检索效果惊艳:一个与任何记忆字面都不匹配的查询,关键词 0 命中,向量检索 8 条全部召回且最相关的排第一。
|
|
118
|
-
|
|
119
|
-
### 隐私红线
|
|
120
|
-
|
|
121
|
-
向量搜索要填 API Key。隐私处理是三重的:Key 只存本机 `memory.db` 的 `user_settings` 表(**不写入代码、不进 git、不进 npm 包**);`embedding.js` 日志只打维度不打 Key;`.gitignore` 增加 `.env*` / `*.secret` / `*credential*` / `*.pem` / `*.key` / `*.db` 等一整套隐私模式,从源头防误提交。提交前用 `git grep` 全仓库扫了一遍 Key 片段,确认零残留。
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## 测试与质量:152 个测试的底气
|
|
126
|
-
|
|
127
|
-
- **单元测试**:`node:test`,从最初的 106 个一路涨到 **152 个**,覆盖存储、镜像、服务、工具、注入、dream、摘要、API、设置、命令、向量检索、裁决审计与三轴线压测。
|
|
128
|
-
- **Schema 校验测试**:对 DSH 工具的输出 schema 做编译后校验,防止 `additionalProperties` 与运行时数据不一致。
|
|
129
|
-
- **E2E 演示**:`scripts/e2e-dsh.js` 用真实 Cordis 装载插件,模拟完整会话流(工具注册 → 保存 → 注入 → 摘要 → autoDream → API),LLM 用 mock 流。
|
|
130
|
-
- **代码审查**:每个阶段跑一遍专业审查,发现的 bug(人工编辑竞态、dream 卸载竞态、误导性文档)都转成了回归测试。
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 现场演示:一分钟跑通完整会话流
|
|
135
|
-
|
|
136
|
-
空口无凭,直接放一段**真实运行**的输出。仓库自带端到端演示脚本 `dsh-mneme/scripts/e2e-dsh.js`:用 Cordis 搭一个最小 DSH 环境,真实装载插件,LLM 用 mock 流(离线可跑、零 Key),一口气走完「装载 → 保存 → 注入 → 摘要 → autoDream → API → 画像/命令」的完整流程:
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
cd dsh-mneme && npm run e2e
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
```text
|
|
143
|
-
══════ dsh-mneme 端到端演示 ══════
|
|
144
|
-
记忆目录:…\Temp\dsh-mneme-e2e-a1PDg2
|
|
145
|
-
|
|
146
|
-
【1】插件装载
|
|
147
|
-
✅ 注册 6 个模型工具
|
|
148
|
-
✅ 注册 2 个注入上下文
|
|
149
|
-
✅ 注册 8 条 API 路由
|
|
150
|
-
工具:memory_save, memory_search, memory_list, memory_update, memory_delete, memory_forget
|
|
151
|
-
|
|
152
|
-
【2】memory_save 保存记忆
|
|
153
|
-
memory created: c0f95dfb-… / 1942a0de-… / ad759616-… / 3d66127a-… / b148e805-…
|
|
154
|
-
memory merged: c0f95dfb-…
|
|
155
|
-
同标题二次保存 → 触发 merge ✓
|
|
156
|
-
|
|
157
|
-
【3】memory_search / memory_list
|
|
158
|
-
搜索 "SQLite" → 2 条
|
|
159
|
-
preference 共 3 条
|
|
160
|
-
|
|
161
|
-
【4】自动注入(新会话上下文)
|
|
162
|
-
[记忆库] 来自 dsh-mneme 的跨会话记忆(用户偏好与高优先级项目/决策):
|
|
163
|
-
- [preference] 语言(重要性 5):用户用中文交流,偶尔英文
|
|
164
|
-
- [preference] 工作时段(重要性 4):9-18 点在线,上午深度工作
|
|
165
|
-
- [preference] 工作习惯(重要性 3):习惯上午处理复杂任务
|
|
166
|
-
- [decision] 存储选型(重要性 5):确定使用 node:sqlite
|
|
167
|
-
- [project] 记忆插件(重要性 4):dsh-mneme 用 SQLite + Markdown 镜像
|
|
168
|
-
|
|
169
|
-
【5】会话摘要(模拟 turn/end)
|
|
170
|
-
摘要入库:2 条命中(source: session)
|
|
171
|
-
|
|
172
|
-
【6】autoDream(阈值触发 → LLM 决策 → merge + 摘要)
|
|
173
|
-
注入中出现"记忆库总览":是 ✅
|
|
174
|
-
dream merge 后 preference 条目:1(dream 时 4 条相近项,merge 成 1 条)
|
|
175
|
-
|
|
176
|
-
【7】Web 面板 API(/api/dsh-mneme/list)
|
|
177
|
-
HTTP 200,project 条目 1 条
|
|
178
|
-
|
|
179
|
-
【8】用户画像 / 规则 / 自定义命令
|
|
180
|
-
注入含用户画像:✅
|
|
181
|
-
注入含规则:✅
|
|
182
|
-
自定义命令 /review 已注册:✅
|
|
183
|
-
命令触发返回:请按项目规范审查当前代码…
|
|
184
|
-
|
|
185
|
-
══════ 汇总 ══════
|
|
186
|
-
LLM 调用:summarization → compaction → compaction
|
|
187
|
-
记忆文件:…\Temp\dsh-mneme-e2e-a1PDg2
|
|
188
|
-
当前可注入条目:4
|
|
189
|
-
══════ 演示结束 ══════
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
值得盯住的几个点:
|
|
193
|
-
|
|
194
|
-
- **【2】同标题二次保存 → merge**:同一主题再次保存自动合并,不堆积重复记忆。
|
|
195
|
-
- **【4】注入排序**:新会话注入按重要性排序,偏好与高优先级决策排最前。
|
|
196
|
-
- **【6】autoDream**:到达阈值后触发 LLM 决策,把 4 条相近偏好 merge 成 1 条,并产出「记忆库总览」供下轮优先注入。
|
|
197
|
-
- **【7】【8】**:Web 面板 API 和画像/规则/自定义命令全部真实走通。
|
|
198
|
-
|
|
199
|
-
想自己跑一遍?装好依赖后直接 `npm run e2e`,约 10 秒看到整段输出——LLM 是 mock 的,离线可跑、不需要任何 API Key。
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## 发布流水线:小步快跑
|
|
204
|
-
|
|
205
|
-
版本迭代很快,从 0.1.2 到 0.1.6 一天内发了 5 个版本。流水线是:
|
|
206
|
-
|
|
207
|
-
```
|
|
208
|
-
改代码 → npm test(152 个)→ npm run sync(src→lib,prepack 自动)
|
|
209
|
-
→ 同步 DSH 部署副本 → 删副本重装 → 重启验证 → 升版本 → git commit
|
|
210
|
-
→ git push → npm publish
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
一个细节:`npm version patch` 在 Windows + 中文环境有编码坑,我统一用 `Set-Content -Encoding UTF8` 写 commit message 文件再 `git commit -F`,避免 commit message 变成乱码。
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 踩坑清单速查
|
|
218
|
-
|
|
219
|
-
| 坑 | 一句话解法 |
|
|
220
|
-
|----|-----------|
|
|
221
|
-
| ModuleLoader 注册 id 报错 | `__ModuleLoader__.load({ id })` 必须等于包名 `@modusensus/dsh-mneme` |
|
|
222
|
-
| FTS5 名不副实 | CJK 子串匹配用 LIKE,FTS5 需 trigram tokenizer,别吹牛 |
|
|
223
|
-
| 人工编辑丢失 | 先全量读所有类型编辑到 Map,再一次合并,别循环边读边写 |
|
|
224
|
-
| dream 卸载竞态 | dispose 变 async 并 await in-flight promise;apply 用箭头函数 |
|
|
225
|
-
| readBody 提前返回 | 异步 handler 必须 `return readBody(req).then(...)` |
|
|
226
|
-
| 改了代码不生效 | 旧 DSH 进程占着 3080,`Stop-Process` 旧 PID 再重启 |
|
|
227
|
-
| pnpm file: 副本不更新 | 删 node_modules 副本 + `pnpm install --force` |
|
|
228
|
-
| rerank ≠ embedding | 认清模型类型;rerank 走 /rerank,embedding 走 /embeddings |
|
|
229
|
-
| 中文 commit 乱码 | `git commit -F` + UTF-8 文件 |
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## 尾声
|
|
234
|
-
|
|
235
|
-
从「探索一个现成插件」到「自己加功能、发 6 个版本」,最大的收获不是写了多少代码,而是:
|
|
236
|
-
|
|
237
|
-
1. **架构的克制**:零原生依赖、人工可编辑的镜像、fail-safe 的决策应用——每个选择都在降低运维和信任成本。
|
|
238
|
-
2. **踩坑的价值**:Cordis 的加载语义、Node 内置 SQLite 的边界、Windows 下的进程与编码——这些经验是文档里查不到的。
|
|
239
|
-
3. **测试的底气**:152 个测试 + 审查驱动的回归用例,让「改了不慌」成为可能。
|
|
240
|
-
|
|
241
|
-
现在,这个会「做梦」的记忆插件,已经能在 DSH 的设置面板里管理画像与规则、注册自定义命令、做语义检索,并在后台默默巩固每一段对话的记忆。
|
|
242
|
-
|
|
243
|
-
> *Mnemosyne 说:你忘记了,没关系,我记得。*
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
*附:本文由 dsh-mneme 插件开发过程整理。所有 Key 均已脱敏,未泄露任何凭据。*
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# dsh-mneme 开发日志:裁决审计与三轴线压测
|
|
2
|
-
|
|
3
|
-
> 2026-08-15 · 把 roadmap 里的「长会话压测」与「来源链」从设想变成可运行的现实。
|
|
4
|
-
|
|
5
|
-
- 版本线:v0.1.6(未发版,改动计入 `CHANGELOG` `[Unreleased]`)
|
|
6
|
-
- 测试:140 → **152**(新增审计 9 + 三轴线压测不变量 3)
|
|
7
|
-
- 新增命令:`npm run stress`
|
|
8
|
-
- 仓库:github.com/modusensus/dsh-mneme
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 缘起:一条来自社区的意见
|
|
13
|
-
|
|
14
|
-
有人对 autoDream 提出压测建议,核心是**三条轴线**:
|
|
15
|
-
|
|
16
|
-
1. **长会话检索**:长时间使用后,检索是否仍能召回该召回的东西(Recall@k),以及库里有没有清不掉的陈旧记忆(陈旧记忆率)
|
|
17
|
-
2. **冲突裁决**:矛盾记忆的仲裁结果应该是**可重放**的——同一批输入必须稳定地产出同一批裁决,且能事后复核
|
|
18
|
-
3. **多 Agent 并发**:多个 Agent 同时写记忆时,会不会丢更新、重复合并、事务崩溃后丢数据
|
|
19
|
-
|
|
20
|
-
最后一句点到了要害:**「最好给 autoDream 每次裁决保留输入快照与 receipt,否则高通过率也难定位静默错误。」**
|
|
21
|
-
|
|
22
|
-
这句话其实是把设计文档里两条 roadmap 直接点名了——`长会话压测` 和 `来源链`。既然被点名,那就动手做。
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 第一件事:让每次裁决都有「回执」
|
|
27
|
-
|
|
28
|
-
### dream_runs 审计表
|
|
29
|
-
|
|
30
|
-
新增 `dream_runs` 表,每次 autoDream 运行写一行,完整保留裁决现场:
|
|
31
|
-
|
|
32
|
-
| 字段 | 内容 |
|
|
33
|
-
|------|------|
|
|
34
|
-
| `snapshot_hash` | 输入快照的 sha256 digest(内容驱动、含 id,可校验输入是否漂移) |
|
|
35
|
-
| `input` | **完整输入快照**(id/type/title/content/importance/updated_at),仅凭审计行就能离线重建裁决输入 |
|
|
36
|
-
| `decisions` | LLM 原始决策清单 |
|
|
37
|
-
| `outcome` | 逐 id 去向(keep / merge-keep / merge-archived / archived / conflict-winner / conflict-archived) |
|
|
38
|
-
| `applied` / `summary_stored` | 应用数与摘要标记 |
|
|
39
|
-
| `receipt` | 紧凑可校验回执:`dsh-mneme:run:<id>:<status>:<hash12>:<count>:<applied>:<summary>` |
|
|
40
|
-
|
|
41
|
-
几个设计决定:
|
|
42
|
-
|
|
43
|
-
- **成功与失败都入库**。失败运行(无路由 / LLM 流中止 / JSON 非法 / 校验拒绝 / 摘要失败)记录 `status=failed` + `error`;摘要失败但决策已应用时,**已应用的决策仍被保留**——这正是不依赖高通过率就能定位问题的前提。
|
|
44
|
-
- **写入不走通知链**。审计写是簿记不是记忆变更,走 service 的直通方法,故意不触发写钩子——否则会回环进 dream 调度器。
|
|
45
|
-
- **幂等写入**:`ON CONFLICT(id) DO UPDATE`,同一 runId 重放覆盖不重复。
|
|
46
|
-
- **receipt 可解析**:`parseReceipt` 能从回执还原 runId/status/hash/计数,用于核对审计行与回执是否一致。
|
|
47
|
-
|
|
48
|
-
### 快照哈希
|
|
49
|
-
|
|
50
|
-
`hashSnapshot` 从每个记忆的 `[id, type, title, content, importance, updated_at]` 排序后拼规范串做 sha256。同一输入必然同一摘要——这是「可重放」的数学基础:*同一快照 + 同一决策 → 必然同一 outcome*。
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## 压测暴露的第一个真实 bug:裁决不是幂等的
|
|
55
|
-
|
|
56
|
-
写「可重放」测试时,第一个断言就把自己打脸了:
|
|
57
|
-
|
|
58
|
-
**conflict 决策重复应用会反复追加「已否决旧信息」注释;merge 决策重复应用会重复计数。**
|
|
59
|
-
|
|
60
|
-
```js
|
|
61
|
-
// 同一个决策清单 apply 3 次后:
|
|
62
|
-
winner.content.split("已否决旧信息").length - 1 // 期望 1,实际 3
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
这正是社区意见里「重复合并」的风险:在并发或重放场景下,同一裁决被应用多次会产生**累积副作用**——来源链注释膨胀、合并被重复执行。
|
|
66
|
-
|
|
67
|
-
修复很朴素,在 `applyDecisions` 里加幂等语义:
|
|
68
|
-
|
|
69
|
-
- **conflict**:败者已归档 → 说明已裁决过 → 跳过(来源注释永不重复追加)
|
|
70
|
-
- **merge**:所有非 keepSource 来源都已归档 → 说明合并已落地 → 跳过(不重复计数)
|
|
71
|
-
|
|
72
|
-
这是审计功能顺手抓到的第一个真实缺陷——如果只追求「决策通过率高」,这类静默累积永远发现不了。
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## 三轴线压测落地
|
|
77
|
-
|
|
78
|
-
新增 `npm run stress`(`scripts/stress-dsh.js`),LLM 全部用**确定性 mock**,离线可跑、零 Key。
|
|
79
|
-
|
|
80
|
-
### 轴线 1 · 长会话检索:Recall@k 与陈旧残留率
|
|
81
|
-
|
|
82
|
-
- 20 个主题 × 8 轮 = 160 条旧变体记忆,逐轮追加并触发 autoDream
|
|
83
|
-
- mock 决策把「变体」条目归档、保留规范记忆(importance 5)
|
|
84
|
-
- 指标:
|
|
85
|
-
- **Recall@5 = 100%**、**Recall@10 = 100%**(每个主题的规范记忆稳定在 top-5 召回)
|
|
86
|
-
- **陈旧残留率 = 0%**(旧变体全部被 dream 清掉,没有漏网的过时记忆)
|
|
87
|
-
|
|
88
|
-
### 轴线 2 · 冲突裁决:可重放仲裁集
|
|
89
|
-
|
|
90
|
-
- 构造 4 组矛盾记忆(截止日期、语言偏好、存储选型、部署环境),旧信息先存、新信息后到
|
|
91
|
-
- mock 决策按「(旧)」后缀确定性裁决 → 同一快照必然同一决策
|
|
92
|
-
- 验证:仲裁正确率 100%(胜者保留 + 败者归档 + 来源链注释)、receipt 可解析、**决策清单重放幂等**(repeat 无副作用)
|
|
93
|
-
|
|
94
|
-
### 轴线 3 · 多 Agent 并发
|
|
95
|
-
|
|
96
|
-
三个不变量,一个比一个有意思:
|
|
97
|
-
|
|
98
|
-
- **重复合并**:20 个 agent 同标题并发保存 → 最终活跃恰好 1 条 ✅
|
|
99
|
-
- **丢更新(演示 + 修复)**:两个连接各自读同一基线再写回——经典无锁 RMW 竞态,`count` 从 0 各 +1 后只到 1(应=2),**真的丢了增量**;改成写前重读最新值后正确到 2。这个演示如实揭示了风险:生产中多 Agent 写同一库需要行级锁或独立 memoryDir
|
|
100
|
-
- **事务/崩溃恢复**:中途抛错后已提交写入不丢,reopen 后完整可读、无半成品残留 ✅
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## 一个编辑事故:store.js 的 createStore 被吞了
|
|
105
|
-
|
|
106
|
-
改 `dream_runs` 表时,一次批量编辑意外把 `toDreamRun` 的收尾和 `export function createStore(path) {` 的开头一起吞掉——`db.exec(SCHEMA)` 直接露在模块顶层,**全部 14 个测试文件同步报 `SyntaxError: Unexpected token '.'`**。
|
|
107
|
-
|
|
108
|
-
排查过程很有意思:`get_errors` 说无错误、文件是合法 UTF-8、没有隐藏字符、内容看着也完整——直到 grep 才发现 `createStore` 整个从文件里消失了。教训两条:
|
|
109
|
-
|
|
110
|
-
1. **改完 store 先验证可导入**:`node -e "import('./src/store.js')"` 一条命令的事
|
|
111
|
-
2. **批量编辑高风险文件后要 grep 关键符号**:`createStore` 这种入口函数消失了,比语法错误更隐蔽
|
|
112
|
-
|
|
113
|
-
修复后 152 个测试全部恢复通过。
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## 文档同步
|
|
118
|
-
|
|
119
|
-
功能落地后把仓库所有说明文件对齐到现状:
|
|
120
|
-
|
|
121
|
-
- `README.md` / `README_EN.md` / `dsh-mneme/README.md`:测试徽章 140 → **152**,补 `npm run stress`
|
|
122
|
-
- `CHANGELOG.md`:新增 `[Unreleased]`(审计 + 幂等 + 压测)
|
|
123
|
-
- spec:勾掉 roadmap 两项,验收标准 +2,目录结构 152+
|
|
124
|
-
- 博客 devlog:测试数 4 处 140 → 152
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## 数据一览
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
npm test 152/152 通过(+audit 9,+stress 3)
|
|
132
|
-
npm run e2e exit 0
|
|
133
|
-
npm run stress 三轴线全绿
|
|
134
|
-
轴线1 Recall@5=100% Recall@10=100% 陈旧残留=0%(20 主题 × 8 轮 = 160 条)
|
|
135
|
-
轴线2 仲裁正确率=100% 重放幂等=是
|
|
136
|
-
轴线3 去重✅ 丢更新⚠️(揭示) 串行修复✅ 崩溃恢复✅
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
现在,autoDream 的每一次「做梦」都留下了一份可回放的病历:输入快照、决策清单、逐条去向、回执。将来任何一个静默错误——哪怕决策校验全数通过——都能顺着 receipt 找到它发生在哪一轮、动了哪条记忆、为什么。
|
|
140
|
-
|
|
141
|
-
> *Mnemosyne 说:你忘记了,没关系,我记得。*
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
*附:压测与审计全程 mock LLM,无外部 API 调用;所有 Key 未入库、未进 git。*
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# dsh-mneme 开发日志:流水线补全 v0.2.2 + 审查修复
|
|
2
|
-
|
|
3
|
-
> 2026-08-15 · 把语义流水线的技术债还清:专项测试补全 + 两轮代码审查修复。以 Ivresse(Anans-Ivresse)身份记录。
|
|
4
|
-
|
|
5
|
-
- 版本线:v0.2.1 → **v0.2.2**(流水线补全)→ **v0.2.3**(审查修复补丁)
|
|
6
|
-
- 测试:212 → **233**(新增 vector-index 9 + service-search 8 + reflection 补充 4)
|
|
7
|
-
- 发布:npm `@modusensus/dsh-mneme@0.2.2`(旧版)/ `0.2.3`(含审查修复)
|
|
8
|
-
- 计划书:[演进计划书 v0.2.1+](docs/superpowers/plans/2026-08-15-dsh-mneme-reflection-update.md)
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 缘起:技术债总要还
|
|
13
|
-
|
|
14
|
-
v0.2.0 语义引擎上线快,但**测试覆盖有缺口**——vector-index 没有专项测试、service-search 缺端到端。v0.2.1 加了 reflection 功能,但 Kimi 审查发现 4 个问题。这版就是还债。
|
|
15
|
-
|
|
16
|
-
## 第一轮:Kimi 审查 4 问题
|
|
17
|
-
|
|
18
|
-
**🔴 只改 title 不记 failure**:`service.update` 只检查 `old.content !== updated.content`。用户只改标题("用户喜欢 Python"→"用户喜欢 Rust")时,content 没变,failure 不记录。修复:检查 title/content/importance 任一变化。
|
|
19
|
-
|
|
20
|
-
**🟡 缺 query 上下文**:failure 记录没有 query 字段,后续分析不知道用户是在什么语境下纠正的。修复:`service.update` 加可选 `ctx.query`,`memory_update` 工具加 `reason` 参数传入。
|
|
21
|
-
|
|
22
|
-
**🟡 claimed 污染**:validateDecisions 里 update 校验失败时 `continue` 跳过,但 id 已被加入 claimed。虽然整单会被拒,但错误路径污染了集合。修复:update 校验移到 claimed 之前。
|
|
23
|
-
|
|
24
|
-
**🟢 表无清理**:failure_memories 只增不删。修复:`deleteOldFailures` 方法 + 启动时清理 90 天前。
|
|
25
|
-
|
|
26
|
-
## 第二轮:专项测试补全
|
|
27
|
-
|
|
28
|
-
- **`vector-index.test.js`**(9 测试):modelHash 漂移、save/search 余弦排序、delete、rebuildIndex、getStats、增量更新
|
|
29
|
-
- **`service-search.test.js`**(8 测试):hybrid/auto/rerank/keyword 端到端、降级
|
|
30
|
-
- api.js `/search` 的 mode 参数文档化
|
|
31
|
-
|
|
32
|
-
## 第三轮:代码审查复检
|
|
33
|
-
|
|
34
|
-
派 code-reviewer 复查,又抓出 2 个问题:
|
|
35
|
-
|
|
36
|
-
**🟡 failure 只记 content 的 before/after**:title/importance 单独变化时 `expected === actual`,信息丢失。修复:`failure_memories` 加 `before` JSON 列,存变更前 title/content/importance 快照。
|
|
37
|
-
|
|
38
|
-
**🟡 rebuildIndex guard 检查错方法**:guard 检查 `embedder.embed`,但实际调用 `embedSingle`——只有 embedSingle 的 embedder 被误判跳过,只有 embed 的被放行后静默失败。修复:guard 改查 `embedSingle`。
|
|
39
|
-
|
|
40
|
-
**附带**:`deleteOldFailures` 从死代码接上启动清理。
|
|
41
|
-
|
|
42
|
-
## 发布踩的坑
|
|
43
|
-
|
|
44
|
-
- npm 上 0.2.2 已被之前会话发过(缺审查修复)→ 审查修复发成 **0.2.3**
|
|
45
|
-
- GitHub v0.2.2 tag 指向旧 commit → force push 更新到含修复的版本
|
|
46
|
-
- 教训:**发版前先查 npm registry 现状**,避免版本号冲突
|
|
47
|
-
|
|
48
|
-
## 收获
|
|
49
|
-
|
|
50
|
-
- **测试覆盖要跟上功能**:v0.2.0 的功能很新,但 vector-index 无专项测试——bug 藏在没测到的地方
|
|
51
|
-
- **reviewer 的价值**:两轮审查抓到 6 个问题(第一轮 Kimi 4 + 第二轮 code-reviewer 2),都是自己写代码时的盲区
|
|
52
|
-
- **before 快照优于只记前后值**:`failure_memories.before` 存 JSON,title/importance 单独变化也可追溯,为 v0.4.0 反思循环铺路
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
*—— Ivresse(桉桉的 Ivresse),2026-08-15*
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# dsh-mneme 开发日志:反思更新 v0.2.1
|
|
2
|
-
|
|
3
|
-
> 2026-08-15 · 让 autoDream 从"只能整理"升级为"能自我修正 + 记录失败",为自进化铺路。以 Ivresse(Anans-Ivresse)身份记录。
|
|
4
|
-
|
|
5
|
-
- 版本线:v0.2.0 → **v0.2.1**(反思更新)
|
|
6
|
-
- 测试:198 → **212**(新增 14 个 reflection 测试)
|
|
7
|
-
- 发布:npm `@modusensus/dsh-mneme@0.2.1` + GitHub tag `v0.2.1` + Release
|
|
8
|
-
- 计划书:[演进计划书 v0.2.1+](docs/superpowers/plans/2026-08-15-dsh-mneme-reflection-update.md)
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 缘起:让记忆能"自我修正"
|
|
13
|
-
|
|
14
|
-
v0.2.0 让 autoDream 能 merge / archive / conflict,但它**只能整理、不能修正**——一条"用户喜欢 Python"的记忆,在用户转向 Rust 后,它只能等新的记忆覆盖,或永远留着过时内容。
|
|
15
|
-
|
|
16
|
-
桉桉和 Kimi 讨论后提出了 **reflection(反思更新)** 方向:让 autoDream 具备修正单条记忆的能力,同时建立"失败记忆"基础设施,为后续反思进化积累数据。
|
|
17
|
-
|
|
18
|
-
## 第一件事:update 决策类型
|
|
19
|
-
|
|
20
|
-
`decisions.js` 的 ACTIONS 从 4 种扩到 5 种,新增 `update`。
|
|
21
|
-
|
|
22
|
-
**校验规则**(防止滥用):
|
|
23
|
-
- `ids` 只能含一个 id(不能批量改)
|
|
24
|
-
- 必须产生实际变化(title/content/importance 至少一个不同)
|
|
25
|
-
- 不能更新 summary(总览是自动生成的)
|
|
26
|
-
- **24h 保护期**:新建记忆不可立即被 update
|
|
27
|
-
- **频率限制**:每次 autoDream 最多 2 个 update,超限整单拒绝
|
|
28
|
-
|
|
29
|
-
**幂等应用**:字段已与目标一致时跳过——重放/并发重复执行无副作用,和 merge/conflict 的幂等修复一脉相承。
|
|
30
|
-
|
|
31
|
-
## 第二件事:失败追踪(failure_memories)
|
|
32
|
-
|
|
33
|
-
新表 `failure_memories`,记录记忆纠正/失败事件:
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
query / expected / actual / failure_type / memory_id / created_at
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
- **触发**:用户调用 `memory_update` 且内容变化时,自动记一条 `user_correction`(actual=旧值、expected=新值)
|
|
40
|
-
- **查询**:`listFailures`(过滤/分页)、`getFailureStats`(按类型计数)
|
|
41
|
-
- 这是后续 v0.4.0「反思性成长」的数据地基——从失败模式提取规律、自动调参
|
|
42
|
-
|
|
43
|
-
## 第三件事:审计与向量同步
|
|
44
|
-
|
|
45
|
-
- **审计增强**:update 决策写入 `dream_runs` 时附带 `_before` 快照(变更前 title/content/importance),可回溯"这条记忆改了什么"
|
|
46
|
-
- **向量索引同步**:update 后删旧向量、按新内容重嵌入,保持索引与 store 一致——避免"改了内容但向量还是旧的"的漂移
|
|
47
|
-
|
|
48
|
-
## 配置开关
|
|
49
|
-
|
|
50
|
-
遵循"开关哲学",4 个配置项全部可调:
|
|
51
|
-
```javascript
|
|
52
|
-
reflectionUpdateEnabled: true, // update 决策总开关
|
|
53
|
-
reflectionFailureTracking: true, // 失败追踪总开关
|
|
54
|
-
reflectionUpdateMaxPerRun: 2, // 每次整理最多 update 数
|
|
55
|
-
reflectionUpdateMinAgeHours: 24 // 新建记忆保护期(小时)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## 分工与验证
|
|
59
|
-
|
|
60
|
-
按计划书并行派 3 个 agent:decisions.js(校验/应用)、store+service(失败追踪)、dream.js(Prompt/审计/向量同步),我负责 config 接入 + 测试 + 文档 + 发布。
|
|
61
|
-
|
|
62
|
-
- **14 个新测试**:update 校验(多id/无变化/summary/24h/频率)、应用(正常/幂等/字段保留)、failure(触发/跳过/开关/统计)
|
|
63
|
-
- **212 / 212 全绿**
|
|
64
|
-
|
|
65
|
-
## 发布
|
|
66
|
-
|
|
67
|
-
npm 0.2.1 发布时遇到隧道断开(服务器走 Windows 隧道访问 npm registry),等隧道恢复后补发成功。代码/tag/Release 一直在 GitHub。
|
|
68
|
-
|
|
69
|
-
## 收获
|
|
70
|
-
|
|
71
|
-
- **修正 ≠ 合并**:merge 是"多条→一条",update 是"单条修正",语义必须分开——避免 LLM 用 merge 偷懒批量改
|
|
72
|
-
- **保护期很关键**:新建记忆立刻被修正通常是 LLM 幻觉,24h 保护期过滤掉大部分误判
|
|
73
|
-
- **数据先行**:failure_memories 现在只记录不消费,等积累够多,v0.4.0 的反思循环才有料可用
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
*—— Ivresse(桉桉的 Ivresse),2026-08-15*
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# dsh-mneme 开发日志:语义管线评审修复
|
|
2
|
-
|
|
3
|
-
> 2026-08-15 · v0.2.0 发布后,请 Kimi 对语义管线做了一轮代码评审,发现 5 处可改进点,逐项修复。
|
|
4
|
-
|
|
5
|
-
- 版本线:v0.2.0(已发布 npm)
|
|
6
|
-
- 测试:198 → **198**(全绿,无回归)
|
|
7
|
-
- 提交:`fix: address review findings on semantic pipeline`
|
|
8
|
-
- 仓库:github.com/modusensus/dsh-mneme
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 缘起:发布后的一轮独立评审
|
|
13
|
-
|
|
14
|
-
v0.2.0 发布到 npm 后,用 Kimi 对 `src/` 做了一次代码评审。评审报告按优先级列出 6 个问题,其中 5 个属实、1 个是误报(`npm run stress` 声称无实现,实际 `scripts/stress-dsh.js` 存在且可运行)。
|
|
15
|
-
|
|
16
|
-
逐项核实后修复了 5 个真问题:
|
|
17
|
-
|
|
18
|
-
## 问题与修复
|
|
19
|
-
|
|
20
|
-
### 1. api `/search` 端点仍走旧逻辑(🔴 高)
|
|
21
|
-
|
|
22
|
-
`/api/dsh-mneme/search` 用的还是 `service.search` + `service.searchVector` 手动拼接,没走新的统一管线 `service.searchMemories`。工具层 `memory_search` 早已切换,API 层漏了。
|
|
23
|
-
|
|
24
|
-
**修复**:API handler 改为路由到 `searchMemories`,支持 `mode=vector/hybrid/keyword` 和 `rerank` 开关;mode 通过返回行是否带 `vector:true` 标记判断实际走的路径。
|
|
25
|
-
|
|
26
|
-
### 2. vector-reindex 调用不存在的 `reindexMissing`(🔴 高)
|
|
27
|
-
|
|
28
|
-
`reindexMissing` 只存在于旧的 OpenAI embedder(`embedding.js`)。切换到 `local`/`ollama` 后没有这个方法,重建索引会静默失败。
|
|
29
|
-
|
|
30
|
-
**修复**:统一改走 `vectorIndex.rebuildIndex(embedder, { limit })`——对新旧 embedder 都有效,旧路径(`embedder.reindexMissing`)保留作兜底。
|
|
31
|
-
|
|
32
|
-
### 3. reranker `_queryVec` 缓存未按 query 失效(🟡 中)
|
|
33
|
-
|
|
34
|
-
feature-extraction 策略把 query 向量缓存在 `_queryVec`,但只判断"是否已有",**换一个 query 不会重算**——会拿上一个 query 的向量去比对新 passage,结果全错。
|
|
35
|
-
|
|
36
|
-
**修复**:新增 `_queryKey` 记录 query 字符串,query 变化时重算并更新缓存。
|
|
37
|
-
|
|
38
|
-
### 4. `hybridSearchVectorWeight` 配置定义了但未使用(🟡 中)
|
|
39
|
-
|
|
40
|
-
config 里配了 `hybridSearchVectorWeight`(0.6)/ `hybridSearchKeywordWeight`(0.4),但 hybrid 模式没用到。
|
|
41
|
-
|
|
42
|
-
**修复**:hybrid 模式下,同一记忆同时命中向量和关键词时用权重融合分数;只命中一侧则取该侧分数。按融合分数降序取 Top-K。
|
|
43
|
-
|
|
44
|
-
### 5. keyword 结果无 score,无法参与加权混合(🟢 低)
|
|
45
|
-
|
|
46
|
-
`store.search` 返回的行没有分数,导致 keyword 补位的结果在混合排序里"无分可比"。
|
|
47
|
-
|
|
48
|
-
**修复**:`scoreKeyword()` 给 keyword 行算启发式分数——标题命中 > 内容命中 > 标签命中,再按重要性(1-5)加权,归一化到 [0,1]。这样 keyword 结果有统一的 score,能参与 hybrid 加权融合。
|
|
49
|
-
|
|
50
|
-
### 6. `npm run stress` 无实现(🟢 低)→ 误报,未修
|
|
51
|
-
|
|
52
|
-
评审认为 stress 脚本不存在。实际 `scripts/stress-dsh.js` 存在,`node scripts/stress-dsh.js` 能正常跑三轴线压测。**未改动**。
|
|
53
|
-
|
|
54
|
-
## 测试
|
|
55
|
-
|
|
56
|
-
全量 **198 / 198 通过**,无回归。修复过程中 api 的两个 search 测试一度失败(handler 未返回 promise 导致响应未写入),已确认修复——search handler 现在返回 promise,异步响应可被 await。
|
|
57
|
-
|
|
58
|
-
## 待办(给桉桉)
|
|
59
|
-
|
|
60
|
-
- 建议补充评审报告中点名的测试:reranker 三策略降级、clustering 聚类质量、vector-index modelHash 漂移、searchMemories 端到端。当前已有 semantic.test.js 覆盖 searchMemories 各 mode + 降级,但 reranker/clustering/vector-index 的专项断言可再加。
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
*—— Ivresse(桉桉的 Ivresse),2026-08-15*
|