扩展 Managed Agents:把“大脑”和“手”解耦
发布于 2026 年 4 月 8 日
随着模型能力提升,harness(运行框架)里编码的假设会逐渐过时。Managed Agents(托管智能体)是我们为长周期智能体工作提供的托管服务,它围绕一组稳定接口构建,即使 harness 不断变化,这些接口也能保持稳定。
你可以按照我们的文档开始使用 Claude Managed Agents。Engineering Blog 上一个持续讨论的主题,是如何构建有效的智能体,以及如何为长时间运行的工作设计 harness。贯穿这些工作的一个共同点是:harness 往往会编码一些关于 Claude “还不能独立完成什么”的假设。然而,这些假设需要经常被重新审视,因为随着模型改进,它们可能会过时。
举一个例子:在此前的工作中,我们发现 Claude Sonnet 4.5 在感知到自己的 context limit 接近时,会过早地收尾任务——这种行为有时被称为“context anxiety”。我们通过在 harness 中加入 context reset 来解决这个问题。但当我们把同一个 harness 用在 Claude Opus 4.5 上时,发现这种行为已经消失了。那些 reset 变成了累赘。
我们预计 harness 会持续演进。因此,我们构建了 Managed Agents:这是 Claude Platform 中的一个托管服务,可以代表你运行长周期智能体;它通过一小组接口工作,而这些接口被设计为比任何特定实现都更长寿——包括我们今天正在运行的实现。
构建 Managed Agents,意味着要解决计算领域一个古老的问题:如何为“尚未被想象出来的程序”设计系统。几十年前,操作系统通过把硬件虚拟化为抽象来解决这个问题——比如 process、file——这些抽象足够通用,可以服务于当时还不存在的程序。抽象的寿命超过了硬件。read() 命令并不关心它访问的是 20 世纪 70 年代的磁盘组,还是现代 SSD。上层抽象保持稳定,而底层实现可以自由变化。
Managed Agents 遵循同样的模式。我们把智能体的组成部分虚拟化了:session(发生过的一切事件的 append-only log)、harness(调用 Claude 并把 Claude 的工具调用路由到相关基础设施的循环),以及 sandbox(Claude 可以在其中运行代码和编辑文件的执行环境)。这样一来,每个组件的实现都可以被替换,而不会扰动其他组件。我们对接口的形状有明确主张,但不对接口背后运行的具体实现下注。
不要养“宠物”
一开始,我们把所有智能体组件都放进同一个容器里,这意味着 session、agent harness 和 sandbox 共享同一个环境。这种做法有一些好处,比如文件编辑就是直接的系统调用,也不需要设计服务边界。
但把所有东西耦合到一个容器之后,我们遇到了一个老的基础设施问题:我们养了一只“宠物”。在 pets-vs-cattle 的类比中,pet 是一个有名字、需要人工照料、不能轻易丢失的个体;cattle 则是可互换的。在我们的场景里,服务器变成了那只 pet:如果容器失败,session 就丢了;如果容器无响应,我们就必须把它“救活”。
照料容器意味着要调试无响应、卡死的 session。我们唯一的观察窗口是 WebSocket event stream,但它无法告诉我们故障出在哪里:harness 中的 bug、event stream 中的丢包、或者容器离线,看起来都一样。为了弄清楚问题,工程师必须进入容器 shell;但由于这个容器通常也保存着用户数据,这种方式本质上意味着我们没有真正可用的调试能力。
第二个问题是,harness 假设 Claude 处理的任何东西都和它一起存在于容器中。当客户要求我们把 Claude 连接到他们的 virtual private cloud 时,他们要么必须把自己的网络和我们的网络做 peering,要么在自己的环境中运行我们的 harness。一个被写死在 harness 里的假设,在我们想把它连接到不同基础设施时变成了问题。
把“大脑”和“手”解耦
我们最终采用的解决方案,是把我们称为“大脑”的部分(Claude 及其 harness)与“手”(执行动作的 sandbox 和工具)以及“session”(session event 的日志)解耦。每一部分都成为一个接口,并且尽量少地假设其他部分;每一部分都可以独立失败,也可以独立替换。
harness 离开容器。 把大脑和手解耦,意味着 harness 不再住在容器里。它调用容器的方式,就像调用任何其他工具一样:execute(name, input) → string。容器变成了 cattle。如果容器死掉,harness 会把这次失败捕获为一次 tool-call error,并把它传回 Claude。如果 Claude 决定重试,一个新容器可以用标准配方重新初始化:provision({resources})。我们不再需要把失败的容器救回健康状态。
从 harness 失败中恢复。 harness 本身也变成了 cattle。因为 session log 位于 harness 之外,harness 中没有任何东西必须在崩溃后幸存。当一个 harness 失败时,可以用 wake(sessionId) 启动一个新的 harness,用 getSession(id) 取回 event log,并从最后一个事件继续。在 agent loop 运行期间,harness 通过 emitEvent(id, event) 写入 session,以保留一个持久的事件记录。
安全边界。 在耦合式设计中,Claude 生成的任何不受信任代码,都会和 credentials 运行在同一个容器里——因此 prompt injection 只需要说服 Claude 读取自己的环境变量。一旦攻击者拿到这些 token,就可以启动新的、无限制的 session,并把工作委派给它们。窄范围授权当然是一种缓解手段,但这又编码了一个关于 Claude “用受限 token 做不到什么”的假设——而 Claude 正变得越来越聪明。结构性的修复方式,是确保 token 永远不能被 Claude 生成代码运行的 sandbox 访问到。
我们使用了两种模式来保证这一点。Auth 可以和某个 resource 打包在一起,也可以保存在 sandbox 外部的 vault 中。对于 Git,我们使用每个仓库的 access token 在 sandbox 初始化期间 clone 仓库,并把它接入本地 git remote。这样,git push 和 pull 可以在 sandbox 内工作,但 agent 永远不会直接处理 token。对于自定义工具,我们支持 MCP,并把 OAuth token 存在安全 vault 中。Claude 通过专用 proxy 调用 MCP 工具;这个 proxy 接收一个与 session 关联的 token,然后可以从 vault 中取出对应 credentials,并调用外部服务。harness 永远不会知道任何 credentials。
session 不是 Claude 的 context window
长周期任务经常会超过 Claude context window 的长度,而处理这个问题的标准方法都涉及不可逆的取舍:到底保留什么。我们在此前关于 context engineering 的工作中探索过这些技术。例如,compaction 允许 Claude 保存 context window 的摘要,而 memory tool 允许 Claude 把 context 写入文件,从而跨 session 学习。这些方法可以与 context trimming 结合使用,后者会选择性移除某些 token,例如旧的 tool result 或 thinking block。
但选择性保留或丢弃 context 的不可逆决策,可能导致失败。很难提前知道未来的 turn 会需要哪些 token。如果 message 被 compaction 步骤转换,harness 会从 Claude 的 context window 中移除被 compact 的 message;只有在它们被存储下来时,才可以恢复。此前的工作探索过一种解决方式:把 context 存成一个存在于 context window 之外的对象。例如,context 可以是 REPL 中的一个对象,LLM 通过编写代码来过滤或切片,从而以程序化方式访问它。
在 Managed Agents 中,session 提供了同样的好处:它是一个存在于 Claude context window 之外的 context object。但它不是被存放在 sandbox 或 REPL 里,而是被持久化存储在 session log 中。getEvents() 接口允许大脑通过选择 event stream 中的位置切片来查询 context。这个接口可以灵活使用:大脑可以从上次停止阅读的位置继续,也可以在某个特定时刻之前倒回几个 event 查看前因,或者在某个特定动作之前重新读取 context。
任何被取回的 event,也可以在传入 Claude context window 之前先由 harness 转换。这些转换可以是 harness 所编码的任何东西,包括为了提高 prompt cache 命中率而进行的 context organization,以及 context engineering。我们把 session 中可恢复的 context storage 与 harness 中任意形式的 context management 分离开来,因为我们无法预测未来模型会需要哪些具体的 context engineering。接口把 context management 推给 harness,只保证 session 是持久的,并且可以被查询。
多个大脑,多个手
多个大脑。 把大脑和手解耦,解决了我们最早收到的一个客户抱怨。当团队希望 Claude 在他们自己的 VPC 中操作资源时,唯一途径是把他们的网络和我们的网络做 peering,因为包含 harness 的容器假设所有资源都在它旁边。一旦 harness 不再位于容器中,这个假设就消失了。同一个变化也带来了性能收益。当我们最初把大脑放进容器时,多个大脑就意味着需要同样数量的容器。对每个大脑来说,在容器 provision 完成之前都无法开始 inference;每个 session 都必须在一开始支付完整的容器初始化成本。即使那些永远不会碰 sandbox 的 session,也必须 clone 仓库、启动进程、从我们的服务器取回 pending event。
这种死时间会体现在 time-to-first-token(TTFT)上。TTFT 衡量的是一个 session 从接收工作到产出第一个 response token 之间等待了多久。TTFT 是用户最直接感受到的延迟。
把大脑和手解耦,意味着容器只有在真正需要时,才会由大脑通过 tool call(execute(name, input) → string)来 provision。因此,一个一开始不需要容器的 session,就不用等待容器。只要 orchestration layer 从 session log 中取到 pending event,inference 就可以开始。使用这种架构,我们的 p50 TTFT 大约下降了 60%,p95 下降超过 90%。扩展到多个大脑,只意味着启动多个无状态 harness,并且只在需要时把它们连接到手。
多个手。 我们也希望每个大脑能够连接到多个手。在实践中,这意味着 Claude 必须对多个执行环境进行推理,并决定把工作发送到哪里——这比在单一 shell 中操作更难。我们一开始把大脑放在单个容器里,是因为早期模型还不具备这种能力。随着智能水平提升,单个容器反而变成了限制:当那个容器失败时,我们会丢失大脑正在触达的每一只手的状态。
把大脑和手解耦,让每只手都成为一个工具:execute(name, input) → string。输入一个 name 和 input,返回一个 string。这个接口可以支持任何自定义工具、任何 MCP server,以及我们自己的工具。harness 不需要知道 sandbox 到底是容器、手机,还是 Pokémon emulator。而且,因为没有任何手与任何大脑耦合,大脑之间也可以把手传递给彼此。
结论
我们面对的挑战是一个古老问题:如何为“尚未被想象出来的程序”设计系统。操作系统之所以能持续数十年,是因为它们把硬件虚拟化为足够通用的抽象,让当时尚不存在的程序也能使用。通过 Managed Agents,我们希望设计一个系统,能够容纳 Claude 周围未来的 harness、sandbox 或其他组件。
从这个意义上说,Managed Agents 是一种 meta-harness:它并不预设 Claude 未来会需要某一种具体 harness。相反,它是一个拥有通用接口的系统,允许许多不同的 harness 存在。例如,Claude Code 是一个非常出色的 harness,我们在各种任务中广泛使用它。我们也展示过,面向特定任务的 agent harness 在狭窄领域中表现出色。Managed Agents 可以容纳这些不同形态,并随 Claude 的智能水平提升而匹配演进。
meta-harness 设计意味着:我们要对 Claude 周围的接口保持明确主张。我们预期 Claude 需要能够操纵状态(session),也需要能够执行计算(sandbox)。我们还预期 Claude 需要扩展到多个大脑和多个手。我们设计这些接口,是为了让它们可以在长时间跨度内可靠、安全地运行。但我们不对 Claude 将来需要多少个大脑或多少只手、这些大脑和手位于哪里,做任何假设。
致谢
作者:Lance Martin、Gabe Cemaj 和 Michael Cohen。感谢 Nodir Turakulov 和 Jeremy Fox 就这些主题进行的有益讨论。特别感谢 Agents API team 和 Jake Eaton 的贡献。