项目地址:https://github.com/openAgentsLab/claude-managed-agents

Dynamic Workflow 技术方案


Claude Code 官方发布:Dynamic Workflow

2025 年底,Anthropic 在 Claude Code 中正式发布了 Dynamic Workflow。这是 Claude Code 从"交互式编码助手"向"自主后台任务引擎"演进的关键一步。

发布背景

Claude Code 在 2024 年推出以来,核心使用方式是用户与 Claude 在 Session 里来回对话,Claude 可以读写文件、执行命令、调用工具。随后引入了 sub-agent 能力,允许 Claude 在单次 Session 中并发派发多个子任务。这套机制工作良好,但随着用户开始用 Claude Code 处理更大规模的工程任务,一个根本性的瓶颈暴露出来:

所有任务的推理、派发、结果汇总都发生在同一个 LLM 上下文里。 任务越多,上下文膨胀越严重;用户必须保持 Session 在线;没有任何中间状态持久化,进程一旦中断,一切从头开始。

为什么要发布这个

Anthropic 发布 Dynamic Workflow,本质上是在回答一个问题:当一项工程任务需要几十到几百个 Agent 协同完成、跨越数小时甚至数天、中间需要依据前一阶段的结果动态调整方向,Claude Code 该怎么处理?

答案不是让 Claude 更聪明地管理更大的上下文,而是换一种执行模型

  • Claude 不再作为实时编排者,只负责一次性生成编排脚本
  • 脚本由独立的 Runtime 引擎在后台执行,与 Session 生命周期解绑
  • 中间结果持久化到存储,进程重启可以从上次中断的地方续跑
  • 用户触发任务后可以直接离开,通过独立进度页面随时查看状态

这个设计在 Claude Code 官方文档里有一句话概括得很准确:

"No direct filesystem or shell access from the workflow itself — Agents read, write, and run commands. The script coordinates the agents."

脚本只负责编排,Agent 负责执行,两者职责分离。

与本项目的关系

本文档描述的是在我们自己的平台上实现 Dynamic Workflow 的技术方案。我们在复用现有 Brain、Worker、EventBus 等基础设施的前提下,参照 Claude Code 官方的设计思路,结合平台多租户架构,实现了支持持久化续跑、细粒度权限控制、以及完整前端进度视图的 Workflow 系统。


目录


为什么需要 Dynamic Workflow

现有的 dispatch_agent_task 机制已经支持多智能体并发执行,但它有一个根本性的规模瓶颈:所有子任务的结果都通过 tool_result 回流进 Coordinator 的 LLM 上下文

这意味着:

  • 子任务越多,上下文膨胀越严重,推理质量下降,成本急剧上升
  • Coordinator 必须全程在线等待所有结果,Session 不能提前结束
  • 不支持多阶段流水线——无法在第一批任务全部完成后,再根据结果动态决定第二批
  • 用户必须盯着屏幕等,无法离开

Dynamic Workflow 解决的是同一类编排问题,但在完全不同的量级上:几十到几百个 Agent、需要多阶段依赖、用户可以离线等待。


核心设计思路

把"Claude 逐步决定下一步"变成"Claude 先写出编排脚本,Runtime 来执行这个脚本"。

三个关键转变:

1. 计划与执行分离

Claude 不再是编排者,只是脚本生成者。它根据任务描述,一次性生成一段 JavaScript 编排脚本,描述清楚有哪些阶段、每个阶段派发哪些 Agent、如何根据前一阶段的结果动态决定下一步。脚本生成后,由 Runtime(Goja 引擎)负责执行。

2. 中间结果不进 LLM 上下文

每个 Agent 的结果直接存入脚本运行时变量(并持久化到数据库),不回流给 Claude。Claude 最终只看到整个 Workflow 的汇总结果。

3. 异步后台执行,Session 立即释放

触发 Workflow 后,任务在后台独立运行,与触发它的 Session 生命周期完全解绑。用户不需要等在屏幕前,通过独立的进度页面随时查看状态。


与现有多智能体方案的关系

两者互补,不竞争,服务不同的场景。

dispatch_agent_taskDynamic Workflow
决策者Claude 实时推理脚本按逻辑执行
中间结果回流进 LLM 上下文存运行时变量 + DB
Session 依赖同步阻塞 Session异步后台,Session 立即结束
适合规模20 个以内子任务最多 1000 个 Agent
续跑支持不支持支持,进程重启不丢失
适合场景探索性、需要看中间结果再决策预定义流程、规模大、可离线等待
触发方式Claude 在推理中自主调用Claude 调用 trigger_workflow 工具

两者可以嵌套组合:Workflow 的某个 Agent 内部,依然可以使用 dispatch_agent_task 派发更细粒度的子任务,形成两层编排结构。


整体架构

系统分三层角色,职责清晰:

┌─────────────────────────────────────────────────────┐
│                    Claude(脚本生成者)                │
│  根据任务描述,一次性生成 JS 编排脚本,之后不再参与     │
└───────────────────────┬─────────────────────────────┘
                        │ JS 脚本
                        ▼
┌─────────────────────────────────────────────────────┐
│              Runtime(脚本执行者)                    │
│  Goja 引擎嵌入 Go 进程,解释执行脚本                  │
│  调用 runAgent() / runAgents() 派发任务               │
│  中间结果存运行时变量 + 持久化 DB                      │
└───────────────────────┬─────────────────────────────┘
                        │ AcquireBrain + RunStateless
                        ▼
┌─────────────────────────────────────────────────────┐
│              Workers(任务执行者)                    │
│  复用现有 harness.RunStateless,完整工具权限           │
│  读文件、写文件、执行命令……                            │
└─────────────────────────────────────────────────────┘

触发与审批流程

用户在 Session 里描述任务
         │
         ▼
Coordinator Brain 推理
判断任务规模超出 dispatch_agent_task 适用范围
调用 trigger_workflow 工具
         │
         ├─ Step 1:调用模型单次补全(2~5 秒)
         │          生成 JS 编排脚本
         │          system prompt 约束脚本结构和可用 API
         │          user message 是任务描述 + 可用 Agent 列表
         │
         ├─ Step 2:写库,创建 WorkflowRun
         │          状态:pending_approval
         │
         └─ Step 3:在 Session 消息流里返回"审批卡片"
                    展示:Run 名称、Phase 概要、完整脚本预览
                    ┌──────────────────────┐
                    │  [运行]     [取消]   │  ← 用户操作
                    └──────────────────────┘
                              │
                    用户点击"运行"
                              │
                              ▼
                    POST /v1/workflow-runs/:id/approve
                              │
                              ▼
                    后台 goroutine 启动
                    WorkflowRuntime 开始执行脚本

审批卡片上提供"查看完整脚本"入口,用户确认脚本内容后才真正启动,与 Claude Code 官方的人机审批流程对齐。


Workflow 脚本

脚本能力边界

脚本是一段 JavaScript,由 Claude 生成,在受限沙箱中执行:

能做不能做
调用 runAgent() 派发单个 Agent读写文件系统
调用 runAgents() 批量并发派发执行 shell 命令
用 JS 的 forwhileif 处理结果发起 HTTP 请求
在脚本变量里暂存中间结果访问任何 Node.js 模块
调用 getResult() 读取已完成 Agent 的结果访问任何外部服务

脚本只是编排逻辑,所有实际操作由它派发的 Agent 完成。

可用 API

函数行为
runAgent(options)派发单个 Agent,阻塞等待结果,返回 Agent 的输出字符串
runAgents(optionsArray, options?)批量并发派发,等待全部完成,返回结果数组;可通过 options.concurrency 控制本次并发数
getResult(agentId)从 DB 读取已完成的 Agent 结果,续跑时使用

脚本示例

以"批量代码安全审查"为例:

// Phase 1:并发扫描所有模块
const scanResults = runAgents([
  { agent: "code-scanner", prompt: "扫描 auth 模块,输出高危漏洞列表" },
  { agent: "code-scanner", prompt: "扫描 payment 模块,输出高危漏洞列表" },
  { agent: "code-scanner", prompt: "扫描 user 模块,输出高危漏洞列表" },
], { concurrency: 3 });

// Phase 2:对有高危问题的模块做深度分析(动态决定派什么)
const highRisk = scanResults.filter(r => r.includes("HIGH"));
const analysisResults = runAgents(
  highRisk.map(r => ({
    agent: "security-analyst",
    prompt: `深度分析以下漏洞并给出修复建议:\n${r}`
  }))
);

// Phase 3:汇总生成报告
runAgent({
  agent: "report-writer",
  prompt: `根据以下分析结果生成安全报告:\n${analysisResults.join("\n\n")}`
});

这段脚本体现了 Dynamic Workflow 的核心价值:Phase 2 的任务列表是根据 Phase 1 的返回值动态生成的,Claude 不需要参与,Runtime 在脚本内用普通 JS 逻辑来做这个判断。


Runtime 执行机制

Goja 沙箱

使用 Goja(嵌入式 JS 引擎)在 Go 进程内执行脚本,无需跨进程通信。Goja 的默认状态没有任何内置能力——没有 require,没有 fsnet,没有任何 Node.js 模块。只有主动注册进去的函数才能被调用,这天然实现了沙箱隔离。

执行流程

后台 goroutine 启动
      │
      ├─ 创建 Goja VM
      ├─ 注册 runAgent / runAgents / getResult 三个 Go 函数
      ├─ 从 DB 加载已完成 Agent 的缓存结果(续跑支持)
      │
      ▼
脚本开始执行
      │
      └─ 调用 runAgents([...]) 时:
              │
              ├─ 并发启动 N 个 Agent(受并发上限控制)
              │
              │  每个 Agent:
              │      AcquireBrain → RunStateless
              │      完成后立即写库
              │      向 EventBus 发布 workflow.agent_completed 事件
              │
              └─ 全部完成,runAgents() 返回结果数组给脚本
                        │
              脚本用 JS 逻辑处理结果,决定下一阶段
                        │
              所有阶段完成
                        │
              更新 Run 状态 → completed

runAgent() 对 Goja 协程来说是同步阻塞的,但对 Go 侧是异步的,不会占用线程。Runtime 的 context 从 HTTP 请求 context 上 detach 出来(携带 tenantID 和 userID),不绑定任何 HTTP 请求的生命周期。


并发与安全控制

并发控制(两层)

租户配置层(最大并发上限,默认 25)
    └─ 脚本层(runAgents 的 concurrency 参数,不得超过上层)

服务端的瓶颈是 API 速率限制和 Brain 缓存容量,与本机 CPU 核心数无关,不硬编码为固定值,不同套餐可配置不同上限。

总量控制

每次 Run 累计派发的 Agent 数量有上限,防止脚本写出死循环无限派发。具体数值做成租户配置项,超出后 Runtime 拒绝新的 runAgent() 调用并以错误终止脚本。


续跑机制

这是平台化实现相比 Claude Code 本地方案的核心优势:进程崩溃或重启不丢失已完成的工作

进程重启 / 用户手动 Resume
          │
          ▼
Runtime 从 DB 加载所有已完成 Agent 的结果(按 seq 顺序)
          │
脚本重新从头执行
          │
          ├─ runAgent() 检测到该 seq 已有缓存 → 直接返回缓存,跳过执行
          └─ runAgent() 没有缓存 → 正常执行

已完成的 Agent 结果持久化在 workflow_agents 表中,不依赖进程存活,也不依赖原始 Session 是否存在。


事件流设计

Workflow 的 SSE 事件通过现有 EventBus 分发,topic 为 "wf:"+runID,与 Session 事件流完全隔离。事件结构复用现有 harness.Event 类型,EventType 新增 workflow.* 前缀常量。

每个事件携带足够信息让前端无需额外请求即可更新 UI:

事件携带信息
workflow.run_startedrun_id, name, script_preview
workflow.phase_startedphase_name, agent_count
workflow.agent_startedagent_seq, phase_name, agent_id
workflow.agent_completedagent_seq, phase_name, input_tokens, output_tokens, elapsed_ms
workflow.agent_failedagent_seq, phase_name, error
workflow.phase_completedphase_name, elapsed_ms
workflow.run_completedrun_id, total_tokens, elapsed_ms
workflow.run_failedrun_id, error

前端状态机:

审批通过
    workflow.run_started      → 审批卡片变为进度卡片,展示运行中
        workflow.phase_started    → Phase 行展示 loading 状态
            workflow.agent_started    → Agent 状态:运行中
            workflow.agent_completed  → Agent 状态:完成,进度条 +1
            workflow.agent_failed     → Agent 状态:失败,展示重试按钮
        workflow.phase_completed  → Phase 标记完成
    workflow.run_completed    → Run 完成,SSE 断开
    workflow.run_failed       → Run 失败,展示错误信息

前端方案

三个新页面

Run 列表页

入口位置与现有 Session 列表并列,作为顶层导航的新 Tab。展示:Run 名称、状态(带颜色)、进度百分比(done_agents / total_agents)、触发时间、关联 Session 链接。支持按状态筛选。

Run 详情页

从 Session 的审批卡片链接或列表页进入。页面分为四层:

┌──────────────────────────────────────────────────────┐
│  Run 状态 · 进度条 · [暂停] [续跑] [取消]             │
│  [查看脚本 ▼]  [保存为模板]                           │
├──────────────────────────────────────────────────────┤
│  脚本预览区(可折叠,带语法高亮)                       │
├──────────────────────────────────────────────────────┤
│  Phase 1  ████████████  12/12  2.3k tokens  45s      │
│  Phase 2  ████████░░░░   8/12  1.8k tokens  …        │
├──────────────────────────────────────────────────────┤
│  ▼ Phase 2 详情                                       │
│    Agent #9  ✓  完成  prompt / result                 │
│    Agent #10 ✗  失败  [重试]                          │
│    Agent #11 ⟳  运行中                               │
└──────────────────────────────────────────────────────┘

数据加载策略:进入时拿全量快照(含脚本),再建立 SSE 连接接收增量事件。Workflow 完成后断开 SSE。

模板库页面

展示用户保存的所有模板,支持:查看脚本内容、编辑名称和描述、直接触发运行(走审批流)、删除模板。

Session 内的卡片

Session 消息流里有两种 Workflow 相关卡片:

审批卡片(trigger_workflow 调用后展示)

┌──────────────────────────────────────────┐
│ 📋 批量代码安全审查                       │
│ Phase 1:扫描 3 个模块(3 个 Agent)      │
│ Phase 2:深度分析高危模块(动态)          │
│ Phase 3:生成汇总报告(1 个 Agent)       │
│                                          │
│ [查看完整脚本 ▼]                         │
│                                          │
│        [运行]        [取消]              │
└──────────────────────────────────────────┘

进度卡片(审批通过后转变)

┌──────────────────────────────────────────┐
│ ⟳ 批量代码安全审查  运行中               │
│ ████████████░░░░░░░  8 / 16 个 Agent     │
│                                          │
│ [查看详情 →]                             │
└──────────────────────────────────────────┘

触发入口

前端不需要"新建 Workflow"按钮。Workflow 由 Claude 在 Session 对话中自主触发。已保存的模板可以从模板库页面直接触发,同样走审批流。


李烁
162 声望98 粉丝