Files
HomeAgent/docs/zh/memory-usability-plan.md
JianFeeeee 4963d70215 docs: 逐篇核实全部文档的完成状态 —— 修正 6 处与代码不符的自述
## 上一轮的遗漏

上次只补了"缺状态行",但**32 篇正文里的状态声明基本没动**。而那些声明
大多是 1~2 周前写的,代码早已推进。本轮逐篇对代码核实。

## 逐篇核实结果(方法:读当前代码,不看文档的历史描述)

### 1. memory-usability-plan:待执行 → 已实施

文档写「状态:**待执行**(2026-10-22)」。三条根因实测**全部已修**:

| 根因 | 实测 |
|---|---|
| R1 向量化输入是 jieba 词袋 | ✅ `coldcontext.go` 的 `denseFor` 明写「用事件正文(原文),不用 Summary(jieba 词袋)」 |
| R2 媒体块无语义 | ✅ 块 union 化,媒体即块自带描述 |
| R3 提示词无引导 | ✅ `tooldefs.go` 明写 `coldcontext_query` 把历史**插回时序**,并下沉到 kernel-memory skill |

### 2. entities-retirement-plan:执行中 → 方案已定、执行未完成

写「三表全退,执行中」,容易被读成"快完成了"。实测生产库:
`entities 1292 / relations 978 / sentences 66` 行**仍在用**。
迁移工具就绪,但生产未切换 —— 这个区别对判断风险很关键。

### 3. scene-memory-fix-plan:部分落地 → 7 步里 5 步完成

逐条核实:步骤 1/2/3/5/6 已完成(含 SDK 的 `ScenePolicy`、
`scene_emerge.go` 已区分 `peer:group_*` 与 `peer:user_*`);
步骤 4(清现网垃圾场景)与 7(收口验证)属现场操作,未复核。

### 4. prod-migration-checklist:⚠️ 最危险的一处

写「**状态**:HologramAgent 已停机(inactive + disabled)」——
那是一次性迁移(2026-10-04)当场的真实状态,**不是现在的**。
照着它读会以为服务停着。改为明确归档说明 + 指向现行 runbook。

### 5. abstention-criterion:「拒答尚未接入生产路径」已不成立

实测拒答已下沉到图库层(`RecallBlocksGuarded`),生产路径
`toolcall.go:1312` 与端到端探针**走同一条路**。保留了下沉的理由
(第一版放上层,探针绕过它 ⇒ 判据测不到被测路径)。
剩余两项(泛指词检测、n=10 样本偏小)仍开放。

### 6. toolcall-parallel-execution-plan:阶段 0.5 起为待办 → 全部已完成

实测阶段 0/0.2/0.5/1/2/2.5 与 3/4 全部完成,遗留项也已解决。

## 顺带修正两处写法

- `memory-restructure-plan.md`:顶部有「执行结果摘要」但正文各步骤仍写
  `✗ 未开始`(原始计划快照,有意保留)。加一句读法说明,
  避免读者拿正文的快照当现状。
- `future-plan.md` §六:新增 5 条实测确认的待办并去掉 2 条重复项
  (遗留表退役、中断子系统原先各出现两次)。

## 核对

- docs/zh 32 篇全部有状态行,且与代码实测一致
- 代码块全部配平;文档间相对链接全部有效
- go build 通过(纯文档改动)
2026-10-31 08:20:47 +08:00

14 KiB
Raw Blame History

三层记忆可用性修复计划:统一向量化 + 提示词让 agent 真正理解

归档说明(2026-10-27):本文是历史计划,术语已过时 —— doc_query / doc_commit 已改名为 coldcontext_query / coldcontext_commit, 图记忆(Graph)在对外语义上改称 stablemem(稳定记忆)。 现行架构与术语以 assets/docs/zh/ARCHITECTURE.md 为准;本文保留作为决策记录。

状态:已实施(2026-10-22 计划,2026-10-31 逐条复核确认)。

三条根因的实测结论(复核方法:读当前代码,不看本文的历史描述):

根因 本文当时的判断 2026-10-31 实测
R1 向量化输入是 jieba 词袋 需改为正文 ✅ 已修 —— coldcontext.go 的 denseFor 明写「用事件正文(原文),不用 Summary(jieba 词袋)」
R2 媒体块无语义 需补 ✅ 已修(块 union 化,媒体即块,自带描述)
R3 提示词无引导 需补 L1/L2/L3 与回填语义 ✅ 已修 —— tooldefs.go 明写 coldcontext_query 把历史事件插回时序,并下沉到 kernel-memory skill

本文保留作为**「结构判据不能替代质量判据」的决策记录 —— 那条教训(16 条判据 6 个变异全绿,而没有一条验证"检索结果与查询相关") 比计划本身更值得留。 前置:docs/zh/memory-blockflow-repair-plan.md(L1↔L2 结构同构,已实施) 本文修的是上一轮没修的那一层**:结构对了,但检索质量与提示词仍然不可用。


〇、起点:承认上一轮的判断错误

上一轮我修了 L1↔L2 的结构(事件列表 + 正文进块 + 按原时序回填), 然后声称「机制已通」。结构确实通了,但功能不可用。

用生产数据实测(1054 篇文档、chineseclip 512 维)三个互不相关的查询:

查询 "doc 层召回是怎么设计的"
  score=0.7834  来自 4 个来源的 61 条对话 (cold_storage, agentcli...)
  score=0.7795  来自 1 个来源的 2 条对话 (agent) 涉及: map, content, dsh, 占用
  score=0.7768  来自 1 个来源的 1 条对话 (agent) 涉及: 自检, OK

查询 "hmap 插件安装验签"
  score=0.7922  来自 1 个来源的 1 条对话 (agent) 涉及: map, content, 是不是, pi

查询 "QQ 头像"
  score=0.7601  来自 1 个来源的 1 条对话 (agent) 涉及: CQ, qq, ok

分数全挤在 0.75~0.79,区分度≈0,命中内容与查询无关。

判据缺失(最该记的一条)

上一轮写了 16 条判据、6 个变异,全部通过。但没有一条验证「检索结果必须与查询相关」。 所以「全绿」证明的只是我实现了我以为的东西 —— 不是它可用。

★ 教训:结构判据不能替代质量判据。 「字段非空」「条数正确」「不 panic」都可能在检索完全失效时依然全绿。


一、根因(三条,按依赖排序)

根因 1:向量的输入是 jieba 词袋,不是正文

internal/memory/document/document.go — denseFor:

if tv, err := s.denseSpace.VectorizeDense(doc.Summary + " " + doc.Content); err == nil {

而 Summary 由 summarizeEntries 生成,后者调用链是:

summarizeEntries  → memory.ExtractKeywords → TokenizeContentWords → jieba.Tag()
extractTags       → 同上
extractEntities   → 同上

TokenizeContentWords(internal/memory/cut.go:256)就是 jieba.Tag() 分词 + 停用词过滤, 产出形如 涉及: map, content, 是不是, pi。

这是与设计直接矛盾的。ARCHITECTURE.md L131 / L174 明写向量层级:

统一多模态空间(主,带 fingerprint)→ StaticEmbedder 词嵌入 → TF-IDF(回退)

Summary 是主路径的输入,却由最末一级的词法分词产出。 全库所有文档的 Summary 因此高度同质(都是「来自 N 个来源的 M 条对话 涉及: …」), 余弦相似度自然全挤在高位、无区分度。

根因 2:媒体块没有语义,且不需要单独生成

设计要求「doc 支持多模态摘要」。当前实现里:

  • Doc.Summary 只有文本路径,媒体块从不参与
  • crossModalMarkdown 里媒体只渲染成 [image/png blk_media_xxx](blockLabel:MIME + 短 ID)
  • 一篇带 20 张图的文档,摘要里没有「有图」的任何信息

但「多模态摘要」这个提法本身是歧路(用户 2026-10-22 指出):

媒体 doc 块一般是和文本紧邻的,所以对应的文本就足够了

Doc.Events[i].Blocks 里媒体块与文本块在同一事件内。媒体块的语义由 同事件的文本天然覆盖,不需要为每个媒体单独生成描述(那正是 CAS 时代 「描述式索引」被拆除的同一个错误)。

⇒ 正确做法不是「给媒体写摘要」,而是:向量用事件正文算,媒体自动被覆盖。

根因 3:提示词与注入文案没有任何引导

(a)doc_query 的工具描述只有一句话

toolDefWith("doc_query", "查询文档记忆。输入查询内容,返回相关文档摘要。", ...)

逐条对照问题:

应有信息 现状
L2 是什么 ❌ 只说「文档记忆」,没有定义
何时该用 ❌ 无
调用后会发生什么 ❌ 「返回相关文档摘要」与真实行为不符(真实是逐条按原时序回填到对话时序)
与 memory_recall 的分工 ❌ 无

对比同文件里 【输出规则】(6 条)、【工具执行顺序】(4 条)的详细程度, doc_query 是最敷衍的一个。

(b)预填充注入文案没有引导

crossModalMarkdown 输出:

【跨模态相关记忆】
[1] 来自 4 个来源的 61 条对话 (cold_storage, agentcli...) (来源:context_archived) 相关度:0.78(文本命中)

没有一个字说「可以用 doc_query 取全文」。模型看到这段只能忽略。

(c)系统提示词从未解释三层记忆是什么

grep 记忆 internal/agent/core/tooldefs.go 全量命中里,没有任何一段说明 Context / Document / Graph 三者的分工与使用时机。agent 只能从工具名猜。


二、修复步骤(按依赖,每步先写会红的判据)

步骤 1:判据先行 —— 检索质量

先写测试,此时必须红。

internal/memory/document/retrieval_quality_test.go:

  • 构造真实语料(约 50 篇,内容取自生产副本的真实事件正文 + 人工标注的查询-答案对)
  • 断言:给定查询,正确文档必须排第一(top1 准确率 100%,或至少 top3 内)
  • 断言:互不相关的查询,top1 结果必须不同
  • 断言:正确命中的分数必须显著高于非命中(区分度阈值)

变异:把 denseFor 改回 Summary + Content ⇒ 必须红。 变异:把正文替换成 jieba 词袋 ⇒ 必须红。

这一步是整个计划的地基。没有它,后面每一步都只是「我以为改对了」。

步骤 2:向量输入改为正文

internal/memory/document/document.go — denseFor:

现在:VectorizeDense(doc.Summary + " " + doc.Content)
改为:VectorizeDense(事件正文拼接)
      有 Events → 按事件顺序取 InOut() 的 input + response(文本块内容)
      无 Events(存量)→ 退回 Content

媒体如何被覆盖:媒体块与文本块在同一事件内,事件的正文进入向量即覆盖它。 不需要为媒体单独编码 —— 若将来要「按图搜图」,那是视觉路(RecallBlocks)的职责, 已有独立通路,与文本路不冲突。

步骤 3:删掉 jieba 摘要路径

Summary / Tags / Entities 目前全部由 ExtractKeywords(jieba)产出。

  • Tags:唯一消费点是 doc_query 返回值里的「标签: …」(toolcall.go:1038), 是词袋噪声,删除该行
  • Entities:Doc.Entities 无任何非测试读取点(跨包的 injected.Entities 是 memory.Entity 另一个类型,无关),字段可删
  • Summary:改为纯展示(人工阅读 / 日志),由正文首句截取生成,不参与向量

⚠️ 需先确认:Doc.Tags / Doc.Entities 是否有 WebUI / SDK 侧读取。 (已知 sdk/memory_impl.go 的 Doc 只有 ID/Title/Content,无 Tags/Entities。)

步骤 4:预填充注入正文片段 + 引导

crossModalMarkdown 改为:

【历史记忆(按相关度)】
[1] 2026-10-12 11:09 · qq · 相关度 0.83
    紫蓝红三色带的配色参考,老大要求…(正文首 2 行)
[2] …(含 2 张图:image/png, image/jpeg)

这些是**历史**片段(不是当前对话)。要看完整原文,
调用 doc_query 把对应事件按原时间戳拉回对话时序。

要点:

  • 去掉「来自 N 个来源的 M 条对话」这类统计描述
  • 给出时间 + 来源 + 正文开头(真实内容,让模型能判断相关性)
  • 媒体只报数量与类型,不编描述
  • 明确告知 doc_query 的作用

步骤 5:doc_query 工具描述重写

按 memory_* 的详细度,必须覆盖:

  • L2 是什么(历史事件层,不直接进当前对话)
  • 何时用(记得办过但想不起细节 / 跨会话跨天 / 需要原始过程而非结论)
  • 调用后发生什么(把事件逐条按原时间戳回填到对话时序,不是返回一段摘要)
  • 与 memory_recall 的分工(图记忆=关系与最新值;doc=整段过程与原文)
  • 副作用(从 L2 取出,取走后该文档不再在 L2)

步骤 6:系统提示词增加「三层记忆」段落

在 buildSystemPrompt 里加一段,说清三层是什么、怎么用:

【三层记忆】记忆分三层,你现在看到的是第一层。
- 对话时序(L1):最近的对话,已在你的上下文里。超过窗口的会被移出。
- 历史记忆(L2):移出后的事件与原文仍完整保存。自动预填充会给你相关片段;
  要给看全文/完整过程,用 doc_query —— 它会把那些事件按原时间戳插回对话时序。
- 图记忆(L3):关系与事实的长期存储(谁是什么、什么等于什么、最新值是什么)。
  用 memory_recall 查;用 memory_commit 记。
选择:要"原文与过程"→ doc_query;要"关系与当前值"→ memory_recall;
       要"成文知识"→ knowledge_search。

注意:这段属于「内核运行时块」(提示词分层规范:内核行为由内核生成), 不是工具用法(工具用法归 description)。它讲的是内核的真实执行语义 (回填到时序、自动预填充),符合分层原则。


三、判据清单(每步都要能红)

# 判据 变异(必须红)
1 检索质量:top1 命中 / 不相关查询 top1 不同 / 分数有区分度 denseFor 改回 Summary+Content;或换回 jieba 词袋
2 向量输入是正文:断言 denseFor 读的是 Events 正文(可用哨兵串) 正文里埋哨兵,改回 Summary ⇒ 哨兵不出现在向量输入
3 摘要不含 jieba 词袋:Summary 不出现「来自 N 个来源的 M 条对话」 恢复 summarizeEntries ⇒ 红
4 预填充文案含正文与引导:必须含 doc_query 字样 + 正文片段 改回旧渲染 ⇒ 红
5 工具描述覆盖五项(L2 定义/何时用/调用后行为/分工/副作用) 删任一项 ⇒ 红
6 系统提示词含三层记忆段落且语义与实现一致 回填行为改掉而提示词不改 ⇒ 红(跨文件一致性)

判据 6 特别说明:提示词说「按原时间戳插回时序」这件事, 必须与 restoreDocEvents 的真实行为绑在一条判据里 —— 否则就是「提示词在说谎」,那比没说更糟。


四、禁止事项

禁止 原因
用 jieba 词袋当向量输入或摘要内容 与「统一向量化」直接矛盾;全库同质,检索失效
为媒体块单独生成文本描述 CAS 时代的「描述式索引」错误;媒体语义由同事件文本覆盖
只改结构不改提示词 机制改了模型不知道 = 等于没改
用「字段非空 / 条数正确 / 不 panic」当检索判据 检索完全失效时这些也全绿
声称「机制已通」而不给出生产数据的检索质量实测 上一轮的教训

五、验证

  1. go test ./... -count=1 全绿(现有 58 包基线)
  2. 新判据全绿,且 6 个变异各自被抓住
  3. 生产副本实测:用真实数据跑检索,给出 top1/top3 命中率与分数分布 (对比修复前的 0.75~0.79 无区分度)
  4. 生产部署后用真实对话观察:agent 是否在需要时调用 doc_query

六、标准流程(本次严格执行)

上次直接提交 main + rebase + amend + 绕过 CI,不符合 docs/git-branching.md。 这次必须:

1. git checkout -b feature/memory-usability main
2. 按步骤 1→6 提交(每步独立 commit,先写判据再改)
3. git push -u origin feature/memory-usability
4. 开 PR
5. 等 CI 10 项全部通过
6. 合并 main(不绕过必需检查)
7. 部署 + 生产验证

⚠️ 远端仓库已改名 JianFeeeee/HologramAgent(origin 与 gitcode 均已更新)。


七、待确认

  1. Doc.Tags / Doc.Entities 是否可删? 我查到非测试读取点只有 doc_query 返回里的「标签:」一行(可删)。 但需确认 WebUI / 外部插件是否依赖(SDK 的 Doc 无这两个字段)。

  2. 判据 1 的语料从哪来? 建议从生产副本抽 50 篇真实文档 + 人工标注 10~15 个查询-答案对。 是否允许我把这些真实文本(脱敏后)作为测试 fixture 入库?