Workflow 基础:用代码调度多个 Agent
前两篇讲的是单个 Agent 怎么定义、Prompt 怎么写。这篇开始讲怎么把多个 Agent 串起来干活。
Shell 脚本也能串(claude -p 一个接一个),但数据靠文件传,并行写起来麻烦。
Workflow 解决的就是这个:用一段 JS 代码决定什么时候派谁、数据传给谁,你只需要说一句"开始"。
别怕,不需要会 JS。这篇看完你就知道,写 Workflow 只需要四样东西。
一、先看效果:一个完整的 Workflow 长什么样
这是我实际用过的自动写文章流程:三源并行搜索 → 汇总列大纲 → 写初稿。
javascript
export const meta = {
name: 'auto-article',
description: '三源并行搜索 → 汇总大纲 → 写初稿',
phases: [
{ title: '搜集资料' },
{ title: '列大纲' },
{ title: '写初稿' },
],
}
const TOPIC = args.topic || 'Claude Code Agent 编排'
// 阶段 1:三个 Agent 同时搜(搜资料用 haiku,便宜)
phase('搜集资料')
const [chinese, english, github] = await parallel([
() => agent(`搜索「${TOPIC}」的中文资料,找 3 个最有价值的来源`, { label: '搜中文', model: 'haiku' }),
() => agent(`Search for "${TOPIC}" in English, find 3 sources`, { label: '搜英文', model: 'haiku' }),
() => agent(`搜索「${TOPIC}」相关的开源项目和官方文档`, { label: '搜开源', model: 'haiku' }),
])
// 阶段 2:汇总资料,列大纲
phase('列大纲')
const allMaterials = [chinese, english, github].join('\n---\n')
const outline = await agent(
`基于以下资料列出文章大纲:\n${allMaterials}`,
{ phase: '列大纲' }
)
// 阶段 3:写初稿
phase('写初稿')
const draft = await agent(
`根据大纲写一篇 1200 字初稿:\n${outline}`,
{ phase: '写初稿' }
)
return draft
这段代码做的事:
arduino
你说"开始" → 三个 Agent 同时搜三个源
→ 结果拼在一起,派一个 Agent 列大纲
→ 大纲喂给另一个 Agent 写初稿
→ 返回文章
二、你只需要会四样 JS
写 Workflow 不需要学完整的 JS,简单总结了一下:这四样基本就够了:
1. await agent():派活,等结果
vbnet
const result = await agent('帮我搜一下 Claude Code Workflow 的用法')
读作:派一个子 Agent 去做引号里的事,等它干完,把结果(一段文字)存进 result 变量。
await 就是"等它回来再往下走"。没有它,代码不会等 Agent 干完就直接执行下一行了。
2. 反引号和 ${}:拼字符串
ini
const topic = 'Claude Code'
const prompt = `帮我搜一下${topic}的用法`
// prompt 的实际内容是:帮我搜一下Claude Code的用法
反引号 ````` 就是键盘左上角和 ~ 在一起的那个键。它和普通引号的区别是:可以跨行,可以用 ${变量名} 把变量插进去。
你在 Prompt 里塞资料、塞上一步结果,全靠这个。
3. parallel([]):同时派多个
scss
const [a, b, c] = await parallel([
() => agent('搜中文'),
() => agent('搜英文'),
() => agent('搜开源'),
])
两个要点:
- 每个
agent()外面包一层() =>,意思是"先别跑,等 parallel 发令"。不包的话写的时候就执行了,变成串行。 const [a, b, c]叫解构赋值,就是把三个结果分别放进三个变量。顺序和数组里的顺序对应。
parallel 会等所有 Agent 都回来才往下走。
4. return:交结果
kotlin
return draft
最后把最终产物返回。你在对话里看到的输出就是它。
没了。变量声明用 const,字符串拼接用 + 或 ${},条件判断偶尔用个 ||(给默认值),其他 JS 特性一概不需要。
三、逐行拆解
回头看开头那段代码,一行行说。
meta 头
css
export const meta = {
name: 'auto-article',
description: '三源并行搜索 → 汇总大纲 → 写初稿',
phases: [
{ title: '搜集资料' },
{ title: '列大纲' },
{ title: '写初稿' },
],
}
告诉系统这个 Workflow 叫什么、分几个阶段。phases 只影响进度面板的显示,不写也能跑,写了能看到跑到哪一步。
phases 只影响进度面板的显示,不写也能跑,写了能看到跑到哪一步。想让某个阶段用便宜模型,在 agent() 调用里传 { model: 'haiku' }。
注意:meta 必须是纯字面量,不能在里面写变量或函数调用。
接参数
ini
const TOPIC = args.topic || 'Claude Code Agent 编排'
args 是系统给的全局变量。你运行时说"主题是 Remotion",args.topic 就是 "Remotion"。|| 后面是默认值,不传就用它。
阶段切换和日志
scss
phase('搜集资料')
切换当前阶段,后面的 Agent 会归到这个阶段下显示。和 meta 里的 title 对应。
三个 Agent 并行搜索
javascript
const [chinese, english, github] = await parallel([
() => agent(`搜索「${TOPIC}」的中文资料,找 3 个最有价值的来源`, { label: '搜中文' }),
() => agent(`Search for "${TOPIC}" in English, find 3 sources`, { label: '搜英文' }),
() => agent(`搜索「${TOPIC}」相关的开源项目和官方文档`, { label: '搜开源' }),
])
三个 Agent 同时启动,各搜各的。{ label: '搜中文' } 只影响进度面板上显示的名字。
三个都回来后,结果按顺序进 chinese、english、github 三个变量。
拼资料
csharp
const allMaterials = [chinese, english, github].join('\n---\n')
把三个搜索结果用分隔线拼成一个大字符串。等价于 chinese + '\n---\n' + english + '\n---\n' + github,但更好读。
列大纲和写初稿
javascript
phase('列大纲')
const outline = await agent(
`基于以下资料列出文章大纲:\n${allMaterials}`,
{ phase: '列大纲' }
)
phase('写初稿')
const draft = await agent(
`根据大纲写一篇 1200 字初稿:\n${outline}`,
{ phase: '写初稿' }
)
return draft
串行的两步:大纲 Agent 拿到全部资料,产出大纲;初稿 Agent 拿到大纲,产出文章。每一步的输出是下一步的输入,数据在变量之间传递,不写临时文件。
四、怎么运行
在 Claude Code 对话里直接说:
arduino
运行 workflows/auto-article.js,主题是 Remotion 视频渲染优化
系统会读这个文件,把 { topic: 'Remotion 视频渲染优化' } 传给 args,然后执行。会看到进度面板实时显示三个阶段的状态。
不传主题就用默认值:
arduino
运行 workflows/auto-article.js
五、Shell vs Workflow:什么时候用哪个
你可能已经在用 Shell 脚本调 claude -p 串流程了。两者不是替代关系,是场景不同。
| 维度 | Shell claude -p |
Workflow agent() |
|---|---|---|
| 运行条件 | 终端能跑就行,Claude Code 不用开着 | 必须在 Claude Code 会话里 |
| 定时任务 | 能,cron/launchd 直接调 | 不能 |
| 并行 | for 循环串行,写并行麻烦 | parallel() 原生支持 |
| 数据传递 | 写临时文件再读 | JS 变量直接传 |
| 中间产物 | 每步落盘,可审计 | 只在内存里 |
| 进度显示 | 看终端输出 | 进度面板分阶段显示 |
| 一个 Agent 挂了 | 整条链路断 | 返回 null,其他继续 |
我的判断标准很简单:
人不在电脑前(定时跑、无人值守) → Shell
人在电脑前,需要并行 → Workflow
人在电脑前,串行简单任务 → 随便,哪个顺手用哪个
需要中途看一眼再决定下一步 → 别写脚本,在对话里手动来
比如我那个每天自动生成视频的流水线,用 Shell(launchd 定时触发)。但我在电脑前要快速出一篇文章,用 Workflow(并行快)。
六、常见坑
1. 忘了 () =>,变成串行
scss
// 错:写的时候就执行了,parallel 拿到的是结果不是任务
await parallel([
agent('搜中文'),
agent('搜英文'),
])
// 对:包成函数,parallel 内部统一发射
await parallel([
() => agent('搜中文'),
() => agent('搜英文'),
])
2. 忘了 await
csharp
// 错:result 不是结果,是一个"还没干完的凭据"
const result = agent('搜一下')
// 对:等它干完
const result = await agent('搜一下')
3. meta 里写变量
meta 必须是纯字面量。想根据参数改阶段名做不到,阶段名是固定的。
4. 给 Agent 的 Prompt 里忘塞资料
javascript
// 错:Agent 不知道你要它分析什么
const outline = await agent('列个大纲')
// 对:把资料拼进去
const outline = await agent(`基于以下资料列大纲:\n${allMaterials}`)
子 Agent 看不到主对话和其他变量,它只拿到你写在 Prompt 里的东西。
小结
javascript
Workflow = 一段 JS 代码,决定什么时候派谁、数据传给谁
四样 JS 走天下:
await agent('任务') 派活等结果
`文字${变量}` 拼字符串
parallel([() => agent(), ...]) 同时派多个
return 结果 交差
和 Shell 的区别:
定时无人值守 → Shell
在电脑前要并行 → Workflow
要中途人工判断 → 手动对话