# df-workflow 工作流引擎 > 创建: 2026-06-10 | 状态: 初稿 | 最后更新: 2026-06-15 --- ## 概述 df-workflow 是 DevFlow 的核心引擎,负责 DAG 定义、拓扑排序、节点执行、状态流转和事件广播。引擎不感知具体业务逻辑。 ## 当前状态 | 功能 | 状态 | |------|------| | DAG 数据结构 | ✅ 已实现 | | 拓扑排序 | ✅ 已实现 | | DagExecutor (顺序执行) | ✅ 已实现 | | 同层节点并行执行 | ✅ 已实现 | | Node trait 定义 | ✅ 已实现 | | 状态机 (WorkflowRunStatus) | ✅ 已实现 | | EventBus (broadcast) | ✅ 已实现 | | 条件表达式引擎 | ⚡ 仅支持 true/false(未识别默认 **false** 保守拒绝) | | 断点续跑 | ⬜ 待实施(引擎整体无暂停/恢复/快照机制,缺乏底层基础设施支撑) | ## 核心设计 ### Node trait ```rust #[async_trait] pub trait Node: Send + Sync { async fn execute(&self, ctx: NodeContext) -> NodeResult; fn schema(&self) -> NodeSchema; fn is_blocking(&self) -> bool { false } fn node_type(&self) -> &str; } // NodeResult = anyhow::Result 的别名 // ctx 按值传递(非引用),由 executor 在每层构建 ``` ### NodeContext / NodeOutput(node.rs) 执行上下文与输出结构,由 executor 在每层为每个节点构建。 **NodeContext 字段**: | 字段 | 类型 | 职责 | |------|------|------| | `node_id` | `NodeId` | 当前节点 ID | | `inputs` | `HashMap` | 上游节点输出,key 为上游节点 ID | | `config` | `serde_json::Value` | 节点配置参数 | | `execution_id` | `String` | 工作流执行 ID | | `event_bus` | `EventBus` | 事件总线(可 Clone,广播状态变更) | | `node_status` | `StateMachine` | 节点状态机(用于检查取消状态) | **NodeOutput 字段与构造器**: | 成员 | 签名 | 说明 | |------|------|------| | `data` | `serde_json::Value` | 输出数据 | | `metadata` | `HashMap` | 输出元数据 | | `empty()` | `() -> Self` | 空输出(`data = Null`,空 metadata) | | `from_value(data)` | `(Value) -> Self` | 从 JSON 值构造(空 metadata) | > `NodeOutput` derive `Debug/Clone/Serialize/Deserialize`;`NodeContext` 仅 `Debug/Clone`(`StateMachine` 非 Serialize)。 ### 节点注册机制(NodeRegistry) 节点工厂注册表,据 `DagDef.node_type` 字符串创建 `Box` 实例,桥接可序列化定义与运行时 trait object。 | 方法 | 签名 | 职责 | |------|------|------| | `new` | `() -> Self` | 创建空注册表 | | `register` | `(&mut self, type_name: &str, factory: F)` | 注册一个节点工厂(`F: Fn(&Value) -> Box`) | | `create` | `(&self, type_name: &str, config: &Value) -> Result>` | 按类型名 + 配置创建节点实例,未注册则报错 | | `build_dag` | `(&self, def: &DagDef) -> Result` | 从 `DagDef` 构建完整运行时 `Dag`(建节点 + 加边,自动分发条件边) | | `is_registered` | `(&self, type_name: &str) -> bool` | 检查类型是否已注册 | | `registered_types` | `(&self) -> Vec<&str>` | 列出所有已注册类型名 | > **不实现 `Default`**:原 `Default` 注册了一个会 panic 的 `script` 工厂(`unimplemented!`),违反项目铁律「无 panic——所有占位代码返回空/默认值」,已删除。所有调用方应显式 `new()` + 手动 `register` 真实节点(如 `state.rs::build_registry` 的做法),实际 `ScriptNode`/`AiNode`/`HumanNode` 由 `df-nodes` crate 注册。 ### DAG 执行流程 ``` 1. 接收 WorkflowDef (DAG 定义) 2. 拓扑排序 → 得到执行层 (layers) 3. 逐层执行: - 同层节点并行(`futures::future::join_all`,已实现) - 阻塞/非阻塞节点当前同等异步执行(`is_blocking` 未被 executor 消费,人工等待逻辑规划中、当前未实现) 4. 状态变更通过 EventBus 广播 5. 节点完成后仅更新内存态(`StateMachine` + `outputs` HashMap),无持久化快照(规划中) ``` ### DAG 定义序列化(dag_def.rs) 区分两层表示:运行时 `Dag` 持 `Box`(trait object,不可序列化);可持久化的 `DagDef` / `NodeDef` / `EdgeDef` 均 `#[derive(Serialize, Deserialize)]`,用于模板与存盘。`NodeRegistry::build_dag` 负责从 `DagDef` 还原运行时 `Dag`。 | 方法 | 签名 | 职责 | |------|------|------| | `DagDef::new` | `() -> Self` | 空定义 | | `DagDef::add_node` | `(&mut self, id, node_type, config: Value)` | 加节点定义(label 默认 None) | | `DagDef::add_edge` | `(&mut self, source, target)` | 加普通边(condition=None) | | `add_edge_with_condition` | `(&mut self, source, target, condition)` | 加带条件表达式的边 | | `from_dag_edges` | `(&dag: &Dag) -> Self` | 从运行时 `Dag` 反推定义;**注意**只能还原边的 condition 与节点的 `node_type`,`config` 一律填 `Value::Null`(无法从 trait object 反推) | ### 事件类型 事件枚举定义在 `df-core::events::WorkflowEvent`,`DagExecutor` 通过 `EventBus::send` 广播。 **执行器实际广播的(executor.rs)**: - `NodeStarted` / `NodeCompleted` / `NodeFailed` - `WorkflowCompleted` **枚举已定义但执行器当前未触发**(`df-core::events` 中存在,DagExecutor 不发): - `NodeProgress`(节点进度) - `NodeOutput`(节点输出流) - `WorkflowPaused`(暂停等待外部输入) - `WorkflowFailed`(工作流失败,带 failed_node) - `HumanApprovalRequest` / `HumanApprovalResponse`(人工审批) > 注:枚举中无 `WorkflowStarted` / `WorkflowResumed`,旧文档所述为误。 ### EventBus(eventbus.rs) 基于 `tokio::sync::broadcast` 的发布/订阅,内部持 `broadcast::Sender`。 | 方法/成员 | 签名 | 职责 | |------|------|------| | `DEFAULT_CAPACITY` | `const usize = 256` | 默认通道容量 | | `new` | `() -> Self` | 用默认容量建总线 | | `with_capacity` | `(usize) -> Self` | 指定容量建总线 | | `send` | `(&self, WorkflowEvent) -> ()` | 广播事件(忽略接收者已关闭错误,异步) | | `subscribe` | `(&self) -> broadcast::Receiver` | 订阅事件流 | | `emit_human_approval_request` | `(&self, WorkflowEvent) -> Result` | 发送人工审批请求(返回接收者计数) | | `Default` / `Clone` | — | `Default` 走 `new`;`Clone` 复刻 `sender`(broadcast sender 可 clone,共享通道) | > 人工审批当前**只发不收**:`emit_human_approval_request` 发出 `WorkflowEvent`,对应的审批响应(`HumanApprovalResponse`)由 `df-nodes::human_node` 通过 `EventBus::subscribe()` 订阅后自行消费,`EventBus` 本身不再持审批响应存储/检索接口。 ### 状态机(state.rs) `StateMachine` 维护 `HashMap`,校验节点状态转换,非法转换返回错误。 **合法转换链**(`is_legal`):`Pending → Running`,`Running → Completed`,`Running → Failed`。其余均拒绝。 | 方法 | 签名 | 职责 | |------|------|------| | `new` / `get` | `... -> Self` / `(&NodeId) -> NodeStatus` | 创建;取状态(缺失默认 `Pending`) | | `set_running` | `(&self, NodeId) -> Result<()>` | `Pending → Running` | | `set_completed` | `(&self, NodeId) -> Result<()>` | `Running → Completed` | | `set_failed` | `(&self, NodeId) -> Result<()>` | `Running → Failed` | | `set_cancelled` | `(&self, NodeId)` | 置 `Cancelled`,**唯一受控旁路**:不经 `transition` 合法性校验直接 `insert`(人工审批取消由 `cancel_workflow_node` IPC 异步触发,节点可能处于 Running 之外的任意态,走 `is_legal` 会被拒绝) | | `is_cancelled` | `(&NodeId) -> bool` | 是否为 `Cancelled` | | `snapshot` | `() -> HashMap` | 全量状态快照(clone 返回,调用方持独立副本) | > **`set_waiting` / `set_skipped` 已删除**:两者同为旁路置位但全仓零调用,已删。`Waiting` / `Skipped` 两态当前无对应 setter。`Cancelled` 由 `set_cancelled` 作为唯一受控旁路置位,可从任意态跳转。 > > 共享语义:内部 `Arc>`,`clone()` 为浅拷贝(Arc 引用计数 +1),所有 clone 共享同一底层 HashMap。`DagExecutor` 的 `state_machine` 与下沉到 `NodeContext.node_status` 的 clone 共享底层;`run_workflow` 把执行器状态机注册到 `AppState` 全局表后,`cancel_workflow_node` IPC 的 `set_cancelled` 可直达运行中阻塞节点(如 `HumanNode`)的 `is_cancelled` 轮询。 ### Dag 对外 API(dag.rs) | 方法 | 签名 | 职责 | |------|------|------| | `new` | `() -> Self` | 空 DAG | | `add_node` | `(&mut self, id: NodeId, node: Box)` | 加节点 | | `add_edge` | `(&mut self, source, target)` | 加普通边(condition=None) | | `add_edge_with_condition` | `(&mut self, source, target, condition: String)` | 加带条件边 | | `predecessors` | `(&NodeId) -> Vec` | 上游节点 ID | | `successors` | `(&NodeId) -> Vec` | 下游节点 ID | | `topological_layers` | `() -> Result>>` | BFS 分层拓扑排序,同层可并行;**检测到环时报 `Workflow` 错误**("DAG 中存在环")。✅ FR-D1(2026-06-14 commit 4b5f096):复杂度由 O(V·E) 降为 **O(V+E)**——一次性遍历边建 `adjacency_out`(出边表)+ 入度表,BFS 分层走索引而非每节点重扫 `self.edges`。`executor.rs::run` 预建 `adjacency_in`(入边索引)取前驱,消除逐节点 `predecessors()` 全表扫描 | `Edge { source, target, condition: Option }` — condition 为可选条件表达式,由 `conditions.rs` 求值。 ### 条件表达式引擎(conditions.rs) `ConditionEngine::evaluate(expr: &str, context: &Value) -> Result` — 仅支持 `"true"` / `"false"` 字面量(区分大小写,先 `trim()` 去空白)。 **默认保守拒绝**:空串、`"True"`/`"FALSE"` 等大小写不匹配字面量、以及任意非 `true`/`false` 字面量(如 `"yes"`、`"1"`、`"$.status == 'completed'"`)均回退为 `Ok(false)` 并 `tracing::warn!`。即**条件未识别时阻断而非放行**(B-260614-02 反转)——条件分支写错或引擎未实现时不静默放行。 > 现状:JSON Path、比较运算、`contains`、逻辑组合均未实现(`context` 参数当前未被使用,仅占位对齐签名)。详见 [B 路线决策接入需求](#🔮-决策能力接入需求b-路线)。 ## 依赖关系 ``` df-core (类型、事件、错误) ← df-workflow ← df-nodes (节点实现) ← df-stages (阶段节点) ``` ## 文件结构 ``` crates/df-workflow/src/ ├── lib.rs — 模块入口 ├── dag.rs — DAG 数据结构与拓扑排序 ├── dag_def.rs — 可序列化 DAG 定义(DagDef/NodeDef/EdgeDef) ├── executor.rs — DagExecutor ├── node.rs — Node trait + NodeContext/Output ├── state.rs — 状态机 ├── eventbus.rs — EventBus(broadcast) ├── registry.rs — NodeRegistry(节点类型注册表) └── conditions.rs — 条件表达式引擎 ``` ## 🔮 决策能力接入需求(B 路线) > 2026-06-12 记录。executor 分层并行已具骨架,conditions 为最大空壳。 AI Chat(B 路线)从单链 ReAct 升级为规划式协作时,本引擎需补两处: 1. **`condition.rs::ConditionEngine::evaluate()`** —— 当前只认 `"true"` / `"false"` 字面量,未识别表达式默认 `Ok(false)`(保守拒绝),无法据 AI 输出做条件分支。需补: - JSON Path 取值(从上游节点输出读字段) - 比较运算(`==` `!=` `>` `<` `>=` `<=`) - `contains` / 字符串匹配 - `and` / `or` / `not` 组合 2. **executor 分层并行接入 agentic loop** —— `topological_layers` + `join_all` 已实现(测试 `test_same_layer_runs_in_parallel`),但仅喂静态 DAG。需让 `run_agentic_loop` 内动态生成的 DAG 走这条并行通道,而非单轮串行 `process_tool_calls`。 详见 [Phase 2 计划 - 决策能力升级](../07-项目管理/Phase2计划-2026-06-12.md)。 ## 相关文档 - [df-nodes 节点集合](./df-nodes-节点集合-2026-06-12.md) - [Phase1 架构决策](../02-架构设计/滚动规范/Phase1架构决策-2026-06-12.md)