智能体编排的图执行引擎:Onyx VM如何用计算图驯服非确定性


Agent编排领域正从临时脚本转向基于图的可编程运行时。Random Labs推出的Onyx虚拟机将Agent协调转化为有向无环图执行模型,通过持久化状态节点、类型化边约束和条件分支控制Agent非确定性。

本文从图工程视角剖析Onyx的设计决策,展示如何将Agent任务建模为计算图,实现可观测、可组合、可恢复的Agent系统。从ReAct的线性链到Onyx的有向图,行业正在用图结构解决编排确定性难题。

计算图视角下的Agent系统:节点、边与状态张量

把Agent系统画成一张图,每个Agent就是一个计算节点,它们之间的文本流动就是有向边。这不只是画图好看,图结构给了你一个数学框架来思考Agent编排。每个节点接收输入张量——就是prompt和上下文——经过非确定性变换后产生输出张量,然后沿着边流向下一个节点。问题的关键在于,这条边上的数据类型是不确定的。

传统软件工程里,函数调用图是确定的。你调用calculateTax(income),返回的一定是个数字,类型签名(number) -> number给了你硬保证。Agent节点的类型签名是(string) -> string,但返回的这个字符串可能是一段JSON、一段代码、一个错误信息,或者一坨胡言乱语。这就是非确定性的根源。

ReAct循环就是一张最简单的图,三个节点串成一条链:Thought节点生成思考,Action节点调度工具调用,Observation节点从环境获取反馈。边就是文本流,没有类型约束。Deep Research把这张图扩展成了批次循环结构,规划节点连接到多个搜索节点,搜索节点汇聚到评估节点,评估节点再连回规划节点。这张图是个有环图,但环的形状是固定的。

Ralph Loop引入了一个关键创新:它把单个Agent节点替换成了一个迭代器模式。每次迭代都创建一个全新的Agent节点,图的状态完全序列化到磁盘上。这就相当于把图的状态从运行时内存里搬到了持久化存储,让图可以在不同时间步之间保持连贯性。

状态节点与边约束:图结构中的类型化存储

Onyx对图工程的第一个贡献是引入状态节点。在其他系统里,状态散落在磁盘上的一堆markdown文件和bash变量里,图中的边只是数据的单向流动。Onyx把状态提升为图中的一等公民——状态节点是持久化的、可命名的、带类型签名的存储单元。

状态节点用zod模式定义形状,比如z.object({ risk: z.enum(["low", "high"]), why: z.string() })。所有指向这个状态节点的写入边都必须遵守这个模式,运行时会在写入时做强制校验。这就相当于在图的边上加了类型约束——从Agent节点流出的数据必须符合目标状态节点的模式才能被接受。

这种设计把Agent系统的图从文本流动图升级成了类型化数据流图。每个状态节点就是一个类型化的存储桶,Agent节点通过专用的读写工具与状态节点交互。读操作返回强类型数据,写操作必须经过模式校验。这让你可以在图的层面推理数据形状,而不只是文本内容。

状态节点还支持跨Agent通信。父程序可以把状态节点的引用传递给子Agent,子Agent通过这个共享通道读写数据。在图的表示里,这就是多个计算节点共享同一个存储节点,形成了一种超边结构。这种设计让图可以表达复杂的通信模式,而不需要显式的消息传递边。

控制流图与执行形状:从固定模式到动态路由

Onyx的第二个图工程贡献是把控制流从Agent内部外化到了图结构里。在传统Agent系统里,控制流是隐式的——Agent自己决定下一步调用哪个工具、什么时候结束。你没法从图层面知道执行路径会怎么走。

Onyx程序本身就是控制流图。runspawn创建的是执行节点,if-elsewhile是条件分支和循环结构,Promise.all是并行扇出节点。所有Agent调用都嵌入在这个控制流图里,Agent只负责在节点内部做非确定性的工作,节点之间的路由逻辑是确定性的代码。

这种设计产生了一个关键特性:执行图形状在运行前就是可知的。你看一眼程序代码就知道会有多少个Agent节点、它们之间的依赖关系是什么、哪些节点会并行、哪些会串行。Agent能影响的只是节点内部的输出内容,不能改变图的结构。

这在图工程里叫做静态控制流图。对比之下,Claude的动态工作流是动态生成执行图,Agent自己写编排脚本,图结构在运行时才确定。Onyx选择静态图是因为它能给你可推理的保证:你可以预判最大并行度、预估预算上限、设计错误恢复路径。

循环结构在Onyx的图里是显式的迭代子图。while循环每次迭代都创建一个新的Agent节点实例,所有实例共享同一个状态节点。这就把循环展开了——你可以把每一轮迭代看作图中的一个时间步,每个时间步的Agent节点是新鲜创建、独立执行的。这种模式完美适配Ralph Loop和autoresearch的需求。

并行子图与扇出模式:从串行流水线到并行探针

Onyx的spawn原语让你可以创建并行执行子图。每个spawn调用创建一个后台Agent节点,多个spawn节点可以同时运行,形成扇出结构。这些并行节点可以共享状态节点做协调,父节点用await等待所有子节点完成,形成扇入汇聚。

Deep Codebase Research程序就是这种模式的标准实现。程序创建N个Reader节点,每个节点通过spawn并行启动,各自读取代码库的不同部分,把发现写入共享状态节点。Evaluator节点在所有Reader完成后启动,综合状态节点里的数据判断研究深度。如果不够深,就再启动新一轮的Reader节点——这就形成了一个动态深度的探针图。

这个图的执行模式是迭代式扇出-汇聚。每次迭代的扇出宽度是N,汇聚点在Evaluator节点。Evaluator的输出决定是否进入下一次迭代,或者终止并输出Research Packet。这种模式在搜索空间巨大的场景下特别有效,因为你可以在每次迭代后根据已收集的信息动态调整探针策略。

对比ReAct的线性链,这种并行扇出图把吞吐量提升了N倍。原本要串行执行N次查询才能完成的探索,现在可以并行完成,只要你的预算和模型速率限制允许。

错误传播与恢复子图:失败路径的显式建模

图工程的另一个优势是你可以显式建模错误路径。在Onyx的图里,每个Agent节点都可能抛异常,异常就是图执行过程中的一个分支事件。你可以用try-catch捕获异常节点,然后连接到恢复子图。

看autoresearch程序里对实验节点的处理。ar-exp-i节点可能因为Agent卡死、模型超时、训练脚本崩溃等原因抛出异常。程序捕获这个异常后,不中断整个图执行,而是构造一个特殊的结果对象——把status设为"crash",valBpb设为null。然后这个数据流入记录节点,记录节点把它写入状态节点,程序继续执行下一次循环迭代。

这就把错误变成了图中的一个正常状态转换路径,而不是执行终止信号。整个图在遇到节点故障时依然保持连贯性,只是路径选择了错误分支而不是成功分支。

更复杂的是重试模式。程序可以在catch块里连接到另一个使用不同模型的Agent节点,比如从Sonnet切换到Opus重试同一个任务。这种重试子图可以嵌套多层,形成带有退避策略的恢复路径。从图视角看,这就是在错误节点后面挂了多条备选路径,路径选择策略由程序代码决定。

图拓扑优化:减小状态空间与提升可观测性

Onyx的图设计还有一个隐藏优势:它天然限制了Agent的动作空间。在ReAct循环里,Agent可以任意选择工具调用序列,理论上可以探索的状态空间是无限的。但在Onyx的图里,Agent只能在节点内部操作,不能跳出图结构去修改控制流。

这相当于把Agent的非确定性搜索空间裁剪到了一个可控的子空间。每个Agent节点只需要解决一个明确定义的子任务,父图负责任务分解和结果聚合。这种分层图结构让整个系统的行为更可预测,因为每个节点的工作范围被图的拓扑结构天然约束了。

可观测性也在图层面得到了保证。每个节点的执行都有明确的开始和结束时间点,节点之间的数据流动是显式的状态读写操作。Onyx运行时可以记录完整的执行迹——哪个节点在什么时候执行了什么操作、消耗了多少token、输出了什么结果。这种粒度的可观测性让debug变成图上路径的追溯,而不是在黑箱日志里盲人摸象。

Onyx把Agent系统从文本管道工程升级成了计算图工程。每个Agent任务变成图中的一个节点,状态变成类型化的存储节点,控制流变成显式的边和分支结构。这种工程化让Agent系统获得了和普通软件一样的可组合性、可观测性和可恢复性。

剩下的工作就是定义图的持久化模型,让程序能从崩溃点恢复执行——这是图工程的最后一个拼图。