全部文章

我们的 agent 循环只有 239 行:框架和运行时是两层东西

SOIT Team

我们的 agent 循环只有 239 行:框架和运行时是两层东西

「Agent 平台」这个词现在至少吃掉了三层东西:写 agent 的开发库、跑 agent 的执行引擎、管 agent 的治理面。三层混在一个词里,于是每次讨论都变成鸡同鸭讲——一方在说「我三行就搭出一个 agent」,另一方在说「我要知道昨天下午三点那次调用是谁批的」。两句话都对,但根本不在回答同一个问题。

我不打算去评判任何一个具体框架的内部实现,那需要把别人的代码读透才有资格说。所以本文只做两件事:描述「框架这一层通常负责什么」,这部分是共识;再拿我们自己的仓库当样本,把「运行时这一层负责什么」定位到具体文件。样本是 github.com/soit-ai/soit,Apache 2.0,所有行号对应提交 3a57ae1。

先数自家的 agent 循环。planner、executor、verifier 三件加起来 239 行。而包着这 239 行的服务有 1617 行,它的 import 列表已经把答案说完了:工作区守卫、审批台账、限流器、带租约与幂等键的工具执行台账、trace 写入器、审批规则、工具解析器。真正的循环只有一行 while。循环本身一行,循环之外一千六百多行,而这一千六百行里绝大部分不在解决「agent 怎么想」。

两层之间的分界线不是一张示意图,是一个端口。每一次工具调用在抵达适配器之前,都要先过一个 475 行的策略网关,而那些不体面的问题都住在网关里——它不关心你的 agent 逻辑写得漂不漂亮,它关心「下游不支持幂等键的时候,一次重试会不会把工单开两遍」。这个边界上的调用之所以是至多一次,原因就在这里。

你完全可以反驳说:那也许只是你们把治理代码塞进了 agent 循环的下游而已。回答是同一个仓库里的第二套执行模型——6269 行的 DAG 工作流引擎,结构上和 agent 循环毫无相似之处。它调工具走的是同一个工具端口,调模型走的是同一个模型端口。两套毫无关系的执行模型,共用一整套保证,这就是「分层」这个词的可操作定义:治理是端口的属性,不是循环的属性,所以换掉上面那一层,下面十道关一道都不少。

分层最常见的下场是写在文档里、烂在代码里,所以这一条我们交给了工具去管。仓库里第一条 import 契约禁止内核 import API 层、业务模块、适配器、基础设施与装配层;内核需要产品或基础设施数据时,只能通过在装配层注册的 provider 接口去拿。这是让一条边界长期活下去的机制成本,而这笔成本由 CI 付,不由自觉付。

诚实的那部分:那套工作流引擎确实和编排类框架重叠,装作不重叠没有意义。它有编译器、变量解析、节点执行器、恢复与回收。更准确的说法不是「我们不做框架」,而是我们在框架的地盘上只做了必要的那一部分,并且要求它跟别人守一样的规矩。

那么「不是竞品」具体意味着什么?框架侧写的工具可以通过 MCP 接进来,此后它的每一次调用都要过同样的关;框架侧的服务可以作为 HTTP 插件挂进来;前端不用换,因为交互走的是 AG-UI 事件流。而缺的那一块比这些都重要:目前没有「把别人写的 agent 整个塞进来托管」的入口。你的循环仍然跑在你自己的进程里,被治理的是它伸出来的那只手——工具调用、模型调用、出网请求——不是它脑子里的那段逻辑。

框架决定 agent 怎么想,运行时决定它伸手的那一下算不算数。这两件事没有竞争关系,因为它们甚至不在同一个时间维度上:一个作用于你写代码的那几天,另一个作用于之后的每一次执行。真要说有竞争,竞争的是注意力——而从前者转向后者,通常发生得太晚,晚到第一次事故之后。