## 上一轮的遗漏 上次只补了"缺状态行",但**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 通过(纯文档改动)
10 KiB
记忆系统重构计划:块节点统一载体
归档说明(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 回填)
★ 读法:下面「三、分步执行」里各步骤仍写着
✗ 未开始—— 那是 计划当时的原始快照,有意不改(改了就看不出计划与实际的差距)。 以本摘要为准。
步骤 状态 关键提交 ① 重写设计文档 ✅ 6a0d6b7971cd727248558② distill 沉库改块节点 ✅ 4b54c031c260e30586f6b③ memory_recall 加块召回路 ✅ 688ad0840a6acb④ memory_commit 止血 ⚠️ 部分(见下) 3bafe1c⑤ 存量迁移 ✅ 09f31e2e0a234c5a70452⑥ 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 步稳定后再做
四、每步通用约束
- provider 边界:内核不出现任何 provider 名分支;一切经
pkg/embedding/pkg/generation接口 - 禁止静默错误:fingerprint/维度不匹配必须显式报;无向量必须显式留空
- 回填必落盘(document.go:137 陷阱:不落盘则判定条件永远成立、每次启动重算)
- 变异自证:每条关键判据写测试后,手动破坏对应实现,测试必须变红
- 长任务纪律:导出/回填等内存密集任务绝不与测试并行(本会话三次 OOM 教训)
- 验证通过 ≠ 架构正确:每步完成后对照本计划第一节逐条自检
五、暂不做
- entities 表的物理删除(存量过渡期内仍服务 LIKE 兜底)
- KV cache(qwen3vlgen 全量前向 O(N²),生成低频,已注释标明)
- 多模态拆解(第 6 步之后,依赖块路稳定)
- 切换生产实例 /var/tmp/ha-c 的 provider 配置(需人工确认)