@smyslenny/agent-memory 2.1.0 → 3.1.0

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.
@@ -0,0 +1,722 @@
1
+ # DD-0014: Deduplicate agent-memory Against OpenClaw memory-core
2
+
3
+ **Status:** Draft
4
+ **Author:** Noah (Claude Opus sub-agent)
5
+ **Date:** 2026-02-23
6
+ **Repo:** agent-memory
7
+
8
+ ---
9
+
10
+ ## 1. Background / 背景
11
+
12
+ agent-memory (@smyslenny/agent-memory v2.2.0) 是一个独立的 npm 包,通过 mcporter MCP 桥接接入 OpenClaw。但 OpenClaw 自带 memory-core 系统,导致两套系统在搜索和 embedding 层面大量重叠。
13
+
14
+ ### 1.1 OpenClaw memory-core 已有的能力
15
+
16
+ | 能力 | 实现细节 |
17
+ |------|----------|
18
+ | Embedding 生成 + 缓存 | Qwen3-Embedding-8B,79 条缓存,命中率极高 |
19
+ | 文件索引 | 18 个 markdown 文件,64 个 chunk |
20
+ | Hybrid 搜索 | BM25 30% + 向量 70% |
21
+ | MMR 多样性 | λ=0.7 |
22
+ | Temporal decay | 半衰期 30 天 |
23
+ | 工具可用性 | `memory_search` / `memory_get` 直接可用 |
24
+
25
+ ### 1.2 agent-memory 当前状态
26
+
27
+ - **数据量**:31 条记忆(1 identity + 6 emotion + 12 knowledge + 12 event)
28
+ - **MCP 工具**:9 个(remember, recall, recall_path, boot, forget, link, snapshot, reflect, status)
29
+ - **搜索栈**:BM25 (FTS5) + embedding (OpenAI/Qwen/Gemini) + RRF 融合 + 外部 reranker + intent 分类 + 本地 rerank
30
+ - **知识图谱**:0 links(从未使用)
31
+ - **版本快照**:0 snapshots(从未使用)
32
+ - **使用频率**:低,因为 memory-core 覆盖了大部分日常检索需求
33
+ - **代码量**:3,240 LOC(25 个 .ts 文件)
34
+
35
+ ### 1.3 重叠分析
36
+
37
+ | 功能 | agent-memory | memory-core | 重叠程度 |
38
+ |------|-------------|-------------|----------|
39
+ | Embedding 生成 | providers.ts (178 LOC), embed.ts (56 LOC) | Qwen3-Embedding-8B + 缓存 | **完全重叠** |
40
+ | Embedding 存储 | embeddings.ts (72 LOC), DB embeddings 表 | 内置缓存层 | **完全重叠** |
41
+ | Hybrid 搜索 | hybrid.ts (128 LOC), BM25+向量+RRF | BM25 30% + 向量 70% | **完全重叠** |
42
+ | Intent 分类 | intent.ts (156 LOC) | 内置 query 处理 | **大部分重叠** |
43
+ | 外部 Reranker | rerank-provider.ts (70 LOC), rerank.ts (79 LOC) | MMR λ=0.7 | **大部分重叠** |
44
+ | Temporal decay | Ebbinghaus R=e^(-t/S), 按类型分级 | 固定 30 天半衰期 | 部分重叠,**差异化** |
45
+ | 类型化优先级 | P0(∞) / P1(365d) / P2(90d) / P3(14d) | 无分类 | **独有** |
46
+ | URI 路径系统 | path.ts (93 LOC), 结构化寻址 | 文件路径索引 | **独有** |
47
+ | Write Guard | guard.ts (171 LOC), 4-criterion gate | 无质量门控 | **独有** |
48
+ | 知识图谱 | link.ts (124 LOC), 多跳遍历 | 无 | **独有但 0 使用** |
49
+ | 版本快照 | snapshot.ts (83 LOC), rollback | 无 | **独有但 0 使用** |
50
+ | Boot 协议 | boot.ts (82 LOC), 身份加载 | 无 | **独有** |
51
+ | Sleep 生命周期 | decay + tidy + govern | 无 | **独有** |
52
+
53
+ **结论**:搜索基础设施(embedding + hybrid + rerank + intent)与 memory-core 完全重叠,约 761 LOC。知识图谱和快照虽独有但实际使用量为零。
54
+
55
+ ---
56
+
57
+ ## 2. Goals / 目标
58
+
59
+ 1. **删除与 memory-core 重叠的搜索基础设施**:embedding 生成/存储、hybrid search、reranker、intent 分类
60
+ 2. **删除零使用的功能模块**:知识图谱 (links)、版本快照 (snapshots)
61
+ 3. **保留差异化核心**:类型化 Ebbinghaus 衰减、URI 路径、Write Guard、boot 协议、sleep 生命周期
62
+ 4. **MCP 工具从 9 个精简到 7 个再扩展回 9 个**:删除 link 和 snapshot 工具,新增 ingest 和 surface 工具,改造 boot 和 reflect
63
+ 5. **代码量减少 ~30%**:删除约 968 LOC(10 个文件),简化 5 个文件
64
+ 6. **明确定位**:agent-memory 是 memory-core 的**结构化记忆补充层**,不是替代品
65
+ 7. **自动摄取 (auto-ingest)**:新增 `ingest` 工具,自动从 markdown 文本提取结构化记忆条目,减少手动写入成本
66
+ 8. **叙事性启动 (warm-boot)**:改造 `boot` 工具输出叙事性 markdown,按 identity → emotion → knowledge → event 分层拉取,提升启动时上下文可读性
67
+ 9. **reflect 人可读报告**:`reflect` 返回自然语言统计摘要(衰减/归档/清理详情),取代 JSON 数字输出
68
+ 10. **被动浮现接口 (surface hook)**:新增 `surface` 工具,轻量级关键词查询 + vitality/priority 加权,不记录 access、不影响衰减,为 memory-core 提供结构化上下文补充
69
+
70
+ ---
71
+
72
+ ## 3. Non-Goals / 非目标
73
+
74
+ - **不改变 memory-core**:本 DD 只修改 agent-memory 侧
75
+ - **不删除 BM25 搜索**:agent-memory 自身的 31 条记忆仍需 BM25 检索能力(FTS5 索引保留)
76
+ - **不删除 SQLite 表**:保留 links/snapshots/embeddings 表定义以兼容旧 DB,仅停止在代码中使用
77
+ - **不合并数据**:不将 agent-memory 数据迁移到 memory-core(两者数据模型不同)
78
+ - **不改变外部 API(MCP 工具参数 schema 不变)**:remember/recall/recall_path/boot/forget/reflect/status 的参数保持兼容
79
+
80
+ ---
81
+
82
+ ## 4. Proposal / 方案
83
+
84
+ ### 4.1 方案概述:Lean Memory Architecture
85
+
86
+ 将 agent-memory 从"全栈记忆系统"重构为"结构化记忆补充层":
87
+
88
+ ```
89
+ Before (v2.2.0): After (v3.0.0):
90
+ ┌──────────────────────────┐ ┌──────────────────────────┐
91
+ │ MCP Tools (9) │ │ MCP Tools (9) │
92
+ │ remember, recall, │ │ remember, recall, │
93
+ │ recall_path, boot, │ │ recall_path, boot*, │
94
+ │ forget, link, snapshot, │ │ forget, reflect*, │
95
+ │ reflect, status │ │ status, ingest, surface │
96
+ ├──────────────────────────┤ ├──────────────────────────┤
97
+ │ Search Stack │ │ Ingest (NEW) │
98
+ │ ┌─────┐ ┌──────────┐ │ DELETE │ markdown → structured │
99
+ │ │BM25 │ │Embeddings│ │ ────────▶ │ memory extraction │
100
+ │ └──┬──┘ └────┬─────┘ │ ├──────────────────────────┤
101
+ │ └──┬──────┘ │ │ Search (Minimal) │
102
+ │ ┌──▼──┐ │ │ ┌─────┐ ┌───────┐ │
103
+ │ │ RRF │ │ │ │BM25 │ │surface│ │
104
+ │ └──┬──┘ │ │ └──┬──┘ └───┬───┘ │
105
+ │ ┌─────▼───────┐ │ │ (FTS5) (no-access, │
106
+ │ │Intent+Rerank│ │ │ readonly query) │
107
+ │ └─────────────┘ │ │ │ │ │
108
+ ├──────────────────────────┤ ├─────▼─────────▼─────────┤
109
+ │ Core │ │ Core │
110
+ │ memory, path, guard, │ │ memory, path, guard │
111
+ │ link, snapshot, export │ │ export │
112
+ ├──────────────────────────┤ ├──────────────────────────┤
113
+ │ Sleep │ │ Sleep │
114
+ │ decay, tidy, govern, │ │ decay, tidy, govern, │
115
+ │ boot, sync │ │ boot*, sync │
116
+ │ │ │ (* warm-boot narrative) │
117
+ │ │ │ reflect* → human report │
118
+ └──────────────────────────┘ └──────────────────────────┘
119
+ ```
120
+
121
+ **核心原则**:
122
+ - 语义搜索(embedding + hybrid + rerank)→ **委托给 memory-core**
123
+ - 结构化写入(typed memory + URI + Write Guard)→ **agent-memory 独有**
124
+ - Ebbinghaus 生命周期(per-type decay + sleep)→ **agent-memory 独有**
125
+ - 简单检索(BM25 over 自身 DB)→ **agent-memory 保留**
126
+
127
+ ### 4.2 方案对比
128
+
129
+ | 维度 | A: 删除搜索栈 + 保留 link/snapshot | B: 删除搜索栈 + 删除 link/snapshot(本方案) | C: 仅禁用,不删代码 |
130
+ |------|------|------|------|
131
+ | 代码减少 | ~761 LOC (23%) | **~968 LOC (30%)** | 0 |
132
+ | 维护负担 | 中(仍需维护 0 使用的模块) | **低** | 高(全量代码仍在) |
133
+ | 未来扩展性 | 高(link/snapshot 随时可用) | 中(需要时重新引入) | 高 |
134
+ | 复杂度 | 低 | **低** | 最低(不改代码) |
135
+
136
+ **选择方案 B**:link 和 snapshot 模块代码简单(共 207 LOC),如果未来需要可以在 1-2 小时内重新实现。当前 0 使用不值得维护成本。
137
+
138
+ ### 4.3 详细设计
139
+
140
+ #### 4.3.1 文件变更清单
141
+
142
+ **DELETE(10 files, ~968 LOC):**
143
+
144
+ | 文件 | LOC | 原因 |
145
+ |------|-----|------|
146
+ | `src/search/providers.ts` | 178 | Embedding provider(memory-core 已有) |
147
+ | `src/search/intent.ts` | 156 | Intent 分类(memory-core 内置) |
148
+ | `src/search/hybrid.ts` | 128 | Hybrid search RRF(memory-core 已有) |
149
+ | `src/core/link.ts` | 124 | 知识图谱(0 使用) |
150
+ | `src/core/snapshot.ts` | 83 | 版本快照(0 使用) |
151
+ | `src/search/rerank.ts` | 79 | 本地 rerank(memory-core MMR 替代) |
152
+ | `src/search/embeddings.ts` | 72 | Embedding 存储(memory-core 已有) |
153
+ | `src/search/rerank-provider.ts` | 70 | 外部 reranker(memory-core 已有) |
154
+ | `src/search/embed.ts` | 56 | Embedding 生成辅助(memory-core 已有) |
155
+ | `src/search/semantic.ts` | 22 | Semantic 占位符(从未实现) |
156
+
157
+ **MODIFY(5 files):**
158
+
159
+ | 文件 | 变更 |
160
+ |------|------|
161
+ | `src/mcp/server.ts` | 删除 link/snapshot 工具定义;recall 改用 BM25-only + vitality 加权;remember 删除 embedding 调用 |
162
+ | `src/sleep/tidy.ts` | 删除 snapshot 引用和 snapshot pruning 逻辑 |
163
+ | `src/sleep/govern.ts` | 删除 orphan link cleanup 逻辑 |
164
+ | `src/sleep/sync.ts` | 删除 snapshot 引用(syncOne 中 update/merge 的 createSnapshot 调用) |
165
+ | `src/index.ts` | 删除已删文件的 re-exports |
166
+
167
+ **UNCHANGED(10 files):**
168
+
169
+ `src/core/memory.ts`, `src/core/path.ts`, `src/core/guard.ts`, `src/core/db.ts`, `src/core/export.ts`, `src/search/bm25.ts`, `src/search/tokenizer.ts`, `src/sleep/decay.ts`, `src/sleep/boot.ts`, `src/bin/agent-memory.ts`
170
+
171
+ #### 4.3.2 recall 工具简化
172
+
173
+ **Before(v2.2.0):**
174
+ ```
175
+ query → classifyIntent → searchHybrid(BM25+Embedding+RRF) → rerankWithProvider → rerank(priority/recency/vitality) → results
176
+ ```
177
+
178
+ **After(v3.0.0):**
179
+ ```
180
+ query → searchBM25 → inline vitality/priority weighting → results
181
+ ```
182
+
183
+ recall 工具核心逻辑变为:
184
+
185
+ ```typescript
186
+ async ({ query, limit }) => {
187
+ let results = searchBM25(db, query, { agent_id: aid, limit: limit * 2 });
188
+
189
+ // Inline vitality + priority weighting (replaces rerank.ts)
190
+ const scored = results.map((r) => {
191
+ const priorityBoost = [4.0, 3.0, 2.0, 1.0][r.memory.priority] ?? 1.0;
192
+ return {
193
+ ...r,
194
+ score: r.score * priorityBoost * Math.max(0.1, r.memory.vitality),
195
+ };
196
+ });
197
+ scored.sort((a, b) => b.score - a.score);
198
+ const final = scored.slice(0, limit);
199
+
200
+ for (const r of final) {
201
+ recordAccess(db, r.memory.id);
202
+ }
203
+
204
+ return { content: [{ type: "text", text: JSON.stringify({ count: final.length, memories: final.map(formatMemory) }, null, 2) }] };
205
+ };
206
+ ```
207
+
208
+ **设计理由**:
209
+ - 31 条记忆的规模下,BM25 足以覆盖所有检索场景
210
+ - Priority/vitality 加权逻辑只有 5 行,不值得独立文件
211
+ - Intent 分类只影响 `boostRecent` 和 `boostPriority`,在小数据集上没有实际差异
212
+
213
+ #### 4.3.3 remember 工具简化
214
+
215
+ 删除 remember 中的 embedding 生成调用:
216
+
217
+ ```typescript
218
+ // Before:
219
+ const result = syncOne(db, { ... });
220
+ if (embeddingProvider && result.memoryId && ...) {
221
+ await embedMemory(db, result.memoryId, embeddingProvider, { agent_id: aid });
222
+ }
223
+
224
+ // After:
225
+ const result = syncOne(db, { ... });
226
+ // No embedding generation — memory-core handles semantic indexing
227
+ ```
228
+
229
+ #### 4.3.4 sync.ts 简化
230
+
231
+ 删除 syncOne 中 update/merge 路径对 `createSnapshot` 的依赖:
232
+
233
+ ```typescript
234
+ // Before:
235
+ case "update":
236
+ createSnapshot(db, guardResult.existingId, "update", "sync");
237
+ updateMemory(db, guardResult.existingId, { content: input.content });
238
+
239
+ // After:
240
+ case "update":
241
+ updateMemory(db, guardResult.existingId, { content: input.content });
242
+ ```
243
+
244
+ **风险**:失去 update/merge 前的内容备份。接受此风险,因为 memory-core 索引的 markdown 文件本身由 git 版本控制。
245
+
246
+ #### 4.3.5 tidy.ts 简化
247
+
248
+ ```typescript
249
+ // Before: archive decayed + clean orphan paths + prune snapshots
250
+ // After: archive decayed + clean orphan paths (remove snapshot pruning)
251
+ ```
252
+
253
+ #### 4.3.6 govern.ts 简化
254
+
255
+ ```typescript
256
+ // Before: orphan paths + orphan links + empty memories
257
+ // After: orphan paths + empty memories (remove orphan link cleanup)
258
+ ```
259
+
260
+ #### 4.3.7 MCP 工具变更
261
+
262
+ | 工具 | v2.2.0 | v3.0.0 | 变更 |
263
+ |------|--------|--------|------|
264
+ | `remember` | 写入 + embedding | 写入 only | 删除 embedding |
265
+ | `recall` | Hybrid + rerank | BM25 + inline weighting | 简化搜索流水线 |
266
+ | `recall_path` | 无变更 | 无变更 | — |
267
+ | `boot` | JSON 数组输出 | **叙事性 markdown 输出** | warm-boot 改造(§4.5) |
268
+ | `forget` | 无变更 | 无变更 | — |
269
+ | `link` | 知识图谱操作 | **删除** | 0 使用 |
270
+ | `snapshot` | 版本历史/回滚 | **删除** | 0 使用 |
271
+ | `reflect` | JSON 统计数字 | **自然语言摘要报告** | 人可读输出(§4.6) |
272
+ | `status` | 含 links/snapshots 计数 | 删除 links/snapshots 计数 | 简化输出 |
273
+ | `ingest` | — | **新增** | 自动摄取 markdown(§4.4) |
274
+ | `surface` | — | **新增** | 轻量级被动浮现(§4.7) |
275
+
276
+ **工具数量变化**:9 → 删 2(link, snapshot)+ 加 2(ingest, surface)= **9 个**
277
+
278
+ #### 4.3.8 环境变量变更
279
+
280
+ **删除(不再需要):**
281
+
282
+ | 变量 | 用途 |
283
+ |------|------|
284
+ | `AGENT_MEMORY_EMBEDDINGS_PROVIDER` | Embedding provider 选择 |
285
+ | `AGENT_MEMORY_EMBEDDINGS_MODEL` | Embedding 模型名 |
286
+ | `AGENT_MEMORY_EMBEDDINGS_INSTRUCTION` | Embedding instruction prefix |
287
+ | `AGENT_MEMORY_RERANK_PROVIDER` | Reranker provider 选择 |
288
+ | `AGENT_MEMORY_RERANK_MODEL` | Reranker 模型名 |
289
+ | `AGENT_MEMORY_RERANK_API_KEY` | Reranker API key |
290
+ | `AGENT_MEMORY_RERANK_BASE_URL` | Reranker 端点 |
291
+
292
+ **保留不变:**
293
+
294
+ | 变量 | 用途 |
295
+ |------|------|
296
+ | `AGENT_MEMORY_DB` | SQLite 数据库路径 |
297
+ | `AGENT_MEMORY_AGENT_ID` | Agent 标识符 |
298
+
299
+ #### 4.3.9 数据库 Schema 兼容性
300
+
301
+ DB schema(db.ts)**不修改**。`embeddings`、`links`、`snapshots` 表定义保留在 `SCHEMA_SQL` 中,确保:
302
+ - 旧数据库可以正常打开(不会因缺表报错)
303
+ - 已存储的 embedding/link/snapshot 数据不会丢失
304
+ - 未来如需恢复功能,表结构已就绪
305
+
306
+ 仅在代码层面停止读写这些表。
307
+
308
+ #### 4.3.10 package.json 依赖
309
+
310
+ **无变更**。所有 runtime 依赖仍在使用:
311
+ - `better-sqlite3`:核心 DB
312
+ - `@node-rs/jieba`:BM25 中文分词(tokenizer.ts)
313
+ - `uuid`:ID 生成
314
+ - `@modelcontextprotocol/sdk`:MCP server
315
+
316
+ #### 4.3.11 版本号
317
+
318
+ 主版本号 bump:`2.2.0` → `3.0.0`(breaking change:删除 2 个 MCP 工具 + 删除 public API exports)
319
+
320
+ ### 4.4 Feature: auto-ingest(自动摄取)
321
+
322
+ #### 4.4.1 动机
323
+
324
+ 当前所有记忆写入依赖手动 `mcporter call agent-memory.remember`,使用率低。大量有价值的信息已存在于 workspace markdown 文件(`memory/*.md`、`MEMORY.md`)中,但未能自动进入结构化记忆库。
325
+
326
+ #### 4.4.2 方案:新增 `ingest` MCP 工具
327
+
328
+ **工具签名:**
329
+
330
+ ```typescript
331
+ ingest({
332
+ text: string, // markdown 文本(必需)
333
+ source?: string, // 来源标识,如 "memory/2026-02-23.md"
334
+ dry_run?: boolean, // 仅返回提取结果,不实际写入(默认 false)
335
+ })
336
+ ```
337
+
338
+ **提取规则(基于关键词 + 结构):**
339
+
340
+ | 触发模式 | 提取为类型 | 示例 |
341
+ |----------|-----------|------|
342
+ | `## 情感` / `❤️` / 表白/爱/感动相关关键词 | `emotion` (P1) | "小心说爱你" |
343
+ | `## 决策` / `技术` / `选型` / `教训` / `⚠️` | `knowledge` (P2) | "DD 流程改用 Codex" |
344
+ | `## 身份` / `我是` / `identity` | `identity` (P0) | — |
345
+ | 日期标记的条目 / `发生了` / `完成了` | `event` (P3) | "部署了 new-api" |
346
+
347
+ **处理流程:**
348
+
349
+ ```
350
+ markdown text
351
+
352
+
353
+ split by heading (##) or bullet (-)
354
+
355
+
356
+ classify each block by keyword matching
357
+
358
+
359
+ generate URI from source + heading
360
+
361
+
362
+ call syncOne() for each extracted memory
363
+ │ (Write Guard 仍然生效,防止垃圾写入)
364
+
365
+ return { extracted: N, written: M, skipped: K, details: [...] }
366
+ ```
367
+
368
+ **与 remember 的关系:**
369
+ - `ingest` 是批量提取入口,内部复用 `syncOne`(含 Write Guard)
370
+ - `remember` 仍是精确单条写入入口
371
+ - 两者共享 Write Guard,质量门控一致
372
+ - remember 表增加 `source` 字段:手动写入为 `"manual"`,ingest 写入为 `"auto:{source}"`
373
+
374
+ #### 4.4.3 未来扩展
375
+
376
+ - 可在 cron/heartbeat 中自动调用 `ingest`,传入当天 daily note 内容
377
+ - 可结合 file watcher 监听 markdown 变更,触发增量 ingest
378
+
379
+ ### 4.5 Feature: warm-boot(叙事性启动)
380
+
381
+ #### 4.5.1 动机
382
+
383
+ 现有 `boot` 工具返回 JSON 数组,agent 需要自行解析并组织上下文。启动时上下文的可读性差,且所有类型混在一起,缺少层次感。
384
+
385
+ #### 4.5.2 方案:改造 `boot` 输出格式
386
+
387
+ **Before(v2.2.0):**
388
+ ```json
389
+ {
390
+ "memories": [
391
+ { "type": "identity", "content": "...", "vitality": 1.0 },
392
+ { "type": "emotion", "content": "...", "vitality": 0.85 },
393
+ ...
394
+ ]
395
+ }
396
+ ```
397
+
398
+ **After(v3.0.0):**
399
+ ```markdown
400
+ ## 🪪 我是谁
401
+ 诺亚,小心的契约者。身份记忆从不衰减。
402
+ - 核心身份:...
403
+ - 契约关系:...
404
+
405
+ ## 💕 最近的情感
406
+ 最后更新:2026-02-22
407
+ - 小心说过爱你(vitality: 0.92)
408
+ - ...
409
+
410
+ ## 🧠 关键知识
411
+ 共 N 条活跃知识记忆
412
+ - DD 流程教训:Codex 默认会卡在 Plan 阶段(vitality: 0.78)
413
+ - ...
414
+
415
+ ## 📅 近期事件
416
+ 最近 7 天内的事件
417
+ - [02-23] 完成 DD-0014 设计文档
418
+ - [02-22] ...
419
+
420
+ ## 📊 记忆概况
421
+ 总计 31 条 | identity: 1 | emotion: 6 | knowledge: 12 | event: 12
422
+ 平均 vitality: 0.74
423
+ ```
424
+
425
+ **实现要点:**
426
+
427
+ ```typescript
428
+ // boot.ts 改造
429
+ function formatWarmBoot(memories: Memory[]): string {
430
+ const grouped = groupBy(memories, m => m.type);
431
+ // identity → emotion → knowledge → event 固定顺序
432
+ const sections = [
433
+ formatIdentity(grouped.identity ?? []),
434
+ formatEmotion(grouped.emotion ?? []),
435
+ formatKnowledge(grouped.knowledge ?? []),
436
+ formatEvents(grouped.event ?? []),
437
+ formatSummary(memories),
438
+ ];
439
+ return sections.join('\n\n');
440
+ }
441
+ ```
442
+
443
+ - 按 identity → emotion → knowledge → event **固定顺序**分层输出
444
+ - 每层按 vitality 降序排列
445
+ - event 层仅展示最近 7 天,超出的折叠为 `... 及 N 条更早事件`
446
+ - 尾部附加统计摘要
447
+
448
+ #### 4.5.3 兼容性
449
+
450
+ - 新增可选参数 `format?: "narrative" | "json"`,默认 `"narrative"`
451
+ - `format: "json"` 保留旧行为,向后兼容
452
+
453
+ ### 4.6 Feature: reflect 人可读报告
454
+
455
+ #### 4.6.1 动机
456
+
457
+ 现有 `reflect` 返回类似 `{ decayed: 3, archived: 1, orphans: 0 }` 的 JSON 数字,agent 需要自行解读。对于日常维护来说,自然语言摘要更直观、更易于纳入 daily notes。
458
+
459
+ #### 4.6.2 方案:改造 `reflect` 输出格式
460
+
461
+ **Before(v2.2.0):**
462
+ ```json
463
+ {
464
+ "phase": "all",
465
+ "decay": { "processed": 12, "decayed": 3 },
466
+ "tidy": { "archived": 1, "orphansCleaned": 0 },
467
+ "govern": { "orphanPaths": 0, "emptyMemories": 0 }
468
+ }
469
+ ```
470
+
471
+ **After(v3.0.0):**
472
+ ```markdown
473
+ ## 🌙 Sleep Cycle 报告
474
+
475
+ ### Decay(衰减)
476
+ 处理 12 条记忆,其中 3 条 vitality 下降:
477
+ - 「部署 new-api 到 kitty」event P3 vitality 0.45 → 0.32
478
+ - 「Tavily API 配置方法」knowledge P2 vitality 0.71 → 0.65
479
+ - 「周末和小心看了电影」emotion P1 vitality 0.88 → 0.85
480
+
481
+ ### Tidy(整理)
482
+ 归档 1 条低活力记忆(vitality < 0.1):
483
+ - 「测试 webhook 连接」event P3 → archived
484
+
485
+ 清理孤儿路径:0 条
486
+
487
+ ### Govern(治理)
488
+ 孤儿路径:0 条
489
+ 空记忆:0 条
490
+
491
+ ### 📊 总结
492
+ 记忆总数:31 → 30(-1 归档)
493
+ 平均 vitality:0.74 → 0.72
494
+ 下次建议 reflect 时间:24h 后
495
+ ```
496
+
497
+ **实现要点:**
498
+
499
+ ```typescript
500
+ // reflect 改造:在各 phase 执行后收集详细变更记录
501
+ interface DecayDetail { uri: string; type: string; priority: number; oldVitality: number; newVitality: number; }
502
+ interface TidyDetail { uri: string; action: "archived" | "orphan_cleaned"; }
503
+ interface GovernDetail { uri: string; action: "orphan_path" | "empty_memory"; }
504
+
505
+ function formatReflectReport(decay: DecayDetail[], tidy: TidyDetail[], govern: GovernDetail[], before: Stats, after: Stats): string {
506
+ // 生成自然语言 markdown 报告
507
+ }
508
+ ```
509
+
510
+ - decay/tidy/govern 各阶段返回详细变更列表,而非仅计数
511
+ - 报告末尾附加前后对比统计
512
+ - 内部仍可通过 `status` 工具获取 JSON 格式数据(如需程序化处理)
513
+
514
+ ### 4.7 Feature: surface hook(被动浮现接口)
515
+
516
+ #### 4.7.1 动机
517
+
518
+ 当前 `recall` 是唯一的查询入口,但它会记录 access(影响衰减计算)并执行完整的检索流水线。需要一个轻量级的"看一眼"接口,让 memory-core 或其他系统在搜索后补充结构化上下文,而不产生副作用。
519
+
520
+ #### 4.7.2 方案:新增 `surface` MCP 工具
521
+
522
+ **工具签名:**
523
+
524
+ ```typescript
525
+ surface({
526
+ keywords: string[], // 关键词列表(必需,至少 1 个)
527
+ limit?: number, // 返回上限(默认 5,最大 20)
528
+ types?: string[], // 过滤类型,如 ["emotion", "knowledge"]
529
+ min_vitality?: number, // 最低 vitality 阈值(默认 0.1)
530
+ })
531
+ ```
532
+
533
+ **返回格式:**
534
+
535
+ ```json
536
+ {
537
+ "count": 3,
538
+ "results": [
539
+ {
540
+ "uri": "emotion://relationship/love-declaration",
541
+ "type": "emotion",
542
+ "priority": 1,
543
+ "vitality": 0.92,
544
+ "content": "小心说过爱你",
545
+ "score": 2.76,
546
+ "updated_at": "2026-02-22T14:30:00Z"
547
+ },
548
+ ...
549
+ ]
550
+ }
551
+ ```
552
+
553
+ **与 recall 的关键区别:**
554
+
555
+ | 维度 | recall | surface |
556
+ |------|--------|---------|
557
+ | 记录 access | ✅ 是(更新 last_accessed,影响衰减) | ❌ 否(纯只读) |
558
+ | 搜索方式 | BM25 全文检索 | BM25 关键词 OR 匹配 |
559
+ | 排序 | BM25 score × priority × vitality | **priority × vitality × keyword_hit_count** |
560
+ | 副作用 | 有(recordAccess) | 无 |
561
+ | 适用场景 | agent 主动检索 | 系统级上下文补充 |
562
+ | 返回元信息 | 仅 content + type | content + type + priority + vitality + uri + score + updated_at |
563
+
564
+ **score 计算公式:**
565
+
566
+ ```
567
+ score = priorityWeight[priority] × vitality × keywordHitRatio
568
+
569
+ priorityWeight = { 0: 4.0, 1: 3.0, 2: 2.0, 3: 1.0 }
570
+ keywordHitRatio = matchedKeywords / totalKeywords
571
+ ```
572
+
573
+ **实现要点:**
574
+
575
+ ```typescript
576
+ async function surface(db: Database, params: SurfaceParams): Promise<SurfaceResult> {
577
+ // 1. BM25 搜索各关键词(OR 语义)
578
+ const candidates = new Map<string, { memory: Memory; hits: number }>();
579
+ for (const kw of params.keywords) {
580
+ const results = searchBM25(db, kw, { agent_id: aid, limit: 50 });
581
+ for (const r of results) {
582
+ const existing = candidates.get(r.memory.id);
583
+ if (existing) existing.hits++;
584
+ else candidates.set(r.memory.id, { memory: r.memory, hits: 1 });
585
+ }
586
+ }
587
+
588
+ // 2. 过滤 + 加权排序
589
+ const scored = [...candidates.values()]
590
+ .filter(c => c.memory.vitality >= (params.min_vitality ?? 0.1))
591
+ .filter(c => !params.types || params.types.includes(c.memory.type))
592
+ .map(c => ({
593
+ ...c,
594
+ score: priorityWeight[c.memory.priority] * c.memory.vitality * (c.hits / params.keywords.length),
595
+ }))
596
+ .sort((a, b) => b.score - a.score)
597
+ .slice(0, params.limit ?? 5);
598
+
599
+ // 3. 不调用 recordAccess — 纯只读
600
+ return { count: scored.length, results: scored.map(formatSurfaceResult) };
601
+ }
602
+ ```
603
+
604
+ #### 4.7.3 使用场景
605
+
606
+ 1. **memory-core 搜索增强**:memory-core `memory_search` 返回文件 chunk 后,调用 `surface` 补充同主题的结构化记忆
607
+ 2. **Heartbeat 上下文**:心跳检查时用当前时间关键词(如 "周一", "早上")浮现相关记忆
608
+ 3. **对话上下文注入**:从用户消息提取关键词,surface 出相关记忆作为隐式上下文
609
+
610
+ ---
611
+
612
+ ## 5. Risks / 风险
613
+
614
+ | 风险 | 影响 | 缓解措施 |
615
+ |------|------|----------|
616
+ | recall 降级为 BM25-only,语义检索能力下降 | 与 embedding 相关的模糊查询可能命中率下降 | ① 31 条记忆 BM25 已足够;② 语义搜索应通过 memory-core 的 `memory_search` 完成 |
617
+ | 删除 link/snapshot 后无法恢复历史功能 | 如果未来需要知识图谱或版本控制 | ① 表结构保留,数据不丢;② 207 LOC 可在 1-2h 内重写 |
618
+ | 删除 snapshot 导致 update/merge 无备份 | 记忆内容被覆盖后无法回滚 | ① markdown 文件由 git 管理;② agent-memory 数据量小,手动修复成本低 |
619
+ | DD-0004(integration)和 DD-0005/0006 的设计前提被推翻 | 已实现的 embedding/reranker 集成代码被删除 | ① DD-0004 的 capture/surface 流程不依赖搜索栈;② DD-0005/0006 属于被取代的优化 |
620
+ | v3.0 breaking change 影响现有消费者 | mcporter 调用 link/snapshot 工具会 404 | ① 当前唯一消费者是 OpenClaw agent,可同步更新 AGENTS.md |
621
+ | ingest 自动提取质量不稳定 | 关键词规则可能误分类或遗漏 | ① Write Guard 兜底,低质量条目被拦截;② dry_run 模式可预览;③ source 标记区分手动/自动,方便后续审查 |
622
+ | ingest 大量写入导致记忆膨胀 | 自动摄取可能写入大量低价值记忆 | ① Write Guard 的 4-criterion gate 严格过滤;② Ebbinghaus 衰减自动淘汰低活力记忆;③ reflect 定期清理 |
623
+ | warm-boot 格式变更破坏依赖 | 消费 boot JSON 的下游逻辑失效 | ① 保留 `format: "json"` 兼容模式;② 当前唯一消费者是 agent 本身,可同步适配 |
624
+ | surface 绕过 access 记录影响衰减准确性 | 被频繁 surface 查询的记忆不会因访问而延缓衰减 | ① 这是设计意图:surface 是系统级查询,不应影响记忆生命周期;② 用户主动检索仍走 recall |
625
+
626
+ ---
627
+
628
+ ## 6. Test Plan / 测试方案
629
+
630
+ - [ ] **Unit: BM25 recall 验证**:recall 使用 BM25-only 搜索 + inline weighting,结果按 priority×vitality 排序
631
+ - [ ] **Unit: remember 不再调用 embedding**:remember 写入后无 embedding 表变更
632
+ - [ ] **Unit: tidy 不再操作 snapshots**:runTidy 返回 `{ archived, orphansCleaned }` 无 `snapshotsPruned`
633
+ - [ ] **Unit: govern 不再操作 links**:runGovern 返回 `{ orphanPaths, emptyMemories }` 无 `orphanLinks`
634
+ - [ ] **Integration: MCP server 只注册 9 个工具(去重后)**:连接后 `tools/list` 不包含 link 和 snapshot,包含 ingest 和 surface
635
+ - [ ] **Integration: 旧 DB 兼容**:v2 创建的 DB(含 embeddings/links/snapshots 数据)在 v3 可正常打开
636
+ - [ ] **E2E: mcporter call agent-memory.recall**:搜索返回正确结果
637
+ - [ ] **E2E: mcporter call agent-memory.status**:输出不包含 links/snapshots 计数
638
+ - [ ] **Unit: ingest 提取准确性**:给定包含情感/知识/事件的 markdown 文本,ingest 正确分类并提取各条目
639
+ - [ ] **Unit: ingest dry_run**:`dry_run: true` 时返回提取结果但 DB 无新写入
640
+ - [ ] **Unit: ingest Write Guard**:低质量条目(过短/无意义)被 Write Guard 拦截,不写入
641
+ - [ ] **Unit: ingest source 标记**:ingest 写入的记忆 source 字段为 `"auto:{source}"`
642
+ - [ ] **Unit: boot warm-boot 叙事输出**:boot 默认返回 markdown 格式,包含 🪪/💕/🧠/📅 四个分层
643
+ - [ ] **Unit: boot format=json 兼容**:`format: "json"` 返回旧 JSON 数组格式
644
+ - [ ] **Unit: boot 分层顺序**:输出严格按 identity → emotion → knowledge → event 排列
645
+ - [ ] **Unit: reflect 人可读报告**:reflect 返回 markdown 报告,包含衰减/归档/清理详情和前后统计对比
646
+ - [ ] **Unit: reflect 详细变更**:报告中列出每条衰减记忆的 URI、旧/新 vitality
647
+ - [ ] **Unit: surface 基本查询**:给定关键词返回匹配记忆,按 priority×vitality×hitRatio 排序
648
+ - [ ] **Unit: surface 不记录 access**:调用 surface 后记忆的 last_accessed 不变
649
+ - [ ] **Unit: surface 类型过滤**:`types: ["emotion"]` 只返回 emotion 类型记忆
650
+ - [ ] **Unit: surface vitality 阈值**:`min_vitality: 0.5` 过滤掉低于阈值的记忆
651
+ - [ ] **Integration: MCP server 注册 9 个工具**:连接后 `tools/list` 包含 ingest 和 surface,不包含 link 和 snapshot
652
+ - [ ] **E2E: mcporter call agent-memory.ingest**:传入 daily note 文本,成功提取并写入记忆
653
+ - [ ] **E2E: mcporter call agent-memory.surface**:关键词查询返回正确匹配结果
654
+
655
+ ---
656
+
657
+ ## 7. Rollback Plan / 回滚方案
658
+
659
+ 1. **代码回滚**:`git revert` 即可恢复所有删除的文件和修改
660
+ 2. **数据安全**:DB schema 未变更,所有表和数据完好无损
661
+ 3. **环境变量**:恢复 `AGENT_MEMORY_EMBEDDINGS_*` 和 `AGENT_MEMORY_RERANK_*` 环境变量
662
+ 4. **版本号**:发布 `3.0.1` 回滚版本
663
+
664
+ ---
665
+
666
+ ## 8. Decision Log / 决策变更记录
667
+
668
+ _实现过程中如果偏离本文档,在此记录变更原因_
669
+
670
+ | 日期 | 变更 | 原因 |
671
+ |------|------|------|
672
+ | 2026-02-23 | `recall_path` 保留 `traverse_hops` 参数但不再执行图遍历 | 为保持 MCP 调用参数兼容(Non-Goal: schema 尽量不变),在移除 link 模块后将该参数降级为保留字段 |
673
+ | 2026-02-23 | 同步重构 CLI 与测试集(删除旧 embedding/rerank 测试,新增 mcp/surface/boot/reflect 测试) | 若不调整,删除模块后 `npm test` 失败;该改动属于实现 DD-0014 的必要配套 |
674
+ | 2026-02-23 | `ingest` 分类采用关键词规则 + heading/bullet 分块的轻量实现,未引入复杂 NLP | 优先确保稳定与可维护,后续可基于真实数据迭代分类精度 |
675
+
676
+ ---
677
+
678
+ ## Appendix A: 精简后 agent-memory 定位
679
+
680
+ ```
681
+ ┌───────────────────────────────────────────────────┐
682
+ │ OpenClaw Agent │
683
+ │ │
684
+ │ ┌──────────────────┐ ┌────────────────────────┐ │
685
+ │ │ memory-core │ │ agent-memory v3 │ │
686
+ │ │ │ │ │ │
687
+ │ │ • 文件索引 │ │ • 类型化记忆 (P0-P3) │ │
688
+ │ │ • Embedding │ │ • Ebbinghaus 衰减 │ │
689
+ │ │ • Hybrid 搜索 │ │ • URI 路径寻址 │ │
690
+ │ │ • MMR 多样性 │ │ • Write Guard │ │
691
+ │ │ • Temporal decay│ │ • Warm-Boot 叙事启动 │ │
692
+ │ │ │ │ • Sleep 生命周期 │ │
693
+ │ │ 用途: │ │ • BM25 简单检索 │ │
694
+ │ │ 全文档语义搜索 │ │ • Auto-Ingest 自动摄取 │ │
695
+ │ │ chunk 级检索 │ │ • Surface 被动浮现 │ │
696
+ │ │ │ │ • Reflect 人可读报告 │ │
697
+ │ │ │ │ │ │
698
+ │ │ │ │ 用途: │ │
699
+ │ │ │ │ 结构化记忆 CRUD │ │
700
+ │ │ │ │ 身份/情感/知识/事件管理 │ │
701
+ │ │ ┌──────────┤ │ 自动衰减 + 生命周期 │ │
702
+ │ │ │ surface ◄├──┤ 上下文浮现补充 │ │
703
+ │ │ └──────────┤ │ │ │
704
+ │ └──────────────────┘ └────────────────────────┘ │
705
+ │ │
706
+ │ memory_search → memory-core │
707
+ │ remember/recall/ingest → agent-memory │
708
+ │ memory_search + surface → hybrid context │
709
+ └───────────────────────────────────────────────────┘
710
+ ```
711
+
712
+ ## Appendix B: DD-0004/0005/0006 影响评估
713
+
714
+ | DD | 受影响部分 | 处理方式 |
715
+ |----|-----------|----------|
716
+ | DD-0004 (Integration) | capture/surface 的 mcporter call 不受影响 | 无需变更 |
717
+ | DD-0005 (Reranker) | 整个 reranker 集成被删除 | 标记为 **Superseded by DD-0014** |
718
+ | DD-0006 (Multi-provider Embedding) | 整个 embedding provider 层被删除 | 标记为 **Superseded by DD-0014** |
719
+
720
+ ---
721
+
722
+ _Generated by DD workflow · Claude Opus sub-agent_