![]()
新智元报道
![]()
【新智元导读】当Agent数量增多时,管理混乱成为难题。OpenRath提出用Session作为核心,代替Agent为中心的设计,使多个Agent能共享状态,实现更清晰的协作与控制。
Agent越来越多,Session却越来越乱。
这是几乎所有人把多智能体系统真正跑大之后,都会撞上的一堵墙。
一个Agent维护一份上下文,另一个Agent又复制一份历史;一个任务分叉出好几条推理路径,最后没人说得清哪条分支产出了最终答案;模型调用、工具执行、沙箱环境、长期记忆各管各的状态——demo 跑得挺好,可当系统扩到几十上百个 Agent,调试、复现、编排全部开始失控。
最近,一个来自清华大学与中山大学的团队(Rath Team)把他们的解法开源了,叫OpenRath:这是一个像PyTorch的多智能体、多会话运行时。
它的主张是:别再围着Agent转了。真正该被当成一等公民的,是Session。
![]()
官网:https://www.openrath.com/
文档:https://docs.openrath.com/
博客:https://blog.openrath.com/
GitHub:https://github.com/Rath-Team/OpenRath
开源协议:BSD-3-Clause | 当前版本:v1.2.1(PyPI)
目前OpenRath已在PyPI发布到 v1.2.1,pip install openrath就能装,BSD-3-Clause协议,官网、文档、博客、GitHub一应俱全。
本文将从「为什么」一路讲到「怎么用」,重点拆OpenRath和AutoGen、LangGraph这些框架到底不一样在哪——以及它为什么敢借PyTorch的名字。
Agent运行时用「聊天记录」思考
第一性问题:当Agent真的动了手,证据该存在哪?
第一代大模型应用,可以概括成「提示词进、回答出」。Agent系统改变了这条边界。
一个有用的Agent不只是产出文本。它会检索、规划、调用工具、读文件、写代码、查API、跑测试、操作浏览器,有时还会改动外部状态。ReAct让推理和行动在一个循环里交替,Toolformer 让模型学会何时调用工具,再到Model Context Protocol把工具变成协议级的边界,这条线一直在往前走。
可一旦Agent真的对世界动了手,一个运行时层面的问题就冒出来了:这些动作的证据,到底存在哪?
如果一次工具调用读了文件,我们需要它的参数和结果;如果它改了仓库,我们需要diff;如果它跑在某个沙箱里,我们需要沙箱的身份;如果它失败重试了,我们需要那条失败路径;如果有人批准或否决了某个动作,我们需要那个校验信号。一份聊天记录顶多叙述这些事,却不足以还原这些事。
举个具体例子。
一个软件任务:研究Agent读了issue、检索了笔记;编码Agent改了仓库;沙箱跑了测试;校验Agent否决了第一版补丁,于是工作流分叉;记忆后端记下这次失败,免得以后重犯。如果这些事件散落在各自的日志里,那么最终答案几乎是最不重要的产物,真正有价值的,是那条「工作如何一步步推进」的证据链。
这就是OpenRath的出发点:把Session当成证据的载体,而不只是聊天历史。
为什么是Agent Cluster
单个Agent会膨胀成一个巨大的prompt,于是要把它拆开。
早期一个Agent基本够用:接收输入、理解任务、调用工具、返回结果,像个增强版聊天机器人。但真实任务很快超出单个Agent的边界。
一个像样的软件工程任务,往往要拆成需求理解、资料检索、架构设计、代码实现、测试验证、结果审查。不同环节要的能力并不一样——有的擅长规划,有的擅长写码,有的擅长挑错。继续让一个Agent全包,它就会膨胀成一个巨大的prompt和一个越来越混乱的上下文窗口。
于是有了Agent Cluster:让Planner、Researcher、Coder、Reviewer、Executor、Memory Agent各司其职,围绕一个复杂目标协作。
多个专业Agent围绕共享的Session协作:各自读取当前状态、完成局部任务、把结果写回,供下一个Agent接力。
可一旦真把它跑起来,难题就冒出来了:这些Agent怎么共享上下文?某个结论到底来自哪个Agent、哪条分支、哪次工具调用?一个Agent出了错,能不能回滚到对应分支重来?
说白了,Agent Cluster真正的挑战,从来都不在「造更多Agent」——难的是管住这些Agent之间的状态怎么流动。
![]()
OpenRath多问了一句
别人解决Agent之间怎么说话,它问说完之后谁拥有这份工作。
多智能体这个词,常让人想到一个群聊:一个Agent提议,一个批评,一个执行,一个主管决定什么时候收尾。这个模式有用,但不够。
这条路上已经有不少工作:AutoGen把多Agent对话做成了一个实用的编程模型;CrewAI把Agent团队和更结构化的流程分开;LangGraph用图状态和supervisor节点来表达路由与控制。它们都解决Agent之间怎么说话。
OpenRath接着往下问了一句:Agent们说完话之后,谁来拥有这份工作的状态?
一个生产级的Agent Cluster,需要决定:当前这个Session该交给哪个 Agent、它该看到什么上下文、读了哪些记忆、下一条命令在哪个沙箱跑、继续之前需要什么校验信号。这些都是控制平面的问题,靠往群聊里再加一个角色是解决不了的。OpenRath的答案是:让Session成为路由的单位,让Session Graph成为那张控制平面——Agent、工具、工作流、记忆、沙箱位置,都在这张图上交汇。
一句话:Agent集群不是群聊,而是建立在持久Session状态之上的运行时控制平面。
这也是为什么,从Agent数量×Session数量两个维度看,多智能体系统会分成四象限:
单Agent单Session是ChatGPT式聊天;多Agent单Session是子代理协作;单Agent多Session是OpenClaw式分支扇出;而多Agent多Session(MAMS),正是OpenRath面向的方向。
OpenRath把这套思路称为MAMS(Multi-Agent Multi-Session)。它的判断很干脆:真正需要被fork(分叉)、merge(合并)、复用、追踪的,是整条Session数据流——而非某个Agent内部那份各自维护的消息列表。
换个说法:大多数框架攒的是一屋子聪明的工人,OpenRath先把工位、工单和流水线建好。用官方那句话说就是——Agent是工人,Session才是工作本身。
![]()
像PyTorch一样搭Agent集群
这不是蹭名字。
PyTorch之所以好用的三个设计,OpenRath一一对应搬了过来。
OpenRath最聪明的一步,是把深度学习开发者最熟的那套抽象,整套搬到了Agent系统上。
PyTorch为什么好用?因为它把复杂计算拆成了清晰的积木:Tensor是流动的数据,Module/Layer是变换这份数据的可组合单元,device决定算在哪,而整张计算图是跑起来才长出来的。OpenRath给Agent系统做了几乎一一对应的映射:
核心映射:Tensor→Session、Module/Linear→Workflow/Agent、Device→Sandbox / Backend、Parameter→Memory、Function→Tool、控制流→Selector。
下面三节,正是把这张表里最关键的三组对应,从术语对照讲成为什么这么设计。
这套映射不是噱头。把它拆开,PyTorch真正教给OpenRath的,其实是三件事——下面三节,正好是理解OpenRath的三根支柱。
![]()
支柱一:Agent是变换层 ,不是全能助手
Layer不持有数据;Agent也不持有状态。
PyTorch里,nn.Linear不是一个应用,它只是一层变换:吃进一个Tensor,吐出一个Tensor。一个网络的本事,来自很多这样的层被叠起来。
OpenRath把Agent设计成了同一种东西。Agent就是Session上的一层变换。它的核心,就是一条forward(session) -> session的路径:进来一个Session,出去一个Session。
关键在于,变换层不止一种。同样是forward(session) ->session这个形状,可以装下完全不同的活儿:
一个Agent调工具、改workspace里的文件,把执行结果写回Session;
一个
Compressor把跑了几十轮的长会话压缩成一条精简消息(官方example第 8课就是它);一个Agent在跑之前
recall记忆、跑之后commit记忆,相当于给这次会话做了一次「索引与归档」;你也可以写一个只做摘要、只做校验、只做改写的Agent。
它们对外都是同一个接口,于是能像神经网络的层一样任意堆叠、任意嵌套。这正是Workflow(对应nn.Module)的意义:子类只要实现一个forward(session) -> session,里面就能串联多个Agent、fork Session、压缩上下文、调用工具、分发到子工作流。因为每一层进出都是 Session,Workflow能像nn.Module一样层层包起来,每层不必重新发明一套状态格式。
管上百个Agent,于是从拼提示词变成了搭模块。Layer不持有数据,数据是 Tensor;Agent 也不持有状态,状态是Session。
这里还藏着一个容易被忽略的好处。因为Agent不拥有整个世界——Session loop仍是引擎,Sandbox仍是执行位置,Memory仍是独立store——所以单个Agent的场景足够简单,同一个Agent又能被原样塞进更大的 Workflow,不用改一行。
至于工具本身,OpenRath抽象成了FlowToolCall:一手攥着给模型看的name / description / JSON schema,一手攥着真正在Python里执行的行为,让工具长什么样」和工具干什么始终待在一起。内置了文件、shell、代码执行工具,stdio的MCP工具也能直接适配进同一个循环。底层还有个清晰的分层:FlowToolCall是flow层模型可见的函数,BackendTool*才是沙箱后端真正消费的载荷。
支柱二:Sandbox与Memory是「可插拔后端」
把后端写死,等于把模型焊在CPU上。
PyTorch第二个聪明的地方,是把「算在哪」从「算什么」里剥了出来。同一份模型代码,.to("cuda")就上GPU,换个后端就换块卡,计算逻辑一行不用动。device / compute backend是可插拔的。
OpenRath把这个思想用在了两个最容易被写死的地方:执行环境和长期记忆。
Sandbox(对应 Device)——工具到底在哪运行。很多框架把「对话历史」和「工具实际执行的位置」分开管,模型以为自己还在某个工作区,shell或容器其实早就切走了。
OpenRath把Sandbox绑在Session上:工具跑在Session当前的backend上,返回的Session会记住自己的执行位置,不会悄悄漂移。
而它真正的巧思,是把Sandbox做成了可插拔的backend:本地进程始终可用(session.to("local", spec="./")),容器化的OpenSandbox是可选项(pip install "openrath[opensandbox]"),未来任何第三方执行后端,只要接到同一套 Session placement 模型后面就能用。执行环境,从此不再硬编码进某一个 shell。
Memory(对应 Parameter)——跨运行保留的记忆。
它是独立的一层持久状态,能绑定到 Agent、运行前 recall、运行后 commit;既不像工具结果那样用完即弃,也不只是塞进 prompt 的几行文本。
基础安装自带零依赖的本地后端,把数据存在.openrath/memory/,不用 LLM 也能做BM25词法检索;
配了embedding就能用向量排序;想要更强的,可以接OpenViking这类外部记忆服务。和Sandbox一样,Memory同样是可插拔backend——recall不绑死在某一个数据库上。
这一招对自带本地状态的团队尤其友好:你已经有自己的容器调度、有自己的向量库或知识库,不必推倒重来,只要把它包成一个backend接进去,就能复用OpenRath的整套Session / Workflow抽象。说到底,把执行环境和记忆都做成可换的device,OpenRath才能让状态、执行、记忆、编排彼此解耦到足以各自替换,却又都串在同一个流动的值——Session——上。
支柱三:Session Graph是动态图
PyTorch的图是跑起来才长出来的,OpenRath的Session Graph也是。
PyTorch还有第三个让人上瘾的设计:动态图(define-by-run)。它不要求你先把整张计算图画死再喂数据,而是代码跑到哪、图就长到哪。控制流就是普通的Pythonif/for,灵活到可以在运行时根据中间结果改变走向。
OpenRath的Session Graph,是同一个性格的东西。
先看Session长什么样。它远不止一串聊天记录——而是一张结构化的chunk表,大致是这个形状:
Session├─ chunks: [ {role: system, ...}, # Agent 指令也是一条 chunk│ {role: user, text: "..."},│ {role: assistant, text: "..."},│ {role: tool_result, name, args, result} ] # 工具证据├─ placement: "local" / "opensandbox" # 这一段在哪执行├─ lineage: parent / fork / merge 关系 # 我从哪条分支来└─ usage: token 用量它能fork出分支,能detach切断父链,能merge合并,还能序列化成JSONL直接交给下一个Workflow。而这张由fork / merge织出来的图,是Agent们跑起来、一步步演化出来的,并非事先画死的剧本——这正是「动态图」的含义。
Session沿着fork / merge演化,每一步都留下血缘;工具运行环境则由Sandbox backend绑定。这张图不是预先编排好的,而是随Agent运行实时生长。
为什么这件事对Agent Cluster是决定性的?因为集群规模一大,你迟早要回答:这个结论到底是哪个Agent、走哪条分支、调哪次工具、在哪个workspace产出的?散落的日志答不了,一张带血缘的动态图能答。Session Graph于是从实现细节升格成了集群的可观测层与控制层:路由、复现、回滚、审计,全在同一张图上做。
![]()
几十行,把这套东西跑通
抽象讲再多,不如看一段能跑的代码。
下面这个最小例子(取自官方README的最小完整工作流),把Session、Sandbox、Tool、Agent、Memory、Workflow、Compressor一次性串了起来:
from rath import flowfrom rath.session import Sessionclass ReadmeWorkflow(flow.Workflow): def __init__(self): provider = flow.Provider(model="gpt-5.5")# Agent:一层带 prompt / 工具 / 记忆的变换 self.agent = flow.Agent( "Use the `word_count` tool, then answer briefly.", provider, tools=[WordCountTool()], memory="local", )# Compressor:另一种变换层——把长会话压成一条精简消息 self.compressor = flow.Compressor("Compress the run into one message.", provider) def forward(self, session: Session) -> Session: self.agent.remember_memory("The user likes compact summaries.") # 运行前写记忆 session = self.agent(session) # 一层变换 self.agent.commit_memory(session) # 运行后提交记忆return self.compressor(session) # 再叠一层变换# Session 承载数据,Sandbox 决定执行位置session = Session.from_user_message("Count the words in: OpenRath makes agent clusters traceable.")session = session.to("local", spec="./")out = ReadmeWorkflow()(session)读这段代码,三根支柱全在里面:数据是Session,执行位置由.to()决定(支柱二),agent和compressor是两层不同的变换叠起来(支柱一),而它们怎么串、串几层,是forward里用普通Python写出来的(支柱三)。每一步进出,都是同一个Session。
真正动态的地方Selector
把流程写进提示词是焊死,交给Selector才是让系统学会拐弯。
上一节的forward是写死的顺序。但真实任务往往要等跑起来才知道该往哪拐——这时候就轮到动态图的「运行时路由」登场。
很多框架的做法是把流程提前编排死:if走A,else走B。
OpenRath的答案是Selector:一个由大模型驱动的路由器。它在若干个「会自我描述」的Workflow之间做选择,返回下一个该跑的Workflow,任务结束就返回一个空操作。妙处在于——它让Agent之间的if/while,依然是普通的Python:
selector = flow.Selector(provider)while not isinstance( nxt := selector.forward(session, triage, tech, wrapup), flow.EmptyWorkflow): session = nxt(session)把流程写进提示词,是把不确定性焊死;交给 Selector,才是让系统学会拐弯。这也正是官方把OpenRath称为"dynamic multi-agent workflow"的底气:流程从写死的剧本,变成了运行时才定下来的路由——和PyTorch 动态图里那句代码跑到哪、图就长到哪是同一种自由。
它现在到底能不能用?
能装、能跑、能照着学——不是一份PPT。
最能说明问题的是它的example/目录——一条编号递进的学习阶梯,每个脚本只讲一个概念,前一个的产出正好是后一个的输入:
已在GitHub开源,BSD-3-Clause协议,可直接pip install openrath。
01_hello_agent:最小程序,构造Agent、在Session上调用、流式输出02_session_lineage:用fork分叉、detach切断血缘、查看session graph、导出JSONL03_sandbox_backend:把同一个Session放到local或opensandbox,看工具在哪执行04_tools_builtin/05_custom_tool/06_mcp_tool:内置工具、自定义工具、借用MCP工具07_streaming/08_compress/09_memory/10_provider_variation:流式、上下文压缩、记忆、换模型厂商11_dynamic_selector:用Selector做if分支和while循环
从「先让一个Agent跑起来」到「让一群Agent动态协作」,11步走完,OpenRath的核心也就理解透了。
安装也分层:基础pip install openrath,要容器沙箱加[opensandbox],要外部记忆加[openviking],模型则走 OpenAI 兼容的环境变量或~/.openrath/config.json。
更值得说的是这套 example 的设计取向:官方强调,例子的目标是产出一份「证据档案」,而不是一张截图。一个软件任务跑完,理想的产物不该只停在一句「成功了」,而该是一整份可回溯的卷宗——
issue 原文 → Session Graph → 调了哪些工具 → 副作用落在哪个 sandbox → 被否决的那条分支 → 最终采纳的补丁 → 测试结果 → 这次往 Memory 里写了什么。
这份卷宗,正是「一个 demo」和「一个能拿来做技术报告的运行时」之间的区别。按团队自己的说法,他们在内部已经用 OpenRath 组织起接近 Transformer 结构的 Agent Workflow——不过这更偏系统能力验证,还不是公开 benchmark,这点他们说得很坦白。
从持久Session,到Agent Cluster
v1.1让单个Agent的工作可追溯,v1.2让一群Agent的协作可追溯。
把视野拉远一点:OpenRath的版本演进本身就是一条干净的线。v1.1解决「持久」——如果一个Agent的工作是跨时间展开的,凭什么唯一被保存的只有最终答案?于是有了持久Session,把干活的证据完整留下。v1.2再抬高一层:让Session从「单个Agent事后可查的记录」,升级成「能在多个Agent和工作流之间被路由的对象」。一行代码就概括了这个转变:
session = workflow.forward(session)它意味着,工作的单位从一个prompt、一个答案或一个Agent角色,挪到了一份持久、可路由的Session状态上。
从Prompt 工程
走向系统工程
OpenRath的意义,不只是「又一个Agent框架」。
它真正想解决的是:当Agent Cluster成为主流形态,开发者能不能像写深度学习那样,获得一套可组合、可追踪的工程体验。这正是它借PyTorch之名的底气——同一套直觉迁移了过来:层是变换(Agent),device可插拔(Sandbox / Memory backend),图是动态的(Session Graph)。
在PyTorch里,你定义Module,让Tensor在网络中流动;在OpenRath里,你定义Agent和Workflow,让Session在系统中流动。剩下的——血缘记录、工具调度、沙箱绑定、长期记忆、动态路由——交给框架。
如果说过去的Agent框架面向的是「一个智能助手」,那么OpenRath面向的是「一个智能体系统」。
而这件事的起点,朴素得有点反直觉——不是再多造一个Agent,而是先把Session当回事。
参考资料:
https://www.openrath.com/
编辑:LRST
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.