OpenClaw Agent Teams 实战
把 sessions_spawn 用对地方
跟上一篇
agent-teams.md不一样的视角。上一篇讲”Agent teams 是什么、为什么值得用”,这一篇讲”到底怎么把它跑起来”,而且只聚焦一件事——怎么在 OpenClaw 里用sessions_spawn编排并行子任务,以及它跟sessions_send到底有什么区别。
TL;DR
-
• sessions_spawn是”起一个新会话,丢一个任务进去“。它会立刻返回runId,子任务在独立的子会话里跑,跑完通过 announce 机制把结果推回给你。适合并行、可拆、互相不依赖的活儿。 -
• sessions_send是”给一个已经存在的会话塞条消息“。对方在自己的会话里继续转,你可选等回复,最多 5 轮 ping-pong。适合跟另一个长期 Agent 协作(比如把它叫醒、催进度、对账)。 -
• sessions_yield是”我先不说了,等我那些子任务跑完再回来“。spawn 完一票并行任务之后,接一个 yield,等 announce 当成下一条消息回来——别自己写 poll 循环。 -
• openclaw agentCLI 是给脚本/外部触发的,不是会话内编排工具。 -
• 一个直接判断:多数情况下你根本不需要 spawn。只有当任务能干净地拆出去、跑得慢、会污染主上下文,你才上 spawn;不然就用主会话自己干。
先把几个概念摞清楚
写代码前,先统一术语,免得后面在工具名上打转。
-
• Agent:一个完整的”人设包”——workspace、auth profiles、模型注册表、会话库,各自独立。 agentId就是它的身份证。 -
• Session:一段连续的对话。一个 Agent 可以有 N 个 session(主私聊、群聊、cron、sub-agent 都是 session)。 -
• Sub-agent(子 Agent):一个临时的、为一个具体任务新开的子会话。会话键形如 agent:<agentId>:subagent:<uuid>,跑完默认 60 分钟后被归档。 -
• Multi-agent routing:开多个长期并存的 Agent(比如 main和ops同时跑),用 binding 路由流量——这是横向的多 Agent。 -
• Sub-agents / sessions_spawn:用编排工具临时拉一个子会话出来干活——这是纵向的多 Agent。
横向和纵向经常被混在一起说”agent teams”,但 OpenClaw 里它们走的是完全不同的机制:横向靠 agents.list + bindings 配,纵向靠 sessions_spawn 触发。本文只讲纵向。
四个工具,各管一摊
工具组 group:sessions 一共 7 个工具,真正用来”把活派出去”的就 4 个,把它们的职责先画清楚:
|
|
|
|
|
|---|---|---|---|
sessions_spawn |
|
runId,非阻塞 |
|
sessions_send |
|
timeoutSeconds) |
|
sessions_yield |
结束当前轮次
|
|
|
openclaw agent
|
--deliver 推到频道 |
--deliver |
|
剩下 3 个是辅助:sessions_list、sessions_history、session_status 用来查和读;subagents 是只读的子任务看板。
决定性差异:spawn 是”立项”,send 是”发消息”
这是我跟同事解释时最常用的一句话:
-
• sessions_spawn就像开一个项目。你写一个任务简报(task字符串),建一个独立会场,配预算,让它自己跑,最后回来交付物。 -
• sessions_send就像敲门进别人的办公室。人家在干自己的事,你塞条消息进去,他看完决定要不要回、怎么回、要不要再抛回来。
典型反模式:你拿 sessions_send 去叫一个不存在的会话”帮我跑一个调研”——它没会话、没人设、没简报,只会回个错。起新活儿,必须用 spawn。
sessions_spawn 的内部契约(官方文档)
tools.profile: "coding" 默认就把 spawn / yield / subagents 都开出来;messaging profile 没有 spawn,要加一行 alsoAllow: ["sessions_spawn", "sessions_yield", "subagents"]。光看这一条,你就知道 spawn 是个”重型”工具,不是默认人人都能用的。
它最重要的几个不变量
-
1. 永远非阻塞: sessions_spawn调下去,马上拿到{ status: "accepted", runId, childSessionKey }。不能传 timeout 进去改这个行为——runTimeoutSeconds是配置级的默认值,不是 per-call 参数。 -
2. 不接受通道参数:你不能写 channel: "telegram"或to: "@foo"让子任务自己发消息。子 Agent 没有message工具,它只回文本给父 Agent,人类可见的回复永远由父/requester Agent 的正常回复策略决定。 -
3. 结果通过 announce 推回,不是返回值。子任务跑完,OpenClaw 走一个 announce 流程:如果是顶层子任务,就调一次 agentturn 把结果送到原始频道(保留 thread/topic 绑定);如果是嵌套的(depth-2),就内嵌注入到父编排器的下一轮。 -
4. 子 Agent 默认不拿 session 工具—— sessions_list、sessions_history、sessions_send、sessions_spawn、message全被剥掉。要让子 Agent 能管理自己的孙子(orchestrator 模式),得把maxSpawnDepth调到 2。 -
5. 上下文只带 AGENTS.md+TOOLS.md,不带SOUL.md、IDENTITY.md、USER.md、MEMORY.md、HEARTBEAT.md、BOOTSTRAP.md。子 Agent 不是”克隆的你”,它是借了你的工具箱,按任务简报现拼的小工。 -
6. 默认 context: "isolated"——子任务不继承主会话历史。fork才会把当前 transcript 复刻一份过去(线程绑定的子任务默认 fork)。少用 fork,它会让 token 暴涨。 -
7. 可以 sandbox: "require"强制沙箱——主会话被沙箱化时,这个 flag 会拒绝跑非沙箱子任务。 -
8. 跨 Agent spawn 受 allowlist 控制—— agents.defaults.subagents.allowAgents,默认只能 spawn 自己。要 spawn 别的 Agent,得在 allowlist 里点名。
怎么 spawn 一个任务
最小调用:
sessions_spawn( task: "把 inbox 里今天收到的 12 封邮件分类成 [紧急/一般/垃圾],并给每封写一行摘要", taskName: "inbox_triage_2025-07-03")
立刻拿回:
{ "status": "accepted", "runId": "...", "childSessionKey": "agent:main:subagent:abc-123" }
然后接一个 sessions_yield——
sessions_yield()
当前轮结束,等子任务跑完。announce 事件以下一条 model-visible 消息的形式回到主会话。不要写 while 循环去 subagents list 轮询状态,OpenClaw 官方文档明确说”Completion is push-based. Once spawned, do not poll … in a loop just to wait for it to finish”。
完整流程图(纯文本)
主 Agent 视角,一次典型的并行编排:
[用户消息] │ ▼[主 Agent turn 启动] │ ├─► sessions_spawn(task="A") ─► child_session_A(并行跑) ├─► sessions_spawn(task="B") ─► child_session_B(并行跑) ├─► sessions_spawn(task="C") ─► child_session_C(并行跑) │ ▼sessions_yield() ← 把控制权交还给 runtime │ ▼[runtime 等子任务陆续完成] │ ▼[announce 事件作为下一条 model-visible 消息回来] │ ▼[主 Agent 综合 A/B/C 的结果,写最终回复] │ ▼[交付给用户]
注意两点:
-
• A/B/C 默认并行——它们跑在 OpenClaw 的 subagent队列里,默认maxConcurrent: 8。就算你连发 5 个 spawn,也几乎是同时起步。 -
• 主 Agent 在 yield之后什么都不能做,必须等 announce。这是 push 模型的代价,也是它不浪费 token 的原因。
场景一:多文章并行生产
这是上一版文章里写过的高频场景,这一版讲具体怎么 spawn。
需求:周一早 9 点前,3 篇文章并行起——一篇行业研究、一篇产品评测、一篇技术解读。
错的写法(常见踩坑):
sessions_send(sessionKey: "writer_main", message: "写一篇新能源车 2026 行业研究")# 然后死等# 然后再 send 第二个# 然后死等# ...
这是把 spawn 当 send 用。writer_main 这个 session 串行处理 3 条消息,比主会话自己写还慢,而且 maxPingPongTurns=5 一不留神就把对话车成回合制扯皮。
对的写法:
sessions_spawn(task: "行业研究:2026 新能源车 Q2 趋势,数据来源 IEA/乘联会/中信证券研报,输出 1500 字带图表说明", taskName: "article_industry")sessions_spawn(task: "产品评测:小米 SU7 Ultra vs 极氪 001 FR 真实车主视角,800 字", taskName: "article_review")sessions_spawn(task: "技术解读:800V 高压平台为什么是 2026 关键拐点,1200 字", taskName: "article_tech")sessions_yield()
3 个子任务并行起步,跑完各自回 announce。主 Agent 在下一轮拿到三份初稿,做一次合并润色、定标题、统一术语,然后一条消息交付给用户。
关键参数选型:
-
• context: "isolated"(默认就够):3 篇文章彼此不相关,不需要共享上下文。 -
• model:每个子任务挂便宜模型——agents.defaults.subagents.model = "anthropic/claude-haiku-4-5",主 Agent 留 Opus 给综合润色。这是 token 成本最容易被忽视的地方:每个子任务有自己的 context 和计费,默认会继承主 Agent 的贵模型。 -
• taskName:起可读的 stable handle,这样/subagents info industry这种命令能直接定位。 -
• cleanup: "delete":跑完立刻归档,免得 60 分钟后再来一次清理(归档依然保留 transcript 文件,只是 session 标记成 deleted)。
场景二:Inbox Triage 流水线
workspace 里已经有
taskflow-inbox-triageskill,这套流程天然适合纵向多 Agent。
需求:每天早上 9 点,主 Agent 自动把昨晚到今早的邮件/工单/通知分类、起草回复、识别需要人介入的紧急项,最后只把人必须看的推给你。
架构:
c┌─ cron(7d tz Asia/Shanghai, --announce) │ ▼[主 Agent:orchestrator] │ ├─► sessions_spawn: classifier (把 47 封邮件按 [urgent/followup/info/spam] 分类) ├─► sessions_spawn: drafter_for_urgent (对 urgent 类起草回复) ├─► sessions_spawn: drafter_for_followup(对 followup 类起草回复) │ └─► sessions_yield │ ▼ [主 Agent 拿到三份结果,只把"必须人看"的卡片回推到频道]
为什么不直接让主 Agent 自己干?
因为:
-
1. 上下文会被脏数据撑爆。47 封邮件的原文(尤其是工单和长线程)能把主 Agent 的 context 推到 100k+ token,后面任何后续对话都会变慢、变贵。 -
2. 时间被串行拖垮。主 Agent 自己写 prompt 一封一封读 + 起草,10 分钟以上;3 个子 Agent 并行 2 分钟搞定。 -
3. 隔离天然解决权限问题。 drafter_for_urgent可以挂一个只读邮箱权限的子 Agent(sandbox: "require"+ 限制工具),它就算被 prompt injection 攻陷,也发不出去。
这里 spawn vs send 的判断点:
-
• “分类”是新任务 → spawn。 -
• “对已存在的会话(比如 family 群那个长期 Agent)塞一条消息” → send。 -
• “叫醒 ops agent 帮我查一下部署状态” → send,不是 spawn,别再起一个新会话了。
场景三(简短):Code Review 并行
4 个 PR 排队等 review。串行一个一个让主 Agent 看,要 8 分钟;4 个 sessions_spawn 一起发,主 Agent 自己 yield,2 分钟左右拿到 4 份 review,最后综合成”哪些要 blocker,哪些可以合”的总结。
这里的关键 trade-off:
-
• 简单 PR:直接让主 Agent 顺手看,不 spawn。起一个子会话的开销(模型加载、context 初始化、工具预热)对短任务不划算。 -
• 复杂 PR(>500 行 diff、跨服务、要查 issue tracker):spawn,而且可以考虑 context: "fork"把当前开发上下文带过去。
三个最容易踩的坑
坑 1:写了 poll 循环等结果
# 这是反模式sessions_spawn(task: "...")while True: result = subagents(action="list") if result[0].status == "completed": break exec(command: "sleep 5")
文档原话:”Do not poll /subagents list, sessions_list, or sessions_history in a loop just to wait for it to finish“。OpenClaw 走 push 模型,等结果请用 sessions_yield——它会主动结束当前 turn,让 announce 当成下一条消息进来。
坑 2:把 spawn 当成”更高级的 send”
send 的核心用途是把消息注入到已有会话。它最适合:
-
• 唤醒/催一个长期 Agent(比如 cron 跑的 ops agent,你临时发”今天的部署怎么样”)。 -
• 跨 Agent 对账(主 Agent 跟 ops Agent 来回两轮确认 SLA)。 -
• A2A 工作流(配置 tools.agentToAgent.allow后才能跨 Agent 互发)。
不要用 send 干 spawn 的活——没有新会话、没有任务简报、子任务”借壳”在别人的上下文里跑,既污染对方历史又大概率把对方的车跑偏。
坑 3:子 Agent 期待”它像我一样”
子 Agent 不是 你的副本。它:
-
• 没有 SOUL.md的人格。 -
• 没有 MEMORY.md的长期记忆。 -
• 没有 message工具,不能直接给用户发消息。 -
• 默认没有 session 工具,管不了别的会话。 -
• context 默认是干净的,不知道你之前跟用户聊过啥。
所以 spawn 时把 task 写成”自包含的简报”——给足背景、给足输入、给足交付物标准。不要写”接着我们刚才的讨论继续”,除非你显式传 context: "fork" 而且你清楚 token 代价。
决策清单:什么场景用哪个
拿到一个任务,按这个顺序过一遍:
Q1: 这活能并行拆吗?每个子任务有干净的输入和交付物吗? ├─ 否 → 主会话自己干 └─ 是 ↓Q2: 拆出去之后会污染主上下文(超长/重资源/慢工具)吗? ├─ 否(短小)→ 主会话自己干 └─ 是 ↓Q3: 任务能写成"自包含简报",子 Agent 不需要知道之前聊了啥? ├─ 否 → 写不出来就别 spawn,改主会话干或者先整理输入 └─ 是 ↓Q4: 需要保留子任务的 transcript 以便后面回溯? ├─ 否 → spawn + cleanup:"delete" 跑完即归档 └─ 是 → spawn + cleanup:"keep" 默认 60 分钟后归档→ ✅ 选 sessions_spawn,接 sessions_yield。
用 sessions_send 的场景:
-
• ✅ 跟一个长期存在的 Agent(比如绑了 Slack 的 ops agent)互动——发”查一下今天 P95 延迟”、催”这个 ticket 跟到底了没”。 -
• ✅ 跨 Agent 协调(配了 tools.agentToAgent.allow)——主 Agent 跟家庭 Agent 对账本周购物清单。 -
• ✅ 想复用对方已有的上下文和工具配置,不想开新会话。
用 openclaw agent CLI 的场景:
-
• ✅ 外部脚本触发(CI、shell hook、定时任务)。 -
• ✅ 推消息到频道(配 --deliver --reply-channel telegram --reply-to @admin)。 -
• ✅ 不是会话内编排工具——别在 Agent 自己 turn 里调它。
自己干就好(别 spawn 也没必要 send)的情况:
-
• 单步、几秒钟能跑完的小任务(读个文件、查个天气、做个简单计算)。 -
• 强上下文依赖——任务描述里有 80% 是”我们刚才说的那个”。 -
• 一次性的 prompt engineering 调试,你在尝试 prompt 而不是真生产。
一句话判断标准
spawn 的真正价值是隔离和并发,不是”更聪明的 AI”。
如果你的痛点是”这活太慢”、”上下文要炸了”、”我想并行跑”——上 spawn。如果你的痛点是”我想要更准的答案”、”我想复用历史”——那不是 spawn 的活,换模型、换 prompt、加 skill。
下次有人跟你说”我想要个 agent team”,先问一句:“你到底是要拆活,还是想要个更聪明的?” 这俩解决路径完全不一样。
附:相关配置速查
agents.defaults.subagents.* 主要旋钮(都是 JSON5):
{ agents: { defaults: { subagents: { // 整个 gateway 范围内子任务最多并发多少 maxConcurrent: 8, // 每个 session 最多带几个活跃子任务 maxChildrenPerAgent: 5, // 嵌套层数:1=不能 spawn 孙子,2=可以(orchestrator 模式推荐) maxSpawnDepth: 2, // 子任务默认跑哪个模型(强烈建议挂便宜模型) model: "anthropic/claude-haiku-4-5", // 子任务跑多久算超时(0=永不超时) runTimeoutSeconds: 900, // announce 失败重试窗口 announceTimeoutMs: 120000, // "suggest"=温和提示主 Agent 用 spawn;"prefer"=主 Agent 倾向把活派出去 delegationMode: "suggest", // 多少分钟后自动归档子会话 archiveAfterMinutes: 60, }, }, }, // Agent 间互发消息(sessions_send 跨 Agent 方向)必须显式打开 tools: { agentToAgent: { enabled: false, allow: ["main", "ops"] }, },}
sessions_send 跨 Agent 行为由 tools.agentToAgent 控制,默认关——这是个安全闸,别随手打开。子任务的 message 工具在所有默认配置下都被剥掉,这是设计如此,不是 bug。
官方文档锚点:/docs/tools/subagents、/docs/concepts/session-tool、/docs/automation/tasks。
夜雨聆风