ThinkBot Workflow:不是 babysit Codex,而是可恢复的 DAG 任务引擎

在 Agent 产品里改仓库、做多步交付,最常见的失败模式不是「模型不够聪明」,而是编排方式错了:主会话里让模型自己 sleep、轮询状态、半途崩溃后从零再讲一遍需求,或者盯着一个外部 Codex/CLI 进程当保姆。429 撞墙、会话超时、上下文被工具结果挤爆——这些都会把「能做完」变成「看起来在做」。

ThinkBot 的 Workflow 想解决的是另一件事:把「改仓库 / 多步交付 / 直到验收达标」从对话轮询里抽出来,变成可持久化、可恢复、有质量闸门的无人职守编排。它挂在主 Agent 上,是一等公民,不是旁路脚本。

本文基于当前 workflow/ 包实现,介绍它是什么、一次任务怎么跑完,以及几条写进代码里的设计原则。


定位:主 Agent 上的 DAG,不是散装 worker

先把边界说清楚:

模式 大致样子 痛点
Babysit Codex / CLI 人(或主会话)盯一个外部 agent 崩溃、超时、状态在进程外;主会话只能轮询
独立 worker 群 多个彼此独立的进程/机器人 协作靠消息,难共享工作区与验收闭环
ThinkBot Workflow 进程内显式 DAG + 持久化状态机;节点是共享 bot 沙箱的 SubAgent 编排在引擎里;跑完可回流主会话

Workflow 不是 heartbeat / cron 本身——后两者走同一套聊天 pipeline,只是触发不同;Workflow 是其中的重型 DAG 工具:模型调用 task 提交需求后,由引擎拆图、调度、执行、评审、恢复。

一句话:引擎拥有编排,对话负责提需求和收结果,而不是让对话去 babysit。


一次 Workflow 怎么跑起来

对外工具面很薄,只有三个:

  • task:提交需求;服务端阻塞等到终态(默认上限约 18 分钟,需小于聊天后台落库窗口),期间向前端推带 workflowId 的进度。
  • task_detail:按 flat / tree 查看节点与状态。
  • task_control:retry / terminate(分析阶段禁止终止;终态 retry 会从节点重启)。

生命周期可以按八步理解:

  1. 入口
    Web 聊天(或同等 pipeline)里模型调用 task(requirement, goalMode?, maxParallel?)。对某些「收敛性」文案,聊天侧还会注入 GoalMode 指令,强制走验收闭环。

  2. Submit
    Manager.Submit 立刻落库 status=analyzing,返回 workflowId,后台跑 analyzeAndRun;工具侧阻塞等待终态,避免模型空转去查状态。

  3. Analyze
    Analyzer SubAgent(可带工作区工具探环境)把需求收成 DAG JSON;Compile 做无环 / 拓扑校验,失败则整单 failed。

  4. Schedule
    status=running;按拓扑就绪节点用信号量并行(默认并行度 3);上游产物写入下游 prompt;节点工具按 ToolProfile 白名单收敛。

  5. Execute
    每个节点 DelegateStream 拉起独立 SubAgent;带 stuck 看门狗(节点卡住约 12 分钟量级,硬上限可到约 120 分钟);写路径记入 WrittenOps;错误按分类重试或熔断。

  6. Review(可选)
    独立 Review SubAgent 给 verdict;不通过则节点内迭代。GoalMode 下还可以通过 Feedback 边回退上游——Feedback 不进 Dependencies,逻辑图仍保持无环。

  7. 横切守护

    • 配额耗尽 → 节点回 pending,工作流 interrupted,记下 QuotaResumeAt,QuotaWatch 到点续跑;
    • 进程崩溃 → 启动 Recover;
    • 卡死 → Sweeper;
    • 硬超时失败 → Heal 可尝试粒度细化、换子图。
  8. 终态与回流
    completed / failed / terminated。若阻塞的 task 已因 18 分钟上限超时返回,后台跑完后会通过 onWorkflowCompleted 向原会话注入系统续跑消息;GoalMode 下会提示「只总结、勿重跑」,避免再撞 LLM 墙钟。

角色上并不是一堆互不相干的 Go「节点类型枚举」,而是一条流水线:Analyzer → Scheduler → SubAgent Executor → Review →(Heal)→ Recover / Sweeper / QuotaWatch。


几条写进代码里的原则

1. 编排在引擎,不在对话轮次

拓扑、并行、重试、Review、级联 skip、GoalMode 闭环在 scheduler / manager 里完成。Prompt 侧明确禁止用 task_status / sleep 空转等待——提交即阻塞,状态机才是真相来源。

2. 反套娃

task / task_detail / task_control(以及 spawn、记忆类工具)带 private|group 作用域,SubAgent 不可见。引擎使用独立的 SubAgentManager,避免「工作流节点里再开工作流」把控制面炸穿。

3. 共享沙箱 + 工具级最小权限

节点共享同一个 per-bot Docker 工作区,以及同容器里的浏览器 MCP——这是刻意选择:多步改同一仓库时,隔离过度等于无法协作。权限不靠「每个 job 一台假机」,而靠 ToolProfile(readonly / analysis / edit / full) 收敛节点能用的工具。默认档位偏 full 时 blast radius 仍然不小,这是产品上要诚实面对的点。

4. 可恢复的无人职守

Recover(崩溃续跑)、Sweeper(卡死清扫)、配额三级熔断(HTTP / 节点 / 工作流)+ 心跳防误杀,目标是:人可以离开,进程可以挂,状态不能丢。

5. Status ≠ Outcome

调度状态(pending / running / …)与业务结果类别(ok / noop / partial / missing_tool / missing_data)正交。缺工具时不应空烧重试——没有能力就该暴露 Outcome,而不是假装再跑一遍会变出浏览器。

6. 写冲突:先可见,不先阻断

并行默认打开时,共享工作区没有文件锁;引擎检测 WriteConflicts 并暴露出来,而不是为了绝对安全把并行砍光。取舍很明确:先要吞吐与真实冲突信号,再谈更强隔离。

7. 不可信反馈要结构化隔离

Review 意见会进到后续仍可能带 sandbox_exec 的节点。实现上用随机定界符 + 幂等清洗,降低「评审文本污染执行上下文」的风险。

8. 跑完要回流主会话

Workflow 不是把活丢进黑洞。终态快照回到阻塞中的 task;若等待方已超时离场,则向原会话注入 continuation,让主 Agent 继续总结或收尾——这和「独立 worker 群各玩各的」不是一条路。


和 heartbeat、cron、普通聊天的关系

三者共享同一套 LLM + Tool pipeline:

  • 普通聊天:即时回合,轻工具;
  • Heartbeat:定时叫醒 bot,默认偏克制外发;
  • Cron:自包含提示词,投递到频道;
  • Workflow:重型 DAG;可以在上述入口里被 task 调起,但子系统是状态机,不是定时器。

可以把它想成:聊天是驾驶座,Workflow 是后车厢里的施工队——你下单,施工队按图纸干,干完把验收单递回驾驶座。


诚实局限(写进代码与事故里的)

  • 共享工作区无文件锁;冲突只检测不阻断;默认并行 + full 档位时 blast radius 大。
  • Analyzer 系统提示里仍残留过时表述(「SubAgent 无工具」),与当前「Analyzer 已继承工具」不完全一致——规划模型可能被旧约束误导。
  • task 阻塞约 18 分钟:更长任务依赖超时返回 + 后台跑 + 会话续跑;续跑依赖 BotID / SessionID。GoalMode 续跑若再大动手脚,容易再次撞墙钟。
  • 非 full 档位白名单以沙箱工具名为主,可能滤掉 browser__* 等 MCP;full 则浏览器与主 Agent 同容器共享。
  • 没有 gh-aw 那套「agent 只读 + safe-outputs 推写权限」;自愈对 capability 不会自动扩权。
  • 前端面板目前偏 REST 轮询,不是 SSE——只发事件用户看不见。
  • 历史上出现过「无 bot 时 ToolMgr 为空、纯计划却标 completed」的恢复事故;现已优先复用 bot 引擎,无 bot 路径仍需警惕。

这些不是「以后再说」的脚注,而是用 Workflow 时要带着的操作预期。


小结

ThinkBot Workflow 想做的不是让对话更会催进度,而是:

  1. 用 DAG + 持久化状态机 接管多步交付;
  2. 用 Review / GoalMode / Outcome 管质量,而不是只看「跑没跑完」;
  3. 用 Recover / 配额续跑 / 会话回流 让人真的可以离开;
  4. 在 共享沙箱 上用工具档位换协作效率,并诚实暴露写冲突与能力缺口。

如果你已经在用 ThinkBot:复杂改仓库、要验收、可能跑很久——优先走 task,别在主会话里 sleep 轮询。若你在设计类似系统:先问「状态机在谁手里」,再问「模型聪不聪明」。

(实现参考:workflow/manager.go、scheduler.go、tools.go、status_wait.go、wire.go 等;细节以仓库当前代码为准。)