
1. 从单步问答到任务闭环多 Agent 编排到底解决了什么用 Claude Code 写代码的人大概都经历过这个阶段打开终端敲一句需求等它回一段代码复制粘贴跑一下报错再把错误贴回去再等它改。一轮一轮下来人变成了人肉消息总线真正干活的其实是你在做上下文搬运和状态管理。这种模式我称之为单步聊天式协作。它的根本问题不在于模型不够聪明而在于没有把任务当成一个可被编排的对象。每一次对话都是无状态的、孤立的模型不知道上一步做了什么、下一步该验证什么、失败了要不要重试。你让它写个函数它写得挺好但你让它把这个模块重构完并保证测试通过它就会在某个环节停下来等你喂下一句话。多 Agent 编排要解决的正是这个任务级自动化的问题。它的核心思路是把一个大目标拆成若干有明确输入输出的子任务每个子任务交给一个职责单一的 Agent 去执行Agent 之间通过结构化的产物文件、diff、测试结果、日志传递状态而不是靠人来回粘贴。再往上加一层闭环自愈——当某个环节失败时系统能自己读取错误、定位原因、修正并重试而不是把错误抛给人。最后用Routine 脚本化把整套流程固化成可重复执行的命令让重构一个模块修一批 lint 错误给新接口补测试这类高频任务变成一条命令的事。这套东西适合谁如果你只是偶尔用 Claude Code 问几个语法问题那单步聊天完全够用没必要上编排。但如果你每天要处理的是跨多个文件的改动需要跑测试验证的重构批量化的重复任务那多 Agent 编排带来的效率提升是数量级的。我自己的体感是一个原本需要我盯着屏幕来回对话四十分钟的重构任务编排好之后一条命令跑完我只需要在最后 review 一下 diff。下面我会从架构分层、Agent 职责划分、闭环自愈的实现、Routine 脚本化落地、以及实际踩过的坑这几个角度把这套东西拆开讲清楚。文中涉及的具体配置和命令都是基于 Claude Code 常见的使用方式整理的不同版本可能有细微差异以你本地实际为准。2. 编排架构的三层拆解调度层、执行层与状态层很多人一上来就想写一个超级 Agent把所有事都干了结果就是提示词越写越长、行为越来越不可控。正确的做法是分层。我把这套架构拆成三层调度层Orchestrator、执行层Worker Agents、状态层State Artifacts。这三层各司其职边界清晰是整个系统能稳定跑起来的前提。2.1 调度层只做决策不碰具体实现调度层的职责非常克制——它只负责决定下一步做什么不负责怎么做。具体来说它要完成三件事解析用户的目标、把目标拆成子任务序列、根据执行层返回的结果决定是继续、重试还是终止。这里有个关键设计原则调度层的提示词里不应该出现任何具体的代码实现细节。它关心的是当前处于哪个阶段上一个 Agent 的产物是否满足进入下一阶段的条件如果失败失败类型是什么。一旦你让调度层去理解具体代码它就会开始越权试图自己把活干了编排就退化成单步聊天了。调度层通常用一个能力较强的模型来担任因为它需要做的是规划和判断对推理能力要求高但对代码细节的精确度要求反而没那么高。在实际配置里你可以把它理解成一个项目经理角色。2.2 执行层一个 Agent 只干一件事执行层是真正动手的部分。我的经验是每个 Worker Agent 的职责要窄到一句话能说清。比如Coder Agent只负责根据给定的任务描述和上下文产出代码改动通常是 diff 或完整文件。Tester Agent只负责运行测试命令收集结果判断通过与否。Reviewer Agent只负责审查代码改动是否符合规范输出问题清单。Fixer Agent只负责根据错误信息或审查意见做最小化修正。为什么要拆这么细因为职责越单一提示词越短行为越可预测。一个既写代码又跑测试又判断结果的 Agent它的输出格式会变得极其不稳定你很难用程序去解析它的结果。而一个只输出 diff的 Agent你可以放心地用正则或结构化解析去处理它的产物。执行层的 Agent 可以用相对轻量的模型因为它们做的是相对确定的工作。如果你的环境支持接入本地模型或第三方模型完全可以把 Coder 这类高频调用的 Agent 换成更经济的模型把调度和审查留给更强的模型。这也是为什么很多人关心Claude Code 能不能接本地模型——在编排场景下模型混用的成本优势非常明显。2.3 状态层用文件系统当共享内存Agent 之间怎么传递状态最朴素也最可靠的做法是用文件系统。每个 Agent 的产物写到约定的目录里下一个 Agent 从目录里读。比如.agent-workspace/ task.json # 调度层维护的任务状态 step-01-diff.patch # Coder 产出的改动 step-02-test.log # Tester 产出的测试日志 step-03-review.md # Reviewer 产出的审查意见 context.md # 跨步骤共享的上下文摘要用文件而不是内存或数据库好处是可观测、可复现、可中断续跑。任务跑到一半挂了你看一眼目录就知道卡在哪一步、上一步产出了什么。想重跑某一步删掉对应的文件再触发即可。这种文件即状态的思路是整套编排能落地的基础。提示状态目录一定要加进.gitignore否则你的 diff 里会混进一堆中间产物review 的时候非常痛苦。三层拆清楚之后整个系统的数据流就变得很清晰调度层读task.json决定下一步调用对应的 WorkerWorker 把产物写回目录调度层再读产物决定下一步。人只需要在起点给一个目标在终点看结果。3. 闭环自愈的实现让失败变成流程的一部分单步聊天最让人崩溃的地方是失败之后一切归零——你得重新描述问题、重新贴上下文。闭环自愈要做的就是把失败当成流程中的一个正常状态来处理而不是一个需要人来介入的异常。3.1 失败分类先搞清楚错在哪一类自愈的第一步不是急着修而是分类。不同类型的失败处理策略完全不同。我在实践里通常把失败分成四类失败类型典型表现处理策略语法/编译错误代码跑不起来报语法错直接把错误喂回 Fixer通常一次能修好测试断言失败代码能跑但结果不符合预期需要区分是代码错还是测试错交给 Reviewer 判断环境/依赖问题命令找不到、依赖缺失不重试直接报给人因为 Agent 改不了环境需求理解偏差代码能跑测试也过但不是用户想要的终止流程回到调度层重新澄清需求这个分类表是我踩了很多坑之后总结出来的。早期我不做分类所有失败都无脑重试结果就是环境问题被反复重试五次浪费大量 token 还解决不了而需求偏差被当成 bug 去修越修越偏。3.2 重试的边界为什么必须设上限自愈不等于无限重试。任何自动重试都必须有次数上限和退出条件否则一个死循环能烧掉你一天的额度。我的做法是同一类失败最多重试3 次。每次重试必须携带上一次的失败信息否则就是原地打转。如果连续两次重试的失败信息高度相似比如错误行号都没变说明 Agent 卡住了立即终止。终止后把完整的失败链路写入context.md方便人工接手。这里有个很实用的技巧让 Fixer Agent 在每次修正时输出我改了什么、为什么这么改。如果它连续两次说不出新的理由基本可以判定它已经无能为力了这时候继续重试纯属浪费。3.3 自愈循环的完整链路把上面这些串起来一个完整的自愈循环是这样的Coder 产出改动写入step-01-diff.patch。Tester 应用改动并运行测试结果写入step-02-test.log。调度层读取测试日志判断结果通过 → 进入下一步比如 Reviewer 审查。失败 → 进入失败分类逻辑。如果是可自愈类型调用 Fixer把错误日志和当前 diff 一起给它。Fixer 产出修正后的 diff覆盖step-01-diff.patch重试计数 1。回到第 2 步直到通过或达到重试上限。这个循环里人只在两个点介入一是重试耗尽二是需求偏差。其余情况系统自己消化。我实测下来语法错误和简单的测试失败90% 以上能在两次重试内解决真正需要人介入的比例很低。注意自愈循环里一定要保留每一步的中间产物不要用覆盖的方式写日志。我习惯给每次重试的日志加时间戳后缀出问题的时候能完整回放整个修正过程定位效率高很多。4. Routine 脚本化把编排固化成一条命令编排跑通一次不算本事能稳定重复跑才是价值所在。Routine 脚本化的目标就是把前面那套三层架构 自愈循环封装成一个可以反复调用的命令让重构模块补测试批量修 lint这些任务变成肌肉记忆。4.1 什么任务值得脚本化不是所有任务都值得写成 Routine。判断标准很简单这个任务你是不是每周都要做、而且步骤基本固定。符合的话就值得。我目前固化成 Routine 的主要有几类模块重构给定一个目录自动分析依赖、拆分、改引用、跑测试。测试补全给定一个源文件自动生成单测并跑到通过。Lint 批量修复跑 lint把错误分类能自动修的自动修不能修的汇总报告。接口文档同步改了接口之后自动更新对应的文档和类型定义。这几类的共同点是输入明确、步骤固定、有客观的验证标准测试通过/lint 清零。反过来帮我设计一个架构这种开放式任务就不适合脚本化因为它的完成没有客观标准。4.2 Routine 脚本的骨架一个 Routine 脚本本质上就是一段把调度逻辑固化下来的 shell 或脚本文件。它的结构大致是#!/usr/bin/env bash # routine-refactor.sh # 用法: ./routine-refactor.sh 目标目录 TARGET_DIR$1 WORKSPACE.agent-workspace MAX_RETRY3 # 1. 初始化状态 mkdir -p $WORKSPACE echo {\target\: \$TARGET_DIR\, \stage\: \analyze\, \retry\: 0} $WORKSPACE/task.json # 2. 分析阶段 claude-code run --agent analyzer --input $TARGET_DIR --output $WORKSPACE/analysis.md # 3. 执行 自愈循环 retry0 while [ $retry -lt $MAX_RETRY ]; do claude-code run --agent coder --context $WORKSPACE/analysis.md --output $WORKSPACE/diff.patch claude-code run --agent tester --patch $WORKSPACE/diff.patch --output $WORKSPACE/test.log if grep -q ALL TESTS PASSED $WORKSPACE/test.log; then echo 重构完成测试通过 break fi retry$((retry 1)) echo 第 $retry 次重试... claude-code run --agent fixer --log $WORKSPACE/test.log --patch $WORKSPACE/diff.patch --output $WORKSPACE/diff.patch done # 4. 收尾审查 claude-code run --agent reviewer --patch $WORKSPACE/diff.patch --output $WORKSPACE/review.md这段脚本里的claude-code run是示意性的调用方式实际命令名和参数以你本地安装的版本为准。核心思想是把调度逻辑从模型判断下沉到脚本控制。能用脚本写死的流程比如先分析再编码再测试就不要交给模型去判断这样既省 token 又稳定。4.3 参数化与复用Routine 脚本要能复用关键是把变化的部分参数化。目标目录、重试次数、使用的模型、是否跳过审查这些都应该做成命令行参数或环境变量。我习惯在脚本开头集中定义这些变量改的时候一目了然TARGET_DIR${1:?请指定目标目录} MAX_RETRY${MAX_RETRY:-3} CODER_MODEL${CODER_MODEL:-default} SKIP_REVIEW${SKIP_REVIEW:-false}这样同一个脚本既能用于日常小改动MAX_RETRY1也能用于大重构MAX_RETRY5灵活性一下就上来了。5. 实战踩坑那些文档里不会写的细节前面讲的都是应该怎么做但真正跑起来坑都在细节里。这一节我挑几个印象最深的坑把排查过程完整还原出来希望能帮你少走弯路。5.1 上下文膨胀为什么跑到第三步就变慢了最早我遇到的问题是任务跑到第三步之后响应明显变慢token 消耗飙升。排查下来发现是每个 Agent 都把前面所有步骤的完整产物读进了上下文。Coder 读了分析报告Tester 读了分析报告加 diffReviewer 读了前面所有东西上下文越滚越大。解决办法是给每个 Agent 只喂它真正需要的上下文。具体做法是在状态目录里维护一个context.md由调度层负责摘要——把前面步骤的产物压缩成几句话而不是把原始文件全塞进去。比如 Tester 不需要知道分析报告的全部内容它只需要知道这次改动涉及哪些文件、预期行为是什么。这个改动之后同样的任务 token 消耗降了大概六成速度也快了很多。上下文管理是编排里最容易被忽视、但收益最大的优化点。5.2 产物格式不稳定解析失败的排查链路第二个坑是产物解析。我让 Coder 输出 diff结果它有时候输出纯 diff有时候包在 markdown 代码块里有时候还带一段解释。脚本里的grep和patch命令经常解析失败。排查过程是这样的先看失败日志发现patch报格式错误手动打开产物文件发现这次输出被包在了diff里再对比成功和失败的案例发现只要任务描述里带了请解释你的改动它就会加解释文字。根因找到了提示词里的措辞会影响输出格式。解决办法是在 Coder 的提示词里明确约束只输出 unified diff 格式不要任何解释文字不要 markdown 代码块标记。 同时在脚本里加一层清洗用sed把可能的代码块标记去掉双保险。这个坑的教训是不要假设模型会稳定遵守格式永远在脚本层做防御性清洗。5.3 环境问题被误判为代码问题第三个坑最隐蔽。有一次自愈循环跑了五次都没通过我以为是模型能力问题结果手动跑了一下测试命令发现是依赖没装——测试根本跑不起来报的是command not found但 Fixer 把它当成了代码错误一直在改代码。这个坑的根因是失败分类没做好。后来我在 Tester 的输出里强制要求它先判断测试是否真的执行了如果命令本身就没跑起来直接标记为环境问题不进入自愈循环。判断方法很简单在测试日志里检查有没有command not foundNo such file这类关键词有的话直接终止。提示环境类失败一定要快速失败、快速上报让 Agent 去修环境问题是南辕北辙它既没有权限也不该有这个职责。5.4 重试时的原地打转第四个坑是重试无效。Fixer 拿到错误日志后改出来的代码和上一版几乎一样测试还是同样的错。这种情况我后来总结出一个检测方法对比连续两次的 diff如果相似度超过 90%判定为原地打转立即终止。实现上可以用diff命令对比两个 patch 文件统计变化行数。变化行数低于阈值就报警。这个检测加上之后无效重试基本被消灭了省下来的额度很可观。6. 模型选型与成本控制编排场景下的现实考量编排跑起来之后成本是个绕不开的话题。一个包含分析、编码、测试、修复、审查五个环节的任务如果每个环节都用最强的模型成本会高得吓人。所以模型混用在编排场景下几乎是必然选择。6.1 按环节分配模型能力我的分配策略是这样的调度层用推理能力强的模型因为它要做规划和判断调用次数少但质量要求高。Coder用代码能力强的模型这是核心环节值得投入。Tester其实不需要太强的模型它主要是执行命令和判断结果用轻量模型即可。Fixer中等能力即可因为错误信息通常很明确不需要太强的推理。Reviewer用较强的模型因为审查需要理解意图和规范。这样分配下来真正用强模型的只有调度、Coder 和 Reviewer 三个环节Tester 和 Fixer 用轻量模型整体成本能降一半以上。6.2 本地模型与第三方模型的接入考量很多人关心能不能接本地模型或第三方模型来进一步降本。技术上Claude Code 这类工具通常支持通过配置切换后端模型。实际使用中要注意几点本地模型的能力边界本地模型在代码生成上可能够用但在复杂规划和长上下文理解上往往力不从心。我的建议是本地模型只用于 Tester、Fixer 这类确定性高的环节调度和 Coder 还是用云端强模型。接口兼容性不同模型的 API 格式不一样接入时要注意请求和响应的字段映射尤其是工具调用tool use的格式很多本地模型支持得并不完整。稳定性本地模型服务如果自己部署要考虑并发和超时问题编排场景下调用频繁服务不稳定会直接拖垮整个流程。6.3 成本监控的实用做法成本控制不能靠感觉要有数据。我的做法是在每次 Routine 执行结束后把本次的 token 消耗、调用次数、重试次数写进一个日志文件定期汇总。这样你能清楚地看到哪个环节最烧钱、哪个环节重试率最高优化就有了方向。我自己的数据是Coder 环节占了总消耗的一半以上Fixer 的重试次数最多。针对性地优化这两个环节给 Coder 更精准的上下文、给 Fixer 更好的错误分类整体成本降了大概四成。7. 从能跑到好用编排系统的日常维护心得一套编排系统跑起来只是开始真正决定它好不好用的是日常维护。这里分享几个我长期使用下来觉得最有价值的习惯。第一给每个 Routine 建一个病历本。每次执行失败、每次人工介入都记一笔什么任务、卡在哪一步、根因是什么、怎么解决的。积累一段时间后你会发现失败模式高度集中针对性地改提示词或加检测比盲目优化有效得多。第二定期回放历史产物。状态目录里的中间产物不要急着删隔一段时间回看几次经常能发现一些当时没注意到的模式比如某类任务总是卡在同一个环节。这些是优化编排逻辑的一手素材。第三保持 Agent 职责的窄。随着需求变化很容易忍不住给某个 Agent 加职责加着加着它就变成了什么都能干但什么都不精的万能 Agent。我的原则是新增需求优先考虑加一个新 Agent而不是给现有 Agent 加职责。宁可 Agent 数量多一点也要保证每个都足够简单可预测。第四人工介入点要显式设计。不要指望系统全自动明确哪些情况必须人来判断比如需求偏差、环境问题在这些点上主动停下来比让系统硬扛要靠谱得多。我现在的做法是凡是自愈循环终止的情况都会生成一份待人工处理的报告包含完整的失败链路和建议的下一步人接手的时候不用重新梳理上下文。这套东西我从最初的单步聊天一路迭代过来最大的体会是编排的价值不在于让 AI 更聪明而在于让流程更可控。模型能力会一直变但把任务拆清楚、把状态管明白、把失败当常态这些工程原则是不变的。把这几件事做好无论底层用哪个模型你都能搭出一套真正省心的自动化流程。