Commit Graph

5 Commits

Author SHA1 Message Date
13a070fbb2 docs(memory): 标记步骤 3/5 完成,补步骤 4 部署前基线与硬约束
步骤 4 加了硬约束提醒:必须先部署含 R1 修复的二进制再清理,否则
重新涌现出来的还是带 +/# 的旧键。并记录部署前实测基线(PID 92115、
子进程插件 25、近 24h 异常日志 0、场景 65),以及 homed --version
这个 flag 并不存在(纪律清单那条要用 status 接口或 ps 核对)。
2026-09-26 20:17:17 +08:00
758ec11832 fix(memory): 图整备覆盖 scenes + 证据桶按桶清(R3/R5)
R3:图整理心跳只查 entities/relations,scenes 完全没有整备路径。
Recall(nil,nil,1,"") 的全量路径只 SELECT 这两张表
(graph.go:609/631),于是同一场面的双胞胎键从建库起无人发现:
auto:chan:qq+part:morning 累积到 strength=271 / 6 features / 0 refs,
孪生的 auto:chan:qq_part:morning 持有 210 refs 却 0 features
(聚类只读 scene_features,所以它永远不被看见)。

新增 GraphDB.DedupeScenes:归一化后同名的场景合成一个——强度相加、
特征取并集(权重取大)、引用全部重定向,存活者保留 id 最小行,
跨 origin 也合。接到 mergeLoop 尾部。

为什么不塞进 detectEntityMerge 的双重循环:
- 实体是全库两两 bigram + LLM 裁决(1 万实体实测 5000 万次配对、
  ~224GB 瞬时分配每轮,是独立问题);
- 场景的判重口径是**归一化后是否同名**——同名即同一场面,键相同
  本身就是证据,不需要 LLM 裁决。而「像不像」是 EnterScene 聚类的
  职责,不是这里的事。
只做同键合并、不做相似度合并:把 chan:qq 与 chan:webui 合并是危险
的,去重不是「把像的一律合并」。

生产库副本实测(sqlite3 备份式复制到 /tmp,未碰生产):
65 个场景 → 合并 8 组 → 57 个;
auto:chan:qq_part:morning 的 refs/rel/ent 一条没丢,strength 1 → 272。

R5:createSceneLocked 新场景成立时执行的是
`DELETE FROM situation_evidence`(全表清),而证据表是多通道共用的
计数桶。后果不是「多清一点」:qq 的场景一长出来,就把 mc/webui/cli
尚未攒够 minSceneEvidence=2 的证据抹掉,它们的计数被反复清零,
于是**永远**攒不到 2 次。判据实测:6 个通道各来 3 次,只长出 2 个场景。
改为 `DELETE ... WHERE label = ?`,只清本指纹那个桶。

判据:scene_dedupe_test.go 8 例(含「不同场面不得被合并」与幂等)、
scene_evidence_test.go 3 例。均先红后绿。修 R5 时差点栽:桶键是
sig.Label(2) 本身、不带 auto: 前缀(base 才是带前缀的场景键),
第一版删错对象会「一条没删却看起来通过」,用探针实测真实桶键后改正。

记忆 8 包 + agent/core 全绿,8 包齐全、无 FAIL/panic/race。
2026-09-26 20:16:11 +08:00
f441574b80 docs(memory): 补 R6/R7 与 SDK 声明项方案,重排为 7 步
R6:ChannelDef 的记忆声明已有三件套(NoMemory/ContextPolicy/
RecallPolicy),唯独没有「这条通道是否参与场面识别」,现状是无条件
参与 ⇒ chan:system/kernel/timer 等内部信噪通道也在场面聚类里。
穷举确认非查漏:go.mod replace 指向 third_party/homeagent-sdk,
plugin.go 中 scene 出现 0 次,SDK 自身 git 历史 -S'Scene' 为空。

R7:payload[scene] 的层级键(chan:qq/peer:group_1)已支持前缀
召回(RecallByScene scene.go:351),但因 R6 无人使用而闲置。

步骤重排为 7 步:SDK 声明项提到步骤 2(用户已授权动公开 SDK,
开发阶段非 release 阶段),并把「peer 覆盖面」拆到步骤 6 单列,
先只读调查插件手上有什么再动。
2026-09-26 20:03:51 +08:00
6e9420965d docs(memory): 补 R4 覆盖面与 R5 证据桶两条根因
R4:现网 65 个场景键里 peer 主导 0 个、topic 主导 0 个,36 个
auto:chan + 26 个 chan: 全部锚在输入通道。两个独立原因:
采集侧全仓无插件在 InjectInput 填 peer/group_id/user_id/chat_id
(日志中 peer 出现 0 次);排序侧 chan 与 peer 权重同为 1.0 而
NewSituation 稳定排序让 chan 恒在前,即使采集到也进不了 Label(2)。
这与 R1/R2 是不同层面:R1 修完只会得到「正确的单一维度」。

R5:createSceneLocked 新场景成立即 DELETE FROM situation_evidence
(全表清),会连带清掉别的场景尚未攒够门槛的证据。
2026-09-26 19:58:00 +08:00
1fa9ef68a4 fix(memory): 场景键归一化 + 排除最弱维度,修双胞胎与空转
现网实测:scenes 表 65 行里有 6 组是同一场面的双胞胎键,最严重的
auto:chan:qq+part:morning 累积到 strength=270、6 个 features、
0 条记忆,日志里被"命中"179 次;孪生的 auto:chan:qq_part:morning
则持有 201 条记忆却有 0 个 features(不参与聚类)。两套特征体系
各活各的,谁也发现不了谁。

病因:键构造不唯一。
- Label() 直接用 "+" 拼接且不过 NormalizeSceneKey,而
  EnsureScene / effectiveScenes / RecallByScene 三处都过了归一化。
  "+" 会被 normalizeSceneSegment 归一成 "_",于是两个字符串都合法,
  key UNIQUE 约束拦不住。
- Label(2) 会把权重仅 0.2 的 part(时段)挤进场景身份。实测
  morning 场景吞掉 evening 指纹:共享 chan:qq 权重 1.0、并集含
  part 0.2×2,相似度 1.0/1.4 = 0.714 > joinSceneThreshold 0.5。
  这本就是加权 Jaccard 的正常行为,但键名不该写进时段。
- createSceneLocked 的 "#N" 冲突后缀同样会被归一成 "_",
  造成 auto:chan:mc:event+topic:mc#2 在库、而写侧归一化后去找
  auto:chan:mc:event_topic:mc_2 —— 又一对匹配不上的双胞胎。

修法:
- Label 只取权重 ≥ 0.5 的主导特征,结果过 NormalizeSceneKey;
  全部特征都弱于门槛时退回最强的一批(宁可名字信息量低,也不能
  没有名字 —— 没名字就没有键,场景根本长不出来)。
- createSceneLocked 对 base 再做一次防御性归一化,并在 label 为空
  时拒建无名场景。
- 冲突后缀由 "#" 改为 "."('#' 会被归一化,'.' 是白名单字符)。

判据:新增 scene_key_test.go 6 例,参照物在生产代码之外("同一场面
⇒ 同一个键"这条不变量 + 直查 scenes 表复算行数)。先跑红确认判据
在跑(4 红 1 绿,失败的正是键唯一性/时段污染/证据桶),再改实现。
其中 TestWrittenRefReachableFromItsScene 一开始就是绿的——写侧到读侧
那条路本身是通的,坏的只是键的构造。

记忆 8 包 + agent/core 全绿。
2026-09-26 19:56:28 +08:00