背景:智能体的时间花在了等待上
绝大多数智能体框架把循环写成一串阻塞步骤:调模型、调工具、等工具返回、再调模型。这个写法在单任务场景里没有问题,一旦智能体需要长时间运行、需要响应外部事件、需要派生子智能体,代价就暴露出来了。
AWS Hydro 团队研究负责人 Shadaj Laddad 在 10 月 23 日的 AWS 开源博客上给出的三个例子很具体:编码智能体启动一次构建要跑二十分钟;分诊智能体要等仓库里下一个拉取请求,可能等几小时;助理智能体要等审批,可能等到第二天早上。他的原话是,在等待期间,智能体「持有一个进程和它的内存」,并且无法响应这期间到达的任何事件。
这句话同时点出了两个成本。其一是资源成本:一个什么都不做只是在等的智能体,占用着一个进程、一份栈、可能还有一条长连接。其二是能力成本:因为循环是阻塞的,等待期间发生的事——有人开了个拉取请求、构建失败了、用户追加了一句——都没有入口进来,框架只能把这些事件塞进系统提示词或者干脆丢掉。
过去两年行业里降低智能体运行成本的主流做法,是从宿主机一侧入手:给每个智能体一台更轻的虚拟机、更快的沙箱、更小的镜像。Infinity 的切入点相反,它问的是「一个空闲智能体最少需要保留什么」。
核心抽象:把智能体当成无栈协程
Infinity 借用的是编程语言实现异步函数时使用的无栈协程(stackless coroutines)。Rust 用它来实现 async fn:编译器把每个函数改写成一个状态机,在每个 await 点只保存相关的值,因此内存占用很小,执行器可以高效地跑大量协程。
把这个结构映射到智能体上,对应关系是这样的:当智能体调用一个工具的那一刻,它的会话历史里已经包含了结果返回后继续运行所需的全部信息。运行时只要把这段历史持久化,就可以丢弃全部内存状态;之后任何能访问这份存储的进程都能把它恢复出来。
| 维度 | 常规阻塞式 harness | Infinity |
|---|---|---|
| 工具调用期间 | 持有进程、栈、内存、连接 | 只保留持久化的会话历史 |
| 等待三天的 CI | 进程或调用全程在线 | 无任何计算在跑,直到结果入队 |
| 恢复所需 | 回到原进程、原上下文 | 任意进程按 thread_id 载入历史 |
| 等待期间的事件 | 无标准入口 | 经输入队列触发新的 slice |
| 空闲期的内存 | 进程常驻开销 | 约 100 KB(官方读数) |
Infinity 把智能体的「续延(continuation)」定义为它的会话历史,而不是它的进程。进程是可丢弃的实现细节,会话历史才是身份。这个决定一旦做出,后面所有设计——有界 slice、共享输入队列、无状态 Lambda、事件即消息——都是它的推论。
执行模型:一个有界 slice 只做四件事
Infinity 把每个智能体的执行切成一串有界 slice(bounded slices)。一个 slice 内,运行时依次做四件事,然后结束:
- 载入:从可插拔存储中读出该线程的会话历史
- 生成:运行一次模型完成(one model completion)
- 持久化:把这次输出写入存储
- 分发:最多分发一次工具调用
关键在于 slice 从不阻塞。它不等工具结果、不等定时器、不等任何外部操作。工具结果到达输入队列之后,会启动一个新的 slice 来恢复这个智能体。用官方的说法,工具调用扮演的就是 await 点的角色。
这个「一次完成 + 最多一次工具调用」的约束看起来很保守,但它正是后面能跑在 Lambda 上的前提:一个 slice 的耗时上界是可预估的,因为它不包含任何时长不确定的等待。
slice 结束的边界条件
从设计上看,一个 slice 有三种结束方式:模型调用了工具(最常见)、模型给出了不含工具调用的最终回复、或者发生了中断与压缩。前两种是正常路径,第三种在长驻进程模式下由每线程的驱动器处理批处理、中断与自动压缩。官方特别说明,两种模式跑的是同一条流水线,因此针对核心库写的工具、模型 provider 和测试,在两种模式下都不需要改动。
队列、线程与顺序:借来的 actor 模型
所有能唤醒一个智能体的东西——用户文本、工具结果、定时器、其他线程的消息——都作为一条消息落在共享输入队列上。因为队列吸收了待处理事件,运行时不需要为了让智能体保持存活而维持任何东西。当运行时从队列取出一条消息,就启动一个 slice,重新载入该线程的状态并处理这条消息。
智能体运行在agent system(智能体系统)之内,这是一个共享存储、工具和模型的agent thread(智能体线程)池。运行时的组织方式接近 actor 系统:
- 每个会话线程有自己的输入队列
- 线程之间不共享可变状态
- 线程之间只通过向彼此的队列发消息通信
- 每条消息携带一个 thread_id 标识目标线程
顺序保证是这套设计里最需要说清楚的一条:同一线程的消息按序处理,一次一个 slice;不同线程的 slice 并行执行。官方给出的理由是正确性——两个同时读写同一份历史的 slice 会互相覆盖各自的回合。而不同线程之间没有需要协调的东西,所以可以放心并行。
在长驻进程模式下,智能体系统给每个活跃线程一个通道和一个轻量任务,调度方式与异步执行器调度 future 相同。当一个线程既没有排队消息也没有未完成工具结果时,它的任务退出;下一条消息到达时运行时再启动一个新的。
无服务器形态:等待期间什么都不跑
因为没有 slice 会等待任何东西,Infinity 可以直接跑在无服务器平台上:每份工作启动一个函数,做完即退出。这带来了一个在成本模型上很不一样的性质——等待期间的智能体不占用任何计算。Laddad 的原话是,一个等待三天 CI 流水线的智能体,在流水线报告结果之前不使用任何计算资源。
AWS 给出的参考部署把这一形态落到了具体服务上:
| 角色 | AWS 参考实现 | 说明 |
|---|---|---|
| 输入队列 | SQS FIFO 队列 | 每个线程一个 MessageGroupId |
| 计算 | AWS Lambda | 每次投递触发一个函数,跑一个 step 后退出 |
| 会话历史存储 | Aurora DSQL | 按 thread_id 存取 |
| 去重状态 | DynamoDB | 记录每个线程已处理的消息 ID |
| 定时器 | SQS 投递延迟 + EventBridge Scheduler | 供 sleep 类工具使用 |
| 资源编排 | 仓库内含 CDK 构造 | 用于开通上述资源 |
能跑在 Serverless 上的五条前提
官方把这套设计能在无服务器平台上成立的条件写得很明确,这五条也构成了理解 Infinity 边界的最好索引:
| 前提 | 官方表述含义 | 工程后果 |
|---|---|---|
| 有界执行时间 | 一个 slice 只有一次完成和少量存储与分发调用,不等待任何时长不确定的操作 | 可直接映射到有超时上限的函数计算 |
| 无进程亲和 | 全部状态按 thread_id 存储,任何调用都能处理任何线程的下一条消息 | 无需会话粘滞,可任意扩缩 |
| 至少一次投递 | 队列可能重复投递,每条输入带稳定 ID,状态存储记录已处理 ID,重投消息被识别并跳过 | slice 处理天然幂等 |
| 持久化回合 | 流式输出先缓冲,回合完成后才提交;流中断则丢弃缓冲重试;工具调用结束回合时先持久化再分发 | 调用先于结果被记录,不会出现「结果先到、调用未落盘」 |
| 免锁串行化 | FIFO 消息组在队列层串行化每个线程的 slice,Lambda 不会同时处理同一消息组的两次调用 | 运行时不需要分布式锁 |
最后一条值得单独看一眼。很多团队在把智能体搬到多副本环境时,最先想到的方案是引入分布式锁来保护会话状态。Infinity 把串行化的责任下沉到了队列的消息组语义上,从而把一件难做对的事变成了托管服务的既有能力。这是这份设计里工程含量较高、也最容易被忽略的一处。
合成工具调用:把异步事件塞进同步对话
真正棘手的问题在这里:模型 API 没有表达「异步事件」的位置。一段对话由用户回合、助手回合、以及成对的工具调用与工具结果组成,每个工具调用必须对应恰好一个结果。那么,当智能体订阅了某个仓库的事件流,三天后有人开了个拉取请求,这条消息该以什么身份进入对话?
官方列出了三条被否决的路径,这三条恰好覆盖了实践中常见的做法:
| 方案 | 问题 |
|---|---|
| 给原工具调用追加第二个结果 | 违反「一个调用对应一个结果」的规则,模型通常会拒绝这种对话 |
| 把事件作为一条用户消息加入 | API 能接受,但事件被归因给了用户,模型无法区分「用户说的」和「工具报的」 |
| 把事件放到对话之外,例如系统提示词 | 事件脱离了历史中的时序,而模型恰恰依赖时序理解事情发生的先后 |
| Infinity 的方案:合成工具调用 | 由运行时插入一个模型并未真正发出的助手工具调用,紧接一个承载事件的工具结果 |
这个合成调用有四个细节值得记录:
- 名字固定为
receive_event__injected,而不是复用原工具名,因此模型可以区分「新事件」与「又一次工具调用」 __injected后缀表明这是运行时创建的- 参数里带上原工具名、原调用 ID 与原参数,告诉模型这个事件属于它的哪一次请求
- 运行时从不把这个名字作为工具暴露给模型,如果模型尝试调用它,会被拒绝
官方对这套设计的解释很实在:以工具调用的形式投递事件,保留了模型在后训练阶段习得的「解读工具结果并据此行动」的能力。换句话说,与其教模型一种全新的消息类型,不如把新东西伪装成它已经会处理的东西。
工具侧产生事件的方式与产生首个结果的方式相同——向智能体的输入队列发消息。工具把它的首个结果标记为「订阅开始」,这告诉运行时同一个调用后面还可能跟更多结果;之后每条消息都被标记为该调用的事件。智能体可以在事件之间进入空闲,每个事件到达时再被唤醒。
事件的两种落点:threaded 与 associative
事件到达之后,往哪儿放?Infinity 给了两种模式。
threaded(默认):运行时不在订阅线程的会话历史里加入这个事件,而是派生一个继承父上下文的临时子线程,把合成事件交给它,由它决定怎么处理。子线程可以据此行动并向父线程汇报,也可以判断事件无关后直接关闭——这种情况下父线程的历史完全不变。多个事件几乎同时到达时,每个事件各有一个子线程,并发处理。
associative:工具可以把事件标记为关联型,此时运行时把合成调用直接加进订阅线程的历史。官方说明这适用于「更新智能体正在跟进的某个操作」的事件,例如智能体在后台启动的一次测试运行的输出。
这里的取舍很清楚:threaded 保护主历史的信噪比,代价是引入一次额外的线程调度与一次上下文继承;associative 保住了严格时序,代价是主历史会被事件流持续拉长。选哪种,取决于事件是「需要判断的事」还是「需要记住的进度」。
父子智能体:spawn_thread 与 report_to_parent
同一套异步工具调用与事件的模型,顺带解决了智能体之间的通信。每个智能体都有一个内建的 spawn_thread 工具,用来创建一个拥有自己输入队列的子线程。
从父线程的角度看,spawn_thread 的行为和一次订阅一样:调用在创建完成后立即返回,父线程继续做自己的事,子线程的汇报稍后作为该调用上的事件抵达。子线程调用 report_to_parent 时,运行时向父线程的输入队列发一条消息,标记为原始 spawn_thread 调用的事件,父线程看到的就是一个指向那次派生的合成工具调用。
由此得到几个性质:
- 一个子线程可以在工作过程中发多次汇报,最终汇报在它调用
close_thread时以同样方式抵达 - 因为汇报指向具体的那次派生,即使同时跑着很多子线程,父线程也知道是哪一项回报的
- 运行时不需要为智能体另建一套消息系统——派生子代和向父代汇报都是普通的工具调用,结果与事件都走输入队列
- 前面说的顺序保证对它们同样适用,父代与子代作为同一智能体系统中的独立线程并发运行
主流多智能体框架通常需要一层独立的编排器或消息总线来管理角色、任务与结果回传。Infinity 把这些全部压回工具调用与事件两个原语:派生是一次工具调用,汇报是一次事件。好处是编排逻辑对模型可见、可审计、可回放;代价是所有跨智能体协作都必须能通过「一个线程的历史」来表达,遇到需要共享可变状态或严格事务语义的协作,这套模型就没有现成答案。
那个 75,000 是怎么测出来的
官方的测试方法需要完整读,否则数字很容易被误用。AWS 写了一个基准,在一个智能体系统中分批启动成波次的智能体,后端接的是模拟模型而非真实模型。
| 测试项 | 设置或读数 |
|---|---|
| 模型 | 模拟模型(非真实模型) |
| 运行环境 | AMD EPYC 9R14 服务器处理器的单个线程,内存存储 |
| 智能体数量 | 80,000 |
| 每智能体回合数 | 20 |
| 每回合构成 | 模型流式产出约 1.2 KB 的完成并调用工具 → 工具结果经输入队列返回 → 第二次完成结束该回合 |
| 完成总数 | 320 万次(3.2 million) |
| 驻留内存增长 | 线性,约 100 KB 每个空闲智能体 |
| 换算结论 | 约 75,000 个智能体可放入 8 GB(树莓派 5 的内存容量) |
官方同时提醒:树莓派 5 只是内存容量的类比,测试并非在树莓派上跑;这些数字是 AWS 自己的,尚未经过独立验证。
本文推算:三个从测试设置里能读出来、但原文没直说的点
其一,每回合是两次完成,不是一次。官方写明了每回合包含一次调用工具的完成和一次结束回合的完成,因此 80,000 × 20 × 2 = 320 万这个等式才成立。如果按「每回合一次完成」去理解,会得到 160 万,与官方数字对不上。这一点在估算真实成本时很关键:真实场景下每个回合的模型调用次数会直接乘进账单。
其二,75,000 与 80,000 之间大约 6% 的差,是给谁留的。按 100 KB 每智能体线性外推,80,000 个智能体约合 7.6 GiB,已经贴着 8 GB;官方给出的可容纳数是约 75,000,约合 7.2 GiB。两者相差约 6%,这部分差额应理解为运行时自身、存储结构与碎片的余量,官方未单独披露构成。
其三,100 KB 里并非全是会话历史。官方说这些内存大部分被会话历史占用,因为空闲智能体没有任务、栈或连接。但按每回合 1.2 KB 的完成文本粗算,20 个回合累计约 24 KB,与 100 KB 之间仍有约 76 KB 的差。这部分来自存储结构、索引与运行时本身,未被单独量化。这提示一件事:如果真实会话的每回合输出远大于 1.2 KB——例如带大段工具返回——那么 100 KB 这个读数会明显上移,而官方没有给出按历史长度变化的曲线。
把这三点合起来看,75,000 这个数字衡量的是「等待的成本」,而且是在把一个很贵的变量(真实模型调用)从回路里摘掉之后衡量的。它证明的是架构层面的内存效率,不是端到端的吞吐或成本优势。
与相邻方案的坐标
Infinity 发布前一天,Google Cloud 公布了 Agent Compute Engine,为每个智能体提供独立的微虚拟机,并在空闲时暂停。两家云厂商在同一个星期给出了对同一个问题的两种回答,这个对照很有价值。
| 维度 | Google Cloud Agent Compute Engine | AWS Infinity |
|---|---|---|
| 暂停的层级 | 虚拟机层:暂停整机 | 应用层:结束 slice,丢弃进程 |
| 保留什么 | 整机状态(内存、进程、连接) | 仅会话历史 |
| 恢复代价 | 恢复虚拟机,上下文原样在 | 重新载入历史并重建上下文 |
| 粒度 | 较粗,以智能体为单位 | 较细,以一次工具调用为单位 |
| 对框架的侵入 | 较低,框架无需改造 | 较高,工具需改为「启动后即返回」 |
| 可运行在哪 | 依赖微虚拟机平台 | 长驻进程或标准无服务器平台 |
这个对照的关键分野是暂停与丢弃的区别。暂停保留了恢复速度,但也保留了内存占用;丢弃把占用降到历史文本的量级,但每次恢复都要重建上下文。哪种更划算,取决于智能体的等待时长分布与历史长度分布,官方未给出两者的交叉分析。
另需说明一处来源差异:有第三方技术媒体转述称,Infinity 仓库的 README 描述了 Reactive Agent Protocol(用于在不保持连接开放的前提下通过网络提供工具)以及「现有 MCP 服务器通过兼容层原样运行」,并提到了构建在该运行时之上的编码智能体 Infinity Code。我们在 AWS 官方博客正文中未检索到 Reactive Agent Protocol、MCP 与 Infinity Code 三项表述,因此本文只将其作为第三方转述列出,不作为已确认事实。目前官方及行业暂未披露更多细节,后续将持续跟进迭代动态。
开放问题:作者自己列出的四道未解之题
官方明确把 Infinity 定位为「早期研究版本」,并公开列出了开放问题。这四条比任何宣传语都更能说明它的成熟度:
| 开放问题 | 官方表述 | 为什么难 |
|---|---|---|
| 交错事件的推理 | 合成工具调用让对话保持格式合法,但模型主要训练于工具调用是同步的轨迹;想理解事件数量、顺序以及 threaded 与 associative 的选择如何影响智能体决策 | 训练分布与推理分布不一致,属于分布外问题 |
| 调度策略 | 运行时目前按消息到达顺序处理线程;智能体系统存在优先级,调度器可考虑让面向用户的线程先于后台事件处理器 | 一旦引入优先级,顺序保证与公平性需要重新定义 |
| 智能体树中的压缩 | 运行时按压缩摘要在历史中的位置建立索引,因此压缩前派生的子线程仍能重建它继承的那段历史;但尚未研究长时间运行的大型会话树该如何压缩 | 压缩与派生相互交织,压缩粒度会跨线程传播 |
| 事件真实性 | 运行时把每个事件锚定到它的发起请求,但暴露在互联网上的智能体可能收到不真实的载荷;如何在维持无状态架构的同时建立事件来源,使行为可追溯、可问责 | 无状态设计与来源证明之间存在张力 |
其中第四条是这套架构最深的一道题。传统长驻进程可以靠连接身份、会话绑定来建立一部分信任;当恢复方可以是任意一个函数实例、触发只是一条队列消息时,信任的锚点就必须从「谁在跑」转移到「消息是谁发的、能否验证」。这已经不只是一个运行时问题,而是一套需要签名、溯源与审计的机制。官方把它列为开放问题,说明目前还没有答案。
采用检查清单
基于上面的拆解,如果要在生产环境评估 Infinity,以下六条建议先做:
- 先算等待占比。统计现有智能体每个回合中,工具等待时长与模型生成时长的比例。等待占比越高,这套模型的收益越大;若瓶颈在生成侧,收益有限。这个比值官方没有给出,需要自己测。
- 把工具改写成「启动即返回」。官方示例中的工具 execute 方法不返回结果,而是启动工作后立即返回,结果由工具自己发回输入队列。这是接入 Infinity 的主要改造量,也是与常规框架差异最大的一处。
- 确认存储层的延迟预算。每个 slice 都要读一次历史、写一次输出。当并发规模上去之后,存储会变成新的热点,官方测试用的是内存存储,参考部署用的是 Aurora DSQL,两者的延迟特征差别很大。
- 为事件定级。明确哪些事件走 threaded、哪些走 associative。判断标准是:需要智能体「做判断」的事件走 threaded,需要智能体「记住进度」的事件走 associative。
- 补一层真实模型冒烟测试。官方基准用的是模拟模型。任何以 3.2 million 完成数为依据的容量规划,都需要换成真实模型重跑一遍,否则模型侧的排队与限流会推翻内存侧的结论。
- 先按研究版本对待。官方明确定位为早期研究发布,四条开放问题未解。建议先在非关键路径上验证,尤其是事件真实性与压缩这两条。
冷静判断:它改变了什么,没改变什么
改变的是「空闲」的定义。在此之前,行业默认一个活着的智能体需要一个活着的进程。Infinity 把这句话改成了:一个活着的智能体需要一份能被读出来的历史。这个改动带来的连锁效应很实在——长任务智能体在等待审批、等待构建、等待人工回复期间不再消耗计算,这让「跑几天的智能体」在经济上开始说得通。
改变的是事件的一等公民地位。合成工具调用是个取巧但有效的设计。它没有去改模型 API,而是在既有结构里给异步事件找到了合法位置,配套的四条约束(专用名称、注入后缀、不暴露为工具、模型调用即拒绝)也把误用风险压到了较低水平。
没有改变的是模型调用的成本结构。75,000 这个数字是在没有真实模型参与的情况下测出来的。真实智能体的账单主要来自 token,而 Infinity 对 token 消耗没有任何结构性改进——它既不减少完成次数,也不压缩提示词。
没有改变的是分布外的风险。模型主要在同步工具调用的轨迹上训练,而 Infinity 恰恰要让它处理交错到达的事件。官方把这条列为开放问题,意味着连作者也不确定影响有多大。
也没有改变的是信任问题。事件真实性、来源证明、可问责性,这些在长驻进程时代就不好做,在无状态架构下更难。这不是 Infinity 的缺陷,而是它把一个原本被进程边界掩盖的问题显式暴露了出来。
综合来看,Infinity 提供的是一个值得认真研究的执行模型,而不是一个可以立刻替换现有框架的产品。它最有价值的部分,可能不是那 75,000,而是它把一个长期被忽略的问题——等待到底值多少钱——用一种可测量的方式摆到了台面上。