大多数人仍然像使用一个非常昂贵的实习生一样使用 Claude Code。
他们给它一个任务,等一个答案,然后手动决定下一步做什么。
但那些从 AI 中获得最大杠杆的团队,正在构建更接近一个小型分布式系统的东西。

一个 Agent 负责界定问题范围。
五个更便宜的 Agent 并行搜索。
一个确定性脚本去除重复项。
三个持怀疑态度的 Agent 试图推翻这些发现。
一个顶级模型做出最终判断。
这就是图工程。
在继续阅读之前:
收藏本指南,这样当你开始构建自己的 Claude 工作流时,可以随时回到这些图模式。
并且关注
@Gyome1_ -
我会拆解 Claude Code、AI Agent,以及将单个模型转化为可靠工程工作流的系统。
我花了数周时间剖析真实的 Agent 架构、工作流图和生产模式,将图工程重新构建成一本实用的操作手册。
与其写一个更长的提示词,不如设计信息在系统中流动的路径:
线性 → 扇出 → 归约 → 验证 → 综合
每个 Agent 成为一个节点,承担有限的工作。每条边携带结构化数据。路由器决定哪个分支运行。验证器拒绝弱输出。循环持续进行,直到图不再发现任何新内容。
重要的转变是,Claude Code 不再需要像单个智能体那样,在一个巨大的检查清单中逐项工作。
它可以生成编排代码,派出一组专门的子 Agent,将其输出路由到不同的模型,并仅在证据通过验证后才组合最终结果。

这些基本模式并不新鲜。软件工程师几十年来一直在使用 DAG、管道、屏障、MapReduce 和分布式工作节点。
改变的是每个节点内部现在放置的是什么。
一个节点可以搜索仓库、审计迁移、挑战架构决策、检查测试失败,或将五十个独立的发现综合成一个带有引用的报告。
本指南将整个系统从最简单的线性 Agent 分解到菱形图、路由器节点、对抗性验证器面板、收敛循环、模型分层,以及直接在 Claude Code 内部生成的动态工作流。
到最后,你将能够面对一个大型任务,不再问:
“我应该写什么提示词?”
1. 图工程从账单开始
图工程通常被呈现为一种运行更多 Agent 的方式。
但这种框架忽略了成本高昂的部分。
你可以启动二十个 Claude Agent 针对同一个仓库,得到二十份重叠的报告、重复的上下文、相互矛盾的结论,以及一张大得多的 API 账单。
一个有用的图控制计算发生在哪里、每个决策由哪个模型处理、以及不确定的发现如何在工作流中移动。
想象一下让 Claude Code 准备一个生产迁移:

检查仓库,找到所有依赖,提出迁移方案,识别风险,验证计划,并撰写最终简报。
在一个提示词内部,这变成了一个漫长而不透明的过程。Claude 搜索代码库,将发现存储在上下文中,设计迁移,审查自己的计划,并生成最终报告。
当报告失败时,很难定位失败的原因。Claude 可能漏掉了一个文件,误解了一个依赖,丢失了早期的一个细节,或者在验证过程中接受了一个薄弱的假设。
每个阶段也可能都在同一个昂贵的模型上运行,即使任务的部分内容涉及简单的提取或排序。
图工程打开了这个工作流,让每个决策都有可见的位置。
检查分支同时运行,因为它们使用相同的界定任务,并且不依赖彼此的输出。
它们的发现在一个归约阶段会合,在那里重复项消失,证据被压缩成更小的数据集。
然后一个路由器读取严重性。常规变更通过轻量级审查。高风险发现经过几个独立审查者的深入分析,然后才到达最终模型。
结果是一个工作流,其中延迟、模型成本、上下文大小和验证深度都通过图的结构得到控制。
一个节点应该做一件事
一个有用的节点有明确的职责范围。
找到所有对已弃用 API 的调用。 将每个迁移风险分类为低、中或高。 测试回滚计划中的失败情况。
每个节点需要清晰的输入、明确的输出和有限的决策面。一个搜索仓库、估算业务影响、设计修复方案并撰写建议的节点,仍然包含几个隐藏的阶段。调试仍然困难,因为中间推理被埋没在一个模型调用中。
更小的边界揭示了证据在何处进入系统,以及其含义在何处发生变化。
一条边应该携带证据
一条边表示下一个节点所需的数据。
扫描器可以返回一个可预测的对象:

1{2 "file": "src/auth/session.ts",3 "lines": [84, 119],4 "dependency": "legacySessionClient",5 "confidence": 0.94,6 "evidence": "两个调用点都依赖于已弃用的 refresh 方法。"7}
风险分类器现在为每个发现接收相同的字段。它可以拒绝不完整的结果,对相关文件进行分组,并将不确定的证据路由到另一个审查。
Schema 减少了节点之间的解释偏差。自由格式的段落会迫使每个下游 Agent 重建前一个 Agent 的含义。经过几个阶段,小的歧义可能会改变最终结论。
结构化输出使证据在图中移动时保持稳定。
有些节点就是普通代码
假设八个搜索 Agent 返回了八十个发现。
工作流需要合并数组、丢弃空响应、删除重复项,并对剩余项进行排序。
这些操作有确定性的答案:
1const uniqueFindings = [2 ...new Map(3 results4 .flatMap(batch => batch ?? [])5 .map(item => [`${item.file}:${item.lines.join("-")}`, item])6 ).values()7];
一个 JavaScript 转换可以立即处理这个操作,并且每次运行产生相同的输出。将同样的任务发送给另一个模型会增加令牌成本,并创造另一个可能丢失证据的地方。
模型节点应围绕搜索、分类、比较、审查和综合。代码可以处理验证、去重、排序、显式路由规则和其他可预测的转换。
这种划分成为图的基础。
每个模型调用都应该对应一个真正需要判断的决策。
2. 菱形:真实 Agent 图如何移动工作
大多数严肃的 Agent 图最终都会呈现相同的形状。
一个任务从一个共享范围开始,分裂到几个独立的工作节点,等待它们的输出,压缩证据,然后将结果传递给最终决策。
这个形状就是菱形。

左侧是扇出。
所有分支汇合的中点称为屏障。
右侧是扇入。
一旦一个任务对于一个上下文窗口来说变得太大,这种模式就无处不在。
仓库审计可以按子系统拆分。市场报告可以按来源拆分。研究任务可以按假设拆分。迁移审查可以按 API 使用、数据库更改、部署风险、测试覆盖率拆分。
每个工作节点接收相同的范围,但分配更窄的任务。
然后图等待,直到有足够有用的证据返回。
扇出应该创建独立的工作
当一个分支可以从共享输入开始,并且无需读取另一个分支的输出就能产生有用结果时,它就应该属于扇出。
对于安全审计,拆分可能如下所示:

Claude Code 可以使用诸如 parallel() 这样的屏障原语并发启动这些调用:
1const findings = await parallel(2 checks.map(check => async () => {3 return agent({4 task: check.task,5 context: auditScope,6 schema: FINDING_SCHEMA7 });8 })9);
编排保持在普通 JavaScript 中。每个分支接收一个有限的任务,并返回一个验证过的对象。
结果作为一组输出到达,可以过滤、检查并传递到下一个阶段。
一个大的扇出仍然需要为每个分支提供理由。
将一个模糊的任务拆分成十二个几乎相同的 Agent,通常会产生重复的发现,只是措辞略有不同。有用的并行性来自不同的来源、视角、代码区域或假设。
屏障创建一个决策点
屏障会暂停下一个阶段,直到所需的分支完成。
这个暂停很重要,因为某些决策依赖于完整集合。
当一半的仓库仍在检查时,排名节点无法识别最重要的漏洞。当部署审查仍在运行时,综合模型无法编写完整的迁移计划。
在屏障处,图有机会检查运行的状态:
1const completed = findings.filter(Boolean);23if (completed.length < MIN_REQUIRED_RESULTS) {4 throw new Error("审计覆盖率不足");5}
这就是部分失败变得可见的地方。
一个工作节点可能超时、返回格式错误的数据或没有结果。过滤掉 null 值可以让运行继续进行,但生产工作流通常需要更清晰的策略:
- 需要多少个成功分支;
- 哪些分支是强制性的;
- 失败节点是否应重试;
- 最终结果是否应标记为不完整。
因此,屏障是可靠性模型的一部分,而不仅仅是同步机制。
在综合之前先归约
扇出之后,图中可能包含数十个重叠的发现。
将它们全部直接发送到一个顶级模型会创建大量的上下文,重复相同的证据,并使重要细节更难区分。
归约阶段准备证据。
一些归约可以在代码中完成:
1const unique = deduplicateByKey(2 completed.flatMap(result => result.findings),3 finding => `${finding.file}:${finding.line}:${finding.type}`4);
下一层可能需要判断:
1const curated = await agent({2 task: `3 将相关发现分组。4 保留所有文件和行引用。5 按运营影响对每组进行排名。6 为每个结论返回最有力的证据。7 `,8 input: unique,9 schema: CURATED_FINDINGS_SCHEMA10});
归约控制着到达最终模型的内容。
一个好的归约器在保留证据的同时移除重复。一个激进的归约器可能会将几个不同的风险压缩成一个模糊的摘要,抹去验证所需的细节。
最安全的模式是保持每个归约后的声明与其源项之间的链接。
1{2 "risk": "迁移后会话刷新可能失败",3 "severity": "high",4 "sourceFindingIds": ["AUTH-04", "API-11", "TEST-07"],5 "evidence": [6 "三个服务调用已弃用的 refresh 方法",7 "不存在回退路径",8 "缺少集成测试覆盖"9 ]10}
现在,综合节点接收一个更小的数据集,而不会丢失可追溯性。
3. 可靠性是图的一部分
一个图可能快速完成,但仍然产生错误的答案。
一旦多个 Agent 开始搜索、分类和审查同一个任务,主要问题就变成了控制。
系统需要规则来决定哪些发现值得深入工作,哪些输出应该被拒绝,以及工作流何时搜索得足够多。
按风险路由
路由器节点读取结构化输出并选择下一个分支。

分类可以来自模型,而分支本身在代码中保持显式。
1const route =2 finding.severity === "high"3 ? runFullAudit(finding)4 : runQuickReview(finding);
这使昂贵的审查集中在具有重大影响的发现上。
一个有用的路由器依赖于图可以检查的字段:严重性、置信度、受影响的系统、财务风险或缺失证据的存在。
添加独立验证
一个 Agent 审查自己的结论,会在两个阶段中携带相同的假设。
一个更强的图将重要发现发送给几个具有不同任务的审查者。

审查者不应收到改进原始答案的指令。他们的任务是寻找可能不完整或错误的理由。
图可以要求达成一致后,发现才能向前推进:
1const accepted = votes.filter(vote => vote.approve).length >= 2;
隔离修改代码的 Agent
当并行编码的 Agent 编辑同一个工作目录时,它们可能会相互干扰。
一个 Agent 可能在另一个 Agent 仍在读取文件时将其覆盖。测试可能针对一组不相关的更改运行。
Git 工作树为每个分支提供其自己的仓库副本。
1主仓库2 │3 ├→ worktree/auth-fix4 ├→ worktree/db-migration5 └→ worktree/test-repair
每个 Agent 可以在自己的环境中修改文件并运行测试。后续节点比较补丁、检查冲突,并选择应合并的内容。
这使隔离成为图的一部分,而不是手动清理步骤。
让发现收敛
有些任务无法在一次遍历中完成。
仓库审计可能发现一个指向另一个包的依赖。那个包可能揭示另一个调用点。图需要一种可控的方式继续搜索,而不重复已经看到的一切。
1const seen = new Set();2let dryRounds = 0;34while (dryRounds < 2) {5 const findings = await discoverNext([...seen]);6 const fresh = findings.filter(item => !seen.has(item.id));78 fresh.forEach(item => seen.add(item.id));9 dryRounds = fresh.length === 0 ? dryRounds + 1 : 0;10}
重要的细节是针对之前见过的每个项目进行去重。
仅针对已确认的发现进行去重,会导致被拒绝或不确定的项目在下一轮返回,并消耗同样的工作。
循环在几个空轮次、固定预算或最大迭代次数后停止。生产图通常需要所有这三个条件。
将模型与节点匹配
并非每个节点都需要最强可用的模型。
提取、基本分类和狭窄搜索通常可以在更快的层级上运行。架构审查、对抗性验证和最终综合可能需要更强的模型。

模型分层成为图的另一个属性。
预算由运行多少个节点、循环重复多少次、每条边传递多少上下文,以及每个阶段由哪个模型处理来决定。
一个包含二十个廉价搜索调用的图,仍然可能比一个强模型调用花费更多。架构在需要另一个分支之前,需要一个令牌预算。
知道何时停止画图
小任务很少需要路由器、投票面板、工作树和收敛循环。
图的开销包括编排代码、Schema、重试、日志记录、中间存储以及更多需要调试的失败状态。
当一个模型可以容纳相关上下文,任务几乎没有独立分支,并且错误答案的成本较低时,线性工作流通常就足够了。
图工程在任务具有并行工作、昂贵决策、大型证据集或有意义的验证需求时变得有用。
完整的工作流最终可能看起来像这样:

价值在于使工作的移动变得可见。
每个节点都有有限的职责。每条边都携带结构化证据。每个分支都有存在的理由。每个循环都有停止条件。
在这一点上,Claude Code 不再是一条长长的指令。
它正在执行一个工程化的系统。





