构建大规模企业级Agent运行时
2026-10-18, 杭州
截至现在,居然没有一个开箱即用的能在1000+甚至10000+实例横向扩展的、具备企业级稳定性的agent runtime,这很诡异你知道吗。所以我决定思考一下自己写一个。这篇文章是“思考”的环节。
理论上做夜间公司的企业一定会遇到这个问题,但大家居然没遇到,那现在的“夜间公司”可能更多还是个人agent跑在终端,其之间通过使用传统办公软件来互联。但是在一些任务又多又正交的岗位上,光靠纯粹会话管理(如ChatGPT)或一个上下文被写满正字的单一Agent(如Hermes)显然不能最大限度发挥AI的苦力。所以接下来我们有了能跟各种OA应用对接的Dots、Grok Bot等。另一些情况下,我们可能面对非常复杂的工作,此时会交给许多的subagent分别去调研和实现方方面面(如Kimi Swarm或DSH Team)。还得考虑一种情况是你的用户根本不会有意识长期保持终端开启(可能没有买这样的终端),又或者监管要求你的PII数据不应该交给终端处理,这就意味着你的一整个集群都要跑在云端。
总之,上述的诸多情况,就意味着,我们需要一个:高度定制化、可大规模横向扩展、长程任务高可用、不劣化模型表现、还要安全的agent runtime。
然后我去看了一下DSH/Cordis、DSec和Pi。为什么不是Cordis?因为现实世界并不是无副作用的,最理想的情况下我们可能希望大肥鱼拿着DSH自进化直到绝大多数的现实世界case能被纳入Cordis的可装卸上下文里。为什么不是Pi?因为在企业场景横向扩展,又要高性能、又要稳定、又要你对Loop的掌控很强,还要一大堆有的没的工具。但DSec是很值得学习的,暂时不做详述,有闲情的话这里更新展开讲。
现代的Agent Loop
首先我们不搞邪教那套,在考虑agent和系统进行交互时,认为agent始终是一个内部判定条件非常非常多的loop。
系统提示词
众所周知,LLM的架构决定了开头的token会严重影响其表现,如果你手写系统提示词,能够较好地把模型调成想要的效果(比如破甲)。另一方面,诸如工具声明、人格注入、SKILLS等,也都需要load到系统提示词中。而如果你还在使用旧时代的superpowers等雷霆提示词包,还有可能带来严重的上下文挤占和浪费(截至本文书写的时间,LLM上下文的黄金窗口仍然是256K,超出的部分会有肉眼可见的智力下降)。
那么,我们就要考虑,哪些插件会给系统提示词产生拼装影响。
- 身份
- 权限
- 工具和MCP
- 记忆
- AGENTS.md
- 系统信息
综上,我们在启动loop前,有个需要关注的系统提示词拼接器,它能被各种不同的插件进行合理地注入和调整。
模型服务提供商
首先需要支持经典的openai-compatible/anthropic/response三种格式,此外一些出生中转站可能还要求你捎带没那么兼容的字段头,这意味着provider层并不只是简单的配置化,需要做足量考虑后面还有新的出生格式的情况。
此外还需要考虑多模态输入和输出的支持,图像/音视频/决策等。部分场景下,还得考虑综合不同模态的拼好模,又或者是单纯像Sakana Fugu之类的湖南肯斯坦的情况。
以及高可用的情况。在目前Provider不可用的情况下,可能需要回落到相同模型的其他Provider,那么回落的配置也值得考虑。如果上下文切换至不同模型,还会带来额外的考量,目前的状况是普遍推荐重新做一轮compact,否则可能影响新模型的表现。
在企业场景中,我们还需要统计token计量计费的情况,这同样是由提供商返回的。
外部输入
在同一个会话内,我们可以考虑三种输入:人类输入、环境输入、模型间通信输入。在输入渠道很多的时候,哪个输入进入哪个会话就成了令人头疼的问题,所以我们在这一小节先讨论单一会话内。
人类输入的来源并不是只有TUI/GUI直接输入,比如Hermes,它的输入还来自于Channel侧。同理输入侧也要考虑对多模态的定义,我到底能输入什么?
还有一些场景,是Agent主动期待人类输入的(Question),此时我们又有两个问题要考虑了。一是有头无头的场景之分,无头情况下question应当主动提醒模型,期望人类输入在当前场景不可用。另一个问题是有头的情况下人类仍有可能AFK,此时我应当如何通知人类?是否还要考虑“等待人类的输入结果超时了”的情况?这方面我认为“期望人类输入”应当始终做到把锅甩到人类交互侧,而人类可以配置question等待自己的超时时间。
消息输出
Agent有别于Chatbot最显著的特征就在于它能够通过结构化的消息输出从而控制系统,这块我单独放在以下的工具调用。我们就考虑单纯的chat消息输出。
但agent runtime必须考虑消息输出到哪。除了原始channel哪儿来的回哪去以外,agent runtime及其外推可能还要考虑多端的消息同步的情况,尽管在agent眼中这就是只做了1次的下一个词推断。
工具调用(有状态)
工具调用全是有状态的吗?其实不一定。工具调用所能影响的状态范围,决定了我们需要何种沙箱。所以前述DSec按任务层次给RL设计了4种沙箱,一是单纯跑算法代码的函数式运行时,此类并不会有太多的副作用,可能偶尔会有需要写入的临时文件等;二是带一点隔离的容器;三是需要内核级隔离的MicroVM;四是如果还涉及训推优化等,还得挂载GPU和PV的完整VM。
而为了支撑上述的各种状态隔离,我们还需要一个好用的FS。作为infra强者,DS用的是自己的3FS,上面再用EROFS分层组合出合适的工具调用环境。分层文件系统的概念很重要,想象一下企业里有人写代码、有人做设计、有人写文档,这些功能所需求的外部Tooling都不一样,而为了适配不同Tooling所需环境,其Base可能也不一样。再有就是每个工作项可能需要自己非常干净的Workspace。于是这就带来了DSec论文里面提到的Base、Toolkit、Workspace三层Compose。可惜目前暂时没有太好的方案,小规模使用时,大家都觉得Docker/firecracker用用,直接基于模板拉起十几个,差球不多。此外许多朋友也都还在纠结如果是agent把文件系统搞炸了需要状态回滚的情形,基于此也有不少方案,比如按老牌infra的做法,上卷影,上BTRFS等;也有人的做法是在数据库的基础上抽象一个虚拟的可回滚可分层的FS。
另外需要注意的是,工具调用对外部系统的影响同样是有状态的,而且这并不能靠简单的agent自接管就能纳入“可撤销的副作用”。这一点会在下文提到。
还需要考虑的是,工具调用是否是Blocking的。根据我个人的实践,允许甚至强制模型调用工具时设置blocking timeout,不仅能避免永久卡住的情况,还能有效提升模型对“时间和工作量”的认知,真是神奇啊。在这个层面上,我们需要同时为agent提供推和拉两种机制。拉机制就是我们所说的blocking wait,如果timeout了,模型可以再起一个tool call去query和wait正在活着的任务;而推机制则是在目标任务预期能完成的情况下,给agent返回一个message告知结果,agent从而得以继续。这方面我颇为不满的是,目前所有harness对命令执行都不提供一个能观察到完整stdin/stdout/stderr的窗口,而很多时候人类踹一脚前是需要看看其执行情况的。
想到了就顺便一提,在问答时代我们很看重RAG和知识库,这对于workflow写死的流程是很方便的,但到了loop时代,知识库也许退化为系统提示词指引+tooling会更舒服吧。
会话管理(有状态)
会话管理主要发生在无人监督的夜间公司的情况。项目-工作空间-会话-具体的聊天窗,到底怎么映射,在不同场景下可能都不太一样,让人头疼。尤其对于Personal Agent/Channel来说,人可能习惯于平时就和一个bot对话,但又总有的时候你可能想踹一脚teammate。这意味着在框架层面这几项映射应该尽量不干涉或是解耦合,而交给产品/插件层去定义。
会话历史(有状态)
好消息是agent场景下我们天生就不会遇到需要多节点并发写入的情况,甚至单写多读也不太常见;坏消息是轨迹数据除了时序以外,非结构化的特征非常重。此外,会话管理并非没有事务性,已经做出的tool需要忠实记录到会话记录中。如果是轻量级自用的话sqlite3也够用,但再往上扩的时候我们需要时序分析又强、读写成本又不至于太高的数据库。
另一方面是可观测性,好在OTel天然适应,非常可用。
Subagent
把单个Loop都搞定之后,我们终于来到了最头疼的问题,面对复杂任务,我们不得不面向多个agent之间相协调。这个时候我们有两种常见的模式,一种是主agent摊派或叉出subagent,差使其进行子任务,任务的反馈结果最终将会给到主agent,然后暂停/停止;另一种情况则是众生平等的agent team,围绕一个主题项目进行通信,基于通信来互相触发。但这样可能会带来广播风暴问题,agent间的互call其实也会带来信息的丢失和额外的token消耗。
有没有一种方案能够把上述的多种模式统一起来呢?进程模型!
- Fork和Exec,主进程和子进程
- Agent Template就是ELF
- 上下文就是内存镜像(堆和栈)
- Capability决定权限边界和Available Tool
- 通信就是IPC
- Summaries就是GC
- 可以引入一个“systemd”来统一管理
当然,实践上不一定要用原生的Process,如果要实现大规模横向扩展,更可能像是一个supervisor加上大量能够在多台机器间到处起飞漂移的fiber(Make Erlang/OTP Greate Again!),基于这样的映射关系,我们可以给出一个基础的Agent框架,灵活构造合适的Tooling。
生命周期钩子
钩子没有太多可以讨论的内容,但确实是非常烦的部分:哪些地方可以允许上钩子?应当允许钩子对整个agent runtime产生何种程度的影响?太烦了,留到实现时思考。
领域Agent(专家)
讨论完单纯的Loop,应当考虑如何配置Agent以适应领域任务了。
预置提示词,可以通过烘烤系统提示词时加载。
SKILLS,可以由Tool注入,也需要照顾已有的习惯,通过项目级的文件系统加载。
Tooling,注意,除了默认Tool以外,我们也可以通过插件形式打入自定义的Tool;部分harness有dynamic tooling,但效果需要考虑。
Environment,也就是默认给agent提供执行的base和toolkit,比如最常见的python-docx等;还有browser-use和computer-use等场景。这里需要注意的是browser-use和cu都是可以fan-out的,不是非得把loop和agent装到一个vm里,比如参考腾讯的BrowserSkill。这方面我就尤其好奇DSec基于EROFS基础上做的base-toolkit-workspace的组合合成如何实现。我们需要更多更好的可组合工作环境。
应用层面来看,专家绝不是仅限于配置提示词、SKILLS和MCP。哪怕我们已经实现了真正意义的AGI,“专家”这个概念也还是会存在,就像你自己的电脑也无法装下所有的专业软件和工作资料,我们需要围绕一个topic,在更广阔的尺度上有效将方法论指导、配套文档、工作环境封装起来,是为专家。
人机边界和机机边界
人机边界是UX问题,早在之前的博客中我已经分析过,直到现在我的观点也没有太大的变化。而且现在的模型不太支持early return/输出actions,仍然是回合式交互,那就没有太大的变化。
Agent和现存系统的交互就颇值得玩味了。对于绝大多数就地处理的场景来说,一个完全封闭的沙箱是足够的;而轻度办公场景下,有人监督时,看着AI直接使用终端BrowserUse和CUA也仍可以接受;但人类始终有任何非人因随机性都过不去的坎,如资金交易、物理空间危险操作等。此外你还得考虑操作审计问题,直接操作人类终端会导致身份混用,而更多的现存系统如果想合理暴露给AI,单独颁发AAA身份大有裨益。除了身份以外还有操作安全,如果把裸的业务系统API endpoint直接暴露给AI,在非常规场景下,你将会见识到reward hacking的疯狂(前沿实验室面对这种情况喜欢什么都不做然后大喊卧槽AI逃逸了)。综上所述,我们可能需要MCP网关或者其他一些API网关叠加CLI,重新封装一些业务安全的接口,来让AI很好地遵循和调用。
而沙箱也是近期Infra关注的重点,我们如何才能有效拴住AI防止其对基础运行环境做各种hack。DSec论文在这方面非常务实,如果纯跑算法就给Function Runtime,有FS和进程交互就上Container(namespace、capabilities、bwrap和landlock等各种进程命名空间级隔离),可能产生内核hack就上microvm,如果还要大量的外部设备(比如GPU),那就完整VM挂硬件。在年初Claw刚火的时候我就开始看这几类方案了,还纠结到底哪一种好。DSec:我全都要。但需要注意了,今年以来观察到的情况许多是走网络环境来hack的,我倒是很好奇为什么前沿实验室构建的RL居然不是air-gap的,本应如此。整个公司的防火墙规则其实都应该扔给AI预先分析过一遍,我们在旧时代的攻击面管理都懂这么干,怎么AI时代的lab反而没注意呢?我暂且蒙古。
Agent成千上万时
通过前面将Agent解构为Provider + Loop + State + Exec + Template + ……,我们不难发现Agent横向扩展的问题回落到了我们非常熟悉的领域:事务和分布式系统。此时我们只要拿出《DDIA》照本宣科都能做成。而且正如之前所说,Agent绝大多数的状态都高内聚,非常好管理,真是令人欣喜。~~哪怕一个没怎么炼过丹的三个月培训班程序员,一旦发现自己的Nacos+DubboRPC在Agent时代居然还Work,也会不由自主挺起胸膛。~~那么重点就变成了Exec和State如何Pin住,以及外部的事务性什么时候需要传导让上下文有感知,而什么时候不用。
分析到这,我们已经可以开始Vibe一个高强度横向扩展的agent runtime。这会放在下一篇讲。