工作流引擎
工作流是「把多个 Agent 调用、工具调用和人工确认串成一张图」:定义以节点 + 边的 JSON 落库,运行前先做图校验,运行时逐个节点执行并留下节点级记录。一个关键事实:它是顺序执行的,不是并行的——共享同一个作用域 DbContext,靠这个换来实现简单与事务一致性。
定义 → 校验 → 运行 → 节点记录,四处都有落库;运行以快照固化当时的图,避免中途改定义影响已开始的运行。
定义模型
| 字段 | 说明 |
Name · Description · SortOrder · IsEnabled | 元数据与列表排序 |
Nodes · Edges | 以 JSON 字符串存储;节点类型常量:start / agent / condition / loop / parallel / merge / tool / human / subworkflow / end |
InputSchema · Variables | 输入契约与变量声明 |
Version | 新建为 1,每次更新自增 |
节点 Config | 各节点类型的 JSON 配置,见下方执行器表 |
端点
| 端点 | 用途 |
GET /api/workflows · GET /api/workflows/{id} | 列表 / 详情 |
POST /api/workflows · PUT /api/workflows/{id} · DELETE /api/workflows/{id} | 增删改(保存时把节点与边序列化为 JSON,更新自增版本) |
POST /api/workflows/{id}/duplicate | 复制定义,名称追加「副本」后缀 |
GET /api/workflows/{id}/validate | 只做图校验,不运行 |
POST /api/workflows/{id}/run | 一次性运行,返回运行结果 |
POST /api/workflows/{id}/run/stream | SSE 流式运行 |
POST /api/workflows/{id}/run/topic/{topicId}/stream | SSE 运行并把过程写入指定话题 |
GET /api/workflows/{id}/runs · GET /api/workflows/runs/{runId} | 运行历史 / 单次运行详情(含节点记录) |
POST /api/workflows/runs/{runId}/approve | 人审节点回传:{ nodeId?, approve } |
图校验规则
- 至少一个
start 节点、至少一个 end 节点。
- 每条边的两端节点必须存在。
- 除
start 外不得存在孤立节点(必须可达)。
agent 节点必须有 AgentId(指向项目智能体预设)。
运行前会再次校验,所以「保存成功但运行报校验失败」通常意味着定义被外部改动或边被删。
节点执行器
| 节点 | 配置与行为 |
start | 把原始输入存为 start.input;若输入是 JSON 对象,还会把每个字段拆成独立变量 |
agent | 配置 instruction / modelId / toolNames / mcpServerIds / toolApprovals / maxIterations / maxToolCallsPerTurn;上游输出注入为输入,无上游则回退运行输入;SessionId = workflow-{runId}-{nodeId};经 Agent 循环执行,额外写回 thinking 与 toolCalls;用量来源 workflow |
condition | { branches: [{ handle, expression }], defaultHandle };首个匹配分支的 handle 决定后续走向 |
loop | { maxIterations, exitCondition };首次进入走 body,满足退出条件或达到上限走 exit |
tool | { toolName, argumentsTemplate };直接调用内置或 MCP 工具,不经过 LLM |
human | { prompt, timeoutSeconds };通过返回 approved、拒绝返回 rejected 作为分支句柄 |
subworkflow | { subWorkflowId, inputTemplate };递归深度上限 5 |
parallel / merge | 普通节点出现多条出边时自动 fan-out,分支结果汇总后在下游公共节点合并 |
end | 有 outputTemplate 则渲染模板,否则取首个非空上游输出,再无则回退运行输入 |
「并行」在这里是结构上并行、执行上顺序:分支会被逐个执行再合并,以共享同一个作用域 DbContext。理解这点能解释两件事:分支里的 Agent 调用是串行的;任何一个分支失败会终止整次运行。
运行与上限
| 项 | 值 / 行为 |
| 运行记录 | WorkflowRun:Status(Pending / Running / Succeeded / Failed / Cancelled)· Input · Output · GraphSnapshot · ChatTopicId · Error · TotalIterations |
| 节点记录 | WorkflowRunNode:NodeId · NodeType · Status · Input · Output · Error · Iterations |
| 总迭代上限 | MaxTotalIterations = 100,超出抛错并终止 |
| 单节点访问上限 | MaxNodeVisits = 20,超出抛错并终止(防循环失控) |
| 取消 | OperationCanceledException → 运行置 Cancelled |
| 其它异常 | 运行置 Failed,Error 写入异常消息 |
人审与工具审批
- 人审节点会把「等待审批」通过 SSE 事件推到前端,前端显示待审批;回传走
/approve。
toolApprovals(节点级)优先;未显式配置时用运行级的全局模式补足。
- 工具审批与权限模式的关系见 权限与审批;Agent 节点内部的循环与工具上限见 Agent 循环。
SSE 事件
| 事件 | 含义 |
run_started · run_completed · run_failed | 运行生命周期 |
node_started · node_completed · node_failed | 节点级进度与错误 |
human_approval_required | 人审节点等待回传 |
agent_tool_call | Agent 节点的工具调用过程(前端可按问答 / 计划 / 审批三类回传) |
run_result | 最终结果载荷 |
更完整的事件协议(三种帧形态、未知事件的处理)见 SSE 事件协议。Agent 节点内部仍会写出 [ERROR] 兜底帧。
与任务看板的关系
- 看板任务的运行记录支持
Kind = Agent | Workflow,触发来源 Trigger = Todo | Comment | Manual。
- 也就是说:卡片可以挂工作流,进入待办后由执行器运行,过程与结果落在运行记录与评论里(见 任务看板与自动化)。
- 工作流内部的 Agent 节点不复用看板的运行记录,而是写自己的节点记录。
实作要点
- 循环一定配
maxIterations:单节点 20 次访问上限是最后一道闸,但它会直接让运行失败。
- Agent 节点的 session id 里带运行与节点 id,所以每次运行都是干净会话;想跨节点共享上下文,靠变量传递而不是靠会话历史。
tool 节点不经 LLM:适合固定动作(跑命令、取数据),比让模型决定更省更稳。
- 运行前把
/validate 接进编辑流程,能挡掉「缺 End 节点」「Agent 没选智能体」这类最常见保存即失败。
- 定义改动不影响已开始的运行:排查看结果不一致时先看该次运行的
GraphSnapshot。