TranFu
返回

Scaling Managed Agents:将“大脑”与“手”解耦

TFTranFu 精选2026/06/25 08:54

Anthropic Engineering Blog 以 Managed Agents 为例,说明其如何为 Claude Platform 上的长周期 agent 工作设计更稳定的接口,并解释为什么要将 harness、session 与 sandbox 从单一容器中解耦。

Anthropic logo
图片来源:anthropic.com
文章作者、来源:anthropic.com

正文

随着模型改进,harness 所编码的假设会变得过时。Managed Agents——我们用于长周期代理工作的托管服务——围绕即使 harness 发生变化也能保持稳定的接口构建。

请按照我们的文档开始使用 Claude Managed Agents。Engineering Blog 上一个持续讨论的主题是如何构建有效的代理,以及如何为长时间运行的工作设计 harness。这项工作中的一个共同线索是,harness 会编码关于 Claude 不能独自完成什么的假设。然而,这些假设需要经常受到质疑,因为随着模型改进,它们可能会变得过时。

仅举一个例子,在先前的工作中,我们发现 Claude Sonnet 4.5 在感觉到自己的上下文限制即将临近时,会过早结束任务——这种行为有时被称为“上下文焦虑”。我们通过向 harness 添加上下文重置来解决这个问题。但当我们在 Claude Opus 4.5 上使用同一个 harness 时,我们发现这种行为消失了。这些重置已经成了累赘。

我们预计 harness 会继续演进。因此,我们构建了 Managed Agents:这是 Claude Platform 中的一项托管服务,它通过一小组旨在比任何特定实现更持久的接口,代表你运行长周期代理——包括我们今天运行的那些实现。

构建 Managed Agents 意味着要解决计算领域的一个老问题:如何为“尚未被想到的程序”设计系统。几十年前,操作系统通过将硬件虚拟化为抽象来解决了这个问题——进程、文件——这些抽象足够通用,能够适用于当时尚不存在的程序。这些抽象比硬件更持久。read() 命令并不关心它访问的是 20 世纪 70 年代的磁盘组还是现代 SSD。上层抽象保持稳定,而底层实现可以自由变化。

Managed Agents 遵循同样的模式。我们虚拟化了代理的组成部分:session(发生过的一切的只追加日志)、harness(调用 Claude 并将 Claude 的工具调用路由到相关基础设施的循环),以及 sandbox(Claude 可以在其中运行代码和编辑文件的执行环境)。这允许每个部分的实现被替换,而不会干扰其他部分。我们对这些接口的形态有明确主张,而不是对其背后运行的东西有明确主张。

我们一开始把所有代理组件都放进一个容器,这意味着 session、agent harness 和 sandbox 都共享一个环境。这种方法有一些好处,包括文件编辑是直接的系统调用,而且没有需要设计的服务边界。

但通过把所有东西耦合进一个容器,我们遇到了一个老的基础设施问题:我们养了一只宠物。在“宠物 vs 牛群”的类比中,宠物是一个有名字、被人工照料、你承受不起失去它的个体,而牛群则是可互换的。在我们的案例中,服务器成了那只宠物;如果容器失败,session 就会丢失。如果容器无响应,我们就必须把它护理恢复健康。

护理容器意味着调试无响应的卡住 session。我们唯一的观察窗口是 WebSocket 事件流,但它无法告诉我们故障发生在哪里,这意味着 harness 中的 bug、事件流中的数据包丢失,或者容器离线,看起来都一样。为了弄清楚哪里出了问题,工程师必须在容器内部打开一个 shell,但因为那个容器通常也保存着用户数据,这种做法本质上意味着我们缺乏调试能力。

第二个问题是,harness 假设 Claude 处理的任何东西都和它一起存在于容器中。当客户要求我们把 Claude 连接到他们的虚拟私有云时,他们必须要么把他们的网络与我们的网络对等互联,要么在他们自己的环境中运行我们的 harness。一个被烘焙进 harness 的假设,在我们想把它连接到不同基础设施时变成了问题。

我们最终得到的解决方案是,将我们认为的“大脑”(Claude 及其 harness)与“手”(执行动作的 sandbox 和工具)以及“session”(session 事件日志)解耦。每个都成为一个对其他部分做很少假设的接口,并且每个都可以独立失败或被替换。

harness 离开容器。将大脑与手解耦意味着 harness 不再存在于容器内部。它调用容器的方式,就像调用任何其他工具一样:execute(name, input) → string。容器变成了牛群。如果容器死亡,harness 会把这个失败作为工具调用错误捕获,并传回给 Claude。如果 Claude 决定重试,一个新容器可以用标准配方重新初始化:provision({resources})。我们不再需要把失败的容器护理恢复健康。

从 harness 故障中恢复。harness 也变成了牛群。因为 session 日志位于 harness 之外,harness 中没有任何东西需要在崩溃后存活。当一个 harness 失败时,可以用 wake(sessionId) 重启一个新的 harness,使用 getSession(id) 取回事件日志,并从最后一个事件继续。在代理循环期间,harness 使用 emitEvent(id, event) 写入 session,以保持事件的持久记录。

安全边界。在耦合设计中,Claude 生成的任何不受信任的代码都运行在与凭证相同的容器中——因此一次提示注入只需要说服 Claude 读取它自己的环境。一旦攻击者拥有这些令牌,他们就可以生成新的、不受限制的 session,并把工作委派给它们。窄范围作用域是一个显而易见的缓解措施,但这编码了一个关于 Claude 不能用受限令牌做什么的假设——而 Claude 正变得越来越聪明。结构性修复是确保令牌永远无法从运行 Claude 生成代码的 sandbox 中触达。

我们使用了两种模式来确保这一点。Auth 可以与资源打包在一起,也可以保存在 sandbox 外部的 vault 中。对于 Git,我们使用每个代码仓库的访问令牌在 sandbox 初始化期间克隆仓库,并将其接入本地 git remote。git push 和 pull 可以在 sandbox 内部工作,而代理从不亲自处理令牌。对于自定义工具,我们支持 MCP,并将 OAuth 令牌存储在安全 vault 中。Claude 通过专用代理调用 MCP 工具;这个代理接收一个与 session 关联的令牌。然后该代理可以从 vault 中获取相应凭证,并向外部服务发起调用。harness 永远不会知道任何凭证。

长周期任务通常会超出 Claude 上下文窗口的长度,而解决这一问题的标准方式都涉及关于保留什么的不可逆决策。我们在先前关于上下文工程的工作中探索过这些技术。例如,压缩允许 Claude 保存其上下文窗口的摘要,而 memory 工具允许 Claude 将上下文写入文件,从而实现跨 session 的学习。这可以与上下文修剪配合使用,后者会选择性地移除旧工具结果或思考块等 token。

但选择性保留或丢弃上下文的不可逆决策可能导致失败。很难知道未来的轮次会需要哪些 token。如果消息经过压缩步骤转换,harness 会从 Claude 的上下文窗口中移除被压缩的消息,而这些消息只有在被存储的情况下才能恢复。先前的工作已经探索了通过将上下文存储为一个存在于上下文窗口之外的对象来解决这一问题的方法。例如,上下文可以是 REPL 中的一个对象,LLM 通过编写代码对其进行过滤或切片来以编程方式访问它。

在 Managed Agents 中,session 提供了同样的好处,充当存在于 Claude 上下文窗口之外的上下文对象。但上下文不是存储在 sandbox 或 REPL 中,而是持久存储在 session 日志中。接口 getEvents() 允许大脑通过选择事件流的位置切片来询问上下文。该接口可以灵活使用,允许大脑从它上次停止读取的地方继续,在某个特定时刻之前倒回几个事件以查看前因,或者在某个特定动作之前重新读取上下文。

任何获取到的事件也可以在传给 Claude 的上下文窗口之前,在 harness 中被转换。这些转换可以是 harness 编码的任何内容,包括用于实现高提示缓存命中率的上下文组织和上下文工程。我们将 session 中可恢复上下文存储与 harness 中任意上下文管理的关注点分离,因为我们无法预测未来模型需要什么具体的上下文工程。这些接口把上下文管理推入 harness,并且只保证 session 是持久的、可供询问的。

许多大脑。将大脑与手解耦解决了我们最早的客户抱怨之一。当团队希望 Claude 针对他们自己 VPC 中的资源工作时,唯一的路径是将他们的网络与我们的网络对等互联,因为容纳 harness 的容器假设每个资源都在它旁边。一旦 harness 不再位于容器中,这个假设就消失了。同样的改变也带来了性能收益。当我们最初把大脑放在容器中时,这意味着许多大脑需要同样多的容器。对于每个大脑,在该容器被配置好之前都无法进行推理;每个 session 都要在前期支付完整的容器设置成本。每个 session,即使是那些永远不会触碰 sandbox 的 session,也必须克隆仓库、启动进程、从我们的服务器获取待处理事件。

这种无效时间体现为首个 token 时间(TTFT),它衡量一个 session 在接受工作和产生第一个响应 token 之间等待了多久。TTFT 是用户最强烈感受到的延迟。

将大脑与手解耦意味着容器由大脑通过工具调用(execute(name, input) → string)按需配置。因此,一个不需要立即使用容器的 session 就不必等待容器。只要编排层从 session 日志中拉取待处理事件,推理就可以开始。使用这种架构,我们的 p50 TTFT 大约下降了 60%,p95 下降超过 90%。扩展到许多大脑只意味着启动许多无状态 harness,并且只有在需要时才把它们连接到手。

许多手。我们还希望能够将每个大脑连接到许多手。实际上,这意味着 Claude 必须对许多执行环境进行推理,并决定把工作发送到哪里——这比在单个 shell 中操作更难的认知任务。我们一开始把大脑放在单个容器中,是因为早期模型还不具备这种能力。随着智能扩展,单个容器反而变成了限制:当该容器失败时,我们会丢失大脑正在触达的每只手的状态。

将大脑与手解耦让每只手都成为一个工具,execute(name, input) → string:输入一个名称和输入,返回一个字符串。该接口支持任何自定义工具、任何 MCP server,以及我们自己的工具。harness 不知道 sandbox 是一个容器、一部手机,还是一个 Pokémon 模拟器。而且因为没有任何手与任何大脑耦合,大脑可以把手传递给彼此。

我们面临的挑战是一个老问题:如何为“尚未被想到的程序”设计系统。操作系统已经存在了几十年,因为它们将硬件虚拟化为足够通用的抽象,从而适用于当时尚不存在的程序。借助 Managed Agents,我们旨在设计一个能够围绕 Claude 容纳未来 harness、sandbox 或其他组件的系统。

Managed Agents 是同一精神下的元 harness,对 Claude 将来需要的具体 harness 不持立场。相反,它是一个具有通用接口的系统,允许许多不同的 harness。例如,Claude Code 是一个出色的 harness,我们在各类任务中广泛使用它。我们也已经展示了面向特定任务的 agent harness 在狭窄领域中表现出色。Managed Agents 可以容纳其中任何一种,并随着 Claude 的智能随时间发展而匹配它。

元 harness 设计意味着要对 Claude 周围的接口有明确主张:我们预期 Claude 将需要操纵状态(session)和执行计算(sandbox)的能力。我们还预期 Claude 将需要扩展到许多大脑和许多手的能力。我们设计这些接口,使其能够在长时间范围内可靠且安全地运行。但我们不对 Claude 将需要的大脑或手的数量或位置做任何假设。

作者:Lance Martin、Gabe Cemaj 和 Michael Cohen。感谢 Nodir Turakulov 和 Jeremy Fox 就这些主题进行的有益讨论。特别感谢 Agents API 团队和 Jake Eaton 的贡献。

产品更新、操作指南、社区亮点等内容。每月发送到你的收件箱。

阅读原文

AI解读

值得关注的是,这篇文章把 agent 基础设施问题从“写一个更强 harness”转向“设计能随模型能力变化而保持稳定的接口”。这对长期运行、可恢复、可调试的 AI agent 系统有参考价值。

对 AI builder、创业者和开发者而言,文章提示 agent 产品可能需要更早考虑 session 持久化、工具调用边界、sandbox 隔离和凭证管理,而不是把所有逻辑放进单一容器或单一执行环境中。

建议继续观察 Claude Managed Agents 的接口、文档和实际可用范围;如果正在构建长周期 agent,可对照检查 harness 与执行环境是否过度耦合,以及凭证是否会暴露给模型生成代码运行的 sandbox。