第17章:Graph Engineering —— 从循环到图,为并行编排设计关系

"There is no new product called Graph. And loops are not going anywhere. loops versus graphs is the wrong argument. Graphs have loops."

没有一个叫"图"的新产品,循环也不会消失。循环还是图,本身就是个伪命题——图里本来就跑着循环。

——towards_AI《这该死的图工程到底是什么》,2026-07-20

第 12 章讲 Loop Engineering,把 Agent 从"一问一答"升级成"行动→观察→评估→调整"的自主循环。第 8 章讲 goal-workflow 怎么把需求、规划、执行、交付串成一条闭环。这两章都在打磨"一个循环怎么跑得更好"。

本章要往上再走一层:当你手里有好几个循环要一起跑,它们之间的先后、并行、依赖该怎么安排?

回答这个问题的新词,2026 年 7 月冒了出来:graph engineering(图工程)

X 上照例吵成一团。有人说这是又一次新瓶装旧酒的炒作,"图"根本不是什么新东西;有人说循环已经过时,图才是 Agent 编排的终极形态。towards_AI 那篇《这该死的图工程到底是什么》给了一个很克制的判断:图里本来就有循环,"循环还是图"是个假问题。

本章分三段走:先讲清楚图工程到底是什么,它的历史、概念和理论根;再看一个把图工程做成可用工具的 skill——goal-workflow 里的 /graph;最后拿一个正在跑的真实项目 pigo,看 /graph 跑起来到底长什么样、产出什么东西。

17.1 一条命名的演化线

要理解图工程,先把这几年的命名演化摆出来看:

towards_AI 一句话点破本质:图工程是在设计这些过程之间的关系,名字是新的,但大部分难题都不是新的。

也就是说,图工程不是发明了新东西,而是给"Agent 编排里那些老大难问题"起了个新名字。这些老问题包括:怎么拆任务、怎么表达依赖、哪些能并行、失败了怎么恢复、状态放哪儿。

17.2 循环 vs 图:一个假问题

社区最大的争论是"循环还是图"。但这个二分本身是错的。

towards_AI 给出的操作路径最清楚:先从循环开始,再画出循环周围必须发生的事,画出来的那张东西就是图。

先有循环,就是那个最小的干活单元:读代码、改、测、再改。当你手里有好几个这样的循环要协调时,你自然会去画它们的先后和并行关系,画出来就是一张图。所以图不是来取代循环的,它更像是装循环的容器,图里头本来就跑着循环。

17.3 更深的理论根:都是状态机

Peter Steinberger 在 7 月中旬挑起过一场"循环还是图"的辩论,最后收束到一个更根本的答案:loops 和 graphs 本质上都是有限状态机(FSM)

有限状态机的核心就一个式子:

(state, event) → nextState

形式化地写成五元组 M = (S, Σ, δ, s₀, F):状态集、事件集、转移函数、初始状态、终止状态集。这套东西的威力在于两点:

  1. 消灭不可能状态(illegal states unrepresentable):一个"加载中且已失败且已成功"的三重矛盾状态,在状态机里根本无法表达,因为转移函数 δ 里没有通向它的边。
  2. 转移函数即数据:δ 可以是一张表、一份 JSON,而不是一堆散落的 if/else。这让编排逻辑可读、可存档、可回放。

从 FSM 往上看:

映射到 Agent,有几个很实际的推论:

17.4 图工程绕不开的一个坑:Reality Anchor

图工程最锋利的一句警告来自 towards_AI:没有现实锚点,图不过是一场被项目管理包装得更漂亮的大型幻觉。

意思是,你可以画出一张无比精美的 DAG,节点整齐、依赖清晰、并行也漂亮,可要是每个节点的产出没跟真实世界挂上钩(测试真跑了吗?PR 真开了吗?CI 真过了吗?),那这张图不过是把幻觉组织得更有条理罢了。所以每个节点都得有个"现实锚点",一个能验证、真发生过的产物。

这其实就是上一节那台状态机的另一面。一个节点报告自己"完成",却拿不出真实产物,就等于状态机吐出了一个当前状态下根本走不通的转移——一次没有边可走的幻觉。锚点的作用,就是给"完成"这个事件配一条真实的边:PR 开了、测试绿了、CI 过了,转移才算数。少了这条边,图工程就退化成 Goodhart 定律在 Agent 系统里的老毛病:一个中间指标一旦变成目标,它就废了。

那怎么给一张图扎上锚点?落到工程上就三件事:

这三点到 17.6 会变得很具体——我最在意 pigo 那份状态文件里的,恰恰不是漂亮的波次分层,而是 "status": "shipped", "pr": 191 这一行真实产物。

顺带厘清一个容易混的点,它决定了本章这张图到底是什么图。Dale Everett(Polygres)说的"图"是数据图,节点是数据、边是数据关系;而经典 FSM/Statecharts 传统里的"图"是控制图,节点是状态、边是转移。同一个字,两拨人指的是两种东西。本章谈的图工程、后面 /graph 排的那张 DAG,都是控制图——排的是"谁先谁后、谁能并行"的执行顺序,不是数据血缘。动手前先分清这一点,多数 Agent 编排要的是后者。

17.5 把图工程做成工具:goal-workflow 的 /graph

理论讲完,来看落地。smallnest 的 goal-workflow skill 套件把图工程做成了一个能直接用的 skill:/graph。官网 https://goal.rpcx.io 的介绍已经很到位,这里提炼要点。

17.5.1 它在整个工作流里的位置

goal-workflow 是一套覆盖"需求→规划→执行→交付"的 skill 链:

/prd → /prd-to-spec → /to-issues ─┬─► /loop-it(串行执行)
                                   └─► /graph(并行执行)

/prd 写需求文档,/prd-to-spec 转成技术规格,/to-issues 拆成一个个 issue。到执行环节有两个分叉:

17.5.2 /graph 的心智模型

/graph 借鉴了 LangGraph 的 StateGraphPregel/BSP 的 superstep 语义,再加上 Claude Code 的 dynamic workflow 思想。核心概念表:

概念含义
Node(节点)一个独立可完成的子任务(对应一个 issue)
Edge(边)节点间的依赖关系(A 完成才能开始 B)
Superstep / Wave(超步/波次)拓扑分层后,同一层的节点可并行执行
Fan-out(扇出)一个波次里同时派多个子 agent 干活
Fan-in(扇入)波次屏障:本波全部完成才进入下一波
State channel.graph_state 文件,持久化整张图的执行状态
Live trackergraph.html,可视化实时进度
Dynamic re-plan执行中可以根据结果动态重规划

17.5.3 执行流程

/graph 跑起来大致是这么几步:

  1. 分解:把 PRD/issues 拆成节点。
  2. 建 DAG:给节点之间连依赖边。
  3. 拓扑分层:用类似 Kahn 算法的拓扑排序把节点分成一波一波(waves),同一波无依赖、可并行。
  4. 确认:把分好的波次拿给人确认。
  5. 逐波执行:每一波 fan-out 出多个子 agent(各自在独立的 git worktree 里干活,互不干扰),全部完成后 fan-in,再进下一波。

每个子 agent 用统一的 prompt 模板派发,跑在自己的 worktree 分支上,完成后开 PR。状态实时写进 .graph_stategraph.html 负责可视化。回头看前面的理论,这套东西其实就是一台状态机:它落了盘,每个节点靠 PR 和测试锚在现实里,波次之间还有 guard 边卡着(依赖没满足就进不了下一波)。

安装:

npx skills add smallnest/goal-workflow --skill graph

17.6 一次真实实践:pigo 的 provider 对齐

光看文档不够,来看一个正在跑的真实项目:pigo(smallnest/pigo,pi 的 Go 复刻版)。我正用 /graph 给它做一个功能:Provider 环境变量与默认端点对齐 pi

17.6.1 任务背景

pigo 当前只内置了 5 个可用的 LLM provider,API key 解析只覆盖 9 个环境变量;而 pi 支持约 30 个 provider,每个都有约定的环境变量名、默认 base_url 和所属协议。这个功能要让 pigo 全量对齐——用户设好 DEEPSEEK_API_KEY,加个 --provider deepseek,pigo 就能用内置默认端点直接对话。

这个任务被 /prd/to-issues 拆成了 9 个节点(#182–#190),节点之间有明确依赖,非常适合 /graph 来并行调度。

17.6.2 看 skill 的产物:.graph_state

/graph 跑起来后,第一个产物就是 .graph_state——整张 DAG 的持久化状态。pigo 里这份文件长这样(节选):

{
  "task": "Provider 环境变量与默认端点对齐 pi (#182-#190)",
  "repo": "smallnest/pigo",
  "waves": [[182], [183, 184], [185], [186, 187, 188], [189, 190]],
  "current_wave": 1,
  "nodes": {
    "182": { "title": "Provider 注册表(元数据单一来源)", "deps": [],
             "status": "shipped", "pr": 191,
             "branch": "feat/node-182-provider-registry" },
    "183": { "title": "统一 API key 解析到注册表", "deps": [182],
             "status": "in_progress" },
    "184": { "title": "--provider 显式标志", "deps": [182],
             "status": "in_progress" },
    "185": { "title": "通用 <PROVIDER>_BASE_URL 覆盖约定", "deps": [182, 184],
             "status": "pending" }
    // ... 186-190
  }
}

把它翻译成人话,这张 DAG 分成 5 个波次:

Wave 0:  [182]                    ← 建 provider 注册表(单一数据来源)
           │
Wave 1:  [183, 184]               ← key 解析 + --provider 标志(并行)
           │
Wave 2:  [185]                    ← <PROVIDER>_BASE_URL 覆盖约定
           │
Wave 3:  [186, 187, 188]          ← OpenAI兼容 / Anthropic / 特殊鉴权(三路并行)
           │
Wave 4:  [189, 190]              ← 模型目录扩展 + 文档(并行)

几个理论概念在这里全都对上号了:

17.6.3 另一个产物:git worktree 隔离

/graph 的并行不是在同一个工作目录里瞎改,而是给每个并行节点开一个独立的 git worktree:

.graph-worktrees/node-183   (feat/node-183-auth-registry)
.graph-worktrees/node-184   (feat/node-184-provider-flag)

183 和 184 各自在自己的分支、自己的目录里跑,互不干扰,做完各自开 PR。所谓"扇出多个子 agent 并行",落到地上就是这么回事:状态机的并行分支,对应文件系统里几个平行的 worktree。

17.6.4 还有 graph.html

/graph 还会渲染一个 graph.html 实时可视化整张图的进度(哪个节点绿了、哪个在跑、哪个还灰着),由 skills/graph/scripts/render_graph_html.py.graph_state 生成:

python3 skills/graph/scripts/render_graph_html.py .graph_state graph.html

17.7 与全书方法论的对接

图工程不是凭空长出来的,它坐在前面几章的方法论上再往上一层。

第 12 章 Loop Engineering 是同一条演化线的下一站。 第 12 章把 Agent 升级成能自主迭代的循环,本章要解决的是多个循环怎么协调。towards_AI 那句"图里本来就有循环"说的正是这个关系:图不取代循环,它是装循环的容器。第 12 章讲的持久化原语"Agent 会忘记,仓库不会",在这里长成了 .graph_state——整张图的状态落盘。

第 8 章 Goal Workflow 是同一套工具链。 /graph 就是 goal-workflow 里 /prd → /to-issues 之后的执行分叉之一,和 /loop-it 并列:一个串行,一个并行。第 8 章打磨的是单条闭环,本章把闭环之间的依赖排成了 DAG。

第 10 章 Harness Engineering 共享同一批硬骨头。 图工程列的那些"老大难问题"——拆任务、表达依赖、失败恢复、状态放哪儿——正是 Harness 要解决的工程问题。图只是把它们组织成了一张显式的关系图。

第 16 章 agent-skills 的反自欺机制殊途同归。 第 16 章用反合理化表阻止 Agent 跳过步骤,本章用现实锚点阻止节点谎报完成。两者的核心信念一样:Agent 会为自己找借口,得用制度性的手段逼它拿出真实证据。反合理化表管的是"别跳过工序",现实锚点管的是"别假装做完"。

17.8 本章小结

图工程说到底是在设计循环之间的关系——谁先谁后、谁能并行、谁的输出喂给谁。名字是新的,难题都是老的。往下刨,循环和图都是有限状态机的不同展开,这给了图工程一套现成的理论工具:guard 边、持久化、"幻觉即无边可走"。

但一张图再漂亮,也可能是"组织得更好看的幻觉"。分水岭是现实锚点:每个节点的完成必须由外部产物定义,由 guard 边把关,落盘可复核。goal-workflow 的 /graph 把这套东西做成了能跑的工具,而 pigo 那 9 个节点、5 个波次就是它跑起来的样子。

我最在意的其实就是 .graph_state 里那行 "status": "shipped", "pr": 191。没有它,这张图再漂亮也只是幻觉。有了它,图才算真的在干活。

留言板

欢迎在此分享你的想法!评论通过 GitHub Issues 存储,需要 GitHub 账号登录。