1. 从一条吵翻天的帖子说起:AX 到底在解决什么问题
Google 开源 AX 这件事,在技术社区里炸开锅的那一天,我正好在翻帖子。评论区两极分化得厉害:一拨人说"这不就是把 agent 编排写成 YAML 吗,有什么新鲜的",另一拨人则兴奋地表示"终于有人把运行时这件事想明白了"。这种撕裂感其实很有意思,因为它暴露了一个行业里长期被含糊带过的核心矛盾——我们到底该怎么描述和调度一个由几十亿个 agent 组成的系统。
先把话说清楚:AX 不是又一个 agent 框架。市面上叫得上名字的 agent 框架,绝大多数解决的是"单个 agent 怎么思考、怎么调用工具、怎么记住上下文"这类问题。而 AX 瞄准的是更底层、更工程化的一层——运行时(runtime)。它要回答的问题是:当你手上有成千上万、甚至理论上可以扩展到数十亿个 agent 实例时,你怎么声明它们之间的关系、怎么编排它们的执行顺序、怎么在分布式环境里保证它们不互相踩踏、怎么让整个系统可观测可恢复。
关键词里出现的 YAML、Kubernetes、Redis 其实已经把技术画像勾勒得很清楚了。YAML 是声明式的表达载体,Kubernetes 是分布式编排的思想母体,Redis 是状态与协调的常见落点。这三者凑在一起,基本就是 AX 这类运行时的典型技术栈。我第一眼看到这个组合的时候,脑子里冒出来的类比是:如果说单个 agent 是一个函数,那 AX 想做的就是这个函数的调度器加运行时环境,类似操作系统之于进程的关系。
为什么这件事值得吵一整天?因为"声明式编排"这个词在 agent 领域一直是个模糊地带。大家嘴上都说要声明式,但真到了实现层面,绝大多数方案最后还是退化成了命令式的代码逻辑——你写一堆 if-else 和循环去控制 agent 的流转。AX 的价值主张是:把这些控制逻辑从代码里抽出来,变成一份可读、可版本管理、可复用的声明文件。这个主张听起来简单,但真要做到能扛住"数十亿"这个量级,背后的工程复杂度是指数级上升的。
这篇文章我想做的事情,不是复述官方文档,而是站在一个实际搭过分布式系统、也踩过 agent 编排坑的从业者角度,把 AX 这类运行时背后的设计逻辑、关键技术点、以及真正落地时会遇到的坑,一层层拆开讲。适合谁看?如果你正在做 agent 相关的项目,尤其是那种需要多个 agent 协同、需要横向扩展的场景,那这篇内容应该能帮你少走一些弯路。如果你只是好奇"声明式编排"到底是个什么概念,我也会用尽量生活化的方式把它讲明白。
2. 声明式编排的内核:为什么不是写代码而是写 YAML
2.1 命令式编排的隐性成本
先讲一个我自己的真实经历。早些年做任务调度系统的时候,我写过一版纯命令式的编排逻辑:一个主控函数,里面嵌套了各种条件判断,根据上游 agent 的返回结果决定下一步调哪个 agent、传什么参数、失败了怎么重试。刚开始只有五六个 agent,代码还能看。等到 agent 数量涨到三十多个,那张调用关系图已经变成了一团毛线,任何人想改一个环节都得先把整个函数读一遍,生怕改出连锁反应。
这就是命令式编排的隐性成本:控制逻辑和业务逻辑纠缠在一起,系统的复杂度随着节点数量呈非线性增长。每增加一个 agent,你不仅要写它的业务逻辑,还要在所有可能调用它的地方加上分支判断。这种耦合在规模小的时候不痛不痒,一旦规模上去就是灾难。
声明式编排的核心思路,是把"系统应该是什么样"和"系统怎么变成这样"分开。你只描述期望的状态——比如"agent A 执行完之后,把结果同时发给 B 和 C,B 和 C 都成功后再触发 D"——至于这个状态怎么达成、失败了怎么重试、并发怎么控制,全部交给运行时去处理。这跟 Kubernetes 的思路是一脉相承的:你在 YAML 里写"我要三个副本",至于调度到哪台机器、怎么拉起容器,那是 kubelet 和 scheduler 的事。
2.2 YAML 作为编排语言的取舍
为什么是 YAML 而不是 JSON 或者某种 DSL?这个问题评论区也吵过。我的看法是,YAML 胜在人类可读性和表达力的平衡点上。JSON 太啰嗦,一个稍微复杂的编排写出来满屏都是括号和引号,人眼很难快速抓住结构。而自定义 DSL 虽然表达力强,但学习成本高,还得配套写解析器和工具链,生态很难起来。
YAML 的缩进结构天然适合表达层级关系,agent 之间的依赖、并行、条件分支都能比较直观地映射成嵌套结构。而且 YAML 有个巨大的隐性优势:它已经是云原生世界的通用语言。任何做过 Kubernetes 的人看到 YAML 编排文件都不会陌生,这种认知迁移成本几乎为零。AX 选择 YAML,本质上是在借云原生生态的势。
不过 YAML 也有它的问题,这个后面讲踩坑的时候会细说。最典型的就是缩进敏感导致的"看起来对但实际错"的配置,以及复杂嵌套下的可读性下降。但总体而言,在"让更多人能上手"这个目标下,YAML 是合理的选择。
2.3 声明式带来的可复现性红利
声明式编排真正杀手级的好处,是可复现性。命令式的编排逻辑藏在代码里,你想复现一个特定的执行流程,得把代码、依赖、环境全部对齐。而声明式的编排文件本身就是一份完整的系统描述,把它交给运行时,理论上就能得到一致的执行结果。
这在 agent 场景下尤其重要。因为 agent 的行为本身带有不确定性——大模型的输出每次可能都不一样。如果连编排逻辑都是不确定的,那整个系统就彻底没法调试了。把编排层固定成声明文件,等于给系统钉了一个确定性的锚点:不管 agent 内部怎么随机,它们之间的流转关系是确定的、可审计的、可回滚的。这一点对于需要合规审计或者需要精确复现线上问题的团队来说,价值巨大。
3. 数十亿 agent 的运行时:规模背后的真问题
3.1 "数十亿"这个数字到底意味着什么
标题里"数十亿 agent"这个说法,很多人第一反应是营销话术。但如果你认真想过分布式系统的规模问题,就会知道这个数字指向的是一类真实存在的挑战。数十亿个 agent 实例,意味着你不可能为每个 agent 维护一个独立的长连接或者独立的内存状态。传统的"一个 agent 一个对象"的建模方式,在这个量级下直接崩溃。
这里的关键转变是:agent 从"实体"变成"事件"。在运行时眼里,一个 agent 的某次执行不是某个常驻对象的方法调用,而是一个可以被调度、被序列化、被持久化、被恢复的事件。这个思路跟 actor 模型有点像,但更轻量——actor 通常还是有身份的常驻实体,而 AX 这类运行时里的 agent 执行更像是无状态的函数调用加上外部化的状态存储。
这个转变带来的直接后果是,运行时的核心能力从"管理对象生命周期"变成了"高效调度海量短生命周期任务"。这就解释了为什么 Redis 会出现在关键词里——高频的任务状态读写、分布式锁、队列协调,Redis 几乎是默认选项。
3.2 状态外置:为什么 agent 不能自己记状态
在单机 agent 框架里,agent 的记忆通常就是内存里的一个列表或者向量库。但到了分布式运行时,agent 的状态必须外置。原因很直接:你无法保证下一次调度这个 agent 的时候,它还落在同一台机器上。如果状态在本地内存里,那横向扩展就无从谈起。
状态外置的代价是每次读写都要走网络,延迟上去了。所以运行时的设计里,状态的粒度划分就成了一门学问。粒度太粗,每次都要搬运大量数据,网络成为瓶颈;粒度太细,读写次数爆炸,Redis 这种存储层扛不住。合理的做法通常是把状态分成热状态和冷状态:热状态(比如当前执行到哪一步、临时变量)放在 Redis 这类内存存储里,冷状态(比如历史记录、大块上下文)放到对象存储或者数据库里。
我踩过的一个坑是:早期设计的时候把所有状态都塞进 Redis,结果单个 agent 的上下文一大,Redis 的内存就告急,而且每次读写都要序列化反序列化一大坨数据,延迟高得离谱。后来把大块上下文拆出去,Redis 里只留指针和轻量状态,性能立刻好转。这个经验对任何做分布式 agent 的人都适用:别把 Redis 当数据库用,它擅长的是小而快的状态,不是大而全的存储。
3.3 调度器的核心矛盾:吞吐与公平
数十亿 agent 的调度,本质上是一个资源分配问题。运行时的调度器要在有限的算力资源上,决定下一刻执行哪些 agent。这里有个经典的矛盾:追求吞吐量就会牺牲公平性,追求公平性就会牺牲吞吐量。
如果调度器只挑那些执行快、资源占用小的 agent 先跑,整体吞吐量会很高,但那些执行慢、资源重的 agent 可能永远排不上队,这就是所谓的饥饿问题。反过来,如果严格按到达顺序排队,公平是公平了,但大量时间浪费在等待重资源 agent 上,吞吐量上不去。
AX 这类运行时的常见做法是引入优先级和配额机制。每个 agent 或者每类 agent 可以声明自己的优先级,调度器在保证高优先级任务及时响应的前提下,用剩余资源去跑低优先级任务。同时给每个来源设置配额,防止某一类 agent 把资源吃光。这套机制在 Kubernetes 里已经非常成熟了,AX 大概率是借鉴了类似的思路。
4. 和 Kubernetes 的异同:借了哪些势,又绕开了哪些坑
4.1 为什么大家第一反应是"这不就是 K8s 吗"
AX 一出来,很多人第一反应是"这不就是把 agent 当成 Pod 来编排吗"。这个类比有道理,但也不完全准确。相同的地方在于,两者都是声明式、都是面向分布式、都有调度器和控制器。不同的地方在于编排对象的性质完全不同。
Kubernetes 编排的是容器,容器的生命周期相对长,启动一次可能跑几个小时甚至几天。而 agent 的执行往往是短促的,一次执行可能就几百毫秒。这个差异导致两者在调度策略、状态管理、故障恢复上的设计取向完全不同。K8s 可以容忍秒级的调度延迟,因为容器启动本身就要几秒;但 agent 运行时如果调度延迟到了秒级,那吞吐量就没法看了。
所以 AX 借的是 Kubernetes 的思想——声明式、控制器模式、期望状态与实际状态的收敛——而不是直接复用 K8s 的组件。这一点很关键,很多团队在选型的时候会误以为可以直接拿 K8s 来跑 agent,结果发现调度开销大得离谱,最后还是要自己写轻量级的调度器。
4.2 控制器模式在 agent 场景的变形
Kubernetes 的控制器模式核心是"观察-比较-行动"的循环:观察当前状态,和期望状态比较,然后采取行动让两者收敛。这个模式在 agent 运行时里同样适用,但收敛的目标变了。
在 K8s 里,期望状态通常是"我要 N 个副本在跑"。在 agent 运行时里,期望状态可能是"我要这批 agent 全部执行完成,且满足某个依赖顺序"。这就意味着控制器的逻辑要复杂得多,它不仅要管数量,还要管依赖关系、执行顺序、数据流转。
我观察到的一个设计要点是:agent 运行时的控制器通常会把"编排图"和"执行状态"分开存储。编排图是静态的、声明式的,来自 YAML 文件;执行状态是动态的、不断变化的,存在 Redis 或者类似的存储里。控制器的工作就是不断地把执行状态往编排图描述的目标上推。这种分离让系统既能保持声明式的清晰,又能应对运行时的动态变化。
4.3 绕开 K8s 重量的代价与收益
不用 K8s 直接跑 agent,收益是轻量和低延迟,代价是你得自己实现一堆 K8s 已经帮你搞定的事情:服务发现、健康检查、故障转移、滚动更新等等。AX 作为运行时,必然要在这些方面给出自己的方案。
从关键词里的 Redis 可以推测,服务发现和协调大概率是走 Redis 的。这在中小规模下没问题,但到了数十亿 agent 的量级,Redis 本身也会成为瓶颈。所以真正大规模的部署,可能还需要引入更专业的一致性协调组件。这也是为什么我说"数十亿"这个数字更多是指设计目标而非当前实际能力——架构上要能撑住,但实际部署规模取决于配套组件的成熟度。
5. 落地时会踩的那些坑:从 YAML 缩进到 Redis 热点
5.1 YAML 编排文件的那些"看起来对"的陷阱
YAML 最大的坑就是缩进。我见过太多次因为一个空格导致的编排错误,而且这类错误往往不会在解析阶段报出来,而是等到运行时行为异常了才被发现。比如两个本该是兄弟关系的 agent 节点,因为缩进差了一格,变成了父子关系,执行顺序完全变了。
提示:写 YAML 编排文件时,强烈建议用支持 YAML schema 校验的编辑器,并且在 CI 里加一道 lint 检查。别指望人眼能看出缩进问题。
另一个坑是 YAML 的类型推断。YAML 会把yes、no、on、off这类词自动转成布尔值,如果你某个 agent 的参数值恰好是这些词,就会出问题。还有数字和字符串的混淆,1.0和"1.0"在某些解析器里行为不一样。这些细节在写编排文件的时候必须格外小心。
5.2 Redis 作为状态存储的热点问题
前面说了 Redis 适合存小而快的状态,但即便如此,在数十亿 agent 的量级下,Redis 也会遇到热点问题。所谓热点,就是大量的读写集中在少数几个 key 上。比如所有 agent 都要去读同一个全局配置,或者都要去更新同一个计数器,这个 key 所在的 Redis 节点就会被打爆。
解决热点问题的常见手段是分片和本地缓存。全局配置这类读多写少的数据,可以在每个运行时节点本地缓存一份,定期刷新,避免每次都去 Redis 读。计数器这类写多的数据,可以用分片计数的方式,把一个大计数器拆成 N 个小计数器分散到不同 key 上,读的时候再汇总。
还有一个容易被忽略的点是序列化。agent 的状态里往往包含复杂的对象,序列化方式选得不好,CPU 开销会很大。JSON 可读性好但性能一般,Protobuf 或者 MessagePack 性能好但可读性差。我的经验是:热路径上用二进制序列化,冷路径和调试场景用 JSON,两者结合。
5.3 agent 执行失败的重试与幂等
分布式系统里失败是常态,agent 执行失败更是家常便饭——大模型调用超时、工具返回异常、网络抖动,任何一个环节都可能让一次执行失败。运行时的重试机制必须设计得当,否则会出现重复执行导致的数据不一致。
这里的关键是幂等性。如果一个 agent 的执行不是幂等的,那重试就可能产生副作用。比如一个负责扣款的 agent,重试一次就多扣一次钱。所以运行时要能识别哪些 agent 是幂等的、可以安全重试,哪些不是、需要特殊处理。
我踩过的一个坑是:早期没考虑幂等,重试机制一开,下游数据就乱了。后来给每个 agent 执行加了一个唯一 ID,下游根据这个 ID 做去重,才算把问题解决。这个经验值得所有做 agent 编排的人记住:重试机制必须和幂等设计配套,缺一不可。
6. 从 AX 看 agent 编排的未来走向
6.1 编排层和智能层的分离会越来越清晰
AX 这类运行时的出现,其实标志着一个趋势:agent 系统正在分层。最上面是智能层,负责"怎么思考、怎么决策",这是大模型和 prompt 工程的地盘;中间是编排层,负责"谁先谁后、怎么协同",这是 AX 这类运行时的地盘;最下面是基础设施层,负责算力、存储、网络。
这个分层的好处是各司其职。智能层可以快速迭代,换模型、改 prompt 都不影响编排;编排层可以独立优化调度算法,不用关心 agent 内部怎么想。这种解耦对于大型系统来说是必然选择,因为把所有这些混在一起,任何一层的改动都会牵一发而动全身。
6.2 声明式会不会成为 agent 编排的事实标准
我的判断是,在需要多 agent 协同的场景下,声明式编排会逐渐成为主流。原因很简单:当系统复杂到一定程度,人脑已经无法可靠地追踪所有执行路径时,把编排逻辑外化成可读的声明文件,是唯一能让人重新掌控系统的方式。
但这不意味着命令式会消失。在简单的、线性的 agent 流程里,直接写代码反而更直接。声明式的价值在复杂度和规模上体现,规模不到的时候,它的额外抽象层反而是负担。所以选型的时候要诚实评估自己的场景,别为了追新而引入不必要的复杂度。
6.3 给正在做 agent 项目的团队的建议
如果你正在做 agent 项目,我的建议是:先把单个 agent 做好,再考虑编排。很多团队一上来就想搞多 agent 协同,结果单个 agent 的可靠性都没解决,编排层再花哨也是空中楼阁。
等到确实需要编排了,先想清楚你的编排需求是静态的还是动态的。如果 agent 之间的流转关系基本固定,那声明式 YAML 很合适;如果流转关系高度依赖运行时数据、变化频繁,那可能命令式的代码反而更灵活。AX 这类工具是好东西,但工具永远是为场景服务的,别本末倒置。
最后分享一个我在实际项目里的体会:agent 编排最难的不是技术,而是可观测性。当几十个 agent 协同工作时,出了问题你根本不知道是哪个环节的锅。所以不管用什么编排方案,一定要把日志、追踪、指标这三样做扎实。我见过太多团队在编排逻辑上花了大功夫,结果线上出问题的时候两眼一抹黑,连问题出在哪个 agent 都定位不了。这个教训,希望后来的人能少踩一次。