
一篇从 OODER 框架实战中提炼的工程博文,聚焦「流程驱动 Agent」架构下,前后端如何通过 SSE 事件流、三态模型与一等公民操作设计,构建可观测、可介入、可回退的智能体交互闭环。
写作日期:2026-07-23 · 背景项目:OODERAI(流程驱动 + 多模式 Agent 开发平台)
传统的 LLM Agent 前后端交互通常是「一问一答 + 流式 token」:
User → POST /chat → Backend (LLM Stream) → SSE token → Frontend render这种模式在简单问答场景工作良好,但当我们引入 Workflow 引擎 后,会立刻暴露三个核心问题:
如果我们继续沿用「token-only」的前端架构,会很快陷入:事件流缺口(后端推了前端不监听)、降级路径失控(前端默默创建 fallback 实例掩盖后端错误)、死代码堆积(监听器永远不触发但不敢删除)。
OODER 框架在 2026 年 Q3 完成了一次系统性的前后端对齐重构,本文就是这次重构的方法论沉淀。
流程驱动架构的基础是「定义、实例、历史」三态分离:
状态 | Java 类 | VFS 路径 | 语义 |
|---|---|---|---|
Def | ProcessDefinition / ActivityDefinition | process-def/{flowId}/... | 静态定义:活动节点、路由、eventDisplayConfigs |
Inst | ProcessInstance / ActivityInstance | process-inst/{processInstId}/... | 运行时实例:状态、上下文、子流程 |
History | (无独立 Java 类,Map 结构) | process-inst/{id}/activities/{actId}/history.json | 历史快照:SkillExecutionSummary、产出物、子流程合并字段 |
关键设计原则:History 不建立独立 Java 类,而是以 Map 结构存储在 VFS 的 HISTORY 层。这样 LLM 生成的非结构化摘要、技能执行明细、产出物清单都可以无 schema 变更地追加。前端通过 historyVfsPath 字段动态加载,避免后端 DTO 频繁膨胀。
后端通过 SseEventPushListener(实现 FlowEventListener 接口)将流程事件统一推送到 SSE 通道:
@Component
public class SseEventPushListener implements FlowEventListener {
@Override
public void onActivityStarted(ProcessInstance instance, ActivityInstance actInstance,
ActivityDefinition actDef, ProcessDefinition procDef) {
Map<String, Object> eventData = sseEventAssembler.buildFlowStepEvent(
instance.getInstanceId(), actDef.getActivityId(), "executing", null);
// 注入 activityInstId、领域信息、展现注解、进度、displayMeta
enrichWithDomainInfo(eventData, instance, actDef, procDef);
enrichWithDisplayAnnotation(eventData, actDef, instance);
injectProgressFromInstance(eventData, instance);
injectDisplayMetaUniversal(actDef.getActivityId(), eventTypeStart, eventData);
pushSseEvent("flow_step", eventData);
}
}前端通过 EventSource.addEventListener(eventName, handler) 精确订阅每个事件类型,而不是用一个 onmessage 处理所有事件。这带来两个好处:
addEventListener 清单本身就是前后端契约文档OODER 将 SSE 事件按生命周期阶段分为四层:
事件 | 触发时机 | 关键 payload |
|---|---|---|
flow_start | onProcessStarted | processInstId, processDefId, flowId, parentInstId |
flow_plan | IntentDispatchScene 预测路径后 | steps[], predictedActivityIds, overallSummary, source |
flow_route | onProcessStarted/onActivityRouted | source=process_started/activity_routed, routeAction |
flow_step | onActivityStarted/Completed/Failed/Paused/Routed | phase, activityInstId, displayAnnotation, meta |
flow_complete | onProcessCompleted | taskSummary, mdContent, stageResults, artifactSummary |
flow_completed | onProcessCompleted(实例状态推进) | status=completed |
flow_failed | onProcessFailed | error, success=false |
flow_archived | onProcessArchived | status=archived |
事件 | 触发时机 | 关键 payload |
|---|---|---|
flow_step (phase=executing/completed/failed/paused/routing) | onActivity* | activityInstId, duration, historyVfsPath |
flow_artifact | onArtifactChanged | artifact:{type,path,action}, toolName=page_ready(cls/page) |
flow_heartbeat | startActivityHeartbeat(每5秒) | elapsedMs, estimatedMs |
human_confirm | onActivityPaused (HUMAN) | confirmId, type=CHOICE, config.actions |
guard_escalation | onGuardEscalation | failedActivityId, interventionActivityId, reasonCode, availableOperations |
事件 | 触发时机 | 关键 payload |
|---|---|---|
file_progress | onFileReadProgress | filePath, totalLines, currentLine, percent |
file_change | onFileWriteChange | operation, content(截断100) |
flow_tool_call | FunctionCallingLoopExecutor | toolName, arguments, result |
flow_thinking | FunctionCallingLoopExecutor | thinking(流式追加) |
事件模式 | 触发时机 | 数量 |
|---|---|---|
flow_crud_* | onCrudEvent(CRUD 编排) | 10 个独立监听 |
flow_dbfirst_* | DBFirst 动态循环 | ~26 个 |
flow_viewfirst_* | ViewFirst 动态循环 | ~26 个 |
flow_designerfirst_* | DesignerFirst 动态循环 | ~28 个 |
flow_db_* | 业务动态循环 | ~18 个 |
关键设计:动态事件通过 _handleCrudEvent 统一处理器分发,按 crudEventType 字段路由到对应的 icon/label/status。这避免了为每个动态事件写独立监听器(会产生 100+ 个相似函数)。
经过实战踩坑,我们提炼出前后端对齐的六个关键维度。每个维度都需要专门的审计。下图展示了 OODER 采用的「定义层 / 编辑层 / 调度消费层」三层对齐模型——L2 编辑的字段必须 ⊆ L1 持久化字段 ⊆ L3 消费字段:

图 4-1:BPM 流程定义三层对齐模型(L1·L2·L3 同名同义同位置)
同时,所有活动类型通过统一的 ActivityDefinition + 类型字段 + 子模式字段建模,避免继承树爆炸:

图 4-2:统一活动模型(单一 ActivityDefinition + type + subMode)
传统 BPM 中 HUMAN 节点只是「等待用户确认」,但在 Agent 开发中,HUMAN 是一等公民,拥有完整的"显示生命"。下图展示了这条"显示生命"的四阶段链路:

图 5-1:HUMAN 一等公民操作的"显示生命"闭环
10 个一等公民操作:
操作 | 默认可用 | 触发条件 |
|---|---|---|
SEND / PAUSE / RESUME / TERMINATE | ✅ | 默认可用 |
SPECIAL_SEND / WITHDRAW / RETURN / DELEGATE_H2A / H2H / H2T | ❌ | 需 DESIGNER config.operations 显式定义 |
RouteToEngine.resolveHumanRoute 必须在 _taskCompleted 检查前先调用 checkHumanOperationRoute,按优先级消费上下文标记:
SPECIAL_SEND → _specialSendFrom (continueExecution)
RETURNED → _returnFrom, _returnToActivityId (continueExecution 或 legacy BACKWARD)
WITHDRAWN → 活动状态 WITHDRAWN (仅终止当前分支)
DELEGATED → _delegatedFrom_{actId} (continueExecution, 类似 AUTO_ADVANCE)ActivityRouteResult 新增 4 种 RouteAction + 工厂方法 + isBackwardRequired() / isBranchTerminated() 辅助函数,让路由决策显式化。
当 HUMAN 活动位于 SceneGroup 内部时,SgHumanStrategy 决定是否阻断:
策略 | 行为 |
|---|---|
AUTO_HANDLE | SG 内自动处理,不传播到父流程 |
SCENE_DRIVE | 场景驱动,不调用 instance.setStatus(PAUSED) |
CRITICAL_PAUSE | 关键暂停,传播到父流程 |
SceneGroupExecutionEngine.handleSgInternalHuman() 只通过 sg.addEvent() 写审计日志,不调用 instance.setStatus(PAUSED),不传播到父流程。
当流程遇到「无路由匹配」「回退次数耗尽」「全局回退超限」等异常时,handleHumanEscalation 会创建合成 HUMAN EXCEPTION_INTERVENTION 活动并触发 guard_escalation 事件。完整闭环如下图:
图 6-1:守卫闭环——从限流触发到人工介入决策
{
"processInstId": "pi-xxx",
"eventType": "guard_escalation",
"failedActivityId": "dbfirst_connect",
"failedActivityName": "数据库连接",
"interventionActivityId": "intervene-dbfirst_connect-1784799834",
"interventionActivityDefId": "EXCEPTION_INTERVENTION",
"reasonCode": "no_route_matched",
"message": "数据库连接活动无可用路由",
"humanMode": "EXCEPTION_INTERVENTION",
"humanModeUiType": "exception_panel",
"pendingOperation": "INTERVENE_EXCEPTION",
"operationPermissions": ["INTERVENE_EXCEPTION", "RESUME", "TERMINATE", "SEND"],
"confirmId": "intervene-dbfirst_connect-1784799834",
"timestamp": 1784799834000,
"interventionRequired": true,
"guardPausedReason": "HUMAN_ESCALATION",
"exceptionContext": {
"globalBackwardCount": 0,
"retryExceeded": false,
"availableOperations": ["INTERVENE_EXCEPTION", "RESUME", "TERMINATE", "SEND"]
},
"availableOperations": ["INTERVENE_EXCEPTION", "RESUME", "TERMINATE", "SEND"],
"globalBackwardCount": 0
}displayMeta 决定前端如何渲染一个活动卡片(icon / template / detailKey / status)。OODER 采用「后端优先注入 + 前端智能兜底」的双层架构:

图 7-1:displayMeta 双层注入架构(后端 priority=30 优先,前端智能兜底)
后端 injectDisplayMetaUniversal 依赖 activityDefCache(从 8 个含 eventDisplayConfigs 的流程定义加载)。当缓存未命中或配置缺失时,前端 _inferDefaultMeta 接管:
_inferDefaultMeta: function (eventType) {
// 状态推断
var status = 'running';
if (eventType.indexOf('_done') >= 0) status = 'done';
else if (eventType.indexOf('_wait') >= 0) status = 'paused';
else if (eventType.indexOf('_fail') >= 0) status = 'failed';
// ★ 智能图标分桶(9 类)
var icon = 'ri-information-line';
if (status === 'done') icon = 'ri-checkbox-circle-line';
else if (status === 'failed') icon = 'ri-error-warning-line';
else if (etLower.indexOf('parse') >= 0) icon = 'ri-file-search-line';
else if (etLower.indexOf('generat') >= 0) icon = 'ri-hammer-line';
// ...
// ★ 模板推断(5 类)
var template = 'plain_text';
if (status === 'paused') template = 'confirm_dialog';
else if (status === 'failed') template = 'error_card';
else if (status === 'done') template = 'result_card';
// ★ detailKey 智能映射
var detailKey = '';
if (status === 'failed') detailKey = 'error';
else if (etLower.indexOf('generat') >= 0) detailKey = 'components';
// ...
}data.meta || host._inferDefaultMeta(eventType) — 后端 meta 永远覆盖前端兜底eventData.containsKey("meta") 检查会跳过已注入 meta 的事件eventDisplayConfigs 的事件,生产环境应保证后端配置完备AGENT_EVENT 是 OODER 中最复杂的活动类型,采用三阶段自活设计:
1. 埋点 (EVENT_WAITING)
↓ AgentEventBridge.registerSubscription() — hookLifecycle.onRegistered
2. 触发 (AgentEventBridge 匹配条件)
↓ hookLifecycle.onMatched — pendingEventInstId 设置
3. 自活 (activateEventBranch 创建一级图层)
↓ hookLifecycle.onEvent → onCompletehookLifecycle Map 追踪四个阶段回调:onRegistered → onMatched → onEvent → onComplete。SseEventPushListener 将这四个阶段推送给前端,前端 _renderEventHookTimeline 渲染四阶段时间线。
本文作者:OODER 团队
本文基于 2026 Q3 一次系统性前后端对齐重构实践沉淀。涵盖:三态模型、SSE 四层 23 事件、六大对齐维度、HUMAN 一等公民操作、guard_escalation 异常介入闭环、displayMeta 双层注入、22 项缺口闭环实战、AGENT_EVENT 三阶段自活设计。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。