Graph Engineering 来了:Claude Code 让 Agent 从一条直线变成一张图

昨天刷到一条推文,782 赞、1948 收藏、31 万阅读,作者 @0xCodez 用一篇长文把 Claude Code 的 Dynamic Workflows 拆成了 14 个可复用的架构步骤。

标题叫「Graph Engineering with Claude」。

我花了一小时把这 14 步啃完,发现它讲的不是工具用法,而是一个更大的东西:Agent 的编排思维,正在从「一条线」进化成「一张图」。

9/10 搭过 Agent 的人,排出来的流程长这样:先做 A,再做 B,然后 C,最后 D。每个步骤乖乖等上一个跑完才启动。

问题是一半的箭头根本不传数据。「总结文件」和「查天气」之间没有依赖关系,但你把它们排成了一条链,天气就必须等总结跑完才开始。链越长越脆弱:C 卡住,D 永远不会执行,A 的结果困在上游出不来。

这是一条直线,不是一张图。

节点做思考,边传递结果

Graph Engineering 的核心概念只有两个:节点和边。

节点是一个工作单元——一个 Agent,一个明确的任务,一个输入对应一个输出。是一条依赖关系:这个节点的输出,喂给那个节点的输入。仅此而已。

多数人犯的错,是把「然后」当成了边。「先总结文件,然后查天气」——这两件事之间没有「然后」,因为天气不读总结的输出。没有数据流动,就没有边,等待就是浪费。

每一个「然后」都要问一句:下一步读不读上一步的输出?不读,就没有边,直接并行。

你写的线性脚本,是一个退化的图

当你写「做 A,再做 B,再做 C」的时候,你其实画了一个图——一条不分叉的链,每个节点恰好一条进边、一条出边。

它能跑对。但它跑得慢、扛不住错,因为链没有冗余:C 卡了,D 永远不跑,A 的结果困死在上游。

Graph Engineering 的第一个技能,就是重画这条链。对每个箭头问那个问题——这一步的输出,下一步真的读吗?砍掉几条不传数据的箭头,链就塌缩成更宽的形状:几个可以同时跑的独立节点,汇入一个需要它们全部结果的汇聚节点。

给每个节点一个契约

你无法推理的节点,你也无法并行。解法是契约:输入有界、输出有界、只做一件事。

输入是节点读取的任何东西——显式传入,绝不能从共享窗口里假设。输出是一个确定的形状,最好经过验证,这样下游节点可以消费它而不用猜。

在 Claude Code 的 workflow 里,契约靠 schema 执行。当你给 agent() 传一个 JSON schema,Claude spawn 出的 subagent 被强制返回经过验证的结构化数据——验证发生在 tool-call 层,格式不对 Claude 会自动重试,而不是扔给你一段自由文本让你祈祷能解析出来。

这就是「节点 Claude 能接进图」和「节点只能靠人读输出才能用」的区别。

边也是数据契约

边不只是「B 在 A 后面」。它是关于什么数据穿过的一个承诺:A 产出这个形状,B 被设计来消费这个形状。

当你用数据而不是顺序来命名边,两件事变简单了:你能立刻看出这条边是不是真的(数据真的在流动吗?),而且你可以在两端换掉节点而不破坏图——只要形状不变就行。

实际操作中,边活在纯 JavaScript 里。扇出和合成之间的 reduce 步骤——flatten、dedupe、filter——就是代码操作数据形状。不需要 Agent。图思维的一个安静胜利:大量被人们烧 token 做的事情,其实只是边操作,而边是免费的。

给每个节点一个契约

你无法推理的节点,你也无法并行。解法是契约:输入有界、输出有界、只做一件事。

输入是节点读取的任何东西——显式传入,绝不能从共享窗口里假设。输出是一个确定的形状,最好经过验证,这样下游节点可以消费它而不用猜。

在 Claude Code 的 workflow 里,契约靠 schema 执行。当你给 agent() 传一个 JSON schema,Claude spawn 出的 subagent 被强制返回经过验证的结构化数据——验证发生在 tool-call 层,格式不对 Claude 会自动重试,而不是扔给你一段自由文本让你祈祷能解析出来。

这就是「节点 Claude 能接进图」和「节点只能靠人读输出才能用」的区别。

边也是数据契约

边不只是「B 在 A 后面」。它是关于什么数据穿过的一个承诺:A 产出这个形状,B 被设计来消费这个形状。

当你用数据而不是顺序来命名边,两件事变简单了:你能立刻看出这条边是不是真的(数据真的在流动吗?),而且你可以在两端换掉节点而不破坏图——只要形状不变就行。

实际操作中,边活在纯 JavaScript 里。扇出和合成之间的 reduce 步骤——flatten、dedupe、filter——就是代码操作数据形状。不需要 Agent。图思维的一个安静胜利:大量被人们烧 token 做的事情,其实只是边操作,而边是免费的。

菱形拓扑:分叉 → 并行 → 合并

把分发和汇聚拼在一起,就得到了所有严肃 Agent 图的主力拓扑:菱形

一个节点拆任务,多个节点并行干活,一个节点合并结果。市场扫描、依赖审计、代码审查、研究报告——换掉数据源和 prompt,骨架一样。

它的标准形态叫 fan out → reduce → synthesize:扇出去收集广度,用纯代码压缩信息密度,用最后一个 Agent 写出最终答案。

对抗性验证:让 Agent 互相挑刺

真正让 Graph 有杠杆效应的,不是更多 Agent,而是能包裹在 Agent 外面的结构。

一个验证节点坐在边上,结果被允许传到下游之前,它的工作就是试着推翻这个发现。活下来了,通过;推翻了,它永远到不了最终答案。

三种验证模式值得掌握:

对抗性验证——给每个发现派 N 个独立的「怀疑者」,prompt 让它们反驳,多数存活才保留。多视角验证——每个验证者用不同镜头:正确性、安全性、可复现性。多样性比 N 个相同检查更能抓住故障。裁判团——从不同角度生成 N 个方案,用并行裁判打分,从赢家出发、嫁接亚军的精华。

运行时路由:图不必是固定的

不是每张图都是固定的。有时候走哪条边,取决于节点发现了什么。

一个路由节点检查结果,决定哪条下游路径被触发——分类工单,然后分支到对应处理器;检查 diff 大小,然后决定快速审查还是全量审计。在 workflow 里,这就是对节点验证输出的一个 JavaScript if 或 switch。

确定性在这里是特性,不是限制。 路由的决策可以由 Claude 驱动(一个 subagent 做分类),但路由本身是 Claude 写的代码——所以对同样的分类,它每次都跑出同样的结果。你在节点处得到 Claude 的判断力,在边处得到脚本的可靠性。不会出现「Claude 自己决定跳过审计」的意外——因为跳过必须被写进图里,而它没有。

运行时路由:图不必是固定的

不是每张图都是固定的。有时候走哪条边,取决于节点发现了什么。

一个路由节点检查结果,决定哪条下游路径被触发——分类工单,然后分支到对应处理器;检查 diff 大小,然后决定快速审查还是全量审计。在 workflow 里,这就是对节点验证输出的一个 JavaScript if 或 switch。

确定性在这里是特性,不是限制。 路由的决策可以由 Claude 驱动(一个 subagent 做分类),但路由本身是 Claude 写的代码——所以对同样的分类,它每次都跑出同样的结果。你在节点处得到 Claude 的判断力,在边处得到脚本的可靠性。不会出现「Claude 自己决定跳过审计」的意外——因为跳过必须被写进图里,而它没有。

隔离故障,别让一个节点毒死整张图

在链里,一个失败会级联——C 死了,D 不跑,整条线停。在图里,失败应该被锁在节点内。

parallel() 里抛异常的 thunk 会 resolve 成 null,八个好 Agent 正常返回,一个坏的被丢掉。.filter(Boolean) 就是故障隔离墙。

更隐蔽的故障是节点互相踩踏——多个 Agent 并行写文件会冲突。解法是隔离:每个 Agent 在自己的 git worktree 里跑,做完再干净合并。

加一个循环——但让它收敛

有时候你不知道任务有多大,直到你已经在里面了:未知规模的发现、bug 扫描中找到一个 bug 会暴露三个。这需要循环——一条控制好的边,指回前面的节点。

危险显而易见:不收敛的循环就是无限循环,Agent 一直 spawn 到预算花光。

能收敛的模式叫 loop-until-dry:持续 spawn 查找器,直到连续 K 轮没有新发现,然后停止。一个决定成败的细节——几乎所有人第一次都会犯的错——是你对什么去重。必须对所有已见结果去重,而不是只对确认结果去重。 否则被拒绝的发现每轮都会重新出现,循环永远不会跑干,你造了一台花钱重新发现相同死胡同的机器。

加一个循环——但让它收敛

有时候你不知道任务有多大,直到你已经在里面了:未知规模的发现、bug 扫描中找到一个 bug 会暴露三个。这需要循环——一条控制好的边,指回前面的节点。

危险显而易见:不收敛的循环就是无限循环,Agent 一直 spawn 到预算花光。

能收敛的模式叫 loop-until-dry:持续 spawn 查找器,直到连续 K 轮没有新发现,然后停止。一个决定成败的细节——几乎所有人第一次都会犯的错——是你对什么去重。必须对所有已见结果去重,而不是只对确认结果去重。 否则被拒绝的发现每轮都会重新出现,循环永远不会跑干,你造了一台花钱重新发现相同死胡同的机器。

模型分层:不是每个节点都需要最强大脑

图让一件事变得显而易见:不是每个节点都需要你的最强模型。

有些节点是重复性、有界的——提取字段、分类工单;有些节点承载真正的判断——写报告、裁决发现。把无聊的节点跑在便宜模型上,把昂贵的 token 花在真正需要判断力的地方。

一个 agent() 调用的 model 选项,就能让 Claude 把特定节点路由到不同模型。大跑之前先查 /model,把扇出的重复节点降级,合并节点保持最高级——这根杠杆能把 token 饥渴的图从昂贵变成经济。

默认用 Pipeline,慎用 Barrier

parallel() 是 barrier——所有节点跑完才返回,最慢的那个决定总耗时。pipeline() 是流式的——每个项目独立跑完所有阶段,快的先完成,不用等慢的。

默认选 pipeline()。 只有当一个阶段真正需要所有上游结果同时在场——跨集合去重、基于总数的提前退出——才用 barrier。「代码更整洁」「阶段感觉更独立」不是理由。barrier 的延迟是真实的、可测量的、被浪费的。

让 Claude 自己画图

终极操作:停止手工画图。

Dynamic Workflows 让 Claude 自己写编排脚本——拆解任务、选择分发策略、spawn 协调的 Agent 舰队、合成结果。你得到的是一张为这次运行量身定制的图,而不是一张你希望它能用的固定流程。

三种入口:在 prompt 里说「workflow」,Claude 就会为任务写一个。跑保存好的 /workflows——deep-research 就是一个真实在生产里跑的图:scope → parallel search → fetch → adversarial verify → synthesize。或者开启 ultracode,Claude 为每个实质性任务自动规划 workflow。跑得好,按 s 保存脚本到 .claude/workflows/——版本控制、可按名重跑、任何 clone 仓库的人都能启动。

6 个本周就能搭的图

安全扫描。 Claude 为每个路由文件 spawn 一个 subagent,各自寻找缺失的认证检查,然后一轮验证器确认每个发现再出报告。单个上下文永远装不下的广度。

带引用的研究报告。 用 /deep-research 就能跑的图:scope → parallel search → fetch → adversarial verify → synthesize。Claude 把问题拆成不同角度,并行搜索,去重来源,然后用三个怀疑者对抗验证每条声明,最后写报告。

逐文件移植模块。 Bun 的做法,放大到你的仓库。Claude 把翻译扇出到各个文件,每个文件跑测试作为门槛,失败的循环回去——对抗性审查抓住单次遗漏的问题。

对抗性 diff 审查。 Claude 按 diff 大小路由:小改动一次快速通过,大改动触发全量并行审计,审查者各有不同镜头——正确性、安全性、性能——然后裁判团合成最终意见。

定时生态扫描。 保存一次,永远重跑。Claude 并行检查多个来源——releases、博客、讨论——在 barrier 处按影响力排序,写 digest。版本控制在 .claude/workflows/ 里,按名启动。

未知规模的发现。 你不知道有多少 bug。Claude 并行跑查找器,每轮对所有已见结果去重,验证存活者,持续循环直到两轮没有新发现——然后停止。

6 个本周就能搭的图

安全扫描。 Claude 为每个路由文件 spawn 一个 subagent,各自寻找缺失的认证检查,然后一轮验证器确认每个发现再出报告。单个上下文永远装不下的广度。

带引用的研究报告。 用 /deep-research 就能跑的图:scope → parallel search → fetch → adversarial verify → synthesize。Claude 把问题拆成不同角度,并行搜索,去重来源,然后用三个怀疑者对抗验证每条声明,最后写报告。

逐文件移植模块。 Bun 的做法,放大到你的仓库。Claude 把翻译扇出到各个文件,每个文件跑测试作为门槛,失败的循环回去——对抗性审查抓住单次遗漏的问题。

对抗性 diff 审查。 Claude 按 diff 大小路由:小改动一次快速通过,大改动触发全量并行审计,审查者各有不同镜头——正确性、安全性、性能——然后裁判团合成最终意见。

定时生态扫描。 保存一次,永远重跑。Claude 并行检查多个来源——releases、博客、讨论——在 barrier 处按影响力排序,写 digest。版本控制在 .claude/workflows/ 里,按名启动。

未知规模的发现。 你不知道有多少 bug。Claude 并行跑查找器,每轮对所有已见结果去重,验证存活者,持续循环直到两轮没有新发现——然后停止。

Prompt Engineering → Loop Engineering → Graph Engineering

如果 Prompt Engineering 是「怎么写一句话让 AI 做对事」,Loop Engineering 是「怎么让 Agent 的每次循环可观测、可中断、可改进」——那 Graph Engineering 是第三层:怎么编程多个 Agent 组织本身的结构。

Prompt 是句子。Loop 是循环。Harness 是 Agent 站的地板。但工作的形状——什么先跑、什么能同时跑、什么必须等一切结束——这个形状是一张图。

多数人会继续把步骤排成一条线,因为那符合我们打字的方式。学会画图的人,会跑一支舰队——并且永远不会注意到其他人卡在天花板下面。

登录查看剩余 70% 内容

相关文章