DeepSeek总结的 规模化托管代理:将大脑与双手解耦
来源:https://www.anthropic.com/engineering/managed-agents
规模化托管代理:将大脑与双手解耦
随着模型能力的提升,那些编码了“模型不能做什么”这一假设的“套件”(Harnesses)会逐渐过时。托管代理(Managed Agents)——我们用于处理长周期代理工作的托管服务——正是围绕那些即使在套件发生变化时也能保持稳定的接口而构建的。
请参阅我们的文档以开始使用 Claude 托管代理。
工程博客上一个持续讨论的话题是如何构建有效的代理,并为长时间运行的工作设计套件。这些工作的一个共同点是,套件编码了关于 Claude 自身无法完成哪些事情的假设。然而,这些假设需要经常被质疑,因为随着模型的改进,它们可能会变得过时。
仅举一例,在我们之前的工作中,我们发现 Claude Sonnet 4.5 在感知到其上下文窗口接近上限时会过早地结束任务——这种行为有时被称为“上下文焦虑”。我们通过在套件中添加上下文重置来解决这个问题。但是,当我们在 Claude Opus 4.5 上使用相同的套件时,我们发现这种行为消失了。重置变成了无用的负担。
我们预计套件将继续发展。因此,我们构建了托管代理:这是 Claude 平台中的一项托管服务,通过一组旨在超越任何特定实现(包括我们今天运行的实现)的接口,代表您运行长周期代理。
构建托管代理意味着要解决计算领域的一个老问题:如何为“尚未想到的程序”设计系统。几十年前,操作系统通过将硬件虚拟化为抽象概念(如进程、文件)来解决这个问题,这些抽象概念具有足够的通用性,可以服务于尚未存在的程序。这些抽象概念比硬件更持久。read() 命令无论访问的是 1970 年代的磁盘包还是现代 SSD,都是无差别的。顶层的抽象保持稳定,而底层的实现则可以自由更换。
托管代理遵循相同的模式。我们将代理的组件虚拟化:会话(Session,所有发生事件的仅追加日志)、套件(Harness,调用 Claude 并将其工具调用路由到相关基础设施的循环)和沙箱(Sandbox,Claude 可以运行代码和编辑文件的执行环境)。这使得每个组件的实现都可以被替换,而不会影响其他组件。我们对这些接口的形态有明确的看法,但对它们背后运行什么则没有。
不要养宠物
我们最初将所有的代理组件都放入一个单一的容器中,这意味着会话、代理套件和沙箱都共享一个环境。这种方法有其好处,包括文件编辑是直接的系统调用,并且没有服务边界需要设计。
但是,通过将所有内容耦合到一个容器中,我们遇到了一个老的基础设施问题:我们养了一只“宠物”。在“宠物 vs. 牲畜”的类比中,“宠物”是一个有名字的、需要手工照料的个体,你承受不起失去它的代价;而“牲畜”则是可互换的。在我们的案例中,服务器变成了那只宠物;如果一个容器发生故障,会话就丢失了。如果一个容器没有响应,我们必须费心使其恢复健康。
费心照料容器意味着要调试那些无响应的、卡住的会话。我们唯一的观察窗口是 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 和 git pull 可以从沙箱内部工作,而代理本身从未处理过令牌。对于自定义工具,我们支持 MCP(模型上下文协议),并将 OAuth 令牌存储在一个安全的保险库中。Claude 通过专用的代理调用 MCP 工具;这个代理接收与会话关联的令牌。然后,代理可以从保险库中获取相应的凭证,并向外部服务发出调用。套件永远不会意识到任何凭证的存在。
会话不是 Claude 的上下文窗口
长周期任务往往会超过 Claude 上下文窗口的长度,而解决这个问题的标准方法都涉及关于保留哪些内容的不可逆的决定。我们在之前关于上下文工程的工作中已经探讨了这些技术。例如,压缩允许 Claude 保存其上下文窗口的摘要,而记忆工具允许 Claude 将上下文写入文件,从而实现跨会话的学习。这可以与上下文裁剪相结合,后者选择性地移除某些令牌,例如旧的工具结果或思考块。
但是,选择性地保留或丢弃上下文的不可逆决定可能导致失败。很难知道未来的轮次需要哪些令牌。如果消息经过压缩步骤转换,套件会将压缩后的消息从 Claude 的上下文窗口中移除,并且这些消息只有在被存储的情况下才能恢复。先前的工作已经探索了通过将上下文存储为存在于上下文窗口之外的对象来解决这个问题的方法。例如,上下文可以是 REPL(读取-求值-打印循环)中的一个对象,LLM 通过编写代码来过滤或切片它以编程方式访问该对象。
在托管代理中,会话提供了同样的好处,充当存在于 Claude 上下文窗口之外的上下文对象。但上下文并非存储在沙箱或 REPL 中,而是持久地存储在会话日志中。getEvents() 接口允许大脑通过选择事件流的位置切片来查询上下文。该接口可以灵活使用,允许大脑从它上次停止阅读的地方继续,在特定时刻之前回退几个事件以查看前因后果,或者在特定操作之前重新阅读上下文。
任何获取到的事件也可以在传递给 Claude 的上下文窗口之前在套件中进行转换。这些转换可以是套件编码的任何内容,包括为实现高提示缓存命中率而进行的上下文组织,以及上下文工程。我们将可恢复的上下文存储(在会话中)和任意的上下文管理(在套件中)的关注点分离开来,因为我们无法预测未来的模型需要什么样的特定上下文工程。这些接口将上下文管理推送到套件中,并且仅保证会话是持久的并且可以被查询。
多大脑,多双手
多大脑。 将大脑与双手解耦解决了我们最早的一批客户投诉。当团队希望 Claude 在他们自己的 VPC 内处理资源时,唯一的途径是将他们的网络与我们的网络对等,因为容纳套件的容器假设每个资源都位于它旁边。一旦套件不再位于容器中,这个假设就消失了。同样的改变也带来了性能上的回报。当我们最初将大脑放在容器中时,这意味着许多大脑需要同样多的容器。对于每个大脑,在该容器被配置好之前,无法进行任何推理;每个会话都预先支付了完整的容器设置成本。每个会话,即使是那些永远不会接触沙箱的会话,也必须克隆仓库、启动进程、从我们的服务器获取待处理事件。
这个空闲时间体现在首字耗时(TTFT)上,它衡量一个会话在接受工作到产生第一个响应令牌之间等待了多长时间。TTFT 是用户最直接感受到的延迟。
将大脑与双手解耦意味着容器仅在需要时,才由大脑通过工具调用 execute(name, input) -> string 来配置。因此,一个不需要立即使用容器的会话不会等待容器。一旦编排层从会话日志中拉取了待处理事件,推理就可以立即开始。使用这种架构,我们的 p50 TTFT 下降了大约 60%,p95 TTFT 下降了超过 90%。扩展到许多大脑仅仅意味着启动许多无状态的套件,并仅在需要时才将它们连接到双手。
多双手。 我们还希望能够将每个大脑连接到许多双手。在实践中,这意味着 Claude 必须推理多个执行环境,并决定将工作发送到哪里——这比在单个 shell 中操作更困难的认知任务。我们最初将大脑放在单个容器中,是因为早期的模型没有能力做到这一点。随着智能水平的提升,单个容器反而成为了限制:当该容器发生故障时,大脑正在触及的每双手的状态都会丢失。
将大脑与双手解耦使得每双手成为一个工具:execute(name, input) -> string:传入一个名称和输入,返回一个字符串。该接口支持任何自定义工具、任何 MCP 服务器以及我们自己的工具。套件不知道沙箱是一个容器、一部手机还是一个宝可梦模拟器。并且,由于没有哪双手与某个大脑耦合,大脑们可以互相传递双手。
结论
我们面临的挑战是一个老问题:如何为“尚未想到的程序”设计系统。操作系统通过将硬件虚拟化为对尚未存在的程序足够通用的抽象,已经持续了几十年。通过托管代理,我们旨在设计一个能够适应围绕 Claude 的未来套件、沙箱或其他组件的系统。
托管代理是一个秉承相同精神的“元套件”,它对 Claude 将来需要的具体套件没有固定的看法。相反,它是一个拥有通用接口的系统,允许多种不同的套件。例如,Claude Code 就是一个优秀的套件,我们在各种任务中广泛使用它。我们也已经证明,针对特定任务的代理套件在狭窄领域表现出色。托管代理可以容纳任何一种套件,与 Claude 随时间推移而增长的智能相匹配。
元套件设计意味着对围绕 Claude 的接口有明确的看法:我们预计 Claude 将需要操纵状态(会话)和执行计算(沙箱)的能力。我们还预计 Claude 将需要扩展到许多大脑和许多双手的能力。我们设计了这些接口,以便它们能够在长时间跨度内可靠且安全地运行。但是,我们对 Claude 将需要的大脑或双手的数量或位置不做任何假设。
致谢
本文由 Lance Martin、Gabe Cemaj 和 Michael Cohen 撰写。感谢 Nodir Turakulov 和 Jeremy Fox 就这些主题进行了有益的对话。特别感谢 Agents API 团队和 Jake Eaton 的贡献。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)