fix(digest): 多智能体评审修正 — 资产索引收敛 + 60cap 保 hot + 文档同步

对照 cold-digest-spec 的对抗式评审(20 项发现)逐条核对实现,两处真实代码问题已修:

- 资产索引膨胀(评审 §6.2 高危):早前把一个 digest 落进它成员命中的【每个】asset 桶,
  跨资产 digest 会复制进几十个桶、顶层条目不降反升(真实图 asset_ids 约 144 个)。改为
  每个 digest 只归一个【代表资产】桶(成员锚定众数、并列取最小 id;无锚定 → 0 号兜底桶),
  条目数 ≤ digest 数。
- 60 兜底截断误砍活跃上下文(评审 §6.3 高危):hot(live 前沿祖先链 / live 意图下新事实)
  是活跃推理上下文,绝不能截。改为 hot 全保、60-cap 只约束【冷但未折】残余;冷区两侧
  均衡保留。graph_overview 每轮计算 render 期 hot 集(兼用于复活检查)。

其余 16 项评审发现经核对,本实现已由设计选择规避/处理:全量扫描维护(静默变冷/传递翻转
盖章)、签名含锚节点、minor+major 统一走同一 group()(§3.1 在两处都生效、major 不会拆散
同父组)、提交时活跃度复核(不折入复活成员)、content_version 列+状态变更 bump、渲染期索引
(无 KindIndex 陈旧)、cold_digests 与索引并列(body 仍在顶层)、singleton 只进 recent。

文档 §6.2 同步上述解决口径(body 归属、一 digest 一桶、singleton 归 recent)。
This commit is contained in:
Autumn-27 committed 2026-09-09 12:04:17 -04:00
1 parent 4f088e519b
commit eefd5ee2f2
2 files changed
+57 -34

No files matched your search

+20 -14
View File
@@ -48,28 +48,34 @@ func (t *ToolSet) coldDigestOverview() (digests []map[string]any, index []map[st
})
}
// index (§6.2): asset → digests touching it. A digest with no anchored asset
// falls into the 0 bucket ("(未锚定资产)"). One digest may list under >1 asset.
// index (§6.2): asset → digests. Each digest lands in EXACTLY ONE bucket — its
// representative asset = the asset anchored on the most of its members (mode;
// tie-break lowest id). This is what makes the index converge: bucketing a digest
// into every asset its members touch would duplicate it across dozens of buckets
// and blow up the top-level count instead of shrinking it (asset_ids are fine-
// grained — a real task has ~144 of them). A digest whose members anchor no asset
// falls into the 0 bucket ("(未锚定资产)").
byAsset := map[int64]map[int64]bool{} // asset id → set of digest ids
assetSet := map[int64]bool{}
for dID, ms := range memByDigest {
touched := map[int64]bool{}
counts := map[int64]int{}
for _, m := range ms {
for _, a := range assetsByNode[m] {
touched[a] = true
counts[a]++
}
}
if len(touched) == 0 {
touched[0] = true
rep, best := int64(0), 0
for a, c := range counts {
if c > best || (c == best && (rep == 0 || a < rep)) {
rep, best = a, c
}
}
for a := range touched {
if byAsset[a] == nil {
byAsset[a] = map[int64]bool{}
}
byAsset[a][dID] = true
if a != 0 {
assetSet[a] = true
}
if byAsset[rep] == nil {
byAsset[rep] = map[int64]bool{}
}
byAsset[rep][dID] = true
if rep != 0 {
assetSet[rep] = true
}
}
labels := map[int64]string{}