当一个任务只有一个步骤时,Agent 的工作方式很直观:接收目标、执行操作、返回结果。真正困难的是一批任务同时存在时:有些任务可以并行,有些必须等前置结果,有些任务完成后还要经过评审,有些失败可以重试,有些失败必须交给人决定。

如果所有任务都被写成“全部做完后再进入下一步”,工作流会被最慢的那个任务拖住;如果所有任务都被强行并行,又会出现依赖未满足、文件互相覆盖和结果无法比较的问题。本文不讨论某个具体业务,而是介绍几种可以迁移到代码分析、批量升级、文档处理和发布验证中的编排形状。

一、先区分四种等待关系

设计工作流之前,先判断任务之间到底是什么关系。常见的等待关系有四种:

独立关系

A 和 B 互不读取对方的结果,也不修改同一个文件。它们可以同时运行,最后再汇总。这是并行 Fan-out 的基础。

阶段关系

每个对象都要依次经过相同的几道工序,例如“翻译、校对、排版”。同一批对象之间不必互相等待,但同一个对象不能跳过阶段。这是流水线。

依赖关系

C 只有在 A 和 B 都完成后才能开始,D 只依赖 C。任务之间形成有向无环图,也就是 DAG。发布前的构建、迁移、集成测试经常属于这种形状。

反馈关系

生成者提交结果,评审者提出问题,生成者修正后再次提交,直到通过或达到轮次上限。这是 Generator-Critic 或 Refinement Loop。

这四种关系可以组合,但不要把它们混成一个模糊的“自动流程”。每种关系都有不同的状态、调度和失败处理方式。

二、并行:什么时候可以同时派工

并行最重要的判断标准不是“任务看起来不同”,而是“任务是否需要互相通信”。如果安全检查做到一半必须知道性能检查的结论,两者就不是真正独立;如果两者都只读代码,且最终各自返回证据,就可以并行。

一个典型的代码评审可以这样拆:

输入:同一个待评审工作树和验收标准

Worker A:只看权限、数据暴露和危险操作
Worker B:只看测试缺口、失败路径和回归风险
Worker C:只看性能、资源释放和并发行为
Worker D:只看公共 API 的兼容性

汇总者:去重、排序、确认严重程度,输出最终评审报告

每个 Worker 都应该带上范围限制。没有范围限制时,四个 Agent 很容易重复阅读同一批文件,然后给出四份相似报告。范围可以按模块、风险类型、文件路径或输出格式划分。

并行任务需要什么验收条件

“完成扫描”不是验收条件。更好的条件是:每条问题都有位置;每个判断有复现或推理依据;没有发现时也说明扫描过的范围;不确定的地方标记为待验证。

如果使用多个持久 Task 承载并行工作,要建立一个 pending 集合,记录每个任务的状态和最近一次进度游标。等待工具通常更适合“谁先有事件就先返回”的 wait-any 形式,因此不能假设一次等待就会收齐所有结果。调度器需要循环等待,收到一个任务的完成事件后移除它,再等待剩余任务。

伪代码如下:

pending = {A, B, C, D}
while pending 不为空:
    event = 等待 pending 中任意任务的新事件
    更新该任务的状态和游标
    如果任务已完成:
        保存结果和证据
        从 pending 移除任务
    如果任务失败:
        判断是重试、换人还是交给人工

并行数量也不是越多越好。并发上限受模型额度、机器资源、文件访问冲突和汇总成本影响。先从两到四个真正独立的方向开始,确认每个结果都能被消费,再扩大规模。

三、流水线:按对象独立推进,而不是整批等待

假设你有 20 个 Markdown 文件,需要依次做术语检查、内容校对和格式验证。最直观但低效的写法是:20 个全部术语检查完,再开始 20 个校对,最后统一验证。

这种写法会形成一个不必要的屏障:第 1 个文件早就完成术语检查,却必须等待第 20 个文件结束才能进入下一阶段。正确的流水线应该让每个文件独立推进:第 1 个完成检查后立即校对,第 2 个可以继续检查,第 20 个仍处于第一阶段。

每个 item 维护自己的 stage
  -> 等待任意 item 完成当前 stage
  -> 校验这个 item 的产物
  -> 只把这个 item 推进到下一 stage
  -> 其他 item 继续保持各自状态

每个流水线项目至少记录什么

item_id:文件、模块或客户任务的标识
stage:当前阶段
input_artifact:当前阶段的输入
output_artifact:成功后的输出
attempt:已尝试次数
status:queued / running / review / done / failed
error:最近一次失败原因

阶段之间要有明确的接口。例如校对阶段接收的是“术语检查报告和原文路径”,而不是一段没有来源的摘要;格式验证阶段接收的是“校对后的文件和格式规则版本”。接口越清楚,流水线越容易替换某个阶段的 Agent。

流水线中最常见的三个错误

第一,把所有 item 放进一个超长会话。这样每个文件的状态只能从聊天历史里猜,失败后很难只重跑某个文件。

第二,没有保存中间产物。一个阶段成功但下个阶段失败时,如果没有持久化输出,就只能重新执行前一阶段。

第三,阶段完成条件不明确。Agent 说“已处理”不代表文件符合规则,必须在每个阶段结束时运行结构检查、测试或差异检查。

四、DAG:用注册表管理任务依赖

当任务之间不是一条直线,而是一张有分支和汇合的网时,应该使用 DAG 思维。比如一次 API 迁移:先分析生产者和消费者,再确定新契约;契约确定后,生产者和多个消费者可以并行修改;全部修改完成后,才能跑跨仓库集成测试,最后进入发布验证。

可以把它表示为:

扫描生产者 ─┐
             ├─> 确定契约 ─┬─> 修改生产者 ─┐
扫描消费者 ─┘              ├─> 修改消费者 A ├─> 集成测试 ─> 发布验证
                           └─> 修改消费者 B ─┘

为什么需要外部 Registry

会话历史保存的是 Agent 的对话,不能稳定表达一个业务 DAG 的节点、边和状态。DAG 调度器需要一个独立 Registry,至少存储:

{
  "nodes": {
    "contract": {"status": "done", "artifact": "contract.md"},
    "producer": {"status": "ready", "depends_on": ["contract"]},
    "consumer-a": {"status": "running", "depends_on": ["contract"]}
  },
  "edges": [["contract", "producer"], ["contract", "consumer-a"]]
}

调度器每次收到节点完成事件后,重新计算哪些节点的前置条件已满足,再派发新的 ready 节点。节点产物应该通过结构化格式或 schema 校验,避免上游只返回一段自然语言,下游却按另一种含义理解。

DAG 的失败处理

节点失败后不能简单地把所有下游节点都标记失败。先判断失败类型:

如果一个节点被重试,Registry 里要记录尝试次数和每次错误摘要。否则调度器可能无限重复相同动作,使用者也无法判断流程为何长期不结束。

五、Generator-Critic:给循环设置边界

评审循环适合处理“第一次生成不一定正确,但可以通过测试逐步收敛”的任务。代码生成、配置迁移和文档规范化都可以使用这种模式。

一个完整循环应包含:

1. 生成者根据规格产出结果
2. 保存 diff、日志和测试输出
3. 独立评审者只按验收标准检查
4. 评审者输出结构化问题
5. 生成者按问题定点修复
6. 重新执行相关测试
7. 通过则结束,失败则进入下一轮

这里的独立不是指“换一个名字”,而是评审者不能依赖生成者的自我解释。评审输入应该包含规格、实际产物、测试结果和限制,不应包含“我已经确认这段代码是正确的”之类的结论。

循环要有两个停止条件:验收通过,或者达到最大轮次。达到最大轮次后,输出当前差异、剩余问题和阻塞原因,交给人工判断。没有最大轮次的自动修复,很容易把一个简单的类型错误演变成一系列互相覆盖的改动。

六、Race 与 Quorum:用冗余换取确定性

有些任务没有可靠的单一验证器,可以让多个 Agent 独立给出结果,然后使用规则收口。

Race 是“谁先通过验证谁胜出”。它适合只要找到一条可行路径就够的任务,例如排查一个偶发测试失败。多个候选同时尝试,谁先复现并通过验证,就采用谁的路径,其他候选结束或归档。

Quorum 是“达到一定数量的一致结果才接受”。它适合单次结果难以判断、但可以通过独立重复提高信心的估算任务。关键是候选必须真的独立。如果几个候选共享同一份带错误的上下文,它们的相同答案不能证明答案正确。

冗余不是免费可靠性。每个候选都会消耗模型调用、时间和计算资源,因此要先定义验证器、停止规则和最大候选数。无法验证的重复,只是制造更多相同的猜测。

七、如何选择编排形状

任务特征 更合适的模式 关键控制点
多个方向互不依赖 Fan-out / Gather 范围隔离、统一输出、去重汇总
多个对象经过相同阶段 Pipeline 每个 item 独立推进、保存中间产物
有明确前置条件和汇合 DAG Registry、依赖计算、节点状态
结果需要反复检查和修正 Generator-Critic 独立评审、轮次上限、测试验证
单次答案难以判断 Race / Quorum 验证器、独立性、停止规则

现实任务通常是组合形状。例如跨仓库依赖升级可以先 Fan-out 扫描,再由一个节点确定改动契约,之后进入 DAG;每个仓库内部又可以使用 Generator-Critic。先画出依赖关系,再决定使用哪种 Agent 原语,比先决定“要开几个 Agent”更重要。

结语

并行解决的是独立任务之间的等待,流水线解决的是同一批对象的阶段推进,DAG 解决的是复杂依赖,评审循环解决的是结果质量,Race 和 Quorum 解决的是验证不足时的冗余问题。

编排的最低要求不是一个漂亮的流程图,而是每一步都有可观察状态、明确产物和可执行的失败处理。先用两三个小任务验证调度逻辑,再逐步增加并行度和依赖复杂度,通常比一次性搭建“大而全”的 Agent 控制器更容易得到稳定结果。