[控场AI]
· 10 分钟阅读· 5,352 字

Decision Brain:用图数据库为AI编码找回“为什么”

Decision Brain:用图数据库为AI编码找回“为什么”

Decision Brain 把决策日志与 git 历史变成图,让代码变更前能看到「为什么」。

文章介绍了 Graph Hacks 黑客松项目 Decision Brain:它把仓库的决策日志、ADR 文件、git 历史以及 AI 代理指令文件的历史版本摄入 FalkorDB 图,形成 Decision、Commit、File、Person 等节点,使「为什么这样写」成为可查询的问题。摄入由四个本地代理通过 Cypher 看板图协作完成,认领具备原子性,关键的「取代」交接需第二个代理引原文确认;模型产出的引用、数字、文件名都要过代码检查。查询侧提供十二个 MCP 工具,答案本身还要被代码核验引用与数字。演示中最有说服力的是代码评审:按提交时点的决策状态还原上下文,被取代的旧约束以限定范围的警告存活。对照实验中图检索在证据召回与前提纠错上明显优于关键词搜索,代价是约 2.6–2.9 倍延迟。

当代码只记得“做什么”,忘了“为什么”

几乎每个团队都遇到过这样的场景:review 时发现一行明显“错误”的代码,于是顺手修正,结果悄悄破坏了某个当年刻意为之的设计。代码记录的是程序做了什么,却从不记录为什么这样做。那个“为什么”通常散落在决策日志、旧提交记录和某个人的记忆里——而这三处,恰恰是编辑代码时最不会被打开的地方。

AI 编码代理让这个问题进一步放大。它们改代码又快又自信,却对代码的历史一无所知。开发者 Peter Green 在 Graph Hacks 黑客松(FalkorDB × WeMakeDevs)上提交的 Decision Brain,正是冲着这个问题去的:把仓库的决策记录和 git 历史变成一张 FalkorDB 图,让每一次变更在合并前都能看到背后的决策、证据和知情者。

一个反直觉的真实案例

Peter 自己的项目里,router prompt 有一行事实错误:写着记忆专家“只能读取”,但记忆模块其实能保存事实。任何 reviewer 或编码代理都会想改掉它。他们确实改过一次,然后做了测量——用修正后的文本,最小的模型把“记住这个…”路由到记忆模块 20 次里 0 次;用那行“错误”的旧文本,20 次里 16 次。

于是那行错误被保留了下来,理由写在一份 1,900 行决策日志的某一段里。下一个打开这个文件的人,什么也看不到。这正是 Decision Brain 要解决的断层:让“为什么”在改动发生的那一刻就出现在眼前。

ADR(Architecture Decision Record,架构决策记录) 是一种轻量级的文档约定:每个重大技术决策写成一份独立短文件,通常记录背景、可选方案、最终决定及其后果,放在版本库中随代码一起演进。它与 DECISIONS.md 这类集中式决策日志的区别在于粒度——ADR 通常一决策一文件、编号命名、状态可标记为 proposed/accepted/superseded。文中提到的 GOV.UK content-publisher 与 log4brains 都属于采用 ADR 生态的仓库,这也是 Decision Brain 需要同时兼容「小节式日志」和「一决策一文件」两种形态的原因。

rss source: Decision Brain: giving AI the why behind the work, with FalkorDB

图里到底装了什么

Decision Brain 的摄入分两条线,这个分工本身就是设计的一部分。

代码负责确定性提取,不经过模型

Git 历史被转成 Commit、File、Person 节点(AI 共同作者会被标记为 agent)。每条决策日志条目——DECISIONS.md 的一个小节,或一个 ADR 文件——变成一个带日期、状态和精确源码行号的 Decision 节点,再由 git blame 链接到记录它的那次提交。仓库中 AI 代理指令文件(CLAUDE.md、AGENTS.md、.cursorrules)的每个历史版本也按日期保存为片段,于是“写这次提交时代理被要求了什么”成了一个可查询的问题。

本地代理团队负责阅读,用 Cypher 协作

四个 worker 从共享的“看板图”上认领条目,抽取度量数据、实验(含结论)、涉及的文…件与概念,以及条目之间的关系。看板本身也是一张 FalkorDB 图,认领、发现、交接都靠 Cypher 完成。由于 FalkorDB 对单图的写入是串行化的,认领操作天然具备原子性:

MATCH (s:Section)
WHERE (s.status = 'pending' OR (s.status = 'claimed' AND s.expiresAt < $now))
  AND s.attempts < $max AND coalesce(s.waiting, 0) = 0
WITH s ORDER BY s.order LIMIT 1
MATCH (a:Agent {name: $agent})
SET s.status = 'claimed', s.claimedBy = $agent, s.expiresAt = $now + $ttl, s.attempts = s.attempts + 1
CREATE (a)-[:CLAIMED {at: $now}]->(s)

认领会过期,崩溃 worker 的条目可以被接管;waiting 字段让目录摘要等待其下的文件先完成。当一个代理认为条目 A 取代了条目 B 时,它会发起一次“交接”——必须由另一个代理各引一段原文确认后才算数。在 Invarail 的 151 个条目上,团队发起了 15 次交接,确认 9 次,其余被第二个代理带理由驳回,理由都能在看板上读到。

Cypher 是 Neo4j 提出的图查询语言,用 ASCII-art 风格的模式描述节点与边,例如 (a:Agent)-[:CLAIMED]->(s:Section) 表示从 Agent 到 Section 的一条 CLAIMED 边。它的核心是 MATCH … WHERE … SET/CREATE 的声明式写法,配合 coalesce、collect、列表推导式等表达式完成条件过滤与聚合。FalkorDB 兼容 OpenCypher 的一个子集,因此文中认领条目的那段语句可以在同一事务语义下完成「筛选—排序—加锁—写入」的复合操作,这正是用图数据库当看板而非关系表的关键理由。

模型写的东西一律先过检查

每一段引用必须在条目中逐字出现,每个数字必须出现在其引用内,每个文件名必须在 git 中存在;文件摘要提到文件里没有的函数或类会被拒绝。Peter 也坦言这套检查有自己的 bug:它曾因为没有任何文件包含 TaskStore.add 这个完整字符串而拒掉了 13 份好摘要,现在改为限定名的每一部分都必须作为标识符出现——但这仍然不能证明 add 属于 TaskStore。

最后,一条派生的 RATIONALE 边把决策连到它所依据的文件,FalkorDB 内置算法直接回答结构性问题:哪些文件承载了最多推理(algo.pageRank),两个东西如何相连(algo.SPpaths)。

提问与验证:答案之后才是重点

查询代理是一个普通的工具调用循环,围绕十二个 MCP 工具展开——why、what_breaks、disproven、who_knows、trace、origin、told、review 等,每个工具对应一小段 Cypher,同样的工具可以服务任何 MCP 客户端。

Peter 最在意的不是模型怎么答,而是答完之后代码怎么查:引用的每个 [id] 必须是本轮工具真实返回过的;每个数字必须出现在所引条目的证据中(0/20 配错引用就判失败);“我不知道”只有在答案其余部分没有断言时才算有效弃答。页面上会写明“已核对 2 条引用 · 8 个数字与证据匹配”,并把正文标注为“模型撰写”,因为措辞本身没有被代码验证。

代码评审:演示里最有说服力的部分

这个演示原本不在计划内,却成了最有价值的用法。评审页完全跑在浏览器里:一位贡献者“修正”了那条错误的 router 规则,合并前,系统会按该提交时的状态(从 git 重建,而非今天的文件)还原决策日志,然后给出:

  • R1:router prompt 里的“错误”规则是承重的——实测 0/20 vs 16/20
  • D1:“修正后的 router prompt 文本能修复 7B 的记忆路由”——尝试过,已被证伪
  • P1:谁了解这个文件

随后在一个明确标注为模拟的一次性副本里,团队记录了一条新决策:7B 退役,在受支持的模型上修正规则测得 20/20 vs 19/20。点一下 Sync,Decision Brain 只拾取变化的部分——一个代理提出“取代”,第二个代理确认——重新提交的修正案以新决策为指引,旧决策列为已被取代,同时还多出一行没被脚本安排的内容:“修正规则对 qwen2.5:7b 有效”作为已证伪记录在新决策之下。旧约束以实时警告的形式存活,并被限定在它仍然适用的范围内。新证据并没有简单地“赢”。

再次评审原始提案,它显示的仍是当时的证据——一次变更,按它被做出时所知道的东西来评判。

图检索到底有没有用:一组对照数据

在跑任何实验之前,Peter 冻结了 24 个问题,覆盖三个仓库:Invarail(自己的项目)、GOV.UK 的 content-publisher,以及他此前从未见过的 log4brains。关键对照是同一个本地模型改用关键词搜索结果,而不是图工具。实验跑了两次,第二次由一个新代理在只看到匿名 A/B 表的情况下打分。

指标Decision Brain同模型 + 关键词搜索
找到必需证据20/20 · 20/2015/20 · 15/20
通过评分标准的答案17/20 · 16/2011/20 · 10/20
纠正错误前提4/4 · 4/42/4 · 2/4
正确的“我不知道”4/4 · 4/44/4 · 4/4
中位延迟20.6s · 22.9s7.9s · 7.8s

差距几乎全部来自检索:关键词搜索漏掉答案所依赖的条目时,模型就无从作答,而图工具能找到它。代价是约 2.6–2.9 倍的延迟。Peter 对余量也很诚实:如果所有边界判断都判给 Decision Brain 的对立面,结果是 14 vs 12——仍然领先,但前提纠错上的优势就消失了。全部数据在 eval/v2/ 里。

MCP(Model Context Protocol,模型上下文协议) 是让模型以统一方式调用外部工具与数据源的开放协议:服务端声明一组带名称、参数 schema 和说明的工具,客户端(如 Claude Desktop、IDE 插件或自建 agent)在对话中按需发起调用并把结果回灌给模型。文中查询代理的十二个工具(why、what_breaks、who_knows 等)就是这个协议的实现,因此同一套图能力无需改动即可接入任何支持 MCP 的客户端——工具即接口,模型只负责决定调用哪一个。

踩过的坑,都写在记录里

每一次修正都在 DECISIONS.md 中标注了日期。几个值得记住的:

  • 接地检查器三次过度断言:把“Not surprisingly…”当成无数字前提,把任何否定当成弃答,接受配错决策的数字。每次都是外部 reviewer 发现的,现在都成了测试。
  • 一次 FalkorDB 升级静默破坏了查询:在 6.x 上,嵌套推导式之后的列表推导式返回 []。CI 跑的镜像比本机新,于是间歇性失败;他先猜负载,再猜哈希冲突,最后才定位。修复方式是改用朴素的 MATCH … collect,并固定镜像版本。
  • 第一次在陌生仓库上全新克隆运行时四种方式失败:按日期命名的 ADR、同一天写入的两条记录冲突、修 bug 时删掉的函数、以及比合并时间早好几个月写下的提交。四种都补了测试,之后一次干净无中断的运行。
  • 演示本身发现了两个 bug:被取代决策的旧实验仍显示为“不要重试”警告;代理让用户复查一个图已知被替换的决策。演示就是测试。

上手与下一步

代码、安装和评估都在 GitHub。需要 Docker、Node 22.9+,以及一个支持工具调用的 OpenAI 兼容模型服务(作者用的是 vLLM 上的 glm-5.3-flash,Ollama 上的 qwen3.8:27B 也可以)。指向任何带 DECISIONS.md 或 ADR 目录的仓库即可。

更大的想法在图之外:同一套图结构适用于任何决策记录,不只是代码——业务文档、电子表格、制度文件,每一次变更都能连回它的理由。近期计划包括问题记忆(已答问题供下一个代理复用)、按小节阅读超长条目,以及一个在每次 pull request 上自动发布评审的 GitHub Action。

对关注 AI 编程工具的读者来说,这个项目最值得借鉴的不是某个技巧,而是一种取向:在代理越来越能改代码的时代,把“为什么”做成基础设施,比让它停留在某个人脑子里更可靠。

分享:

相关推荐