Skip to content

TokenRollAI/claude-code-workflow-research

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Claude Code Workflow 实现原理:从抓包看动态多代理编排

本文基于一次 Claude Code workflow 的 Reqable 抓包和本地运行产物,拆解 Claude Code dynamic workflow 的实现原理、工作机制和 agent 交接方式。

一句话概括:Claude Code workflow 的本质不是“模型在一次请求里并发思考”,而是“主模型生成一段 JavaScript 编排脚本,Claude Code 本地 runtime 执行脚本并启动多个普通子代理会话”。子代理通过 StructuredOutput 返回结构化 JSON,本地 runtime 再把这些 JSON 作为普通 JavaScript 值传给后续阶段,最终把脚本的 return 值通过 <task-notification> 回灌主会话。

证据范围

本仓库保留了同一次 workflow 运行的两类材料:

本次运行的关键 ID:

字段
Session ID 915155ec-d5cc-44ba-b5eb-fb3e513a1d8f
Workflow run ID wf_1b6a0439-04e
Task ID wts66al8s
主题 桌游设计锦标赛:并行发明概念,评委团打分,综合冠军设计稿

关键证据文件:

文件 说明
workflow-artifacts/workflow-tool-prompt.md 主模型看到的 Workflow 工具提示词,包含触发规则、JS DSL、并发语义、恢复规则和质量模式。
workflow-artifacts/workflow-tool-schema.json 主模型看到的 Workflow 工具 schema。
workflow-artifacts/main-workflow-tool-use.json 主模型实际发出的 tool_use: Workflow,其中包含完整内联 JS。
workflow-artifacts/boardgame-tournament-wf_1b6a0439-04e.js Claude Code 持久化保存的本次 workflow JS。
workflow-artifacts/subagent-system-prompt.md 注入 workflow 子代理的系统提示词。
workflow-artifacts/structured-output-schemas/ agent(..., { schema }) 传给 StructuredOutput 的 JSON schema。
workflow-artifacts/subagent-prompts/ 去重后的 21 个逻辑子代理 prompt。
workflow-artifacts/examples/ 子代理 StructuredOutput 工具调用和 tool_result 回灌样例。
workflow-artifacts/journal.jsonl 本地 workflow runtime 的 agent start/result 日志。
workflow-artifacts/workflow-task-notification.md workflow 完成后注入主会话的最终通知。

除非特别说明,下面的结论都来自这些抓包、提示词、JS 脚本和本地运行文件。无法从材料直接证明的内部实现细节,会明确标注为推断。

总体架构

Claude Code workflow 可以分成四层:

  1. 主会话层:主模型看到一个名为 Workflow 的本地工具。
  2. 编排脚本层:主模型生成一段 JavaScript DSL,描述 fan-out、pipeline、评审、聚合和最终返回。
  3. 本地 runtime 层:Claude Code 持久化并执行这段 JS,负责并发、缓存、日志、schema 校验和恢复。
  4. 子代理层:每个 agent() 变成一个普通 Messages API 子会话,子代理通过 StructuredOutput 工具返回结构化结果。

流程可以画成这样:

用户显式触发 workflow
  -> 主会话把 Workflow 工具暴露给主模型
  -> 主模型生成 JavaScript workflow script
  -> 主模型调用 tool_use: Workflow({ script })
  -> Claude Code 本地 runtime 持久化 script 并启动后台任务
  -> runtime 执行 JS DSL: agent / pipeline / parallel / phase / log
  -> 每个 agent() 启动一个子代理 Messages API 请求
  -> 子代理调用 StructuredOutput 返回 JSON
  -> runtime 校验 JSON 并把它作为 agent() 的 resolved value
  -> JS 代码计算、排序、拼 prompt、继续启动后续 agent
  -> JS 最终 return 一个对象
  -> Claude Code 通过 <task-notification> 把结果回灌主会话

这个结构里,模型真正“设计”的是 workflow 脚本;并发执行、进度显示、子代理启动、结构化返回和最终通知,都是 Claude Code 本地 runtime 的职责。

Workflow 工具如何暴露给主模型

在主会话请求里,Claude Code 把 Workflow 当作一个普通工具交给主模型。它的 schema 见 workflow-tool-schema.json,核心输入字段包括:

字段 作用
script 内联 workflow JavaScript。
name 运行内置或 .claude/workflows/ 下的命名 workflow。
args 直接传给脚本的参数,在脚本中以全局 args 访问。
scriptPath 从本地脚本路径运行 workflow。
resumeFromRunId 基于已有 run 恢复,复用未变更 agent 的缓存结果。

提示词明确要求:首次调用通常通过 script 传入内联 JS,不要先写文件。Claude Code 会自动把脚本持久化到 session 目录,并在工具返回中给出路径。后续迭代可以编辑这个文件,再通过 scriptPath 运行。

本次主模型实际调用保存在 main-workflow-tool-use.json。调用后,工具没有同步返回最终设计稿,而是先返回后台任务信息,见 workflow-launch-tool-result.md

Workflow launched in background. Task ID: wts66al8s
Run ID: wf_1b6a0439-04e
Script file: .../boardgame-tournament-wf_1b6a0439-04e.js

这证明 Workflow 是后台任务模型:第一次工具结果只是“任务已启动”和“如何恢复”,最终业务结果会在完成后另行通知。

触发规则:为什么不是随便就能用 Workflow

Workflow 工具提示词对触发条件非常严格。主模型只能在用户明确 opt-in 时调用 workflow。有效 opt-in 包括:

  • 用户消息包含 workflowworkflows,并且系统提醒确认。
  • Ultracode 开启,并且系统提醒确认。
  • 用户明确说要运行 workflow、多代理编排、fan out agents。
  • 某个 skill 或 slash command 指示调用 Workflow
  • 用户要求运行特定命名 workflow。

提示词还强调:即使任务明显受益于并行,只要用户没有 opt-in,也不能擅自调用 Workflow。模型应该使用单个 Agent 工具,或者先解释 workflow 能做什么、预计成本,再询问用户是否运行。

这个设计说明 Claude Code 把 workflow 视为高成本、多代理的显式能力,而不是普通默认行为。

Workflow JS DSL:模型写出的不是 TypeScript,也不是 Node 程序

本次生成的 workflow 脚本是 boardgame-tournament-wf_1b6a0439-04e.js。它看起来像普通 JS,但运行环境是受限 DSL:

  • 必须以 export const meta = {...} 开头。
  • meta 必须是纯字面量,不能有变量、函数调用、spread 或模板插值。
  • 脚本体处于 async context,可以直接 await
  • 可以使用 JSONMathArray 等普通 JS 内置对象。
  • 不能使用 Node.js API 和文件系统 API。
  • 不能使用 Date.now()Math.random()、无参 new Date(),因为这些会破坏恢复缓存。
  • 脚本是 JavaScript,不是 TypeScript,类型标注、interface、泛型都会解析失败。

meta 负责 workflow 的展示信息:

export const meta = {
  name: 'boardgame-tournament',
  description: '桌游设计锦标赛:并行发明概念 → 评委团打分 → 综合出冠军设计稿',
  phases: [
    { title: 'Invent', detail: '5 个不同设计哲学的智能体并行发明桌游概念' },
    { title: 'Judge', detail: '每个概念由 3 个不同视角的评委打分' },
    { title: 'Synthesize', detail: '取冠军概念并嫁接落选者最佳创意,产出完整设计稿' },
  ],
}

meta.phases 和后面的 phase()agent({ phase }) 配合,用于 workflow UI 的进度分组。

DSL 核心能力

Workflow 提示词定义了一组脚本 hook。它们是 workflow 的核心编程模型。

agent()

agent(prompt, opts) 启动一个子代理。

常用 opts

字段 说明
label 进度树里的 agent 标签。
phase 显式归属阶段。
schema 强制子代理通过 StructuredOutput 返回符合 schema 的 JSON。
model 可选模型覆盖。
isolation: 'worktree' 为会修改文件的并行 agent 创建隔离 git worktree。
agentType 使用自定义子代理类型。

没有 schema 时,agent() 返回子代理最终文本。有 schema 时,子代理会被强制调用 StructuredOutputagent() 返回校验后的对象,不需要父流程再解析自然语言。

pipeline()

pipeline(items, stage1, stage2, ...) 是提示词推荐的默认多阶段模式。它的关键特征是没有阶段间全局 barrier。

也就是说,item A 完成 stage 1 后可以立即进入 stage 2,不需要等待 item B、C、D 全部完成 stage 1。这样 wall-clock 更接近“最慢单个 item 的完整链路”,而不是“每个阶段最慢任务之和”。

本次脚本就是典型 pipeline:

const results = await pipeline(
  LENSES,
  (lens) => agent(...invent prompt...),
  (concept, lens) => parallel(...judge agents...)
)

含义是:5 个设计方向并行发明概念;某个概念一生成,就立刻进入评审阶段,不等其他概念全部生成完。

parallel()

parallel(thunks) 并行执行多个 thunk,并等待全部结束。它是 barrier。

提示词强调,只有当下一步确实需要全部结果时才使用 barrier,例如:

  • 需要跨全部结果去重或合并。
  • 需要知道总数是否为 0 来整体跳过下一阶段。
  • 下一阶段 prompt 需要引用“其他发现”。

本次脚本在每个概念的 3 个评委之间使用 parallel

parallel(
  JUDGES.map((j) => () =>
    agent(...judge prompt..., { schema: JUDGE_SCHEMA })
  )
).then((votes) => {
  const valid = votes.filter(Boolean)
  const avg = valid.length ? valid.reduce((s, v) => s + v.score, 0) / valid.length : 0
  return { concept, lens: lens.key, votes: valid, avg }
})

这里 barrier 是合理的,因为计算平均分必须等同一个概念的 3 个评委都返回。

phase() 和 log()

phase(title) 用来切换阶段;log(message) 用来在 workflow 进度中输出叙事信息。

本次脚本使用 log() 标记开赛和冠军概念,并在综合阶段前调用 phase('Synthesize')

args、budget 和 workflow()

工具提示词还定义了三个更高级的 hook:

  • args: 从 Workflow 工具输入传给脚本的参数,必须传真实 JSON,不要传 JSON 字符串。
  • budget: 读取本轮 token 目标、已花费和剩余额度,可用来动态控制 agent 数量或循环深度。
  • workflow(nameOrRef, args): 在当前 workflow 内嵌套运行另一个 workflow,子 workflow 共享并发上限、agent 计数、abort signal 和 token budget。只允许一层嵌套。

本次桌游示例没有使用这三个高级能力,但它们说明 workflow 脚本可以被参数化、受预算约束,也可以组合已有 workflow。

本次桌游 Workflow 的实际结构

本次 workflow 是一个“设计锦标赛”。

脚本中定义了 5 个设计方向:

const LENSES = [
  { key: 'coop-survival', desc: '对抗不断升级威胁的合作生存玩法' },
  { key: 'social-deduction', desc: '隐藏身份、虚张声势的社交推理玩法' },
  { key: 'tile-spatial', desc: '空间拼块铺设与领地建设玩法' },
  { key: 'economic-engine', desc: '经济引擎构筑与资源转化玩法' },
  { key: 'dexterity-realtime', desc: '手部灵巧度与实时动作玩法' },
]

又定义了 3 个评审维度:

const JUDGES = [
  { key: 'originality', desc: '原创性与新颖度...' },
  { key: 'fun', desc: '趣味性与重玩价值...' },
  { key: 'clarity', desc: '规则清晰度与上手门槛...' },
]

逻辑 agent 数量:

阶段 数量 说明
Invent 5 每个设计方向一个发明 agent。
Judge 15 每个概念由 3 个评委独立打分,5 x 3。
Synthesize 1 取冠军概念,嫁接落选概念优点,生成最终单页设计稿。
合计 21 与最终通知中的 agent_count 一致。

最终 workflow-task-notification.md 中的 usage 也记录了:

<usage>
  <agent_count>21</agent_count>
  <subagent_tokens>232900</subagent_tokens>
  <tool_uses>23</tool_uses>
  <duration_ms>257654</duration_ms>
</usage>

注意:逻辑 agent 数不等于 HTTP 请求数。一个逻辑 agent 可能因为工具调用、tool_result continuation、重试等产生多次 Messages API 请求。因此抓包文件数量会多于 21。

Prompt 拼接:一次子代理请求由哪些块组成

从抓包看,workflow 子代理请求不是把所有指令都拼成一个巨大的纯文本 prompt。更准确的理解是:Claude Code 本地 runtime 在发起 POST /v1/messages?beta=true 时,把不同来源的内容放进 Anthropic Messages API 的不同槽位。

以 invent 请求 reqable/[448]、judge 请求 reqable/[493] 和 synthesize 请求 reqable/[682] 对照,可以看到请求大致由这些块组成:

请求位置 内容来源 作用
HTTP headers Claude Code CLI / 本地代理 session id、beta flags、版本、认证占位等传输元信息。
model workflow agent 配置 本次是 claude-opus-4-8。如果 agent(..., { model }) 覆盖,理论上会影响这里。
messages[0].role = user runtime 组装 子代理本轮 user 输入。里面通常包含一个 <system-reminder> 上下文块,再跟 JS agent(prompt) 生成的任务 prompt。
messages[0].content[0].text Claude Code 会话上下文 <system-reminder>,包含用户全局指令、当前日期等。
messages[0].content[1].text workflow JS 的 agent(prompt) 求值结果 真正的子任务说明,例如“请发明一个原创桌游概念...”或“你是桌游评委...”。
system[] Claude Code runtime 顶层 system 消息,包括 Claude Code 身份、workflow subagent 系统提示词、环境信息等。
tools[] Claude Code runtime + agent(..., { schema }) 子代理可用工具。普通工具来自 Claude Code;StructuredOutput 工具由 schema 动态生成。
tools[].StructuredOutput.input_schema workflow JS 的 schema 对象 强制子代理最终返回的结构。schema 不放在 user prompt 里,而是作为工具 schema 进入请求。
metadata / output_config / max_tokens Claude Code runtime 用户/session 元数据、effort、输出上限等运行配置。

也就是说,一次 workflow subagent 请求更像这个伪代码:

function buildWorkflowAgentRequest({ prompt, schema, model, sessionContext, toolRegistry }) {
  return {
    model: model ?? resolvedSessionModel,
    messages: [
      {
        role: 'user',
        content: [
          {
            type: 'text',
            text: renderSystemReminder(sessionContext),
          },
          {
            type: 'text',
            text: prompt,
            cache_control: maybeEphemeral(prompt),
          },
        ],
      },
    ],
    system: [
      { type: 'text', text: renderBillingOrRuntimeHeader() },
      { type: 'text', text: "You are Claude Code, Anthropic's official CLI for Claude." },
      { type: 'text', text: renderWorkflowSubagentSystemPrompt() },
    ],
    tools: [
      ...baseClaudeCodeTools(toolRegistry),
      schema && {
        name: 'StructuredOutput',
        description: 'Use this tool to return your final response in the requested structured format...',
        input_schema: schema,
      },
    ].filter(Boolean),
    output_config: { effort: 'xhigh' },
    stream: true,
  }
}

这段伪代码不是 Claude Code 源码,只是根据抓包结构推算出的组装模型。抓包能直接证明的是:任务 prompt 在 messages[0].content 中,workflow subagent 规则在顶层 system[] 中,StructuredOutput schema 在 tools[] 中。

JS prompt 模板如何变成子代理 user prompt

本次 workflow 的业务 prompt 不是 Claude Code runtime 自己写死的,而是主模型生成的 JS 模板字符串。runtime 执行 JS 时,对 agent(prompt, opts) 的第一个参数做正常 JavaScript 求值,得到最终 user prompt。

invent 阶段来自脚本里的模板:

(lens) => agent(
  `请发明一个原创桌游概念,核心围绕「${lens.desc}」。\n` +
  `要求:具体、富有创意;起一个令人难忘的名字;给出一个真正新颖的核心钩子。\n` +
  `避免陈词滥调,不要照搬任何已有游戏。用中文作答。`,
  { label: `invent:${lens.key}`, phase: 'Invent', schema: CONCEPT_SCHEMA }
)

lens.desc = '空间拼块铺设与领地建设玩法' 时,请求里的任务 prompt 就变成:

请发明一个原创桌游概念,核心围绕「空间拼块铺设与领地建设玩法」。
要求:具体、富有创意;起一个令人难忘的名字;给出一个真正新颖的核心钩子。
避免陈词滥调,不要照搬任何已有游戏。用中文作答。

这个结果和 subagent-prompts/448-invent.md 以及 reqable/[448] 中的 messages[0].content[1].text 对得上。

judge 阶段的 prompt 则把上游 agent 的结构化 JSON 再序列化进文本:

agent(
  `你是桌游评委,专门从这个维度评审:「${j.desc}」。\n` +
  `待评概念:\n${JSON.stringify(concept, null, 2)}\n` +
  `请给出 1-10 分并简述理由。用中文作答。`,
  { label: `judge:${lens.key}:${j.key}`, phase: 'Judge', schema: JUDGE_SCHEMA }
)

这里的 concept 不是自然语言摘要,而是 invent agent 通过 StructuredOutput 返回的 JSON 对象。runtime 把它作为 JS 值传给下一阶段,然后由 JSON.stringify(concept, null, 2) 变成 judge prompt 中的格式化 JSON。抓包 reqable/[493] 里可以看到完整嵌入后的概念对象。

synthesize 阶段同理,只是拼进去的是冠军和落选概念数组:

agent(
  `请把这个胜出的桌游概念打磨成一份完整的单页游戏设计稿。\n\n` +
  `【冠军概念】\n${JSON.stringify(winner.concept, null, 2)}\n\n` +
  `【落选概念(可酌情各取其最佳一个创意嫁接,使设计更强)】\n` +
  `${JSON.stringify(runnersUp.map((r) => r.concept), null, 2)}\n\n` +
  `产出要素:组件清单、开局布置、回合流程、获胜条件、招牌特色机制。用中文作答。`,
  { label: 'synthesize', phase: 'Synthesize', schema: SYNTH_SCHEMA }
)

所以 prompt 拼接的关键不是“把所有 agent 历史都塞给下一个 agent”,而是“由 JS 明确选择哪些结构化字段进入下一个 prompt”。在本次例子里:

  • invent agent 只看到自己的设计方向,不知道其他 4 个 invent agent。
  • judge agent 只看到一个待评概念和自己的评审维度,不知道其他评委的评分。
  • synthesize agent 看到冠军概念和落选概念列表,但看不到所有 judge rationale,除非 JS 作者显式把 votes 也 stringify 进去。

这点很重要:workflow 的上下文传播是显式的、由 JS 脚本控制的,不是自动共享所有子代理 transcript。

子代理系统提示词:为什么它必须返回 StructuredOutput

workflow 子代理不是直接沿用主会话系统提示词。Claude Code 会注入专门的 workflow subagent system prompt,见 subagent-system-prompt.md

核心规则是:

You MUST call the StructuredOutput tool exactly once to return your final answer.
Do NOT put your answer in a text response.
The script reads ONLY the StructuredOutput tool call.
If the schema validation fails, read the error and call StructuredOutput again.

这段提示词证明:在 agent(prompt, { schema }) 模式下,子代理的最终结果不是自然语言文本,而是一次 StructuredOutput 工具调用。父级 JS runtime 只读取这个工具调用里的 input

系统提示词还给子代理加了几条 workflow 专属约束:

  • bash 调用之间 cwd 会重置,所以涉及路径要用绝对路径。
  • 最终响应里应提供相关绝对路径。
  • 不要创建报告、总结、发现、分析类 .md 文件。
  • 成功调用 StructuredOutput 后结束本轮,不需要再补确认。

这些规则说明 workflow subagent 是一个被父流程读取返回值的执行单元,不是一个面向用户聊天的助手。

StructuredOutput 的交接机制

本次所有 agent() 都带了 schema,所以每个子代理都有一个 StructuredOutput 工具。schema 来自 workflow JS 中定义的对象,并被整理到了:

以 invent 阶段为例,CONCEPT_SCHEMA 要求子代理返回:

字段 说明
name 游戏名。
tagline 一句话标语。
players 玩家人数。
playtime 游戏时长。
coreMechanic 核心机制。
howItWorks 玩法概述。
hook 真正新颖的钩子。

examples/invent-structured-output-handoff-492.json 展示了一次完整交接:

  1. assistant 先输出简短思考文本。
  2. assistant 调用 tool_use: StructuredOutput
  3. StructuredOutput.input 是完整的桌游概念 JSON。
  4. user 角色回灌 tool_result: Structured output provided successfully

这之后,JS runtime 在 journal.jsonl 里记录该 agent 的 result。后续阶段使用的是 StructuredOutput.input 对象,而不是 assistant 文本。

Agent 之间到底怎么“交接”

严格说,子代理之间没有直接通信。它们不会互相发消息,也不共享完整上下文。交接由本地 JS runtime 通过普通数据流完成。

本次流程是:

  1. invent agent 返回 concept JSON。
  2. pipeline 把这个 concept 作为下一阶段函数参数传入。
  3. judge 阶段用 JSON.stringify(concept, null, 2) 把概念嵌入新的 prompt。
  4. 3 个 judge agent 分别返回 { score, rationale }
  5. JS 计算平均分,返回 { concept, lens, votes, avg }
  6. 所有 pipeline item 结束后,JS 排序得到 ranked
  7. synthesize prompt 嵌入 winner.conceptrunnersUp.map((r) => r.concept)
  8. synthesize agent 返回最终设计稿 JSON。
  9. JS 最终 return { winner, ranking, allConcepts, finalDesign }

所以 workflow 的“agent 交接”本质是:

StructuredOutput JSON
  -> JS resolved value
  -> JS 计算/排序/拼接 prompt
  -> 下一个 agent 的输入 prompt

这和很多人直觉里的“多个 agent 在同一个群聊中互相讨论”不同。Claude Code workflow 更像一个确定性的 orchestration runtime,agent 是异步函数调用,prompt 是输入,StructuredOutput JSON 是返回值。

本地 runtime 的日志和状态

journal.jsonl 记录了本地 runtime 的 agent 生命周期。典型记录有两类:

{"type":"started","key":"...","agentId":"..."}
{"type":"result","key":"...","agentId":"...","result":{...}}

invent agent 的 result 是一个完整概念对象;judge agent 的 result{ score, rationale };synthesize agent 的 result 是最终设计稿对象。

workflow-final-state.json 是更完整的本地状态文件,包含:

  • runId
  • timestamp
  • taskId
  • script
  • scriptPath
  • result
  • agent 元信息
  • token 和工具调用统计

这说明 workflow runtime 不只是把模型输出流式转发给用户,而是维护了一份可检查、可恢复的本地运行状态。

完成通知:结果如何回到主会话

由于 Workflow 是后台任务,主模型第一次调用工具时不会拿到最终结果。任务完成后,Claude Code 注入一条新的 user message,格式是 <task-notification>,见 workflow-task-notification.md

它包含:

字段 作用
task-id 对应启动阶段返回的后台任务 ID。
tool-use-id 主模型最初调用 Workflow 工具时的 tool use ID。
output-file 本地保存 workflow 输出的文件路径。
status 本次为 completed
summary workflow 的人类可读摘要。
result workflow JS 最终 return 的 JSON 序列化结果。
usage 子代理数量、token 消耗、工具调用数和耗时。

这一步是 workflow 和主会话之间的最终交接。主模型收到这条通知后,再基于 result 向用户总结结果。

Resume 和缓存机制

Workflow 工具提示词和启动返回都说明支持恢复:

Workflow({
  scriptPath: ".../boardgame-tournament-wf_1b6a0439-04e.js",
  resumeFromRunId: "wf_1b6a0439-04e"
})

恢复规则是:未变更的最长 agent 调用前缀直接返回缓存结果;第一个变更或新增调用及其后续步骤重新运行。同一个 script 加同一个 args 可以 100% 命中缓存。

这也解释了为什么 workflow 脚本禁止 Date.now()Math.random() 和无参 new Date()。这些非确定性值会破坏“同样脚本和同样输入对应同样 agent 调用”的缓存假设。

从设计上看,resume 依赖三个条件:

  1. 脚本控制流是确定性的。
  2. agent() 调用的 prompt 和 opts 可被稳定 hash。
  3. 本地 journal 或 state 能保存已完成 agent 的结果。

第 1、2 点来自工具提示词和脚本限制;第 3 点可以从 journal.jsonlworkflow-final-state.json 看到证据。

设计模式:Claude Code 希望你怎样写 Workflow

Workflow 提示词不只是 API 文档,它还写入了很多编排模式偏好。

默认 pipeline,而不是阶段 barrier

提示词反复强调 DEFAULT TO pipeline()。原因很实际:如果阶段之间没有全量依赖,就不应该让快的 item 等慢的 item。

错误倾向是:

const a = await parallel(...)
const b = a.map(...).filter(...)
const c = await parallel(b.map(...))

如果中间只是 map/filter/flatten,不需要等待全量结果。应把转换放进 pipeline stage,让每个 item 自己往下流。

只有全局依赖才用 parallel barrier

barrier 合理场景包括:

  • 全量去重后再验证。
  • 总数为 0 时整体跳过后续工作。
  • 后续 prompt 需要“其他发现”作为对比上下文。

本次对每个概念的 3 个评委使用 barrier,就是因为平均分必须等三个评分都回来。

用 judge panel 处理开放式设计

本次桌游 workflow 实际采用了提示词推荐的 Judge panel 模式:

多个方向独立生成
  -> 多个评委并行评分
  -> 选出 winner
  -> 综合 winner 并嫁接 runners-up 优点

这个模式适合解空间很宽的任务,因为单次迭代容易困在一个方案里;多方案并行加评委打分能扩大探索面。

用 StructuredOutput 消除解析脆弱性

workflow 里最重要的工程选择之一,是让子代理返回 schema 校验后的 JSON,而不是让父流程从自然语言中提取信息。

这带来几个好处:

  • 后续 JS 不需要写脆弱的文本解析。
  • schema 失败可以在工具调用层重试。
  • 结果可以直接 sortmapreduceJSON.stringify
  • journal 和 final state 里保存的是结构化对象,便于恢复和检查。

与普通 Agent 工具的区别

Claude Code 主会话中也有普通 Agent 工具。两者的边界大致是:

能力 Agent Workflow
启动方式 主模型委托一个独立 agent。 主模型生成 JS 编排脚本。
适合场景 单个探索、单个审查、单个实现任务。 fan-out、pipeline、评分、对抗验证、大范围迁移、审计。
控制流 主要由模型自然语言驱动。 由 JS 明确控制 loops、conditionals、parallel、pipeline。
返回值 通常是文本。 可通过 schema 强制结构化 JSON。
成本 通常较低。 可能启动几十个 agent,token 成本高。
使用条件 普通委托即可。 需要用户显式 opt-in。

所以 workflow 不是 Agent 的替代品,而是当任务需要确定性多代理控制流时的更高层编排工具。

可以确认的结论

从本次证据可以确认:

  1. Workflow 是 Claude Code 暴露给主模型的本地工具。
  2. 主模型负责生成 workflow JS,并通过 tool_use: Workflow 交给本地 runtime。
  3. Workflow 调用后先启动后台任务,立即返回 task ID、run ID、script path。
  4. workflow JS 不是完整 Node.js 程序,而是受限的 JavaScript DSL。
  5. agent(prompt, { schema }) 会启动 workflow 子代理,并强制它调用 StructuredOutput
  6. 子代理返回的是 schema 校验后的 JSON,不是父流程解析后的自然语言。
  7. pipelineparallel 的语义由本地 runtime 实现,主模型只是在 JS 中声明控制流。
  8. 子代理之间不直接通信,交接由 JS runtime 通过 JSON 值和新 prompt 完成。
  9. 最终结果通过 <task-notification> 作为新的 user message 回灌主会话。
  10. 本地 journal 和 final state 记录了 agent 开始、结果、脚本、最终输出和统计信息。

仍属于推断的部分

有些内部细节从抓包和本地文件不能完全证明,只能基于现象推断:

  • runtime 内部如何 hash agent() 调用来命中缓存。
  • 并发调度器的具体实现代码。
  • StructuredOutput schema 校验失败后的重试细节在源码层如何实现。
  • /workflows UI 如何读取 progress tree。
  • agent transcript 文件与 journal 的精确写入顺序。

不过这些不影响本文的核心结论:Claude Code workflow 的可见机制已经足以证明它是“模型生成脚本,本地 runtime 执行脚本,子代理结构化返回”的架构。

读者复盘路线

如果想按证据自己复盘,可以按这个顺序读:

  1. workflow-tool-prompt.md,理解主模型看到的完整规则。
  2. workflow-tool-schema.json,确认工具输入字段。
  3. main-workflow-tool-use.json,看主模型实际传入的 JS。
  4. boardgame-tournament-wf_1b6a0439-04e.js,理解本次 workflow 的控制流。
  5. subagent-system-prompt.md,看子代理为什么必须调用 StructuredOutput
  6. subagent-prompts/index.md,看 21 个逻辑 agent 的输入 prompt。
  7. examples/invent-structured-output-handoff-492.json,看一次完整的结构化输出交接。
  8. journal.jsonl,看 runtime 如何记录 agent started/result。
  9. workflow-task-notification.md,看最终结果如何回灌主会话。

最后总结

Claude Code workflow 把多代理协作拆成了两个清晰层次:

  • 模型层:主模型负责设计编排脚本,子模型负责完成具体子任务。
  • 工程层:本地 runtime 负责确定性控制流、并发、schema 校验、缓存、日志和结果回灌。

这个设计的关键价值是把“多代理协作”从纯自然语言协议,变成了可执行、可恢复、可检查的数据流程序。agent() 像异步函数,prompt 是输入,StructuredOutput JSON 是返回值,pipeline()parallel() 是调度结构,最终 return 是 workflow 的产品。

这也解释了为什么它适合审计、迁移、研究、评审、设计锦标赛这类任务:它不是让一堆 agent 随机聊天,而是让主模型写出一个确定性的多代理实验框架,再由 Claude Code 本地 runtime 把这个框架执行到底。

About

Research notes and captured artifacts for Claude Code workflow orchestration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors