乐于分享
好东西不私藏

OpenClaw Agent Teams 实战

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 agent CLI 是给脚本/外部触发的,不是会话内编排工具
  • • 一个直接判断:多数情况下你根本不需要 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
起一个新子会话,丢一个任务进去,跑完通过 announce 推回
立刻返回 runId,非阻塞
并行可拆、互不依赖、跑得慢的活
sessions_send
已有会话塞一条消息,可选等回复(最多 5 轮 ping-pong)
可选阻塞(timeoutSeconds)
唤醒/协调一个长期存在的 Agent
sessions_yield 结束当前轮次

,等子任务完成事件当成下一条消息进来
把控制权交还 runtime
spawn 一票子任务之后必须接它
openclaw agent

(CLI)
外部脚本/CLI 触发一次 agent turn,可配 --deliver 推到频道
取决于你是否 --deliver
调度、CI、cron、bot 命令

剩下 3 个是辅助:sessions_listsessions_historysession_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. 1. 永远非阻塞:sessions_spawn 调下去,马上拿到 { status: "accepted", runId, childSessionKey }不能传 timeout 进去改这个行为——runTimeoutSeconds 是配置级的默认值,不是 per-call 参数。
  2. 2. 不接受通道参数:你不能写 channel: "telegram" 或 to: "@foo" 让子任务自己发消息。子 Agent 没有 message 工具,它只回文本给父 Agent,人类可见的回复永远由父/requester Agent 的正常回复策略决定
  3. 3. 结果通过 announce 推回,不是返回值。子任务跑完,OpenClaw 走一个 announce 流程:如果是顶层子任务,就调一次 agent turn 把结果送到原始频道(保留 thread/topic 绑定);如果是嵌套的(depth-2),就内嵌注入到父编排器的下一轮。
  4. 4. 子 Agent 默认不拿 session 工具——sessions_listsessions_historysessions_sendsessions_spawnmessage 全被剥掉。要让子 Agent 能管理自己的孙子(orchestrator 模式),得把 maxSpawnDepth 调到 2。
  5. 5. 上下文只带 AGENTS.md + TOOLS.md,带 SOUL.mdIDENTITY.mdUSER.mdMEMORY.mdHEARTBEAT.mdBOOTSTRAP.md。子 Agent 不是”克隆的你”,它是借了你的工具箱,按任务简报现拼的小工
  6. 6. 默认 context: "isolated"——子任务不继承主会话历史。fork 才会把当前 transcript 复刻一份过去(线程绑定的子任务默认 fork)。少用 fork,它会让 token 暴涨。
  7. 7. 可以 sandbox: "require" 强制沙箱——主会话被沙箱化时,这个 flag 会拒绝跑非沙箱子任务。
  8. 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-triage skill,这套流程天然适合纵向多 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. 1. 上下文会被脏数据撑爆。47 封邮件的原文(尤其是工单和长线程)能把主 Agent 的 context 推到 100k+ token,后面任何后续对话都会变慢、变贵。
  2. 2. 时间被串行拖垮。主 Agent 自己写 prompt 一封一封读 + 起草,10 分钟以上;3 个子 Agent 并行 2 分钟搞定。
  3. 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 listsessions_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

GEO扩客系统、智能体、电商独立站开发、维护、升级,找我 13411897601