Grok Bot 负责编排拆解、子 Bot 负责执行并把结果交回同一处

文章导读
任务跑完之后结果散在多个 Bot 的输出里,通常不是子 Bot 执行得不好,而是编排层没把「回传位置」当成任务契约的一部分。编排方只负责拆解、派发和决定重派,子 Bot 只负责执行并向同一个约定路径写入结果,汇总脚本只读这个路径。三段职责分清楚之后,追问「哪一步的结果最终被采用」才有可查的痕迹,而不是回去翻每个 Bot 的对话记录。
📋 目录
  1. 列出编排方需要决定的三件事
  2. 约定子 Bot 回传的固定位置和格式
  3. 写一段汇总骨架把子结果拼成一份产出
  4. 验证漏收与重复收两种情况
  5. 把回传约定固化为任务模板字段
A A

任务跑完之后结果散在多个 Bot 的输出里,通常不是子 Bot 执行得不好,而是编排层没把「回传位置」当成任务契约的一部分。编排方只负责拆解、派发和决定重派,子 Bot 只负责执行并向同一个约定路径写入结果,汇总脚本只读这个路径。三段职责分清楚之后,追问「哪一步的结果最终被采用」才有可查的痕迹,而不是回去翻每个 Bot 的对话记录。

把编排与执行分开的关键,不是多开几个 Bot,而是让每个子任务的结果都落到同一个约定位置。适合子任务可并行、需要留痕和事后追溯的场景:编排方先定拆解粒度、派发顺序和失败重派策略,子 Bot 只按固定字段回传。验证方式是故意让一个子 Bot 不回传、另一个回传两次,看汇总里是否出现缺项标记和重复告警。边界在于回传约定一旦变更,历史任务与新汇总脚本需要同步调整,否则旧记录会被判为缺项。

列出编排方需要决定的三件事

编排方的最小职责集只有三项,但它决定了后面所有汇总逻辑能否成立。把这三件事写进任务记录,出问题时才能分辨是拆解错了、派发错了,还是重派覆盖了有效结果。

  • 拆解粒度:一个子任务应该对应一段可独立验收的产出,而不是一句模糊指令。粒度太粗,回传结果里混着多个结论,汇总时无法按顺序拼接;粒度太细,回传条目膨胀,缺项判断会淹没在噪声里。痕迹:任务记录里应能看到子任务列表和每个子任务的输入摘要。
  • 派发顺序:哪些子任务有依赖、必须串行,哪些可以并行下发。顺序决定了汇总时的拼接次序,也决定了某个子任务失败时是否要连带阻塞后继任务。痕迹:任务记录里应有派发时间戳和 parent / depends_on 字段。
  • 失败重派策略:重派上限、重派时是否复用原子任务 ID、重派结果如何标记。这一项最容易出问题——如果重派沿用同一 ID,回传就会出现同 ID 多份结果,必须约定「最新一次成功结果为准」。痕迹:任务记录里应有 attempt 计数和每次尝试的状态。

三段职责可以对照下面这张表核对,重点看「产物落在哪」和「失败时表现」两列:

角色负责的决策产物落地位置失败时的表现
编排方拆解粒度、派发顺序、重派策略任务记录(子任务清单、attempt、状态)缺少子任务条目或状态长期停留在派发中
子 Bot单项执行与结果回传约定回传路径下的结果文件路径下无文件,或文件存在但字段不全
汇总方按序拼接、缺项与重复标记汇总产出 + 汇总日志产出缺段但日志无提示,属于静默错误

约定子 Bot 回传的固定位置和格式

回传位置应该是编排方在派发时就写死并随任务下发的,不能由子 Bot 自己挑。一个可用的通用约定是:以任务 ID 为目录,以子任务 ID 为文件名,路径中不带任何执行者私有信息。

Grok Bot 负责编排拆解、子 Bot 负责执行并把结果交回同一处
# 路径约定(可替换成你自己的存储根目录)
# runs/{task_id}/{subtask_id}.json
# 同一子任务重派时覆盖同一文件,attempt 记录在文件内

{
  "task_id": "task-0001",
  "subtask_id": "s03",
  "attempt": 2,
  "status": "ok",
  "order": 3,
  "produced_at": "2026-01-01T10:00:00Z",
  "summary": "一句话结论",
  "payload": {},
  "error": null
}

字段约定里,order 用于拼接顺序,attempt 用于区分同一子任务的多次回传,status 用于区分成功结果与带错误的记录。命名规则建议只用小写字母、数字和连字符,避免空格与大小写混用导致路径对不上。当同一任务多次回传时,以「status 为 ok 且 attempt 最大」的那份为准,并在汇总日志里记下被覆盖的 attempt,这样事后能说清哪一次结果最终被采用。

写一段汇总骨架把子结果拼成一份产出

汇总逻辑不复杂,关键是不要让缺项悄悄消失。下面这段骨架按子任务顺序遍历,缺项显式写出占位标记,而不是直接跳过。

Grok Bot 负责编排拆解、子 Bot 负责执行并把结果交回同一处
import json, os

RUN_ROOT = os.environ.get("RUN_ROOT", "./runs")

def load_results(task_id, expected_ids):
    base = os.path.join(RUN_ROOT, task_id)
    results, missing, dup = {}, [], []
    for sid in expected_ids:
        path = os.path.join(base, sid + ".json")
        if not os.path.exists(path):
            missing.append(sid)
            continue
        with open(path, encoding="utf-8") as f:
            rec = json.load(f)
        prev = results.get(sid)
        if prev is not None:
            dup.append(sid)
        if prev is None or (rec.get("attempt", 0) >= prev.get("attempt", 0) and rec.get("status") == "ok"):
            results[sid] = rec
    return results, missing, dup

def assemble(results, expected_ids):
    parts = []
    for sid in expected_ids:
        rec = results.get(sid)
        if rec is None or rec.get("status") != "ok":
            parts.append("[MISSING:%s]" % sid)
        else:
            parts.append("## %s\n%s" % (sid, rec.get("summary", "")))
    return "\n\n".join(parts)

拼接完成后建议做四项一致性检查:条目数是否等于子任务清单长度;顺序是否与派发 order 一致;每条记录里 task_id / subtask_id 是否与所在路径匹配(防串任务);payload 是否为空或被截断。任何一项不通过都应在日志里写成一条可见的告警,而不是只在返回值里体现。

验证漏收与重复收两种情况

汇总逻辑只有在异常输入下仍然说话,才算可信。最省事的验证是构造两个异常子任务,跑一轮再对日志。

  1. 漏收:在派发清单中保留某个子任务,但人为让它不写回传文件(例如执行体提前退出)。预期结果是汇总产出中该段落出现 [MISSING:sXX],日志里有对应的缺项条目,整体状态不应显示为成功。
  2. 重复收:让同一个子任务写两次回传,第二次 attempt 更大且 status 为 ok。预期结果是最终产出一份该段结果,日志里同时记录被覆盖的 attempt 编号,且不出现两段重复内容。
  3. 顺序校验:把某次回传的 order 字段改乱,观察汇总是否仍按清单顺序拼接。若拼接结果随文件系统遍历顺序变化,说明顺序依赖了不确定因素,需要显式按 order 排序。

这三步不需要真实故障,构造输入即可。需要结合环境确认的是文件写入是否原子——如果子 Bot 边写边被读取,可能读到半截 JSON,建议先写临时文件再重命名。

Grok Bot 负责编排拆解、子 Bot 负责执行并把结果交回同一处

把回传约定固化为任务模板字段

每次任务都口头约定回传位置,迟早会漂移。把约定收进任务模板字段,下次派发直接复用。

template:
  return_path: runs/{task_id}/{subtask_id}.json   # 必填
  required_fields: [subtask_id, attempt, status, order, summary]
  order_source: task_record                       # 默认取自拆解顺序
  max_attempts: 2                                 # 默认 2
  on_missing: mark_and_report                     # 默认标记缺项并告警
  overwrite_policy: latest_ok_wins                # 默认最新成功结果为准
  allow_empty_payload: false                      # 默认不允许 payload 为空

默认值的作用是让模板在大多数场景下不必逐项填写;但 return_path 和 required_fields 建议设为必填。字段为空时是否拒绝启动,保守做法是:路径模板、必填字段清单、子任务清单三者任一为空即拒绝启动,其余字段留空则回落默认值。这样至少能保证任务一旦跑起来,回传位置是确定且可汇总的。