先看懂这篇文章在讲什么
最简单的理解是:作者想把“让一个 AI 自己不断想下一步”,改造成“先把允许发生的步骤画成路线图,再让 AI 只在路线图允许的范围内工作”。
普通 agent loop 很像把任务交给一个人,然后不断问他:“现在情况怎样?下一步你准备做什么?做完了吗?”这个人可以读代码、运行命令、修改文件、查看结果,再决定继续还是停止。它的优点是灵活,缺点是整个过程藏在模型每一轮的临时判断里:你很难提前知道它会走哪条路,也很难对不同步骤设置不同权限。
作者提出的 graph workflow 更像一张预先画好的办事流程图。每个方框是一项工作,例如“判断 bug 类型”或“运行测试”;箭头规定工作可以流向哪里;一份共享状态记录已经知道的事实。AI 仍然可以参与判断和修复,但它不能凭空发明一个未经允许的新步骤。
Loop / 循环
同一个 agent 反复观察、行动、检查,由它动态决定下一步,适合开放式探索。
Graph / 图工作流
系统预先声明有哪些步骤和允许的转移,适合分支、审批、并行和可审计流程。
作者的主张
任务一旦不再是单一循环,而出现不同角色和路径,就应该把隐式控制流提升为显式图。
原文的推理路线:从一个循环,逐步长成一张图
作者不是一开始就抛出完整框架,而是沿着“现有循环哪里不够用”逐层加能力。理解下面六步,就理解了原文主体。
- 先有 agent loop。系统把 bug 交给 agent;agent 查看问题、尝试修复、运行验证。如果验证失败,就带着新反馈再来一轮,直到成功或达到步数上限。
- 再把一次循环拆成节点。“分诊”“复现”“修代码”“补测试”“最终验证”不再混在一个大提示词里,而是成为职责不同的 node。每个节点读取状态,完成一项工作,再写回结果。
- 用边规定下一步。普通 edge 表示固定顺序;conditional edge 会查看当前状态再选择路线。例如网络故障走重试,代码逻辑错误走修复,数据缺失走补数据。
- 允许局部回环。如果最终验证失败,流程可以回到修复节点继续迭代。因此 graph 并不排斥 loop;loop 只是图里的一条回边。
- 把独立工作并行化。作者设想“修改实现”和“更新测试”可以同时执行,然后在 join 节点汇合。若两支都更新状态,就用 reducer 规定如何合并字段。
- 加入人工审批和恢复。高风险步骤到来时保存 state、暂停执行,等人类批准后再继续。作者据此认为,显式状态让 checkpoint 和 resume 变得自然。
所以,原文标题所说的“一个循环不够时,循环会长成什么”,答案不是“换掉循环”,而是:把多个局部循环、条件分支、并行任务和人工关卡,组织到一张可见的控制流图里。
把全文案例完整跑一遍:一个 bug 怎样经过这张图
假设系统收到一个问题:“用户付款后订单仍显示未支付。”工作流不是立刻让同一个 agent 从头查到尾,而是按下面的路线推进。
贯穿这些步骤的是一份 state。开始时它只有 bug 描述;复现后多了日志;分诊后多了故障类型;修复后多了 patch;验证后多了测试结果。节点不需要重新阅读全部对话来猜测进度,只需读取自己关心的字段。
如果最终测试失败,边会把流程送回修复节点;如果判断为短暂网络故障,则进入重试节点;如果触及计费逻辑,则进入等待审批状态。图的价值就在这里:不同情况可以走不同路线,但所有合法路线都提前登记在系统里。
继续阅读前,只需要认识这些术语
现在再分析:方向正确,但示例还不是完整运行时
这是一篇有价值的架构启蒙文,不是一份可直接采用的图执行引擎设计。它最值得保留的不是“loop 会进化成 graph”这个比喻,而是更精确的一句话:把哪些步骤可以发生、谁有权执行、何时必须停下,从模型的自由裁量移回程序契约。
方向成立
节点负责工作、边负责路由、状态负责传递,是把隐式 agent 行为变成可观察 workflow 的正确第一步。
二分法不成立
循环与图没有计算能力上的高低之分;文章自己的图执行器就是一个循环。变化的是控制流表示、约束与治理方式。
实现未闭环
默认入口、并行分发、join、reducer、checkpoint 与 resume 均有缺口,示例更适合作为伪代码阅读。
它试图解决什么:提示词里的隐式流程失控
文章用一个 bug 流水线贯穿全文:分诊、复现、识别故障类型、修复、更新测试、验证;网络抖动、逻辑错误、数据缺口走不同分支;修复涉及计费时还要等待人工批准。作者认为,继续把这一切塞进“自己决定下一步”的提示词,会让系统的真实控制流只能在运行后猜测。
这个问题真实存在。Anthropic 对 agentic system 的划分也采用相似边界:workflow 由预定义代码路径编排模型与工具,agent 则由模型动态决定过程。文章真正要做的,是从后者收回一部分决策权,形成“有限的允许转移集合”。
它还是作者系列文章的自然延伸:早期的 Loop Engineering 强调外部验证与停止条件,Conductor 一文加入多 worker、依赖图和集成测试,本篇再把这些隐式依赖提升为显式 graph。这个演进脉络解释了文章为何如此强调“不要让节点决定下一步”。
机制拆解:外层 workflow,内层 agent loop
把文章的直觉改写成可落地模型,可以分成五步。输入是 bug 与运行上下文;处理过程不是一个大 agent 连续自由行动,而是让每个阶段只看到所需状态,并把结果写成结构化更新;输出既包括修复产物,也包括可追溯的执行证据。
这里的关键不是把所有判断都写死。分诊仍可由模型完成,修复仍可由 agent 自主探索;但模型只能提出结构化状态,路由器只接受登记过的目标,关键副作用只能在审批后发生。模型负责“不确定空间里的搜索”,workflow 负责“确定边界里的治理”。
文章的三要素只覆盖了第一层
真正的图引擎有三层:拓扑、调度、持久化
文章把一个 current 字符串、节点字典和边字典称为图引擎。在线性或单分支执行时,这其实是一个有限状态机解释器;一旦允许列表返回多个节点,运行模型已经改变,不能继续用同一条 current 指针假装并行存在。
| 层级 | 必须回答的问题 | 文章覆盖 | 缺失会怎样 |
|---|---|---|---|
| 拓扑 / 状态机 | 有哪些节点、允许哪些边、入口与终点是什么? | 覆盖较多 | 路由隐式、无法静态检查、权限边界不清 |
| 调度 / 数据流 | 哪些节点活跃、并行何时开始、join 等待哪些本次激活、失败如何取消兄弟分支? | 只给接口想象 | 列表无法执行、join 死锁、旧分支结果污染新一轮 |
| 持久化 / durable execution | 崩溃后从哪里继续、哪些副作用已发生、重放是否安全、代码版本如何迁移? | 以单个 JSON 文件带过 | 从入口重跑、重复扣款、重复写记录、恢复到错误版本 |
LangGraph 的公开运行时用 message passing 与 super-step 描述并行:同一 super-step 的活跃节点一起执行,直到没有活跃节点和在途消息才终止。Temporal 则把命令与事件写入有序历史,通过确定性重放恢复流程。这两种实现不同,却共同说明:graph topology 只是声明,scheduler 与 history 才让声明变成可恢复执行。
代码审计:概念示意可以,按原样运行不行
将文章给出的主类与扩展示例按原结构组合,可以稳定复现以下断点。这些是代码直接行为,不依赖对作者意图的猜测。
| 位置 | 文章意图 | 可复现事实 | 应有修复 |
|---|---|---|---|
| 默认入口 | START 作为虚拟起点 | entry 被设为 START,但节点表没有该键;第一个执行动作直接触发 KeyError: 'START' | 把 START 当虚拟节点解析到登记过的 entry,或显式设置入口并在编译期验证 |
| 结构验证 | 启动前抓住断边 | 只检查“每个已登记节点是否有出边”;不检查入口、目标节点、可达性、终点可达、条件路由返回范围 | 编译期做入口/终点、源/目标、可达性、非法环与路由映射检查 |
| fan-out | 路由器返回节点列表并行执行 | runner 下一轮把列表当节点字典键,触发不可哈希错误;正文调用的 add_join 在类中不存在 | 引入 active task set、队列或 super-step 调度器,并定义 branch-local input |
| fan-in | 等待 code 与 tests 两支完成 | 没有 join token、分支完成记录、错误传播、超时、取消和多轮隔离 | 用 fan-out 实例生成动态 barrier;记录每支状态与失败策略 |
| checkpoint | 触及 billing 时暂停 | 暂停被编码为 END,与正常完成不可区分;只写业务字典,不保存下一节点与活跃分支 | 使用明确的 WAITING 终态、持久 cursor、待审批事件与恢复令牌 |
| resume | 注入人工答案后继续 | graph.run(state) 每次从 entry 开始;最小复现中入口节点被重复执行 | 从 checkpoint 的 next task / program counter 恢复,并保证重放副作用幂等 |
| 节点安全 | 执行复现命令 | 命令字符串使用 shell=True;若字符串受 issue、模型或外部输入影响,会扩大 shell 注入面 | 使用参数数组、命令 allowlist、隔离执行环境与节点级权限 |
默认入口 → KeyError: 'START'
fan-out 列表 → TypeError: list cannot be used as a node key
join API → 未实现
恢复后入口访问 → 1 次变 2 次
此外,文章称节点应是“纯状态函数”,但示例节点会启动进程、写文件并原地修改字典。真实节点当然可以有副作用,问题不在“不纯”,而在于没有区分可重放的决策和不可随意重放的外部动作。把二者拆开,才是恢复安全的起点。
Reducer 不是魔法:有合并函数,不等于并发确定
文章正确指出,两个并行分支同时写同一字段时不能默认 last-write-wins;但它随后把“为每个字段注册 reducer”写成了充分条件。实际只解决了“如何调用一个二元函数”,没有保证不同完成顺序、批次分组与失败重试得到同一结果。
| 示例字段 | 文章 reducer | 真实性质 | 风险 |
|---|---|---|---|
messages | 旧列表 + 新列表 | 能保留两边内容,但顺序随分支合并顺序改变;重复投递会重复追加 | 日志次序不稳定、重试产生重复记录 |
files | 集合去重后排序 | 对纯文件名集合较接近交换、结合与幂等 | 只能合并“有哪些文件”,不能合并同一文件的补丁冲突 |
risk | 取最大值 | 若风险是同一量纲全序标尺,性质良好 | 多维风险被压成单值时可能丢失语义 |
status | 新值覆盖旧值 | 结果直接依赖谁最后合并 | code 先 / tests 先会得到不同终态,正是文章声称已消除的问题 |
按文章函数分别以 A→B 与 B→A 合并,消息顺序分别是 [code, tests] 与 [tests, code],status 分别停在 tests_done 与 code_done。所以“无论哪支先完成都可预测”并未成立。
结合律
merge(merge(a,b),c) 与 merge(a,merge(b,c)) 相同,避免批次分组改变结果。
交换律
merge(a,b) 与 merge(b,a) 相同,适用于不希望完成先后影响语义的并发字段。
幂等或去重
同一个带唯一 ID 的更新重复到达时不产生第二份副作用,适应至少一次执行与恢复重试。
并非所有 reducer 都必须同时满足三项;关键是调度器承诺什么。若承诺固定、可重放的合并顺序,列表可以保序;若允许按完成顺序合并,又希望结果不受时序影响,就需要交换性;若任务可能重试,则要有幂等更新或稳定去重键。生产设计要先写清执行语义,再选 reducer,不能反过来。
显式 state 是必要条件,不会让暂停与恢复“几乎免费”
文章把“所有状态在一个对象里”直接推到“任意节点后序列化即可恢复”。这忽略了 workflow 至少有四类状态,而示例只保存了第一类。
LangGraph 的 interrupt 需要 checkpointer 与 thread ID,并明确说明节点在恢复时可能从函数开头重新执行,因此中断前的副作用必须幂等。Temporal 更进一步,以有序 Event History 为事实来源;恢复时重放 workflow 决策,但复用已经记录的外部活动结果。两者都远超“把 dict 写进 JSON”。
一个生产级人工审批还要区分 WAITING 与 SUCCEEDED、为请求分配稳定 ID、在审批后校验版本与权限、处理拒绝 / 超时 / 重复回答,并确保批准前没有执行受保护副作用。文章示例在写 checkpoint 后才设置暂停标记,又把暂停路由到 END;这既没有持久化准确 cursor,也没有表达等待是一种仍可继续的开放状态。
证据与新颖性:表达新鲜,机制并不新
已核验事实 文章完整给出 49 个正文块和 6 段代码,但没有附可运行仓库、单元测试、性能比较、故障注入或 loop / graph 的对照数据。代码中宣称的并行与 join 接口不在主类实现中。
已核验事实 节点、边、状态、分支、并发与持久化并不是新的 agent 专属机制。Harel 1987 年的 statecharts 已把层次、并发和通信引入复杂状态系统;Pregel 用消息与 super-step 定义图并行;LangGraph 将相近概念包装进 agent workflow;Temporal 则提供长期执行所需的事件历史和重放。
分析推断 本文的原创价值主要是教学叙事:它用同一个 bug pipeline 把 loop、router、cycle、fan-out/fan-in、reducer 和 human gate 串成一条容易记忆的路线。这种压缩很有用,但不能把可读性误当成实现完备性。
分析推断 作者系列文章反复强调外部验证、预算、超时和隔离;本篇移到 graph 后却只保留 max_steps。这暴露了一个常见迁移陷阱:拓扑变清楚后,人会误以为系统也更安全。实际上一个节点仍可永久阻塞或一次花光预算,图级步数上限无法替代节点级 timeout、费用、权限、重试和取消。
什么时候用哪一层:不要从 loop 直接跳到自制图框架
| 任务形态 | 最小合适抽象 | 升级触发器 | 避免的过度设计 |
|---|---|---|---|
| 一次模型调用即可完成,输入输出稳定 | 单调用 + 结构化输出 + eval | 需要环境反馈后再改 | 不要为了“agent”加循环 |
| 同一个动作围绕一个验证器反复修正 | 有上限的 loop | 出现不同权限、多个终态、人工等待或多种重试政策 | 不要把简单 retry 画成十个节点 |
| 有明确阶段、条件路由与审批,但单次只激活一条路径 | 类型化状态机 / workflow | 需要真正并行、动态 map-reduce 或多分支 join | 异构执行者本身不要求并发图 |
| 独立任务可并行,存在 fan-out / fan-in | 具备任务隔离与 barrier 语义的数据流运行时 | 流程跨进程、跨天、需容灾和重放 | 不要用共享 dict 模拟调度器 |
| 长时间运行、外部副作用、人工审批、故障恢复 | 成熟 durable workflow engine 或具备等价保证的运行时 | 只有在约束非常特殊且有完整测试时才自研 | 不要用单个 JSON 文件承担事务与恢复 |
对文章的 bug pipeline,合理最小实现不是复制“50 行引擎”,而是:
- 分诊节点输出有限枚举、置信度、证据与未知状态;router 对输出做 schema 与 allowlist 校验。
- 每条分支拥有不同权限、环境、模型、时间与费用上限;未知分类或高风险动作安全失败到人工。
- 局部修复节点内部运行 agent loop,但只提交不可变 patch / commit 与验证证据。
- 只有经依赖分析证明独立的工作才并行;合并时使用动态 branch token、固定基线与集成验证。
- 审批是可恢复 interrupt,不是 END;受保护副作用放在审批后,并带幂等键。
- 终态至少区分 succeeded、failed、cancelled、timed_out 与 waiting,不能把“停止”都当“完成”。
独立 Insight:真正被“收回”的应是能力,不只是路线
文章把 graph 的价值描述为收回“下一步去哪”的决定权。更完整的安全模型还要收回三种能力:节点能看什么、能改什么、最多能消耗什么。若所有节点仍共享同一文件系统、凭证、网络和无限预算,那么显式边只让流程更好看,没有缩小任何 blast radius。
Control-plane graph
负责路由、权限、预算、版本、审批、终态与审计。它应尽可能确定、可重放、可静态检查。
Data-plane agent
负责在被授权的局部环境中探索、写代码、调用工具。它可以灵活,但每次激活都有输入、输出与资源边界。
因此,“什么时候从 loop 升级到 graph”的最佳信号,不只是出现第一个 fork,而是出现第一个必须由系统而非模型强制执行的差异化政策:不同权限、不同验证器、不同重试策略、不同预算、需要跨故障存活的等待,或不能重复发生的副作用。此时 graph 才从视觉重构变成治理边界。
边界、反例与什么会推翻当前判断
- 本轮完整核对主文、全部代码与作者相关系列上下文;主帖显示一条回复和一条引用,但公开页面没有返回其正文,因此无法判断是否存在作者后续修正或外部技术反驳。
- 文章没有发布可运行仓库;本轮只能审计其公开代码块,不能排除作者在未链接实现中已经补齐入口、调度、join 与恢复。
- 代码复现证明的是“公开示例不闭环”,不是“图编排无效”。成熟运行时已经证明这类抽象可以成立。
- 这里没有做 loop 与 graph 的真实团队 A/B 实验,关于可维护性、故障率和成本的判断属于由执行语义推导的工程分析。
- 若作者发布带单元测试、故障注入、确定性 merge、动态 join、持久 cursor 与副作用幂等保证的完整实现,“示例只是伪代码”的判断应相应上调。
证据边界与资料索引
主材料的正文、代码块、标题与发布时间已核对;其互动计数会继续变化,回复与引用正文未纳入结论。代码行为来自公开片段的最小复现;关于成熟运行时的对照只采用官方文档与原始论文。文中“分析推断”是基于这些材料的工程判断,不代表作者或框架维护方声明。
- leanxbt:Graph Engineering: What a Loop Grows Into When One Cycle Is Not Enough
- leanxbt:Loop Engineering: You're Not Delegating Code, You're Delegating Judgment
- leanxbt:How to Build a Conductor: Orchestrating Multi-Agent Loops from Scratch
- Anthropic:Building effective agents
- LangGraph:Graph API overview
- LangGraph:Runtime 与 reducer 语义
- LangGraph:Interrupts 与 human-in-the-loop
- LangGraph:Persistence
- Temporal:Workflow、Event History 与 replay
- Temporal:Workflow Execution
- Python:subprocess security considerations
- David Harel:Statecharts: A Visual Formalism for Complex Systems
- Google Research:Pregel: a system for large-scale graph processing