Skip to content
混沌生序

工作流设计

为什么多 Agent 需要脚本编排——确定性、可重放与大规模并行的设计权衡,以及 Peri 如何落地。

子代理解决了”一个 Agent 不够”的问题,但它把并行的决策权留在了 LLM 上下文里。这篇文章从设计角度拆解工作流:为什么需要它、在 Agent 生态里处于什么位置、设计时要关注什么、Peri 是怎么实施的。

为什么需要工作流

子代理方案的三个边界,正是工作流的动机:

决策不可重放。 子代理的派发逻辑在 LLM 上下文里——“审查哪几个文件、并行几个、先做什么后做什么”都是运行时的临时判断。同样的任务跑两次,流程不同,结果不可比。流程没法保存、重跑、分享,也就没法成为质量基线。

规模受限。 默认 3 个并行,扫描 200 个文件找 dead code,要排队几十轮;中间结果不断塞回主会话,上下文先撑不住。

结果无家可归。 几十个子代理的输出散落在会话历史里,没有统一的汇总结构,主 Agent 难以可靠消费。

核心论点:当”下一步做什么”可以从 LLM 上下文里抽出来写成代码时,就应该抽出来。 流程即代码——可版本控制、可重跑、可审查、可分享。需要确定性流程的场景很典型:

  • 批量审计:200 个文件、4 个维度,同一套检查规则
  • 大规模迁移:50 个文件执行同构重构,逐文件过”分析 → 改写 → 检查”
  • 质量门禁:每次合并前跑同一套多路审查,输出可比对的结果
  • 长任务恢复:跑到一半进程崩溃,能从断点继续而不是从头再来

在 Agent 生态中的定位

把谱系拉全,看工作流的位置:

方案控制流在哪特点
子代理LLM 上下文灵活,不可重放
工作流脚本确定,可重放
自主多 Agent 平台系统自行编排无监督,早期不可靠

工作流之于多 Agent,相当于 GitHub Actions / Temporal 之于 CI:agent 是 worker,脚本是 pipeline。控制权从”上下文”转移到”代码”,换来确定性,代价是失去 LLM 的即兴判断。

工作流不取代子代理,两者是互补的:

  • 探索性任务(“看看这个模块怎么改更合理”)→ 子代理。没有固定流程,让 LLM 即兴发挥
  • 固定任务(“审查这 50 个文件,跑同一套检查”)→ 工作流。流程已知,脚本化才有意义

工作流里的每个节点仍然是子代理——脚本用 agent() 原语调度,执行者不变,变的是决策者。

设计时需要关注什么

确定性:可重放的前提

脚本必须没有隐式状态。Peri 的沙箱限制是设计决策的直接产物:

  • 无时间 APIDate.now() 不可用,时间信息通过 args 注入
  • 无随机数Math.random() 不可用
  • 无直接文件系统、无网络:所有外部交互通过 agent() 内部的工具调用完成

为什么这么狠?断点续跑要复用已完成 agent 的结果,重跑要结果可预期。脚本一旦能读系统时间、写随机状态,重放就会产生不一致。

控制流原语

多 Agent 编排只需要三个控制流概念,语义必须清晰:

  • 并发parallel):互不依赖的任务并行跑,结果聚合成数组
  • 流水线pipeline):数据项逐个顺序经过多个阶段。文件级串行,天然避免多个执行者写同一文件
  • 阶段phase):组织可观测性的边界,不是控制流。它不改变执行顺序,只让面板按阶段展示

结果与失败

结果在脚本变量里显式存在,跨阶段传递——这是工作流与子代理的本质区别之一:中间结果不依赖上下文,而是显式数据。相应地,失败语义必须显式设计:agent 失败了是重试、跳过还是中断整个流程?默认策略要有一个,且要可覆盖。

可观测性

几十个 agent 并行执行,没有面板就没法用。至少要看到:每个 agent 的状态(运行/完成/失败/排队)、token 消耗、工具调用数、当前阶段。执行日志要落盘(journal),否则事后无法排查。

成本

并行上限仍然存在,模型分层仍然有效。审计类任务用便宜模型(haiku),核心改写用强模型。脚本里每个 agent() 调用都可以指定模型——成本策略在脚本里是可见的,而不是藏在 LLM 的临时判断里。

Peri 如何实施

脚本模型。 工作流是 JavaScript ESM 脚本,meta 声明名称、描述和阶段(供面板展示),正文是执行逻辑:

export const meta = {
name: 'codebase-audit',
description: '多目录并行审计:安全、性能、代码质量',
phases: [{ title: 'Audit', detail: '四目录并行扫描' }],
}
const dirs = ['src/api/', 'src/services/', 'src/db/', 'src/utils/']
const results = await parallel(dirs.map((dir) => () =>
agent(
`扫描 ${dir} 目录:未使用导入、unwrap 无保护、明文密钥、遗留 TODO。` +
'输出结构化清单:文件路径、行号、类别、严重程度、修复建议。',
{ label: `audit:${dir}`, allowedTools: ['Read', 'Grep', 'Glob'], model: 'haiku' },
),
))
return results

六个原语覆盖全部需求。 agent(调度)、parallel(并发)、pipeline(顺序流水)、phase(阶段标记)、log(日志)、workflow(子工作流复用)。没有更多——控制流就这三样,剩下的都是表达问题。

运行架构是”脚本进程 + 调度器”。 工作流在独立进程运行,通过 JSON-RPC 与 Peri 通信,由调度器把 agent() 调用派发给子代理。主会话不阻塞,完成后收到通知。

断点续跑靠日志重放。 每个 run 的 journal 记录所有生命周期事件。中断后用相同 run_id 恢复,已完成 agent 的结果从 journal 复用,只重跑未完成部分。

使用方式不需要写脚本。 日常使用是对 Peri 说”用 ultracode 并行审查我刚改的代码”,Peri 生成脚本并执行;需要固定流程时,手动写脚本放进 .claude/workflows/,通过 /workflows 面板选择执行和监控。

权衡

工作流用确定性换灵活性:固定流程可重放、可审计、可恢复,但探索性任务塞进脚本只会拖慢节奏。选择标准很简单——这个流程还会重跑吗? 会,就写脚本;一次性探索,直接交给 LLM 和子代理