
在 AI 应用落地的深水区,我们常常面临一个棘手的悖论:大模型赋予了系统无限的创造力,却也让流程控制变得难以捉摸。一个智能体可能因为陷入逻辑死循环而耗尽企业预算,一个自动化流程可能因为外部系统的微小延迟而彻底卡死。在传统的 BPM(业务流程管理)中,规则是刚性的;而在纯粹的 LLM 应用中,行为又是概率性的。

如何将这两者融合,打造一个既"聪明"又"可控"的 Workflow Agent?这个命题在 workflow agent 流程定义属性——用户故事场景说明 一文中被拆解为 15 个用户故事,覆盖流程级守卫、上下文装载、知识绑定、子模式编排、人机操作、外部事件与路由控制七大维度。本文不重复罗列属性定义,而是以这 15 个故事为线索,聚焦"多 Agent 协作"这一核心主题,回答三个工程问题:
Q1 · 拆解:当多个 Agent 需要协同时,Workflow 用什么结构切分它们?子流程与场景流程各自的边界在哪? Q2 · 节拍:Agent 之间、Agent 与人之间、Agent 与外部系统之间,如何用统一机制对齐"谁先动、谁等待"? Q3 · 闭环:Hooks(钩子)和用户决策,如何不被散落在各处,而是收敛到一条可观测、可兜底的主线?
本文结合近期 BPM Designer 重构的真实经验——activitysSet 字段补齐、SwimLane 退役、SceneGroupExecutionEngine 调度链审计、三层会话渲染、human_confirm 闭环与 guard_escalation 超时兜底——把"故事书"变成"工程笔记"。重点 UI(时间轴、事件通知、用户交互)将以 SVG 细致绘制,让架构落到像素上。
原文以标准用户故事格式 US-XX [优先级] [分类] 作为 <角色>,我希望 <能力>,以便 <业务价值> 驱动每个属性的语义。每个故事包含"故事陈述 → 场景描述 → 属性配置 → 三层对齐验证 → 验收标准"五段链路。下表把 15 个故事按分类归纳,并标注其与"多 Agent 协作"的关联度。
编号 | 分类 | 主题 | 优先级 | 核心属性 | 协作关联 |
|---|---|---|---|---|---|
US-01 | 流程级守卫 | LLM 轮次防爆 | P0 | guardConfig.processLevel.maxLlmRounds | 防 Agent 死循环 |
US-02 | 流程级守卫 | Token 预算控制 | P0 | maxTotalTokens/ tokenWarnAt | 成本上限 |
US-13 | 流程级守卫 | 回退超限例外介入 | P0 | maxBackwardCount | ★ 协作兜底 |
US-03 | 上下文策略 | DeepSeek 缓存优化 | P0 | contextLoadPolicy.staticLayers | 多 Agent 共享上下文 |
US-04 | 上下文策略 | 深度设计模式 | P1 | loadLevels.deepDesign | 深度协同上下文 |
US-05 | 知识绑定 | 意图层知识注入 | P1 | knowledgeBindings[].layer | 知识分发 |
US-06 | TASK 子模式 | 技能编排(SKILLS) | P0 | taskMode=SKILLS/ config.skills | ★ Agent 编排 |
US-07 | TASK 子模式 | 场景组驱动(SCENE_GROUP) | P0 | taskMode=SCENE_GROUP/ sceneGroupId | ★★ 场景拆解 |
US-08 | LLM 子模式 | 多 Agent 协同(HARNESS) | P1 | llmMode=HARNESS/ config.harness | ★★ 单活动内多 Agent |
US-09 | HUMAN 操作 | 人工确认 | P0 | humanMode=CONFIRM | ★ 用户决策 |
US-10 | HUMAN 操作 | 退回与特送 | P0 | BACKWARD/ SPECIAL_FORWARD | ★ 路由决策 |
US-11 | HUMAN 操作 | 委托流转 | P0 | DELEGATE | ★ 责任转交 |
US-12 | AGENT_EVENT | 外部事件等待与合并 | P0 | type=AGENT_EVENT/ waitConfig | ★★ 异步握手 |
US-14 | 路由控制 | 排他分支与默认兜底 | P0 | exclusiveGateway/ defaultFlow | 协作分流 |
US-15 | 路由控制 | 回退重试 | P0 | BACKWARD路由 | 协作纠错 |
把"协作关联"列做一次聚类,可以得到本文后续三章的
"多 Agent 协作"不是单一故事,而是四条主线交织——① 用子流程/场景流程把协作"拆"开(US-06/07/08);② 用 Agent-Event 把外部世界"接"进来(US-12);③ 用 HUMAN 把人"嵌"进决策点(US-09/10/11);④ 用守卫把整条链"兜"住(US-13)。本文第三、四章正是沿这四条主线展开。

当多个 Agent 需要协同时,第一性问题不是"怎么通信",而是"怎么切分"。把所有 Agent 塞进一个活动里,会得到一个无法观测、无法干预的"黑盒大脑";把每个 Agent 拆成独立流程,又会让协作上下文在流程边界处断裂。原文给出了三种切分粒度,正好对应三个递进的子模式。
SKILLS 模式是最轻量的协作单元。一个 TASK 活动配置 taskMode=SKILLS,通过 config.skills 声明一组技能 ID,运行时由 ActivityExecutor 顺序调用。它不引入新的 Agent 实体,本质是"一个 Agent 顺序使用多个工具"。
{
"type": "TASK",
"taskMode": "SKILLS",
"config": {
"skills": ["skill-req-collect", "skill-domain-extract"]
}
}💡 三层对齐要点(US-06)
L1(JSON):parseActivity 读 taskMode 顶层字段优先,缺失时回退读 config.taskMode 兜底,兼容旧数据。L2(面板):SubModePanelPlugin 对 TASK 类型显示 taskMode 选择器。L3(运行时):executeTaskActivity() 按 getTaskMode() 分派到技能执行器。
SCENE_GROUP 是本文的核心。它把一个 TASK 活动变成"流程嵌流程"的入口:活动本身只负责"触发并注入上下文",真正的多 Agent 协作发生在被引用的场景组(SceneGroup)内部。
以 intent-dispatch 流程为例,ic_dispatch_to_sg 活动触发 rad 场景组,注入 5 个上下文变量:
{
"type": "TASK",
"taskMode": "SCENE_GROUP",
"sceneGroupId": "rad",
"sceneGroupContextInjection": {
"userIntent": "${intent}",
"matchedFlowId": "${matchedFlowId}",
"executionPlan": "${executionPlan}",
"confirmedIntent": "${confirmed}",
"dispatchSource": "intent-dispatch"
},
"llmConfigInherit": "SCENE_GROUP",
"config": {
"sgHumanStrategy": "AUTO_HANDLE",
"skills": [
{"skillId": "sg_rad_intent_resolve", "priority": 1},
{"skillId": "sg_rad_design_orchestrate", "priority": 2}
],
"sgKnowledgeBindings": [
{"bindingId": "sg_rad_intent_kb", "layer": "INTENT",
"scopeKeys": ["radIntentDictionary"],
"sourceRef": "knowledge/scenes/rad-intent.json"}
],
"sgLlmConfig": {"provider": "openai", "model": "gpt-4-turbo",
"temperature": 0.3, "maxTokens": 8000}
}
}这里有四个设计决策值得展开,它们共同定义了"场景流程"作为协作单元的语义:
父流程不把整个上下文丢给子场景组,而是通过 sceneGroupContextInjection 显式声明要注入哪些变量。这避免了"上下文污染"——子场景组只看到它该看的,父流程的内部状态不会泄漏。injectContext() 把这些变量写入 sg.businessContext,成为子场景组内所有 Agent 共享的"协作黑板"。
llmConfigInherit 有两个值:PROCESS(继承父流程配置)和 SCENE_GROUP(用 SG 自己的 sgLlmConfig)。这意味着同一个场景组可以被不同父流程复用,而每次复用可以指定是否替换底层模型。比如 rad 场景组在 intent-dispatch 下用 GPT-4,在另一个轻量分发流程下可以继承父流程的 DeepSeek。
sgHumanStrategy 有三档:AUTO_HANDLE(自动处理,不阻断父流程)、SCENE_DRIVE(场景驱动)、CRITICAL_PAUSE(关键暂停,阻断父流程)。当设为 AUTO_HANDLE 时,子场景组内部的人机交互只写 auditLog,不向上冒泡暂停。这是"场景流程"区别于"子流程"的关键——它默认是自治的,父流程不需要为它的内部决策负责。
config.skills 和 config.sgKnowledgeBindings 让场景组拥有自己的"工具箱"和"知识库",与父流程完全隔离。sg_rad_intent_resolve(意图解析技能)和 sg_rad_design_orchestrate(设计编排技能)只在 rad 场景组内可见,避免技能污染父流程的技能命名空间。

如果说 SCENE_GROUP 是"流程嵌流程",那 HARNESS 就是"活动内嵌多 Agent"。architect-pipeline 的 llm_architect_design 活动配置 llmMode=HARNESS,协调"架构师 Agent"与"评审 Agent"交替推理,直到达到共识(consensusThreshold=0.85)或达到 maxRounds=3。
HARNESS 的精妙之处在于它复用了 pause+wait FC-Loop 路径。L3 层通过 _llmMode / _harnessConfig 把配置传播到 ChatScene/FC-Loop,由 FC-Loop 的循环结构驱动实际的"轮次交替"。这意味着 HARNESS 不需要新建一套调度引擎,它只是 FC-Loop 的一个"特化配置“。

这是工程实践中最容易被混淆的一对概念。下表把它们对齐到六个维度,帮助判断"这个协作该用哪种拆解":
维度 | 子流程(SUBPROCESS) | 场景流程(SCENE_GROUP) |
|---|---|---|
触发方式 | 显式 calledElement 调用,父流程同步等待 | TASK + sceneGroupId 触发,可异步 |
上下文 | 默认继承父流程上下文(需手动映射 IO) | 显式注入 sceneGroupContextInjection,隔离干净 |
LLM 配置 | 继承父流程 | llmConfigInherit可切 PROCESS/SCENE_GROUP |
内部 HUMAN | 默认冒泡暂停父流程 | sgHumanStrategy三档控制,默认 AUTO_HANDLE 不阻断 |
资源隔离 | 共享父流程技能/知识命名空间 | 独立 skills / sgKnowledgeBindings |
语义定位 | "我要同步等子流程跑完" | "我把这块自治地交给场景组" |
✅ 选型决策树
用子流程:当子逻辑的产出是父流程下一步的直接输入,且需要父流程同步等待(例如"先算报价再下单")。
用场景流程:当子逻辑是一个自治的业务域,有自己的技能/知识/模型配置,父流程只关心"它跑完了"而不关心它内部怎么决策(例如"分发到 rad 设计域")。
用 HARNESS:当多个 Agent 需要在同一活动内交替推理达成共识,不需要跨流程边界(例如"架构师+评审圆桌会议")。
理论讲完,落到代码总会遇到历史包袱。近期 BPM Designer 重构中遇到两个典型问题,正好印证了上述拆解哲学。
旧版本用 SwimLane(泳道)来"分组"活动,本质是想表达"这一组活动属于同一个角色/Agent"。但泳道只是视觉分组,不携带执行语义,反而让流程定义变重。重构中废弃了 SwimLaneDefinition.java,把 7 个流程里的 12 个泳道活动迁移到顶层,并通过 migrate-swimlanes-to-top-level.js 生成备份。迁移后,"谁负责"的语义改由 activityCategory 从 activityType 派生(ActivityDef._deriveCategory),而不是靠泳道这种纯视觉容器。
⚠️ 踩过的坑:activityCategory 默认 'HUMAN'
迁移后发现 LLM_AGENT/TASK/AGENT_EVENT 节点错误地显示了 HUMAN 的"执行者/权限"面板。根因是 Canvas.js / ActivityDef.js / PanelInitializer.js 中 activityCategory 默认值是 'HUMAN',而遗留插件用 activityCategory 过滤面板分组。修复:activityCategory 必须从 activityType 派生,不得默认 'HUMAN'。这是"把视觉分组(泳道)换成执行语义(category)"迁移中必须同步修复的连带问题。
场景组配置需要 activitysSet 字段来声明"场景组包含哪些子场景活动"。但旧版 ActivityDef.js 在解析和序列化时漏了这个字段,导致拖拽保存的场景节点丢失配置。重构中补齐了 activitysSet 的解析与序列化,并在 SubModePanelPlugin.js 新增 _renderActivitysSetExtra 方法渲染场景组配置 UI。
这两个问题的共同教训是:拆解范式的切换(泳道→category、单层→activitysSet)必须同步打通"定义→面板→运行时"三层,任何一层漏改都会表现为"配置丢失"或"面板错配"。这正是下一章 L1/L2/L3 三层对齐要解决的。
拆解解决了"多 Agent 怎么组织",但协作要真正跑起来,还需要解决"节拍"问题——Agent 之间、Agent 与人之间、Agent 与外部系统之间,如何对齐"谁先动、谁等待、谁决策"。原文用 pause+wait FC-Loop、AGENT_EVENT、HUMAN 三套机制回答了这个问题,而它们最终收敛到同一个统一处理模式:把所有"需要外部输入才能继续"的点,都建模成一个可暂停、可观测、可兜底的活动。
FC-Loop(Function Calling Loop)是整个协作体系的节拍器。它的本质是一个 pause → wait → resume 循环:
近期重构中对 FC-Loop 做了三项关键加固:

多 Agent 协作不只在"Agent 之间",还包括"Agent 与外部系统之间"。当一个 Agent 需要等待外部系统(如 CI/CD 完成、第三方审批、消息队列事件)时,就用 AGENT_EVENT 活动类型。它通过 waitConfig 声明等待什么事件、等多久、收到后如何合并。
AGENT_EVENT 的关键设计是"等待即活动"——把"等外部事件"建模成一个一等公民的活动节点,而不是塞在某个 Agent 的代码里 sleep。这样做有三个好处:① 等待状态可被前端观测(时间轴上能看到"正在等待 X 事件");② 等待可被守卫兜底(超时升级而非卡死);③ 多个事件可合并(waitConfig.mergeStrategy)。
HUMAN 活动是"把人嵌进决策点"的标准方式。原文定义了三种 humanMode,覆盖了人机协作中最常见的三类决策:
故事 | humanMode | 语义 | 典型场景 |
|---|---|---|---|
US-09 | CONFIRM | 确认/拒绝当前产出 | 架构方案确认、意图确认 |
US-10 | BACKWARD/ SPECIAL_FORWARD | 退回重做 / 特送跳转 | 方案不合格退回需求阶段 |
US-11 | DELEGATE | 委托给他人/他 Agent | 当前 Agent 处理不了,转交专家 |
这三种模式共享同一套 pause+wait FC-Loop 基础设施——它们都是"暂停流程,等待用户在 UI 上操作,再恢复"。区别只在于用户操作后路由的去向:CONFIRM 往前走,BACKWARD 往回跳,SPECIAL_FORWARD 跳到指定节点,DELEGATE 把控制权交给另一个执行者。这种统一建模让前端的交互组件可以复用(同一个确认弹窗,只是按钮不同)。
这是整个协作体系最精彩的设计。当流程因为反复回退超过 maxBackwardCount(默认 3 次)、或路由无匹配、或全局回退超限时,系统不会让流程卡死,而是合成一个 HUMAN 活动,把控制权交给人工。
具体机制:RouteToEngine 每次 BACKWARD 路由递增 globalBackwardCount,超限触发 handleHumanEscalation(),该函数会:
🎯 核心洞察:把"异常"统一建模为"HUMAN 决策"
这是整个协作体系的哲学统一点。无论是用户主动确认(US-09)、用户退回(US-10)、用户委托(US-11),还是系统检测到异常需要人工介入(US-13),最终都收敛到"合成一个 HUMAN 活动 + 推送 SSE + 等待用户操作"这一条主线。前端的交互组件、后端的 FC-Loop、守卫的兜底逻辑,全部围绕这个统一模型构建。Hooks(钩子)和用户决策,不是散落在各处的 if-else,而是同一种"暂停-等待-恢复"活动。

前面三章讲的是后端编排哲学,但"聪明又可控"最终要落到用户看得见的界面上。近期重构中,会话渲染从单层升级为三层架构,专门用于承载多 Agent 协作的可观测性。本节是全文重点,三个核心 UI 组件将以 SVG 细致绘制。
多 Agent 协作会产生三类截然不同的信息流:① 计划骨架(流程开始前 LLM 预测的工具调用序列);② 实时事件流(运行中 SSE 推送的步骤、技能、工具、思考过程);③ 历史归档(已完成活动的折叠摘要)。单层 UI 无法同时表达这三类信息的时序与层级,因此重构为三层

💡 设计修正记录
初版设计把"已完成项"放在顶部展开、"未完成项"折叠,导致用户看不到当前在做什么。修正后:未完成项顶部展开(聚焦当下),完成项折叠到下方(归档可追溯)。这是"可观测性优先于完整性"的典型取舍。
时间轴是实时事件流(Layer 2)的核心载体。它把一个活动实例(activityInst)从"开始 → 各类子事件 → 完成"的完整生命周期,以垂直时间线呈现。每个事件节点携带类型图标、时间戳、状态、详情摘要,并支持点击展开。近期重构中修复了一个关键 BUG:HUMAN 确认框必须在 activityInst 内部渲染,而不是浮在顶层导致用户看不到上下文图 7 · 时间轴组件:垂直时间线 + 事件卡片 + 状态徽章 + 内嵌人工确认框

✅ 关键修复:HUMAN 确认框内置渲染
初版 human_confirm 弹窗浮在顶层,用户看不到"在确认什么"。重构后确认框作为时间轴的一个事件节点内嵌渲染(图 7 节点 5),用户能同时看到上下文(前面的技能执行、Agent 协同结果)和操作按钮。这是"上下文连续性"原则的体现。
事件通知组件负责实时推送 SSE 事件,并在守卫触发时高亮告警。它和时间轴的关系是:时间轴是"结构化展示",事件通知是"即时提醒"。前者按活动实例组织,后者按时间顺序流式呈现,两者互补。近期重构中优化了深色主题下的文字可读性,并把"执行日志"这种不友好标题改为可点击的状态徽章。图 8 · 事件通知组件:严重事件置顶高亮 + 多类型图标 + 状态徽章 + 倒计时

用户交互组件是"人机闭环"的最后一公里。当 human_confirm 或 guard_escalation 事件到达时,前端需要弹出一个承载完整决策上下文的交互面板。这个面板的设计直接影响用户能否"快速理解、果断决策"。近期重构中简化了过厚的边框、对齐了深色主题、并确保操作栏根据 availableOperations 动态生成按钮。
💡 组件复用要点
两个弹窗共享同一套渲染基础设施(human_confirm SSE → 弹窗组件 → 操作回调 → FC-Loop resume)。区别仅在:① 头部颜色(绿/红);② 操作按钮集(由 availableOperations 动态生成);③ 上下文区域是否显示 reasonCode 横幅。这种"同构不同态"的设计,让 Hooks(守卫升级)和用户决策(常规确认)在前端也实现了统一处理。
回看全文,最难的不是单点设计,而是"定义层、面板层、运行时层"三层必须始终对齐。原文每个用户故事都包含"三层对齐验证"段落,这不是文档模板,而是工程纪律。下表把三层职责和常见错位归纳如下:
层 | 载体 | 职责 | 典型错位 |
|---|---|---|---|
L1 · JSON 定义 | definition.json/ VfsFlowDefinitionLoader | 流程属性的权威来源 | 旧对象 Map 形态未归一化为数组(US-05) |
L2 · 面板编辑 | SubModePanelPlugin/ ProcessAdvancedPanelPlugin | 可视化编辑,保存回 L1 | activityCategory默认 'HUMAN' 导致非 HUMAN 节点错配面板 |
L3 · 运行时消费 | ActivityExecutor/ ProcessGuard / SceneGroupExecutionEngine | 读取属性驱动执行 | isSceneGroupMode()读错字段(config vs 顶层)导致场景组不触发 |
近期重构中遇到的三类错位,可以作为"三层对齐"的活教材:
isSceneGroupMode() 原本只读 config.taskMode,而 executeTaskActivity() 读 getTaskMode()(顶层优先 + config 兜底)。结果:面板配置写在顶层,运行时却读 config,导致场景组模式不触发。修复:isSceneGroupMode() 改为与 executeTaskActivity 同源读取。教训:同一字段的读取入口必须全局统一。
知识绑定旧 JSON 是对象 Map({"INTENT": "xxx.json"}),新数据是数组。VfsFlowDefinitionLoader 必须按 instanceof 分派:JSONArray 走数组解析,JSONObject 走 Map 转数组。教训:历史数据形态迁移必须在加载层兜底,不能要求手工改 JSON。
泳道是纯视觉分组,不携带执行语义;而 activityCategory 是执行语义。退役泳道时必须同步把"谁负责"的语义迁移到 activityCategory,并修复所有默认值。教训:视觉容器的退役要同步处理它承载的隐式语义。
多 Agent 协作的核心难题是上下文如何共享而不污染。原文 US-03/US-04 给出的方案是"静态前缀 + 动态后缀"分层装载,结合 DeepSeek 前缀缓存规则(从第 0 个 token 开始完全匹配才命中,最小 64 tokens):
这套策略与场景组的 sceneGroupContextInjection 形成互补:静态层解决"全局共享上下文的成本",注入变量解决"跨流程边界的上下文传递"。两者结合,让多 Agent 协作既"看得见彼此"又"不互相污染"。
US-14(排他分支与默认兜底)和 US-15(回退重试)看似是路由问题,实则是协作问题。排他分支让一个 Agent 的产出决定"下一个 Agent 是谁",defaultFlow 兜底避免"无人接手";回退重试让协作可以纠错,而 maxBackwardCount + 守卫升级则防止"无限纠错"。三者构成协作的"路由-纠错-兜底"完整链路。

回到开篇的悖论——大模型赋予系统创造力,却让流程控制难以捉摸。本文从 15 个用户故事出发,结合近期 BPM Designer 重构经验,给出了一套可落地的解法。把它浓缩成三句话:
拆解:多 Agent 协作不是"一个大脑管一切",而是按粒度切分——SKILLS(工具箱)、SCENE_GROUP(自治场景域)、HARNESS(圆桌会议)三层递进,分别对应"一个 Agent 用多工具"、"流程嵌流程"、"活动内嵌多 Agent"。 节拍:所有需要外部输入的点——无论是用户确认(US-09)、退回特送(US-10)、委托流转(US-11)、外部事件(US-12),还是守卫升级(US-13)——都收敛到 pause+wait FC-Loop 这一套"暂停-等待-恢复"基础设施,配 confirmId 预注入和 180s 超时兜底。 统一:Hooks 和用户决策不是散落的 if-else,而是同一种"合成 HUMAN 活动 + 推送 SSE + 等待操作"的统一模型。前端三层渲染(计划骨架 / 实时事件流 / 历史归档)+ 三大 UI 组件(时间轴 / 事件通知 / 交互弹窗)让这套机制可观测、可干预。
重构过程中最深的体会是:"聪明又可控"的本质,是把 LLM 的概率性行为关进 Workflow 的确定性笼子。笼子的骨架是 L1/L2/L3 三层对齐(定义→面板→运行时),笼子的锁是守卫升级(异常自动转人工),笼子的窗户是三层 UI(让用户随时看见里面在发生什么)。近期踩过的坑——activityCategory 默认 'HUMAN'、isSceneGroupMode() 读错字段、SwimLane 退役连带语义丢失、HUMAN 确认框浮在顶层——无一不是"笼子某处没焊好"的体现。
下一步的演进方向已经清晰:① 把 LLM 预测从"预测活动"改为"预测工具调用序列"(LLM 不擅长预测活动却擅长预测工具),计划骨架展示"必要工具清单"而非"活动列表";② 完善 C2/C3 前端监听(skill_progress + file_read/write 的实时埋点);③ 把 V1 EVENT_WAITING history 补齐,让外部事件等待也能在时间轴上回溯。这些都是在"确定性笼子"上继续开窗、让协作更透明的工作。
最后,借用原文前言的话作结:如何将这两者完美融合,打造出一个既"聪明"又"可控"的 Workflow Agent,是我们一直在探索的终极命题。15 个用户故事是这个命题的拆解,本文则是拆解的工程注脚——愿它能为同样在 AI 落地深水区跋涉的同行,提供一张可参照的地图。
📚 全文核心要点速览
Workflow 驱动多 Agent 协作:从子流程拆解到人机决策闭环的工程实践
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。