Agent 编排正从线性流程迈向图结构,这是一场面向 AI Agent 工作流的关键升级。Graph Engineering 拆解 Claude Code 的 14 个步骤,帮助解决传统流程中常见的低效、脆弱与难扩展问题。核心内容:1. 传统 Agent 线性工作流中的依赖传递与稳定性问题2. Graph Engineering 的核心概念:节点(工作单元)与边(数据依赖)3. 如何落地 Graph Engineering:重画链路、节点契约(schema 验证)、边的数据契约

昨天刷到一条推文,拿到了 782 赞、1948 收藏、31 万阅读。作者 @0xCodez 用一篇长文,把 Claude Code 的 Dynamic Workflows 拆解成 14 个可复用的架构步骤。
文章标题叫「Graph Engineering with Claude」。
我花了一小时把这 14 步完整看完,发现它讲的并不只是 Claude Code 的使用技巧,而是一个更重要的方向:Agent 编排方式,正在从“一条线”升级为“一张图”。
绝大多数做过 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,以及 B 是否就是为消费这种数据而设计的。
当你开始用数据而不是顺序来定义边时,两件事会立刻变得更简单:第一,你能马上判断这条边是否真实存在,也就是数据是否真的在流动;第二,只要数据形状保持不变,你就可以替换边两端的节点,而不会破坏整个图结构。
在实际工程里,边通常存在于纯 Ja vaScript 代码中。扇出和合成之间常见的 reduce 步骤——flatten、dedupe、filter——本质上都是对数据形状的处理。这些操作并不需要 Agent。图结构思维一个很容易被忽视的优势是:很多人花大量 token 让 Agent 做的事,本质上只是边的数据处理,而边操作几乎是免费的。
给每个节点定义清晰契约
如果一个节点无法被可靠推理,你就无法放心地让它并行执行。解决办法就是契约:输入有边界、输出有边界、并且只做一件事。
输入指的是节点会读取的所有信息——必须显式传入,绝不能默认它会从共享上下文里“自己知道”。输出则应该是一个确定的结构,最好还能通过验证,这样下游节点消费数据时就不需要靠猜。
在 Claude Code 的 workflow 中,这种契约依赖 schema 来执行。当你给 agent() 传入 JSON schema,Claude spawn 出的 subagent 就会被约束为返回经过验证的结构化结果——验证发生在 tool-call 层,格式错误时 Claude 会自动重试,而不是把不稳定的自由文本交给你处理。
这就是“适合接入 Agent 图结构的节点”与“只能靠人手动理解输出的节点”之间的差别。
边同样需要明确的数据契约
边不只是“B 在 A 之后执行”。它实际上是在声明:A 会产出某种数据形状,而 B 会按这个形状去消费。
一旦你开始用数据而不是顺序来命名边,两件事会明显变容易:你能迅速判断这条边是否真实存在,也就是数据是否真的在传递;同时,只要数据结构不变,你就可以替换任意一端的节点,而不破坏整个工作流图。
在工程实践中,边往往活在纯 Ja vaScript 逻辑里。扇出与汇总之间的 reduce 步骤——flatten、dedupe、filter——本质都是对数据形状的处理,不需要 Agent 参与。图结构思维的一个低调优势在于:许多人消耗大量 token 交给 Agent 处理的工作,其实只是边操作,而边几乎没有成本。
菱形拓扑:分叉 → 并行 → 合并
把任务分发与结果汇聚组合起来,你就得到了严肃 Agent 系统里最常见、也最实用的拓扑结构:菱形。
一个节点负责拆解任务,多个节点并行处理,最后由一个节点负责合并结果。无论是市场扫描、依赖审计、代码审查还是研究报告,只要替换数据源和 prompt,底层骨架通常都是同一种模式。
它的标准形态就是 fan out → reduce → synthesize:先扇出获取广度,再用纯代码压缩信息密度,最后由一个 Agent 产出最终答案。
对抗性验证:让多个 Agent 互相挑刺
真正让 Graph Engineering 产生杠杆效应的,不是简单地堆更多 Agent,而是把结构包裹在 Agent 外面。
你可以在边上放一个验证节点,在结果被允许流向下游之前,它的职责就是尽力推翻这个发现。能活下来,就通过;被推翻,就永远到不了最终答案。
有三种验证模式尤其值得掌握:
对抗性验证——每得到一个发现,都不要立刻采信,而是先配上 N 个彼此独立的“怀疑者”,用 prompt 明确要求它们寻找漏洞、提出反驳;只有在多数质疑下依然站得住,结论才值得保留。多视角验证——验证不能只有一种角度,每个验证者最好从不同镜头审视结果:正确性、安全性、可复现性。说得更直接一点,比起 N 个做同样检查的人,视角足够多样往往更容易发现真正的问题。裁判团——先从不同角度生成 N 个候选方案,再交给并行裁判分别打分,最后以胜出方案为主,同时吸收其他高质量方案的优点。
运行时路由:图结构不一定是静态的
并不是每一张工作流图都是固定死的。有时候应该走哪条边,取决于某个节点刚刚发现了什么。
一个路由节点会检查结果,并决定触发哪条下游路径——比如先分类工单,再分发到不同处理器;或者先检查 diff 大小,再决定是快速审查还是全量审计。在 workflow 中,这通常只是对节点验证输出执行一个 Ja vaScript if 或 switch。
这里的确定性是优势,不是局限。 路由决策可以由 Claude 驱动(例如由一个 subagent 负责分类),但真正的路由逻辑仍然是 Claude 编写的代码——所以面对相同分类,它每次都会走出相同路径。你在节点中获得 Claude 的判断力,在边上得到脚本的稳定性。不会出现“Claude 自己决定跳过审计”的意外,因为是否跳过,必须被明确写进图结构里。
运行时路由:图结构不一定是静态的
不是每一张 Agent 图都必须固定不变。有时候到底走哪条边,完全取决于节点在运行时得到了什么结论。
一个路由节点可以先检查结果,再决定触发哪条下游路径——例如先对工单做分类,再分发到对应处理器;或者先检查 diff 规模,再判断是快速评审还是全量审计。在 workflow 里,这往往就是对节点验证输出做一个 Ja vaScript if 或 switch。
确定性在这里恰恰是一种工程优势,而不是限制。 路由判断可以由 Claude 负责,但路由行为本身仍然由代码定义。因此面对同样的分类结果,系统每次都会做出同样的路径选择。节点提供智能判断,边提供可控执行,这正是图结构工作流稳定性的来源。也因此,不会发生“Claude 自己跳过审计”这种失控情况——因为图里根本没有那条边,它就不可能绕过去。
隔离故障,别让单个节点拖垮整张图
在线性链路里,一个失败会层层级联——C 挂了,D 不再执行,整条链就停住了。在图结构里,失败应该被限制在节点内部,而不是污染全局。
parallel() 里抛出异常的 thunk 会 resolve 成 null,八个正常工作的 Agent 依旧返回结果,那个出错的节点会被丢弃。.filter(Boolean) 就是最直接的故障隔离墙。
还有一种更隐蔽的故障,是节点之间相互踩踏——比如多个 Agent 并行写同一个文件时产生冲突。解决方式就是隔离运行环境:让每个 Agent 在自己的 git worktree 里执行,完成后再做干净合并。
加入循环——但一定要让它收敛
有时候你根本不知道任务有多大,直到真正进入执行阶段:例如开放式发现任务,或者 bug 扫描中找到一个 bug 后又暴露出三个新问题。这时就需要循环——也就是一条被控制的边,重新指回前面的节点。
风险也非常明显:不收敛的循环就是无限循环,Agent 会不断 spawn,直到预算被彻底烧光。
真正能在工程中跑通的,通常是这种 loop-until-dry 模式:持续 spawn 查找器,不断向前探索,直到连续 K 轮都没有任何新发现,系统才停止。这里有一个几乎决定最终效果的关键细节,也是很多人第一次搭 Agent 工作流时最容易踩的坑:去重到底应该作用于什么对象。必须对所有已见结果去重,而不能只对确认结果去重。 否则,那些已经被拒绝过的发现会在每一轮里反复出现,循环永远“干”不下来,最终你得到的就是一台持续烧钱、反复发现同一条死胡同的机器。
加入循环——但一定要让它收敛
有些任务的规模在开始前无法预估,只有运行过程中才会逐渐暴露出来:比如未知范围的问题发现,或者 bug 扫描里一个缺陷牵出更多缺陷。这类场景就需要循环——即一条受控的边,重新回到前置节点。
危险同样显而易见:如果循环不收敛,它就会演变成无限循环,Agent 持续 spawn,直到预算被耗尽。
能够收敛的经典模式叫 loop-until-dry:持续 spawn 查找器,直到连续 K 轮都没有新的发现,再结束流程。一个几乎决定成败的细节,也是很多人第一次实践时最容易犯的错误,就是去重范围。必须对所有已见结果去重,而不是只对确认后的结果去重。 否则那些已被否决的发现每一轮都会再次出现,循环永远跑不干,最终只会变成一台不断花钱、重复撞同一堵墙的机器。
模型分层:不是每个节点都值得用最强模型
图结构会让一件事变得非常清楚:并不是每个节点都需要调用最强的大模型。
有些节点是重复、边界清晰的——比如字段提取、工单分类;有些节点才真正承载判断力——比如生成报告、裁决发现。更合理的做法,是把重复且规则明确的节点放到更便宜的模型上运行,把昂贵 token 留给真正需要推理能力的关键节点。
通过一个 agent() 调用里的 model 选项,就可以让 Claude 把不同节点路由到不同模型。大规模运行前先检查 /model,把扇出阶段的重复节点降级,把最终合并和判断节点保留在高能力模型上——这根杠杆能显著降低 Agent 工作流成本。
默认优先 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 运行的基础设施。但真正决定工作流效率与扩展性的,是任务的形状——什么必须先执行、什么可以并行、什么又必须等所有结果到齐后再处理。这个形状,本质上就是一张图。
大多数人仍然会继续把步骤排成一条线,因为这符合人类书写和思考的直觉。可一旦你学会用图结构来设计 Agent 系统,你运行的就不再是单个流程,而是一支可协调的舰队——而且你会很快发现,很多人还卡在线性流程的天花板下面。
—精选文章—
企业 AI 落地最大的难点:懂 AI 的人不懂业务,懂业务的人不懂 AI
Google 工程负责人发长文:AI 正在偷走程序员建立判断力的机会
Emil Kowalski 把苹果设计师的 17 条原则做成了 Claude Skill:AI 写前端终于不再有“那个味儿”
从 Loop Engineering 到 Graph Engineering:Agent 架构的下一次升级,真的要来了吗?
登录查看剩余 70% 内容
