Files
HomeAgent/docs/zh/memory-restructure-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

169 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 记忆系统重构计划:块节点统一载体
> **归档说明(2026-10-27)**:本文是历史计划,**术语已过时** ——
> `doc_query` / `doc_commit` 已改名为 `coldcontext_query` / `coldcontext_commit`,
> 图记忆(Graph)在对外语义上改称 **stablemem(稳定记忆)**。
> 现行架构与术语以 `assets/docs/zh/ARCHITECTURE.md` 为准;本文保留作为决策记录。
> 状态:**已执行完毕(2026-10-04)**。本文是当时的执行计划,保留作为决策记录。
> 日期:2026-10-02(计划) / 2026-10-04(完成)
>
> ## 执行结果摘要(2026-10-04 回填)
>
> ★ **读法**:下面「三、分步执行」里各步骤仍写着 `✗ 未开始` —— 那是
> **计划当时的原始快照**,有意不改(改了就看不出计划与实际的差距)。
> **以本摘要为准。**
>
> | 步骤 | 状态 | 关键提交 |
> |---|---|---|
> | ① 重写设计文档 | ✅ | `6a0d6b7` `971cd72` `7248558` |
> | ② distill 沉库改块节点 | ✅ | `4b54c03` `1c260e3` `0586f6b` |
> | ③ memory_recall 加块召回路 | ✅ | `688ad08` `40a6acb` |
> | ④ memory_commit 止血 | ⚠️ 部分(见下) | `3bafe1c` |
> | ⑤ 存量迁移 | ✅ | `09f31e2` `e0a234c` `5a70452` |
> | ⑥ scene 挂接 | ❌ **取消**(见下) | — |
>
> ### 计划与实际的偏差(都是实测纠正的)
>
> **⑥ scene 挂接取消**:计划假设 blocks 是新层、scene 挂在它上面。
> 但 blocks 不是新层而是图节点升级体,scene 无处可挂。
>
> **蒸馏路线整体否决**(990b824):生产库 429 条实体里 **424 条(99%)无主语**,
> 而 `Split` 的第五道闸门「无主语不写悬空属性」会把 LLM 已拆对的字段全丢弃。
> 蒸馏命令已改为**只对有主语的实体跑**(b21410b),实际处理 5 条。
>
> **注意力网络路线否决**(`6a0d6b7` ~ `57eca02`):分域后 token F1 0.918
> 超过门槛,但**跨域不可泛化**(域外零字段),而通用 Agent 无法分域。
>
> ### 计划里未被采纳的替代方案
>
> - 「给块补语义描述」→ 实验否定(中性描述反而更差,排名 26 → 96,`7248558`)
> - 「给 URL 类事实换更强向量」→ 未测,但裸 URL 无文本可供语义匹配(`971cd72`)
>
> ### 现行架构
>
> ```
> memory_blocks 统一节点载体(文本/媒体都进这里)
> memory_block_edges source_kind → target_kind 多态端点,唯一边表
> graph_centroid 中心向量(迁移/蒸馏后自动重建)
> ```
>
> 原句由 `blk_src_<sha256>` 原句块承载(内容派生、幂等、不带向量),
> `sentences` 表的**写入点已清零**(迁移与蒸馏都不再写)。
>
> 详见 `production-recall-validation.md`(三十三~三十八节)、
> `recall-capability-tiers.md`(能力分层)、
> `vector-provider-decision.md`(provider 决策与缺口)。
> 背景:本会话前期的记忆系统改动(630d3f4 / 978bada / 69150d2 / 25d35f8)建立在对记忆架构的错误认知上,经用户逐条纠正后全部重审。本计划是重审后的执行路线。
> 前置事实核查:全部基于代码与真库实测,标注来源。
## 〇、认知纠错记录(为什么推翻重来)
| 错误认知(当时的做法) | 正确认知(用户纠正 + 代码核实) |
| --- | --- |
| 分层是 L0 对话/L1 文档/L2 graph/L3 blocks | L0=context / L1=doc / L2=graph。**blocks 不是新的一层**,是图节点从文本升级为带向量的多模态节点 |
| 给 entities 加向量字段(graph-vector-design.md) | **entities 该退场**。memory_blocks 已是带 vector+fingerprint 的节点载体(46f833c) |
| LLM 拆句产物 Commit 成 entities+relations(630d3f4) | 拆出的节点放入 block,保留原句;媒体引用是已删除的老设计(mediaref.go 只剩注释) |
| 三元组 = 我发明的 (主语,维度,值) | 三元组 =【节点】【关系边】【节点】;三元组设计文档在知识库 assets/knowledge/homeagent_architecture/content.md |
| provider 是蒸馏的配件 | provider 是内核的模型接入接口,内核对 tfidf 等传统算法与向量化 LLM **无感**,payload 一致才能动态接入 |
| 多模态要"引用"进图 | 多模态节点由 LLM 多模态能力拆分入库 |
| 场景/scene 表忽略不看 | scene 是第三种召回:条件型记忆不吃词法/向量相似,按场面**前缀匹配**直接取回(scene.go:12-38) |
沉痛教训:630d3f4 的 16 三元组端到端验证——验证的是拆分质量,不是正确的落库形态。**验证通过 ≠ 架构正确。**
## 一、目标形态(一句话)
```
入库:拆解器(provider 接口,内核无感)把对话/文档/媒体拆成
memory_blocks 节点(modality/text_content/vector/fingerprint)
+ memory_block_edges 结构边;原句保留为块。
召回:块向量检索为主 + scene 场景召回补条件型记忆;
entities+LIKE 是存量过渡路径,逐步退场。
```
## 二、既有设施盘点(不重复实现)
| 能力 | 已有 | 位置 | 状态 |
| --- | --- | --- | --- |
| 块节点存储 | `PutMemoryBlocks` / `MemoryBlocks` | internal/memory/block.go:67/144 | ✔ 可用 |
| 结构边 | `AddMemoryBlockEdge`(端点校验,测试 453/491 行) | block.go:199 | ✔ 可用 |
| 块检索 | `BlocksForNode`(按端点反查) | block.go:254 | ✔ 可用 |
| 向量检索 | `vector.Store`(Insert/SearchScored/Remove) | internal/memory/vector/store.go | ✔ 可用 |
| provider SPI | `pkg/embedding`(Embed/Info/Close,Fingerprint) | pkg/embedding/embedding.go | ✔ 可用 |
| VL 权重双模式 | qwen3vl(Embed 出向量)+ qwen3vlgen(tied head 生成) | 25d35f8 | ✔ 已验证 |
| 文本拆解 | distill.Extractor(schema 约束+闸门,16 三元组/幻觉0) | internal/memory/distill | 拆分逻辑可复用,**沉库去向要改** |
| 旧实体迁移先例 | `MigrateLegacyMediaEntities` | internal/memory/migrate.go:47 | ✔ 方向先例 |
| scene 召回 | `RecallByScene` | internal/memory/scene.go:323 | ✔ 已有 |
| 拆解生成模型 | ollama+qwen3:1.7b(schema 6/6 零幻觉) | providers/ollama | ✔ 端到端验证过 |
## 三、分步执行
### 第 1 步:重写两份设计文档 ✗ 未开始
- [ ] 删 `docs/zh/graph-vector-design.md`(给 entities 加向量——前提错误)
- [ ] 重写 `docs/zh/triple-distill-llm-design.md`:拆解产物=块节点;entities 标注退场路径
- [ ] 修正 `providers/qwen3vlgen` 与 `pkg/generation` 的定位注释:不是"蒸馏配件",是入库拆解器的模型面
- **验收**:文档里的每张表名/函数名都能 grep 到;无 entities 加向量字段的任何残留表述
### 第 2 步:distill 沉库改块节点(核心) ✗ 未开始
- [ ] `distill.Extractor.Split` 产出改为块结构(新增 `distill.Blocks` 类型,保留 Triple 输出做对照期)
- [ ] 原句 → text block(text_content=原句, vector, fingerprint)
- [ ] 字段 → text block(text_content=字段内容, vector, fingerprint)
- [ ] 边:原句 block —contains→ 字段 block
- [ ] 向量来源走 `pkg/embedding`(默认 provider 由配置决定;无 provider 时 vector 留空+fingerprint 空,**不静默编造向量**)
- [ ] pipeline.go 的 distillBatch 改调块写入
- **判据**:
- [ ] 拆一条真实记录后,MemoryBlocks/MemoryBlockEdges 能查到原句块+字段块+边
- [ ] 断电安全:PutMemoryBlocks 是事务(block.go 已有端点校验事务先例)
- [ ] 无 provider 时块仍入库(vector=''),检索退化为符号路,**不报错不编造**
- **变异自证**:把"contains 边"删掉,块查询测试必须红
### 第 3 步:memory_recall 增加块召回路 ✗ 未开始
- [ ] GraphDB 增加 `RecallBlocks(queryVec, topK)`:走 vector.SearchScored
- [ ] 工具层 `memory_recall`:块向量召回为主,entities LIKE 为存量兜底,输出统一格式
- [ ] 工具名/参数不变(调用方模型无感知)
- [ ] fingerprint 不匹配时**拒绝比较**(报"向量空间已变更,需回填"),绝不拿 2048 维查询比 512 维库
- **判据**(真库 + 跨记录探针,用本会话已验证的判据):
- [ ] 跨维度定位 ≥4/5(现状 LIKE 0/5)
- [ ] 端口类数字串不退化(向量弱、符号强——混合必要性)
- **变异自证**:去掉块召回路,跨维度判据必须退化到现状水平
### 第 4 步:memory_commit 同步止血 ✗ 未开始
- [ ] description 明确要求:拆解后走块形态;禁止 43-50 字符整句实体
- [ ] 工具实现侧加闸门:超长 subject 拒绝并提示(与 distill 闸门同款)
- **判据**:新写入的实体长度分布不再出现 >25 字符主体
### 第 5 步:存量迁移 ✗ 未开始
- [ ] 沿 `MigrateLegacyMediaEntities` 方向写 `MigrateLegacyTextEntities`:
188 实体 → 原句块(带向量)+ 字段块 + contains 边
- [ ] 独立命令入口(参照 homed-kb-migrate),**可回滚**:导出原库快照→迁移→对比→确认后替换
- [ ] 孤儿实体(49/108 无关系无原句)保持原样,不编造
- **判据**:迁移后跨维度探针 ≥4/5;原库快照可完整回滚
### 第 6 步:scene 挂接(可选,后置) ✗ 未开始
- [ ] 拆解块带 scene 键(memory_blocks.scene 字段已在 schema 里)
- [ ] 验证 `RecallByScene` 对块节点生效
- 前置:第 2-5 步稳定后再做
## 四、每步通用约束
1. **provider 边界**:内核不出现任何 provider 名分支;一切经 `pkg/embedding`/`pkg/generation` 接口
2. **禁止静默错误**:fingerprint/维度不匹配必须显式报;无向量必须显式留空
3. **回填必落盘**(document.go:137 陷阱:不落盘则判定条件永远成立、每次启动重算)
4. **变异自证**:每条关键判据写测试后,手动破坏对应实现,测试必须变红
5. **长任务纪律**:导出/回填等内存密集任务**绝不与测试并行**(本会话三次 OOM 教训)
6. **验证通过 ≠ 架构正确**:每步完成后对照本计划第一节逐条自检
## 五、暂不做
- entities 表的物理删除(存量过渡期内仍服务 LIKE 兜底)
- KV cache(qwen3vlgen 全量前向 O(N²),生成低频,已注释标明)
- 多模态拆解(第 6 步之后,依赖块路稳定)
- 切换生产实例 /var/tmp/ha-c 的 provider 配置(需人工确认)