Graph Engineering:从Loop到Graph,以及 graph skill 的一次真实实践
继 prompt engineering、context engineering、harness engineering、loop engineering 之后,2026 年 7 月又冒出来一个新词:graph engineering(图工程)。
X 上照例吵成一团:有人说这是又一次新瓶装旧酒的炒作,"图"根本不是什么新东西;有人说循环(loop)已经过时,图才是 Agent 编排的终极形态。towards_AI 那篇《这该死的图工程到底是什么》给了一个很克制的判断:
There is no new product called Graph. And loops are not going anywhere.
loops versus graphs is the wrong argument. Graphs have loops.
这篇文章分三部分:先讲清楚图工程到底是什么(历史、概念、理论),再介绍一个把图工程做成可用工具的 skill —— goal-workflow 里的 /graph,最后拿我自己正在跑的一个真实项目 pigo,看看 /graph 跑起来到底长什么样、产出什么东西。
第一部分:图工程到底是什么
一条命名的演化线
要理解图工程,先把这几年的命名演化摆出来看:
- prompt engineering:怎么把一句话问好,让模型给出想要的答案。
- context engineering:单次调用喂什么、不喂什么,窗口里装什么。
- harness engineering:围绕模型搭一整套系统(工具、记忆、验证、恢复),让 Agent 在生产环境里可靠。
- loop engineering:不再把 Agent 当"一问一答",而是设计"行动→观察→评估→调整"的循环,让它能自主迭代。
- graph engineering:设计多个过程(往往就是多个循环)之间的关系——谁在谁之前、谁能并行、谁的输出喂给谁。
towards_AI 一句话点破本质:
Graph engineering is about designing the relationships between those processes. The name is new. But most of the hard problems are not.
也就是说,图工程不是发明了新东西,而是给"Agent 编排里那些老大难问题"起了个新名字。这些老问题包括:怎么拆任务、怎么表达依赖、哪些能并行、失败了怎么恢复、状态放哪儿。
循环 vs 图:一个假问题
社区最大的争论是"循环还是图"。但这个二分本身是错的。
towards_AI 给出的操作路径最清楚:
Start with the loop. Then draw what has to happen around it. At that point, you have a graph.
先有循环,就是那个最小的干活单元:读代码、改、测、再改。当你手里有好几个这样的循环要协调时,你自然会去画它们的先后和并行关系,画出来就是一张图。所以图不是来取代循环的,它更像是装循环的容器,图里头本来就跑着循环。
更深的理论根:都是状态机
Peter Steinberger 在 7 月中旬挑起过一场"循环还是图"的辩论,最后收束到一个更根本的答案:loops 和 graphs 本质上都是有限状态机(FSM)。
有限状态机的核心就一个式子:
形式化地写成五元组 M = (S, Σ, δ, s₀, F):状态集、事件集、转移函数、初始状态、终止状态集。这套东西的威力在于两点:
- 消灭不可能状态(illegal states unrepresentable):一个"加载中且已失败且已成功"的三重矛盾状态,在状态机里根本无法表达,因为转移函数 δ 里没有通向它的边。
- 转移函数即数据:δ 可以是一张表、一份 JSON,而不是一堆散落的
if/else。这让编排逻辑可读、可存档、可回放。
从 FSM 往上看:
- 循环是 FSM 的一种退化形态——通常是一两个状态之间反复转移。
- 图是 FSM 的完整展开——多个状态、多条边,天然支持分叉与汇合。
- Harel 的 Statecharts(1987) 在 FSM 上加了层级、并行和历史,正是现代 Agent 编排要的东西。
映射到 Agent,有几个很实际的推论:
- guard 边:某些转移要有守卫条件(比如"测试通过才能进入 ship 状态")。
- 持久化:状态要能落盘,Agent 崩了能从上一个状态恢复。
- 幻觉即"无边可走":如果模型吐出了一个当前状态下根本不存在的事件,转移函数找不到对应的边。这恰好是个天然的护栏点。
图工程绕不开的一个坑:Reality Anchor
图工程最锋利的一句警告来自 towards_AI:
Without anchors, a graph is a larger hallucination with better project management.
(没有现实锚点,图不过是一场被项目管理包装得更漂亮的大型幻觉。)
意思是,你可以画出一张无比精美的 DAG,节点整齐、依赖清晰、并行也漂亮,可要是每个节点的产出没跟真实世界挂上钩(测试真跑了吗?PR 真开了吗?CI 真过了吗?),那这张图不过是把幻觉组织得更有条理罢了。所以每个节点都得有个"现实锚点",一个能验证、真发生过的产物。
这其实就是上一节那台状态机的另一面。一个节点报告自己"完成",却拿不出真实产物,就等于状态机吐出了一个当前状态下根本走不通的转移——一次没有边可走的幻觉。锚点的作用,就是给"完成"这个事件配一条真实的边:PR 开了、测试绿了、CI 过了,转移才算数。少了这条边,图工程就退化成 Goodhart 定律在 Agent 系统里的老毛病:一个中间指标一旦变成目标,它就废了。
那怎么给一张图扎上锚点?落到工程上就三件事:
- 节点的"完成"由外部产物定义,不由 agent 自报。 每个节点退出时得留下一个能被独立验证的东西——一个 PR 号、一次绿色的测试、一条 CI 记录。agent 说"我做完了"不作数,
git log里查得到那条 commit 才作数。 - 依赖边升级成 guard 边。 下一个节点能不能开工,不看上一个节点声称完成,而看它的锚点是否真的落地了;锚点没到位,边就不放行,波次就卡在原地。
- 状态落盘、可复核。 整张图的进度写进一份可读文件,谁完成了、锚在哪个产物上一目了然,agent 崩了也能从上一个真实状态接着跑。
这三点到第三部分会变得很具体——我最在意 pigo 那份状态文件里的,恰恰不是漂亮的波次分层,而是 "status": "shipped", "pr": 191 这一行真实产物。
顺带厘清一个容易混的点,它决定了本文这张图到底是什么图。Dale Everett(Polygres)说的"图"是数据图,节点是数据、边是数据关系;而经典 FSM/Statecharts 传统里的"图"是控制图,节点是状态、边是转移。同一个字,两拨人指的是两种东西。本文谈的图工程、后面 /graph 排的那张 DAG,都是控制图——排的是"谁先谁后、谁能并行"的执行顺序,不是数据血缘。动手前先分清这一点,多数 Agent 编排要的是后者。
第二部分:把图工程做成工具 —— goal-workflow 的 /graph
理论讲完,来看落地。smallnest 的 goal-workflow skill 套件把图工程做成了一个能直接用的 skill:/graph。官网 https://goal.rpcx.io 的介绍已经很到位,这里提炼要点。
它在整个工作流里的位置
goal-workflow 是一套覆盖"需求→规划→执行→交付"的 skill 链:
/prd 写需求文档,/prd-to-spec 转成技术规格,/to-issues 拆成一个个 issue。到执行环节有两个分叉:
/loop-it:把 issue 一个接一个串行做完。简单、稳、适合强依赖的线性任务。/graph:把 issue 之间的依赖建成 DAG,能并行的就并行做。适合任务多、依赖交错的场景。
/graph 的心智模型
/graph 借鉴了 LangGraph 的 StateGraph 和 Pregel/BSP 的 superstep 语义,再加上 Claude Code 的 dynamic workflow 思想。核心概念表:
| 概念 | 含义 |
|---|---|
| Node(节点) | 一个独立可完成的子任务(对应一个 issue) |
| Edge(边) | 节点间的依赖关系(A 完成才能开始 B) |
| Superstep / Wave(超步/波次) | 拓扑分层后,同一层的节点可并行执行 |
| Fan-out(扇出) | 一个波次里同时派多个子 agent 干活 |
| Fan-in(扇入) | 波次屏障:本波全部完成才进入下一波 |
| State channel | .graph_state 文件,持久化整张图的执行状态 |
| Live tracker | graph.html,可视化实时进度 |
| Dynamic re-plan | 执行中可以根据结果动态重规划 |
执行流程
/graph 跑起来大致是这么几步:
- 分解:把 PRD/issues 拆成节点。
- 建 DAG:给节点之间连依赖边。
- 拓扑分层:用类似 Kahn 算法的拓扑排序把节点分成一波一波(waves),同一波无依赖、可并行。
- 确认:把分好的波次拿给人确认。
- 逐波执行:每一波 fan-out 出多个子 agent(各自在独立的 git worktree 里干活,互不干扰),全部完成后 fan-in,再进下一波。
每个子 agent 用统一的 prompt 模板派发,跑在自己的 worktree 分支上,完成后开 PR。状态实时写进 .graph_state,graph.html 负责可视化。回头看第一部分的理论,这套东西其实就是一台状态机:它落了盘,每个节点靠 PR 和测试锚在现实里,波次之间还有 guard 边卡着(依赖没满足就进不了下一波)。
安装:
第三部分:/graph 的一次真实实践 —— pigo 的 provider 对齐
光看文档不够,来看一个正在跑的真实项目:pigo(smallnest/pigo,pi 的 Go 复刻版)。我正用 /graph 给它做一个功能:Provider 环境变量与默认端点对齐 pi。
任务背景
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 来并行调度。
看 skill 的产物:.graph_state
/graph 跑起来后,第一个产物就是 .graph_state——整张 DAG 的持久化状态。pigo 里这份文件长这样(节选):
把它翻译成人话,这张 DAG 分成 5 个波次:
几个理论概念在这里全都对上号了:
- 拓扑分层:182 是所有人的地基(
deps: []),必须先做,独占 Wave 0。 - Fan-out:Wave 1 的 183 和 184 都只依赖 182,互不依赖,于是同时派了两个子 agent 去做。Wave 3 更狠,186/187/188 三路并行。
- Fan-in 屏障:185 依赖 184,所以必须等 Wave 1 整波做完(
current_wave从 1 推进到 2)才能开始。 - 现实锚点:182 的 status 是
shipped,pr: 191。这不是"agent 说自己做完了",而是真的开了 PR #191、真的合进了 master(git log里那条808267d Add central provider registry as single source of truth (#182) (#191))。这正是 towards_AI 说的 anchor:判断一个节点完没完,看的是真实产物,不是模型的自我报告。
另一个产物:git worktree 隔离
/graph 的并行不是在同一个工作目录里瞎改,而是给每个并行节点开一个独立的 git worktree:
183 和 184 各自在自己的分支、自己的目录里跑,互不干扰,做完各自开 PR。所谓"扇出多个子 agent 并行",落到地上就是这么回事:状态机的并行分支,对应文件系统里几个平行的 worktree。
还有 graph.html
/graph 还会渲染一个 graph.html 实时可视化整张图的进度(哪个节点绿了、哪个在跑、哪个还灰着),由 skills/graph/scripts/render_graph_html.py 从 .graph_state 生成:
小结这次实践
绕了一圈,理论和工具在这个项目里终于对上了。图工程说到底是在设计循环之间的关系,goal-workflow 的 /graph 把这件事做成了能跑的东西,而 pigo 这 9 个节点、5 个波次就是它跑起来的样子:182 已经通过 PR #191 合进 master,183 和 184 还在各自的 worktree 里推。
我最在意的其实就是 .graph_state 里那行 "status": "shipped", "pr": 191。没有它,这张图再漂亮也只是 towards_AI 说的"组织得更好看的幻觉"。有了它,图才算真的在干活。
参考
- towards_AI《这该死的图工程到底是什么》,2026-07-20: https://x.com/towards_AI/article/2078892237287801283
- Peter Steinberger 的"循环还是图"辩论,2026-07-18, 见文章中首图
- goal-workflow
/graphskill:https://goal.rpcx.io - 实践项目:smallnest/pigo:https://github.com/smallnest/pigo