Skip to content
混沌生序

Dynamic Workflow

说明 Dynamic Workflow 在子代理执行体之上生成并运行编排脚本,以及 journal 恢复、失败处理和可重放范围。

编写于 2026-08-16

Dynamic Workflow 是 Peri 的多 Agent 编排机制,一段脚本描述执行计划,脚本由 LLM 在任务现场生成,执行交给独立进程并保持确定性。本文讲四件事,workflow 是什么、为什么说它是动态的、它与子代理和普通回合的边界、journal 与恢复怎么工作,最后是适用边界。

编排层的位置

workflow 是用脚本定义多 Agent 执行计划。脚本用 JavaScript 写,meta 声明名称与描述,正文调用少量原语完成编排。下面是一个最小脚本(演示示例,不是真实会话),

export const meta = {
name: 'parallel-review',
description: '从三个视角并行审查一段代码',
}
const [security, perf, bugs] = await parallel([
() => agent('审查这段代码的安全问题:注入、鉴权、数据暴露。', {
label: 'security', allowedTools: ['Read', 'Grep'],
}),
() => agent('审查这段代码的性能问题:N+1 查询、热点路径。', {
label: 'perf', allowedTools: ['Read', 'Grep'],
}),
() => agent('审查这段代码的边界问题:错误处理、空值分支。', {
label: 'bugs', allowedTools: ['Read', 'Grep'],
}),
])
return { security, perf, bugs }

使用者不需要写这段脚本。对 Peri 说用 ultracode 并行审查我改过的代码,或直接输入 /ultracode,ultracode skill 载入上下文,由 LLM 把任务拆成脚本并通过 Workflow 工具提交执行。需要固定流程(比如每次合并前跑同一套审计)时,才把脚本落盘到 .claude/workflows/ 下,用 scriptPath 引用。

计划生成与确定执行

现场生成计划

脚本本身是 LLM 生成的工具参数。拆几路并行、每路 agent() 的 prompt、模型档位、allowedTools、阶段划分,都是在任务当下由 LLM 决定并写进脚本的。这里没有工程师预先写死的 pipeline,也没有工作流作者这个角色——相对 CI 里人维护的静态流水线,动作(actions)来自生成而非手写。

运行期注入状态

  • 外部数据经 args 在启动时注入(审查目录、commit 范围、时间戳)。
  • phase() 在运行中切换阶段,后续 agent() 自动带上当前阶段名。
  • 每个 agent() 是一次独立的 LLM 推理,各跑各的上下文,不共享状态。

固定脚本执行路径

动态指脚本内容,不指执行路径。一旦脚本定型,执行就是可重放的状态机,沙箱拦截 Date.now()new Date()(无参数调用)与 Math.random() 这类非确定性来源,import 也被禁止。真正不可预期的只有 LLM 对每个 agent() 的回答,而它们被逐条记进 journal,成为恢复的依据。

复用子代理执行体

三种执行形态放在一起看,

维度普通回合子代理Dynamic Workflow
决策位置主会话上下文主会话上下文(一次派发)脚本(LLM 生成,随后独立执行)
执行体主 ReAct 循环独立 ReAct 循环独立进程内的多个 ReAct 循环
并发回合内工具并行同步每轮 1 个;后台并发上限 3脚本内 parallel,默认并发 3
可重放journal 前缀可复用
结果去向会话历史回传主 Agent 上下文脚本变量,跨阶段显式传递
  • 普通回合,主会话的单 ReAct 循环,动作随手在上下文里产生,无法保存、重跑、比对。适合需要连续对话的任务——确认边界、逐轮追问、反复试错。
  • 子代理,主 Agent 在回合里用 Agent 工具派发独立 ReAct 循环,输出回传上下文。派发决策仍在 LLM 手里——同一任务跑两次,拆法可能不同、结果不可比。机制细节见 Multi Agent 架构
  • workflow,决策权从上下文移到脚本。脚本生成后脱离主会话的推理路径独立执行,主会话只收完成通知;流程的版本、日志、结果都在脚本和磁盘上留痕。

值得注意的复用点,workflow 的每个 agent() 最终都落到统一的 SubAgent 执行体——复用 Agent Loop、工具过滤和 token 统计,并继承主会话在 session/new 时形成的 owner snapshot。执行体是子代理基础设施,变的是决策者:普通子代理由模型在回合里派发,workflow 由脚本批量声明、runner 统一运行。

边界判据一句话,这个流程还会重跑吗? 会,就交给脚本;一次性探索,直接对话加子代理。两者互补,不互相取代。

脚本进入执行进程

脚本由 meta 字面量与正文组成。引擎抽出 meta(必须是纯字面量,不能是变量或插值),正文以函数体运行,六个原语连同 argsbudget 以注入参数的方式到达脚本,

agent(prompt, opts) 调度一个子代理执行,返回输出
parallel(thunks) 并发执行一组工厂函数,结果聚合成数组
pipeline(items, stages) 数据项逐个顺序经过多个阶段
phase(title) 切换阶段标记,供面板展示
log(message) 写入面板日志
workflow(ref, args) 运行子工作流(仅一层嵌套)
  • 不复制控制流原语。 条件分支、收敛循环这类结构是 JavaScript 自身的 if / while / for,直接在脚本里写。ultracode 文档里的七种模式(编排模式图解)多数依赖脚本语言表达力,而不是原语数量。
  • 脱离 ESM。 import 在脚本里会直接报错;需要外部依赖时经 args 由调用方注入。这既是沙箱的一部分,也让脚本与宿主进程解耦。
  • phase() 只影响展示。 它组织的是可观测性边界,不改变执行顺序;面板按阶段归组,agent() 的归属在 phase() 与显式 opts.phase 之间取运行时当前值。
  • 失败语义。 agent() 返回 dead 或抛错时整体重试一次;用户取消(killed)与中断(interrupted)直接记为失败,不做重试。parallel / pipeline 单项失败返回 null,不中断整轮;是否接受单项失败,由脚本作者在结构里决定。

执行架构三段式,

  1. 进程,runner 拉起独立 Node.js 进程跑引擎,与 Peri 主进程通过新行分隔 JSON-RPC 通信(stdout 走协议,stderr 留给日志)。脚本在启动时写盘到 .claude/workflow-runs/<run_id>/script.js
  2. 回调,脚本里每次 agent() 经 RPC 回到 Peri,WorkflowAgentExecutor 解析 agentType 定义、模型档位、工具白名单,装配完整中间件链,跑一次 ReAct 循环,返回结构化结果(输出、token、模型、阶段、耗时),同时经进度通道实时上报。
  3. 投递,Workflow 工具是 fire-and-forget——调用立即返回 run_id,主会话不阻塞;完成时结果经会话消息流通知,快照写 state.json

日志支撑恢复

一次 run 的全部证据落在 .claude/workflow-runs/<run_id>/

文件内容
script.js本次执行的脚本副本
journal.jsonl追加式 agent 调用记录
state.json结束快照(状态、返回值、错误),原子写入
outputs/超长输出的落盘

journal 每条是 { key, seq, result }key 是 prompt 与参数(剔除 labelphase 后)的 SHA256,result 是该次 agent() 的完整结果。恢复时用 resumeFromRunId 把旧 run 的 journal 载入引擎,对 agent 调用序列做逐条比较,

  • 命中key 一致,旧结果直接返回,不重新执行;
  • 首个未命中,视为新脚本与旧脚本在此处分歧,该点之后的 journal 失效并从该点重新执行,新结果续写。

labelphase 被剔除出 key,意味着改标签、改阶段名不影响命中;恢复宽容的是展示性参数,不宽容 prompt 本身。

确定性沙箱在这里才有价值,脚本能取系统时间或随机数时,同前缀重放就不可预期。需要时间戳的场景由调用方经 args 传入。

两点边界要讲清,

  • resume 依赖的是**同一套 agent 调用序列(prompt 与参数一致)**的前缀复用,不是通用断点续跑。固定脚本每次展开的调用序列稳定,前缀命中率高;临时生成的脚本每次重写,拆解与 prompt 随之变化,key 会对不上——恢复对固定 scriptPath 的脚本才可靠,对随机生成的脚本帮助有限。
  • 恢复后的执行开销与命中率没有实测数据。

磁盘按 mtime 保留最近 50 个 run,超出自动清理。

限制与边界

适合交给 workflow,

  • 可拆解的独立并行子任务,扇出收益大于单次请求开销
  • 同构批量,同一套规则过 N 个文件、N 个 commit,输出可比对
  • 阶段质量门禁,plan → code → review → fix 各有验收
  • 需要跨进程证据的流程,脚本副本、日志、快照都留在磁盘

不适合(ultracode skill 的原话语义),

  • 单个 agent 就能做的小任务——直接做,不写脚本
  • 需要紧密顺序对话的任务——用普通回合
  • 内联做比写脚本更快的任务——脚本化是为固定与批量,不为形式完整

当前实现的硬上限(均可回溯代码),

  • maxConcurrency 默认 3
  • 单 run 总 agent() 调用上限 1000;parallel / pipeline 单次 items 上限 4096
  • 单个 agent() 的 ReAct 循环默认 200 轮(agentType 定义可覆盖)
  • 子工作流嵌套仅一层
  • token 预算按输出 token 计费,预算耗尽即停;工具参数面未设置 budgetTotal 时视为不限

细节,

  • 模型分层是脚本字段,审计类用 haiku、核心改写用 sonnet,写在哪就那么执行。成本策略随脚本可见、可审查,不藏在 LLM 的临时判断里。
  • 监控走 /workflows 面板,阶段与 agent 的实时状态、token、工具调用数,数据经 2 秒轮询拉取(拉取间隔为当前实现值,非承诺)。
  • 编排模板与委托写法见 ultracode 使用建议

相关实现