跳到主要内容

Graph Engineering 实战:用依赖、并行与治理组织 Coding Agent 工作

系列: Loop EngineeringGraph Engineering(本篇)


关系开始成为工程对象

做完 Loop Engineering 教程之后,我以为剩下的事情是"多跑几个 Loop"。结果发现问题并不在 Loop 内部,而在 Loop 之间。

Context 准备需要一个 Loop,但它的局部目标是"把上下文收窄到足够精确",而不是"实现功能"。实现需要一个 Loop,但它没有测试权限,也不应该自己决定什么时候算完。测试、Review、合并、审批——每一步都有不同的局部目标、不同的停止条件、不同的权限边界。当这些 Loop 开始传递状态、等待依赖、独立运行、提出异议、否决或升级时,一张隐藏的 Graph 才逐渐显现出来。

Graph Engineering 是把这些关系显式化的实践。 它不替代 Loop,它描述工作单元之间的拓扑、依赖、状态传递路径、并行机会和权限分配。同一套拓扑可以由一个 Agent 串行遍历,也可以由多个 worker 并行执行;带类型的信号和治理者共同决定哪些结果可以合并、停止、升级或修改目标。

"多个 Agent"不是架构。节点之间的关系才是。


一张 Graph 长什么样

我用 feature 变更流程作为例子,因为它足够具体,同时每一步的权限分离也足够典型:

context
-> implementation Loop
-> tests -----\
-> review -----> merge -> approval governor

下载经过验证的 Loop / Graph 示例。压缩包包含 runner、拓扑、演示入口和全部 12 项行为测试。

这张图里,context 是准备节点;implementation 是核心实现 Loop;testsreview 是相互独立的验证节点,可以并行运行;merge 等待两者完成后合并状态;approval 是治理节点,拥有最终决策权,包括修改整个 Graph 的目标。

用代码定义这张 Graph:

@dataclass(frozen=True)
class GraphDefinition:
aim: str
nodes: tuple[GraphNode, ...]
edges: tuple[GraphEdge, ...]
governor_node: str | None = None

aim 是整体目标,不是某个节点的局部目标。governor_node 指向审批节点,该节点可以修改 aim,而普通 Loop 不能。

边是有类型的:

边类型含义
dependency目标节点等待源节点完成后才能启动
handoff源节点将状态完整移交给目标节点
data_flow源节点的输出成为目标节点的输入之一
veto源节点可以阻断后续节点执行
approval目标节点具有最终审批或目标修改权

这不是通用标准分类,而是我在这个拓扑里用来区分路由语义的一套标记。


串行与并行是两个独立维度

拓扑决定节点之间的关系。执行模式决定运行时谁在跑这些节点。两者是分开的。

graph = build_feature_change_graph(approve_change)

serial = run_graph(graph, ExecutionMode.SERIAL)
parallel = run_graph(graph, ExecutionMode.PARALLEL)

串行模式下,执行器按依赖顺序逐批运行:

(('context',), ('implementation',), ('tests',), ('review',), ('merge',), ('approval',))

并行模式下,没有相互依赖的节点可以同时运行:

(('context',), ('implementation',), ('tests', 'review'), ('merge',), ('approval',))

testsreview 都依赖 implementation 的输出,但彼此之间没有依赖关系,所以并行模式把它们放进同一批次。并行执行器使用 Python 标准库的线程;这里的 worker 是线程,不是 Agent。

在这个确定性示例里,两种模式产生完全相同的合并状态:

{
"retry_limit": 3,
"validation_added": True,
"tests_passed": True,
"review_status": "ok",
}

这说明拓扑的正确性与执行模式无关。先把关系定义对,再决定用多少 worker 并行。如果拓扑本身有隐藏依赖,并行化会让它更早暴露,而不是掩盖它。


沿着流程追踪状态

context

准备节点先运行。当前示例只返回两个约束:compatibility_required=Truemax_retry_limit=3,没有读取项目文件或历史。它的输出进入共享 outputs,并让下游 implementation 节点变为可执行。这个节点可以是 Agent Loop,也可以是确定性函数——Graph 不在意。

implementation

实现 Loop 等待 context 完成后启动,但当前处理器没有读取 context 输出,而是直接运行 retry-feature Loop contract。它有自己的预算和停止条件,但没有权限决定是否合并,也没有权限修改整体 Graph 目标。

当 implementation Loop 遇到超出自己权限的判断时——比如,它判断需要把兼容性要求改成迁移要求——它不能自行执行,只能发出 escalation 信号,把决定交给治理者。

tests 和 review(并行)

tests 节点读取 implementation 的 LoopResult,记录它是否以 success 结束。review 节点返回预先配置的 signal 和 reviewed 标记;这个确定性示例并没有真正检查代码。review_status 由 merge 节点从 review signal 派生。两者在 implementation 完成后可以同时启动。

review 节点可以输出三种不同强度的信号:

  • ok:无问题,按依赖继续。
  • soft_objection:有异议,但不阻断合并,异议保留并交给治理者查看。
  • hard_veto:严重问题,mergeapproval 不得执行。

这三个信号的区分是关键。把所有反馈压缩成"通过/不通过"意味着丢失了"我有保留但允许继续"这个语义,而这恰恰是很多真实 Review 场景里最常见的状态。

merge

merge 节点等待 testsreview 都完成后,把它们的输出合并进整体状态。如果上游有 hard_vetomerge 不会执行。如果有 soft_objectionmerge 执行但保留异议记录。

approval(治理者)

approval 节点是 governor,它有三种决定:

决定行为
approve保留当前 Graph 目标,以成功结果结束。
reject以拒绝结果结束,不执行目标变更。
change_goal应用由治理者提供的新 Graph aim,Graph 结果变为 goal_changed

change_goal 是这里权限设计的核心。Loop 可以发现问题、提出建议、发出升级信号;但只有治理者可以批准并应用新的整体目标。普通节点,包括 implementation Loop,没有这个权限。


escalation 的完整路径

以这个场景为例:implementation Loop 在执行过程中判断当前的兼容性约束不足,需要提供完整的迁移路径。这超出了它的局部目标权限——它不能自己决定修改整体 aim,而是发出 escalation

注入的治理者收到升级信号,查看 Loop 的提案:把兼容性要求改成迁移要求。治理者返回 CHANGE_GOAL,新目标是:

Add bounded retries and provide a migration path.

Graph 结果变为 goal_changed。整个流程不是"Loop 决定了什么",而是"Loop 提出了什么,治理者决定了什么"。

这个边界很重要。如果 implementation Loop 可以自行修改 Graph 目标,那么 approval 节点就失去了意义,整个权限设计就崩塌了。


信号是路由,不只是状态

把这些信号汇总在一起:

信号来源路由结果
ok任意节点按依赖继续执行
soft_objectionreview保留异议,允许 merge,交治理者查看
hard_vetoreview停止 merge 和 approval
escalationimplementation跳过剩余 Loop 迭代,直接交治理者
approvegovernor以成功结果结束
rejectgovernor以拒绝结果结束
change_goalgovernor应用新 aim,结果为 goal_changed

信号的价值在于它们把"发生了什么"和"接下来怎么走"都编码进了同一个对象。在当前实现里,边是否存在决定节点何时就绪,EdgeRelation 记录关系意图,hard_vetoescalation 等提前路由则由 SignalKind 处理。


读者操作

运行示例:

python3 graph_demo.py
python3 -m unittest discover . -p 'test_*.py'

一个小练习: 修改 review 节点的输出,把默认的 OK 改成 SOFT_OBJECTION,观察 result.outputs 里是否还出现 mergeapproval 的输出,以及 review_status 如何被传递给治理者。再把 SOFT_OBJECTION 改成 HARD_VETO,观察 mergeapproval 是否从输出中消失。

这个练习的目的是让你亲手感受三种信号强度的路由差异——不只是读到它,而是在输出里看到它。


Graph 审计:一张清单

一个可用的 Graph 定义应该能够回答这些问题:

审计项检查内容
节点每个节点的局部目标是什么?它是 Agent Loop、确定性函数、evaluator 还是人工审批?
每条边的类型是什么?dependency、handoff、data_flow、veto 还是 approval?
就绪条件每个节点启动前需要哪些前置输出?
状态节点输出哪些键进入共享状态?下游节点消费哪些键?
信号每个节点可以发出哪些信号?对应的路由行为是什么?
合并merge 节点如何处理冲突的上游输出?soft_objection 是否被保留?
权限哪个节点是 governor?只有 governor 可以修改 aim。其余节点没有这个权限。

这张清单不是为了让你填表,而是为了让你在设计时不遗漏权限边界。遗漏权限边界是 Graph 设计里最常见的问题:节点和节点数量都对,但某个节点悄悄做了它不该做的决定,治理者形同虚设。

我对"只有步骤顺序、没有状态和决策权"的工作流没什么信任。步骤的顺序解决不了 Review 提出异议之后怎么办,解决不了 implementation 发现范围超限时谁来拍板,也解决不了测试通过但 Review 认为设计有根本问题时流程应该走向哪里。这些都需要信号、边类型和权限分配——也就是 Graph。


一手来源

  • Anthropic, Building effective agents, 2024. 介绍了 orchestrator-worker 模式和 parallelization 场景,是本文并行执行讨论的背景参考。
  • Kapoor et al., AI Agents That Matter, arXiv 2407.01502, 2024. 关于 Agent benchmark 与评估设计的研究,提示评估成本与控制变量的重要性。
  • Wang et al., A Survey on Large Language Model based Autonomous Agents, arXiv 2308.11432, 2023. Agent 架构概览,涵盖规划、记忆与工具使用的分类框架。
  • Qian et al., Experiential Co-Learning of Software-Developing Agents, arXiv 2312.17025, 2023. 多 Agent 协作开发场景,讨论角色分工与迭代机制。
  • arXiv 2604.11378(立场论文与设计提案)。本文拓扑与治理合同是有光综合多篇来源后的独立判断,不以此论文作为生产效果证明。

本文是 Loop Engineering 系列的直接延续。如果你还没有读过 Loop Engineering 教程,建议先建立局部收敛的基础概念,再回到这里理解 Graph 层的关系设计。