扩展托管智能体:将大脑与双手解耦
按照我们的文档开始使用 Claude Managed Agents。
工程博客上一个持续讨论的话题是如何构建高效的智能体,以及如何为长时间运行的工作设计运行框架。这项工作的一个共同点是,运行框架编码了关于 Claude 自身无法独立完成哪些任务的假设。然而,这些假设需要经常被质疑,因为随着模型的改进,它们可能会过时。
仅举一例,在之前的工作中我们发现,Claude Sonnet 4.5 在感知到其上下文限制临近时会过早地结束任务——这种行为有时被称为“上下文焦虑”。我们通过在运行框架中添加上下文重置来解决这个问题。但当我们把同一个运行框架用于 Claude Opus 4.5 时,我们发现这种行为消失了。那些重置机制变成了无用的负担。
我们预计运行框架会继续演进。因此我们构建了 Managed Agents:这是 Claude Platform 中的一项托管服务,它通过一组小型接口代表您运行长周期智能体,这些接口旨在比任何特定实现都更持久——包括我们今天运行的那些实现。
构建 Managed Agents 意味着要解决计算领域的一个老问题:如何为“尚未想到的程序”设计系统。几十年前,操作系统通过将硬件虚拟化为抽象概念——进程、文件——解决了这个问题,这些抽象足够通用,可以适用于尚不存在的程序。这些抽象比硬件更持久。read() 命令并不关心它访问的是 1970 年代的磁盘组还是现代的 SSD。上层的抽象保持稳定,而底层的实现则可以自由变化。
Managed Agents 遵循相同的模式。我们将智能体的组件进行了虚拟化:一个会话(记录所有发生事件的仅追加日志)、一个运行框架(调用 Claude 并将 Claude 的工具调用路由到相关基础设施的循环)以及一个沙箱(Claude 可以运行代码和编辑文件的执行环境)。这使得每个组件的实现都可以在不干扰其他组件的情况下进行替换。我们对这些接口的形态有明确的看法,但对接口背后运行的内容没有。
不要养宠物
我们首先将所有智能体组件放入一个容器中,这意味着会话、智能体运行框架和沙箱都共享同一个环境。这种方法有其好处,包括文件编辑是直接的系统调用,并且无需设计服务边界。
但通过将所有内容耦合到一个容器中,我们遇到了一个老的基础设施问题:我们养了一只宠物。在宠物与牛群的类比中,宠物是一个有名字的、需要精心照料的个体,你承受不起失去它的代价,而牛群则是可以互换的。在我们的例子中,服务器成了那只宠物;如果一个容器失败,会话就会丢失。如果一个容器无响应,我们就必须精心照料它使其恢复健康。
照料容器意味着调试无响应的卡住会话。我们唯一的切入点是 WebSocket 事件流,但它无法告诉我们故障发生在哪里,这意味着运行框架中的一个 bug、事件流中的一个丢包或一个容器离线,表现出来的症状都是一样的。为了弄清楚出了什么问题,工程师必须在容器内打开一个 shell,但由于该容器通常也保存着用户数据,这种做法实际上意味着我们缺乏调试能力。
另一个问题是,测试框架假设 Claude 所处理的一切都与它一同位于容器内。当客户要求我们将 Claude 连接到他们的虚拟私有云时,他们要么需要将网络与我们的网络对等互联,要么在自己的环境中运行我们的测试框架。当我们想要将其连接到不同的基础设施时,测试框架中固化的假设就成了一大问题。
我们最终的解决方案是,将我们视为“大脑”的部分(Claude 及其测试框架)与“双手”(执行操作的沙箱和工具)以及“会话”(会话事件日志)解耦。每一部分都成为一个对其他部分假设极少的接口,并且每一部分都可以独立地发生故障或被替换。
测试框架离开容器。将大脑与双手解耦,意味着测试框架不再驻留在容器内部。它调用容器的方式与调用任何其他工具的方式相同:execute(name, input) → string。容器变成了“牛群”(可随意处置的资源)。如果容器死亡,测试框架会将其捕获为工具调用错误并传回给 Claude。如果 Claude 决定重试,可以使用标准配方重新初始化一个新容器:provision({resources})。我们不再需要费心将故障容器恢复健康。
从测试框架故障中恢复。测试框架也变成了“牛群”。因为会话日志位于测试框架之外,所以测试框架内部没有任何东西需要在崩溃后幸存。当一个测试框架失败时,可以用 wake(sessionId) 重启一个新的,使用 getSession(id) 找回事件日志,并从最后一个事件继续。在智能体循环期间,测试框架使用 emitEvent(id, event) 写入会话,以保留事件的持久记录。
安全边界。在耦合设计中,Claude 生成的任何不受信任的代码都与凭据在同一个容器中运行——因此,提示注入只需说服 Claude 读取其自身的环境即可。一旦攻击者获得这些令牌,他们就可以启动全新的、不受限制的会话,并将工作委派给这些会话。缩小作用范围是一种明显的缓解措施,但这编码了一个关于 Claude 在使用受限令牌时无法做什么的假设——而 Claude 正变得越来越聪明。结构性的修复方案是确保 Claude 生成的代码运行的沙箱永远无法访问这些令牌。
我们使用了两种模式来确保这一点。认证信息可以与资源捆绑在一起,也可以保存在沙箱外部的保险库中。对于 Git,我们在沙箱初始化期间使用每个仓库的访问令牌来克隆仓库,并将其连接到本地 git 远程地址。Git push 和 pull 可以在沙箱内部工作,而智能体本身从不处理该令牌。对于自定义工具,我们支持 MCP 并将 OAuth 令牌存储在安全的保险库中。Claude 通过专用代理调用 MCP 工具;该代理接收一个与会话关联的令牌。然后,代理可以从保险库中获取相应的凭据,并对外部服务发起调用。测试框架永远不会获知任何凭据。
会话不是 Claude 的上下文窗口
长时任务通常会超出 Claude 上下文窗口的长度,而解决这一问题的标准方法都涉及关于保留哪些内容的不可逆决定。我们在先前的工作中探讨过这些上下文工程技术。例如,压缩(compaction)允许 Claude 保存其上下文窗口的摘要,而记忆工具则允许 Claude 将上下文写入文件,从而实现跨会话的学习。这可以与上下文修剪(context trimming)结合使用,后者可以选择性地移除旧的工具结果或思维块等令牌。
但不可逆地选择性保留或丢弃上下文的决策可能导致失败。很难知道未来的对话轮次需要哪些 token。如果消息通过压缩步骤被转换,harness 会从 Claude 的上下文窗口中移除已压缩的消息,而这些消息只有在被存储的情况下才可恢复。先前的工作 已经探索过通过将上下文存储为一个存在于上下文窗口之外的对象来解决这个问题的方法。例如,上下文可以是 REPL 中的一个对象,LLM 通过编写代码来过滤或切片它,从而以编程方式访问它。
在 Managed Agents 中,会话提供了同样的好处,作为一个存在于 Claude 上下文窗口之外的上下文对象。但上下文不是存储在沙箱或 REPL 中,而是持久地存储在会话日志中。接口 getEvents(), 允许大脑通过选择事件流的位置切片来查询上下文。该接口可以灵活使用,允许大脑从上次停止读取的地方继续,回退到特定时刻之前的几个事件以查看前因,或在特定操作之前重新阅读上下文。
任何获取的事件也可以在 harness 中被转换,然后再传递给 Claude 的上下文窗口。这些转换可以是 harness 编码的任何内容,包括为实现高提示词缓存命中率和上下文工程而进行的上下文组织。我们将会话中可恢复的上下文存储与 harness 中任意的上下文管理这两个关注点分离开来,因为我们无法预测未来模型将需要什么样的具体上下文工程。这些接口将上下文管理推入 harness,并且只保证会话是持久的且可用于查询。
多个大脑,多双手
多个大脑。将大脑与双手解耦解决了我们最早的客户投诉之一。当团队希望 Claude 访问其自身 VPC 中的资源时,唯一的途径是将他们的网络与我们的网络对等互联,因为承载 harness 的容器假定每个资源都位于其旁边。一旦 harness 不再位于容器中,这个假设就消失了。同样的改变也带来了性能收益。最初我们将大脑放在容器中时,这意味着多个大脑需要同样多的容器。对于每个大脑,在该容器被配置好之前无法进行任何推理;每个会话都要预先支付完整的容器设置成本。每个会话,即使是那些永远不会接触沙箱的会话,都必须克隆仓库、启动进程、从我们的服务器获取待处理事件。
这段空闲时间体现在首 token 时间(TTFT)中,它衡量一个会话在接受工作到产生其第一个响应 token 之间等待了多久。TTFT 是用户感受最强烈的延迟。
将大脑与双手解耦意味着容器仅在需要时才由大脑通过工具调用 (execute(name, input) → string) 来配置。因此,不需要立即使用容器的会话就不必等待。只要编排层从会话日志中拉取待处理事件,推理就可以开始。使用这种架构,我们的 p50 TTFT 下降了约 60%,p95 下降了超过 90%。扩展到多个大脑只需启动多个无状态的 harness,并且仅在需要时才将它们连接到双手。
多只手。我们还希望每个大脑都能连接到多只手。在实践中,这意味着 Claude 必须对多个执行环境进行推理,并决定将工作发送到哪里——这比在单个 shell 中操作是一项更难的认知任务。我们最初将大脑放在单个容器中,因为早期模型不具备这种能力。随着智能的扩展,单个容器反而成了限制:当该容器发生故障时,大脑正在访问的每只手的状态都会丢失。
将大脑与手解耦,使每只手成为一个工具,execute(name, input) → string:输入一个名称和输入,返回一个字符串。该接口支持任何自定义工具、任何 MCP 服务器以及我们自己的工具。运行框架不知道沙箱是容器、手机还是宝可梦模拟器。而且由于没有任何手与任何大脑耦合,大脑之间可以互相传递手。
结论
我们面临的挑战是一个由来已久的问题:如何为“尚未想到的程序”设计一个系统。操作系统通过将硬件虚拟化为足够通用的抽象,以适应尚不存在的程序,从而延续了数十年。通过 Managed Agents,我们旨在设计一个能够容纳未来围绕 Claude 的运行框架、沙箱或其他组件的系统。
Managed Agents 是一个具有相同精神的元运行框架,对 Claude 未来所需的具体运行框架不持特定立场。相反,它是一个具有通用接口的系统,允许多种不同的运行框架。例如,Claude Code 是一个优秀的运行框架,我们在各种任务中广泛使用它。我们还表明,针对特定任务的智能体运行框架在狭窄领域表现出色。Managed Agents 可以容纳其中任何一种,并随着时间的推移与 Claude 的智能相匹配。
元运行框架设计意味着对围绕 Claude 的接口有明确的预期:我们预计 Claude 将需要操纵状态(会话)和执行计算(沙箱)的能力。我们还预计 Claude 将需要扩展到多个大脑和多只手的能力。我们设计了这些接口,以便它们能够在长时间范围内可靠、安全地运行。但我们不对 Claude 需要的大脑或手的数量或位置做任何假设。