1. 术语统一:
- doc_query / doc_commit → coldcontext_query / coldcontext_commit
- 「图记忆 / Graph 层」→「稳定记忆 / Stablemem 层(L3)」
- 分层命名补上 L1/L2/L3 语义:Context(热) / Cold-context(冷) / Stablemem(持久)
涉及 README.md、README_EN.md、assets/docs/{zh,en}/{ARCHITECTURE,OVERVIEW}.md
2. 补上本次最大的结构变化(文档里此前完全没有):
- 安全防火墙作为**独立内核模块**:凭据闸门、工具黑白名单+沙箱、
敏感词与提示词注入预警、按主体授权与插件命名限式;显式代理而非 monkey patch
- device 数据面作为**独立内核模块**:工具注册与路由、inputch/outputch、设备登记;
路由表在 internal/toolroute 以避免 core ↔ kernelboot 成环
3. 历史计划文档(memory-*.md / context-overflow-l4-design / entities-retirement /
git-branching)加**归档说明**:明确术语已过时、现行口径以 ARCHITECTURE.md 为准。
刻意不改写历史正文 —— 那是决策记录,改写等于伪造。
剩余旧词只出现在归档文档与 scripts/capability-bench/PLAN.md(评测历史记录)。
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 配置(需人工确认)