工作流图
WorkflowDsl 和 WorkflowDag 负责可移植工作流文档的结构边界。它们解析节点与边、保留不认识的扩展字段、校验图结构,并生成确定性计划。具体节点怎样执行仍由宿主绑定。
Flow 不根据 data.type 自动获得模型、数据库或工具权限。宿主先验证并授权节点,再把节点语义接到持久步骤上。
文档与裸图
完整文档包含版本、应用信息和 workflow.graph。只需要处理画布图时,也可以直接解析 WorkflowDag。
单份 UTF-8 文档上限为 10 MiB。当前经过测试的 DSL 版本是 0.7.0。旧小版本可以带警告导入,新版本或不同主版本需要宿主明确决定是否接受。
最小图结构
节点必须有非空 id 和字符串 data.type。边必须有唯一 ID,并且引用已经存在的起点与终点。
Playground 中的连线名称
Playground 默认把源节点输出端口的名称显示在连线中间。例如批处理节点的 done 端口会显示为“全部完成”。这段文字用于帮助阅读图结构,不是引擎用来路由的端口 ID。
选中连线后,点击名称旁的铅笔按钮,或双击名称即可编辑。也可以先选中连线,再按 Enter 或 F2。输入完成后按 Enter 保存,按 Escape 放弃,失去焦点也会保存。把输入框清空会恢复端口默认名称。自定义名称会保存在本地草稿和导出的工作流文件中。
编辑名称不会改变 sourceHandle、targetHandle、执行计划或运行语义。条件节点的成立与其他分支名称仍应在节点配置中的 matched_label 和 otherwise_label 字段修改,因为这两个字段属于条件节点的业务文案。普通连线名称只是画布展示字段,发布前仍应按端口 ID 校验整张图。
生成执行计划
同一作用域中有多个可执行节点时,计划按稳定 ID 排序消除输入数组顺序的影响。计划只表达结构拓扑,不执行节点,也不决定失败、重试或授权策略。
空画布可以被解析并再次保存,便于编辑器保留草稿。execution_plan() 会拒绝空画布,因为没有可执行节点。
结构校验
编译计划前会拒绝以下情况。
- 重复或空的节点 ID、边 ID
- 缺少字符串
data.type - 边引用不存在的节点
- 节点连接自己
- 任一作用域内存在环
- 边跨越顶层与容器作用域
parentId指向不存在或不支持的容器- 迭代与循环容器缺少对应的起始子节点
- 超过 10,000 个节点或 100,000 条边
这些检查保证拓扑可确定,但不会判断节点参数是否满足某个业务执行器。宿主需要对每个已授权节点类型继续做模式校验。
Playground 性能预算
Playground 保持 React Flow 的可见元素渲染开关。布局输入会传给独立 Worker, 再由 WebAssembly 图布局内核计算坐标。配置校验、DAG 编译、序列化和元素对账 仍然保持确定性,并分别记录耗时。
在 website/ 目录运行下面的命令即可复现基准。
命令会测量 100、500 和 1,000 个节点的扇出图,并输出每项操作的 p50、p95、p99 耗时,单位是毫秒。Node.js 没有浏览器 Worker 和浏览器 WebAssembly 加载器, 所以布局一行测的是 JavaScript 回退实现。浏览器中的 Worker 布局需要在目标浏览器 单独测量。
下面这组数据是 2026-08-26 的本地参考运行,环境为 arm64 Mac14,9、macOS 26.3、 Node.js 26.7.0 和 Vitest 3.2.7。它用于比较本地改动,不代表所有设备或部署环境 都会得到相同结果。
评审时可以把 1,000 个节点的 p95 预算设为布局回退 2 毫秒、配置校验 1 毫秒、
DAG 编译和序列化各 12 毫秒、元素对账 2 毫秒。浏览器版本还要记录首次 Worker
布局、滚动响应和缩略图开关的影响。大图只挂载 React Flow 当前可见的节点,长列表
则使用 CSS content-visibility 和 containment,让屏幕外项目跳过布局计算。
节点数超过 800 或边数超过 4,000 时,缩略图会自动暂停并显示状态提示,避免它
与画布滚动争用绘制资源。
迭代与循环作用域
iteration 和 loop 节点可以成为容器。容器内节点通过 parentId 归属作用域,内部边只能连接同一容器里的节点。
对应的起始节点必须满足三个条件。
parentId等于容器 ID。- 迭代使用
iteration-start,循环使用loop-start。 - 容器内至少还有一个可执行子节点。
plan.scope("for-each-item") 返回容器内部的确定性顺序。容器自身留在顶层计划中。
语义摘要
execution_digest() 为可执行语义生成稳定 SHA-256 身份。
节点位置、选中状态、视口和输入数组顺序不会改变摘要。节点 data、边的语义数据、连接端点和作用域会改变摘要。宿主可以用它判断两次发布是否只调整了画布布局。
摘要只证明输入语义相同,不证明宿主绑定的节点实现相同。节点执行器版本仍要进入宿主发布身份或 runtime_build_id。
Flow 的 digest v2 由 Rust 引擎和 Flow UI 共用。它采用 JavaScript 数字
语义、UTF-16 对象键排序,把边标签视为展示字段,拒绝超出 JavaScript 安全
整数范围的整数,并把规范化 JSON 深度限制为 256 层。digest 格式升级时,
必须显式迁移已持久化的旧值。
无损往返与扩展字段
解析器会保留未解释的顶层字段、应用字段、工作流字段、节点展示字段和边展示字段。to_yaml() 与 to_json() 可把这些字段写回。
保留字段不代表接受其语义。安全边界仍然要采用允许列表。
- 先解析并限制文档大小。
- 再运行结构计划校验。
- 对允许的
data.type做宿主模式校验。 - 解析凭据引用,禁止把明文密钥放入图文档。
- 发布时记录文档摘要和节点执行器版本。
与持久运行的衔接
图编译器输出节点顺序,Flow 引擎持久化运行决定。宿主通常会把一个节点映射为稳定步骤 ID,把分支选择或容器游标写进步骤输出或续段输入。
如果图在运行期间发生语义变化,应创建新的工作流定义版本或补丁路径。不要用新图解释已经开始的旧历史。
完整导入示例位于 examples/workflow_dsl_import.rs。
