多 Agent 编排

Workflows

用一段确定性的 JavaScript 脚本,把一个大任务拆成多个子 agent 并行 / 串行执行。 脚本在后台运行,支持结构化输出、journal 重放、token 预算与实时监控面板。

  • agent / pipeline / parallel / phase
  • journal 重放
  • token 预算

这是什么

Workflows(特性 WORKFLOW_SCRIPTS)让千手大师用确定性 JavaScript 脚本编排多个子 agent。模型通过 Workflow 工具提交脚本,工具把它交给引擎在 后台执行并立即返回一个 runId,完成时以 task 通知回传返回值。

脚本里可使用一组固定的编排原语:

  • agent() —— 派发一个子 agent,无 schema 返回文本,带 schema 返回校验后的对象;
  • parallel() —— 并发跑一组 thunk,屏障语义,等全部完成;
  • pipeline() —— 每个 item 独立链式过各 stage,阶段间无屏障(默认推荐);
  • phase() / log() —— 标记阶段与输出进度旁白;
  • workflow() —— 内联运行一层子 workflow;
  • args / budget —— 透传参数与读取 token 预算。

引擎位于独立包 packages/workflow-engine/ (@claude-code-best/workflow-engine,对核心层零运行时依赖), 核心侧的薄适配层在 src/workflow/:负责装配 Workflow 工具、 解析命名脚本、驱动进度、持久化运行状态。

i
与 /ultracode 的分工

Ultracode 只是把「如何编排」的手册注入上下文; Workflows 是真正执行脚本的引擎。两者配合给出「编排工作法 + 执行载体」。

前置条件

项 要求
feature flag 编译期 WORKFLOW_SCRIPTS 开启时,Workflow 工具与 /workflows 命令才注册。
运行时 脚本在 CLI 进程内执行,无文件系统 / Node.js API 访问。
脚本语言 纯 JavaScript。引擎不转译 TypeScript,.ts 文件中含类型语法会直接报错, 推荐 .js / .mjs。
命名脚本目录 .claude/workflows/<name>.ts|js|mjs(相对项目根目录); 发现的脚本自动生成 /<name> 命令。

安装启用

  1. 确认 Workflow 特性已开启

    Build 产物默认包含 WORKFLOW_SCRIPTS。dev 模式可显式开启:

    Terminal
    # dev 模式手动开启工作流脚本特性
    FEATURE_WORKFLOW_SCRIPTS=1 bun run dev
  2. 建立命名 workflow(可选)

    在项目根目录创建 .claude/workflows/,放入脚本。文件即命令: review-changes.mjs 会生成 /review-changes。

    Terminal
    mkdir -p .claude/workflows
    # 放入 review-changes.mjs(见「实战示例」),即可用 /review-changes 调用
  3. 启动一个 workflow

    三种入口:内联 script、命名 name、文件 scriptPath。

    qsdashi 会话
    # 模型侧调用 Workflow 工具(示例)
    Workflow({ scriptPath: ".claude/workflows/review-changes.mjs", args: ["src/a.ts"] })
    
    # 或使用命名脚本生成的斜杠命令
    /review-changes
  4. 用 /workflows 观察进度

    面板打开后按 Tab 切 run、←/→ 切左右列、q 退出。

编排 API 与约束

脚本内可用的钩子(注入为函数形参,直接调用,不要 import):

原语 签名 语义与失败行为
agent agent(prompt, opts?) => Promise<any>,opts: label / phase / schema / model / isolation: 'worktree' / agentType 派发子 agent;无 schema 返回最终文本,有 schema(JSON Schema) 强制走结构化输出并返回校验对象。用户 skip 或 agent 终态死亡返回 null。
parallel parallel(thunks) => Promise<any[]> 屏障:Promise.all 全部 thunk 后返回。单项抛错 → 该项为 null,调用本身不 reject(用 .filter(Boolean))。
pipeline pipeline(items, ...stages) => Promise<any[]> 无屏障:每个 item 独立走 stage1 → stage2 → …。stage 回调收到 (prevResult, originalItem, index)。某 stage 抛错 → 该 item null 并跳过后续 stage。
phase phase(title) => void 开启新阶段,后续 agent / log 归入该组;面板按此分组展示。
log log(message) => void 向用户输出一行进度旁白,无状态变更。
workflow workflow(name | {scriptPath}, args?) => Promise<any> 内联运行一层子 workflow,共享并发 / 计数 / 预算;子 workflow 内再嵌套 → 抛错(仅一层)。
args 变量 Workflow 工具 args 的原样透传;须传真实 JSON 值,不要传字符串化 JSON。
budget { total, spent(), remaining() } total 为本 turn 的 token 目标(无则 null); spent() 为主循环与所有 workflow 共享的输出 token; 触顶后再调 agent() 抛错。

硬限(超出直接抛错,不静默截断):

常量 值 含义
DEFAULT_MAX_CONCURRENCY 3 每 run 默认并发 agent 数
MAX_CONCURRENCY_CAP 16 maxConcurrency 入参的上限(钳到 [1, 16])
MAX_TOTAL_AGENTS 1000 单个 workflow 生命周期内总 agent 数上限
MAX_ITEMS_PER_CALL 4096 单次 parallel / pipeline 的 items 上限

脚本执行模型与确定性约束:

规则 说明
不是 ESM 模块 脚本是 new AsyncFunction 的函数体;agent / parallel / pipeline / phase / log / workflow / args / budget 均为注入形参,禁 import。
禁 TS 语法 不要类型注解、interface、enum、as、泛型。 即便文件是 .ts 也原样报语法错。
唯一 export 只允许一处 export const meta = {...},其余 export 与 export default 都要去掉;顶层 return 返回结果。
meta 纯字面量 meta 在加载期求值,必须是纯字面量(无变量 / 函数调用 / 展开 / 模板插值), 否则抛 ScriptError。必填 name / description。
确定性 Date.now() / Math.random() / 无参 new Date() 被 shim 屏蔽并抛错,以保证 journal 可重放。时间戳 / 随机种子经 args 传入。

目录与持久化:

路径 内容
.claude/workflows/<name>.ts|js|mjs 命名 workflow 脚本(扫描顺序 .ts → .js → .mjs)
.claude/workflow-runs/<runId>/journal.jsonl 按执行顺序记录的每个 agent() 的 { key, seq, result },用于 resume 重放
.claude/workflow-runs/<runId>/state.json 终态 RunProgress 快照(含 returnValue / error),原子写(tmp + rename),面板重启后可展示历史
i
run 生命周期的写盘与清理

仅在 run 进入终态(completed / failed / killed)时才写 state.json; .claude/workflow-runs/ 保留最近 50 个 run(KEEP_MAX_RUNS), 打开面板时 hydrate 最新 20 个(LOAD_PERSISTED_LIMIT)。持久化失败只记日志,不阻断 workflow 完成。

常用命令与参数

Workflow 工具输入字段(模型侧发起 workflow 时使用):

字段 类型 / 取值 说明
script string(可选) 内联脚本字符串
name string(可选) 命名 workflow,解析到 .claude/workflows/<name>.ts|js|mjs
scriptPath string(可选) 已存在脚本的绝对路径(须位于 cwd 内)
args 任意 JSON 值(可选) 透传给脚本的 args,传真实对象 / 数组,不要传字符串化 JSON
resumeFromRunId string(可选) 从既有 run 重放 journal
description string(可选) 本次调用描述(3–5 词)
title string(可选) 进度视图标题(缺省回退 workflow)
maxConcurrency int,1–16(可选) 每 run 并发上限,缺省 3

斜杠命令:

命令 作用
/workflows 打开全屏监控面板(三区焦点模型:顶部 run tab + 左 phase 侧栏 + 右 agent 列表)
/<name> .claude/workflows/ 脚本自动生成;调用即要求模型以 name="<name>" 发起 Workflow

/workflows 面板键位:

键 作用
Tab / Shift+Tab切换顶部 run tab(正 / 反)
← / →在 phases 与 agents 两列间切换焦点
↑ / ↓当前焦点列内移动(phase 改筛选,agent 移光标)
xkill 当前选中的单个 agent(需二次确认)
Kkill 整个 run(需二次确认)
rresume 当前 run
n新建提示
q / Esc退出面板
i
面板状态含义

每个 run 一个 tab(状态点 + 名称 + #runId短码)。左栏合并 meta.phases 声明的 pending 阶段(○)与实际阶段 (● running / ✓ done),并含固定的 All 项; 右栏按选中 phase 过滤 agent,行尾标注 running / object / text / dead。

实战示例

一个「发现 → 改写 → 验证」的迁移类 workflow:改写阶段用 isolation: 'worktree' 隔离并行写,验证阶段读取原 item 定位文件。

.claude/workflows/migrate-api.mjs
export const meta = {
  name: 'migrate-api',
  description: '发现 legacyFetch 调用点,逐个改写并验证',
  phases: [
    { title: 'Discover', detail: 'grep 调用点' },
    { title: 'Transform', detail: '每处一个 agent(worktree 隔离)' },
    { title: 'Verify', detail: '逐处验证改写' },
  ],
}

const SITES_SCHEMA = {
  type: 'object',
  properties: {
    sites: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          file: { type: 'string' },
          line: { type: 'number' },
        },
        required: ['file', 'line'],
      },
    },
  },
  required: ['sites'],
}

const VERDICT_SCHEMA = {
  type: 'object',
  properties: { ok: { type: 'boolean' } },
  required: ['ok'],
}

phase('Discover')
const found = await agent(
  '列出仓库中所有对 legacyFetch 的调用点(file + line)',
  { label: 'discover:sites', schema: SITES_SCHEMA }
)

// Discover 之后需要全部调用点才能 fan-out,故此处显式屏障后的 pipeline 逐项处理
const results = await pipeline(
  found ? found.sites : [],
  site => agent(`把 ${site.file}:${site.line} 的 legacyFetch 改写为新 API`, {
    label: `transform:${site.file}`,
    phase: 'Transform',
    isolation: 'worktree',
  }),
  (_patch, site) => agent(`验证 ${site.file} 的改写是否正确`, {
    label: `verify:${site.file}`,
    phase: 'Verify',
    schema: VERDICT_SCHEMA,
  })
)

const verified = results.filter(Boolean).filter(v => v.ok)
log(`已验证 ${verified.length} 处改写`)
return { verified }

迭代与 resume:脚本改动后,用同一路径加 resumeFromRunId 重新发起, 未改动前缀里的 agent() 会命中 journal 直接返回缓存,只重跑首个发散点之后的部分。

qsdashi 会话
# 首次运行后,工具结果里会给出 runId
Workflow({ scriptPath: ".claude/workflows/migrate-api.mjs" })

# 改脚本后从该 run 重放
Workflow({
  scriptPath: ".claude/workflows/migrate-api.mjs",
  resumeFromRunId: "<runId>",
})

# 也可在 /workflows 面板中对选中的 run 按 r 续跑

用预算控制深度:当用户在 turn 里给出 token 目标(如 +500k)时, budget.total 有值,可据此动态扩缩 fan-out。

片段
// 无预算目标时 total 为 null,勿裸写 while(会一路跑到 1000 agent 硬顶)
// 需要动态循环时始终以 budget.total 为守卫
const found = []
while (budget.total && budget.remaining() > 50000) {
  const r = await agent('继续寻找遗漏的调用点', { schema: SITES_SCHEMA })
  found.push(...(r ? r.sites : []))
  log(`已收集 ${found.length} 处`)
}

常见问题排错

!
脚本校验失败,直接返回错误给模型

脚本语法错误或 meta 非法时,parseScript 在进后台前就报错, 工具结果直接返回 Error: script validation failed,不产生 run。 常见原因即 import、TS 语法、多个 export、缺 meta。

!
agent() 返回 null

子 agent 被用户 skip、或在重试耗尽后终态死亡(kind:'dead')时会返回 null,但不会杀掉整个 workflow。parallel / pipeline 里的单项失败同样只把该项置 null。 处理结果前统一 .filter(Boolean)。

!
parallel 比 pipeline 慢

parallel 是屏障,wall-clock 等于最慢项;pipeline 无屏障, 快链不会等慢链。只有阶段 N 确实需要上一阶段全部结果时(跨项去重、总数为零提前退出、 提示词要引用「其它发现」)才用屏障。默认选 pipeline。

!
resume 为什么没有全部命中 / 为什么全重跑

journal 按执行顺序比对 agent() 的 key(prompt + 去掉展示字段后的 opts 哈希)。命中「最长未变前缀」,首个发散点之后现场重跑。脚本源码 hash 变化 (改动脚本)会导致整体不命中;此外若脚本绕过 shim 用非确定性来源制造差异,也会导致 resume 命中错误缓存——请勿用 globalThis.Date 之类的旁路。

!
预算耗尽后 agent() 抛错

spent() 达到 total 后,后续 agent() 直接抛错 (脚本可 try/catch)。预算池在主循环与所有 workflow 间共享;无 total 时 remaining() 为 Infinity。

!
面板重启后为空 / 取不到历史 run

只有进入终态(completed / failed / killed)的 run 才会写 state.json; 进程在 run 结束前被强杀(SIGKILL / 断电)会导致该 run 无磁盘快照。若文件存在但面板仍看不到, 检查 .claude/workflow-runs/<runId>/state.json 是否损坏或 schemaVersion 不符(当前为 1)。持久化失败只记日志、不影响 workflow 本身。

*
并发为什么默认只有 3

默认 DEFAULT_MAX_CONCURRENCY=3 是为避免一次扇出十几个 agent。可以传更多 item 给 parallel / pipeline(会排队跑完),但同一时刻只有配置数量的 agent 在跑;需要更高并发时经 maxConcurrency(1–16)覆盖,且工具描述要求先与用户 确认。