# graph-monitor 原语契约(Primitive Contract) > 状态:设计已定,已实现(阶段 A + B)。 > 定位:graph-monitor 从「固定 DAG 可视化」向「可进化工作流引擎」演进的第一份契约。 ## 1. 目标与分工 - **Agent 负责编排**:根据任务动态产出「图编辑指令」,决定走哪些原语、如何组合。 - **引擎负责执行**:提供丰富、健壮的原语库,并**严格、确定性**地执行;原语缺能力或出错时回退,绝不悄悄兜底。 - **graph-monitor 负责可视化**:把执行真相与「图如何进化」实时呈现。 ## 2. 可进化模型(B:incremental) run 持有自己的可变拓扑快照 `run.graph`(初始 = workflow 深拷贝)。agent 通过节点 output 里的 `ops` 数组产出**图编辑指令**,引擎**先整体校验、后原子应用**,并 emit `graph/mutate` 事件。 ### 2.1 图编辑指令(mutation ops) ```json { "ops": [ { "op": "addNode", "node": { "id": "x", "kind": "fn", "code": "return 'hi';" } }, { "op": "addEdge", "edge": { "src": "a", "dst": "x" } }, { "op": "rewire", "edge": { "src": "a", "dst": "x" } }, { "op": "setEntry", "nodeId": "x" }, { "op": "skip", "nodeId": "y" } ] } ``` | op | 载荷 | 语义 | |---|---|---| | `addNode` | `node` | 新增节点(id 唯一,非 START/END) | | `addEdge` | `edge` | 新增边(src/dst 引用存在或为 START/END) | | `rewire` | `edge` | 用 `src`+`dst` 重指已有边(按 `src` 定位旧边) | | `setEntry` | `nodeId` | 改变入口 | | `skip` | `nodeId` | 后续调度跳过该节点(短路到其出边) | ### 2.2 引擎强制的不变量 任一条违反 → **整体拒绝**该批 ops,作为错误反馈回 agent(不部分应用): 1. 节点 id 唯一,且不为 `START`/`END`; 2. 边 `src`/`dst` 引用存在(或为 `START`/`END`); 3. **mutation 只作用于「尚未执行的未来拓扑」**——已完成节点/边/已 emit 历史一律不改(可视化可审计的根基); 4. 允许引入环(为 `loop` 铺路),由 `guard` 上限兜底防失控。 ## 3. 原语契约 ### 3.1 `gate` —— 条件放行/拦截 ```json { "id": "g1", "kind": "gate", "label": "审批", "condition": { "source": "router", "name": "containsYes" }, "pass": "next", "fail": null } ``` | 维度 | 契约 | |---|---| | 输入 | 上游 state(可 `reads` 选取) | | 通过 | **透传** state 到 `pass`,不改动 | | 拦截 | 有 `fail` 目标 → 透传到 `fail`;无 `fail` → run 以 `status:"blocked"` 结束(非失败、非继续) | | 谓词异常 | 一律按「拦截」处理,记录原因,不升级为 run 失败 | | 谓词来源 | `source:"router"`(命名目录)或 `source:"code"`(内联 JS,经 `codeRuntime` 执行,可用 `graph.input()`/`graph.stateGet()`;异常/空一律按拦截处理) | ### 3.2 `switch` —— 多选一 ```json { "id": "s1", "kind": "switch", "label": "意图路由", "cases": [ { "when": { "source": "router", "name": "containsYes" }, "to": "a" }, { "when": { "source": "router", "name": "isLong" }, "to": "b" } ], "default": "c" } ``` | 维度 | 契约 | |---|---| | 匹配 | 按 `cases` 顺序,**第一个**命中的 `when` 走对应 `to`(短路) | | `when` 谓词 | 同 `gate`:`source:"router"` 或 `source:"code"` | | 兜底 | `default` **必填**,保证「**恰好一条**」 | | 输出 | 透传 state 到选中分支 | | 校验期 | 强制 `default` 存在、所有 `to` 引用存在 | ### 3.3 `subgraph` —— 组合(增原语主通道) ```json { "id": "sg1", "kind": "subgraph", "label": "guarded-tool", "graph": { "entry": "g", "nodes": [ ... ], "edges": [ ... ] } } ``` | 维度 | 契约 | |---|---| | 进入 | 用子图 `entry` 起**局部遍历**(递归复用核心循环,天然嵌套) | | 输出 | 子图最终 state 透传回外层继续 | | 可视化 | 画布内嵌套一层子图(树结构) | | 定位 | **增原语主通道**:agent 可 `addNode` 一个 subgraph,把刚固化的套路「现场封装成新原语」 | ### 3.4 `loop` —— 迭代 ```json { "id": "lp1", "kind": "loop", "label": "直到收敛", "while": { "source": "code", "code": "return (await graph.input()) !== 'done'" }, "maxIterations": 10, "graph": { "entry": "step", "nodes": [ ... ], "edges": [ ... ] } } ``` | 维度 | 契约 | |---|---| | 进入 | 用 `graph` 起局部遍历(复用 `runNestedGraph`),每轮以**上一轮最终 state** 作为输入 | | 条件 | `while` 谓词(`source:"router"` / `"code"`)对每轮最终 output 求值,false 即退出;`while` 必填 | | 上限 | `maxIterations` 缺省 10,硬上限防失控 | | 输出 | 最后一轮最终 output 透传回外层 | | 事件 | emit `graph/loop`(`iterations` / `maxIterations`) | ### 3.5 `retry` —— 失败重试 ```json { "id": "r1", "kind": "retry", "label": "重试", "maxAttempts": 3, "graph": { "entry": "step", "nodes": [ ... ], "edges": [ ... ] } } ``` | 维度 | 契约 | |---|---| | 进入 | 每次尝试用**同一份输入**重跑 `graph`(不复用上次输出) | | 判定 | 内嵌最终 output 以 `ERROR` 前缀(`/^ERROR/i`)视为失败 | | 上限 | `maxAttempts` 缺省 3;耗尽仍失败 → 透传最后一次(错误)output,**不升级为 run 失败** | | 输出 | 首次成功的 output 透传 | | 事件 | emit `graph/retry`(`attempts` / `maxAttempts` / `exhausted`) | ### 3.6 `timeout` —— 时间预算 ```json { "id": "t1", "kind": "timeout", "label": "限时", "ms": 5000, "graph": { "entry": "step", "nodes": [ ... ], "edges": [ ... ] } } ``` | 维度 | 契约 | |---|---| | 进入 | 用 `ms` 预算跑 `graph`(缺省 5000) | | 超时 | 预算先到 → 输出 `ERROR: timeout after {ms}ms`(不为 run 失败),孤儿内嵌循环 best-effort 放弃 | | 输出 | 预算内完成 → 透传最终 output | | 事件 | emit `graph/timeout`(`ms` / `timedOut`) | ## 4. 与现有引擎的映射 | 现有 | 改动 | |---|---| | `walk(run, workflow)` | `walk(run)`,读 `run.graph`(可变快照) | | `scheduleNext(task, output, workflow, run, ...)` | 读 `graph` 参数(= `run.graph` 或子图 `node.graph`) | | `executeNode(task, workflow, run, ...)` | 读 `graph` 参数;`subgraph`/`loop`/`retry`/`timeout` 节点递归调用核心循环(共享 `runNestedGraph`) | | `applyNodeOutput` 后 | 解析 `output.ops`(若有)→ `validateMutations` → 应用 → emit `graph/mutate` → 再调度 | | `validateWorkflow` | 增加 `gate`/`switch`/`subgraph`/`loop`/`retry`/`timeout` 静态校验 + `source:"code"` 谓词校验 | | `catalog()` | 暴露全部 kind 的描述 | ## 5. 分阶段 - **阶段 A(已实现)**:`gate` + `switch`(`source:"router"` 命名目录谓词)+ `subgraph` 静态执行;`run.graph` 可变拓扑 + mutation ops + `graph/mutate`。 - **阶段 B(已实现)**: - ✅ **agent 驱动产出 `ops` 的可靠通道** —— LLM/agent 节点设 `emitOps: true` 后,`OPS_PROMPT` 引导模型直接产出 `ops`,`extractOpsFromText` 容错解析(裸 JSON / ```json 围栏 / 括号片段),解析失败走 `opsError` 并以 `graph/mutate` ok:false 反馈。 - ✅ **`source:"code"` 内联谓词** —— `gate`/`switch`/`loop` 的 `condition`/`when`/`while` 可写内联 JS,经 `codeRuntime` 执行(`graph.input()`/`graph.stateGet()`),异常/空归 false(拦截)。 - ✅ **`loop` / `retry` / `timeout`** —— 三类控制流原语(见 §3.4–3.6)。