扩展 Managed Agents:将大脑与双手解耦
Scaling Managed Agents: Decoupling the brain from the hands
Engineering Blog 介绍 Claude Platform 的 Managed Agents:把 brain(Claude+harness)、hands(sandbox/tool)和 session log 解耦,以 execute、provision、wake、getEvents 接口支持故障恢复、凭证隔离和多 brain/hand;实践中 p50 TTFT 降约60%,p95 降超90%。
请按照我们的 docs 开始使用 Claude Managed Agents。Engineering Blog 上一个持续讨论的主题是如何构建有效的 agent,以及如何为长时间运行的工作设计 harness。贯穿这些工作的一个共同点是,harness 会编码一些关于 Claude 无法独立完成哪些事情的假设。不过,这些假设需要经常被重新审视,因为随着模型改进,它们可能会过时。
举一个例子,在之前的工作中,我们发现 Claude Sonnet 4.5 在感觉自己的 context limit 即将接近时,会过早结束任务——这种行为有时被称为“context anxiety”。我们通过在 harness 中加入 context reset 来解决这个问题。但当我们在 Claude Opus 4.5 上使用同一个 harness 时,发现这种行为已经消失了。这些 reset 变成了无用负担。
我们预计 harness 会继续演进。因此我们构建了 Managed Agents:这是 Claude Platform 中的一项 hosted service,通过一小组接口代表你运行 long-horizon agent,这些接口旨在比任何特定实现都更持久——包括我们今天正在运行的实现。
构建 Managed Agents 意味着要解决一个计算领域的老问题:如何为“尚未被设想出来的程序”设计系统。几十年前,操作系统通过将硬件虚拟化为抽象来解决这个问题——例如 process、file——这些抽象足够通用,可以支持当时还不存在的程序。抽象比硬件更持久。read() 命令并不关心它访问的是 1970 年代的 disk pack,还是现代 SSD。上层抽象保持稳定,而底层实现可以自由变化。
Managed Agents 遵循同样的模式。我们将 agent 的组件虚拟化:session(发生过的一切的 append-only log)、harness(调用 Claude 并将 Claude 的 tool call 路由到相关基础设施的 loop),以及 sandbox(Claude 可以在其中运行代码和编辑文件的执行环境)。这允许每个组件的实现被替换,而不会影响其他组件。我们对这些接口的形态有明确取舍,但不规定其背后运行什么。
一开始,我们把所有 agent 组件都放进一个 container,这意味着 session、agent harness 和 sandbox 共享同一个环境。这种做法有一些好处,包括文件编辑是直接的 syscall,也不需要设计服务边界。
但把所有东西耦合到一个 container 中后,我们遇到了一个老的基础设施问题:我们养了一只 pet。在 pets-vs-cattle 的类比中,pet 是一个有名字、需要人工照料、不能失去的个体,而 cattle 是可替换的。在我们的场景中,server 变成了那只 pet;如果 container 失败,session 就会丢失。如果 container 无响应,我们必须把它“护理”回健康状态。
护理 container 意味着调试无响应、卡住的 session。我们唯一的窗口是 WebSocket event stream,但它无法告诉我们故障发生在哪里,这意味着 harness 中的 bug、event stream 中的 packet drop,或 container 离线,看起来都一样。为了弄清楚出了什么问题,工程师必须在 container 内打开 shell,但由于该 container 往往也包含用户数据,这种做法本质上意味着我们缺乏调试能力。
第二个问题是,harness 假设 Claude 处理的内容都与它一起存在于 container 中。当客户要求我们把 Claude 连接到他们的 virtual private cloud 时,他们要么必须将自己的网络与我们的网络 peering,要么在自己的环境中运行我们的 harness。一个固化在 harness 中的假设,在我们想把它连接到不同基础设施时变成了问题。
我们最终采用的解决方案是,将我们所说的“brain”(Claude 及其 harness)与“hands”(执行动作的 sandbox 和 tool)以及“session”(session event 的 log)解耦。每一部分都成为一个接口,对其他部分做尽可能少的假设,并且每一部分都可以独立失败或被替换。
harness 离开 container。将 brain 与 hands 解耦,意味着 harness 不再位于 container 内。它调用 container 的方式就像调用任何其他 tool:execute(name, input) → string。container 变成了 cattle。如果 container 死掉,harness 会把失败捕获为 tool-call error,并将其传回 Claude。如果 Claude 决定重试,可以用标准 recipe 重新初始化一个新的 container:provision({resources})。我们不再需要把失败的 container 护理回健康状态。
从 harness failure 中恢复。harness 也变成了 cattle。由于 session log 位于 harness 之外,harness 中没有任何东西需要在 crash 后存活。当一个 harness 失败时,可以用 wake(sessionId) 启动一个新的 harness,使用 getSession(id) 取回 event log,并从最后一个 event 继续。在 agent loop 期间,harness 会通过 emitEvent(id, event) 写入 session,以保留持久的 event 记录。
安全边界。在耦合设计中,Claude 生成的任何 untrusted code 都在与凭证相同的 container 中运行——因此一次 prompt injection 只需要说服 Claude 读取自己的环境。一旦攻击者获得这些 token,就可以创建新的、不受限制的 session,并把工作委派给它们。窄化 scope 是显而易见的缓解措施,但这会编码一个关于 Claude 无法用受限 token 做什么的假设——而 Claude 正变得越来越聪明。结构性修复方式是确保 token 永远无法从运行 Claude 生成代码的 sandbox 中触达。
我们使用了两种模式来确保这一点。Auth 可以与 resource 绑定,也可以保存在 sandbox 外部的 vault 中。对于 Git,我们使用每个 repository 的 access token 在 sandbox 初始化期间 clone repo,并将其接入本地 git remote。Git push 和 pull 可以从 sandbox 内部工作,而 agent 永远不会直接处理 token。对于 custom tool,我们支持 MCP,并将 OAuth token 存储在 secure vault 中。Claude 通过专用 proxy 调用 MCP tool;这个 proxy 接收一个与 session 关联的 token。随后 proxy 可以从 vault 中获取对应凭证,并调用 external service。harness 永远不会知道任何凭证。
Long-horizon task 往往会超过 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 step 转换,harness 会从 Claude 的 context window 中移除被 compacted 的 message,而这些 message 只有在被存储的情况下才能恢复。此前的工作探索过通过将 context 存储为位于 context window 之外的 object 来解决这个问题。例如,context 可以是 REPL 中的 object,LLM 通过编写代码来过滤或切片它,从而以编程方式访问。
在 Managed Agents 中,session 提供了同样的好处,充当位于 Claude 的 context window 之外的 context object。但 context 不是存储在 sandbox 或 REPL 中,而是持久存储在 session log 中。接口 getEvents() 允许 brain 通过选择 event stream 的位置切片来查询 context。这个接口可以灵活使用,允许 brain 从上次停止阅读的位置继续,在某个特定时刻之前倒回几个 event 以查看前因,或在某个特定 action 之前重新阅读 context。
任何获取到的 event 也可以在传入 Claude 的 context window 之前,在 harness 中进行转换。这些转换可以是 harness 编码的任何内容,包括为实现高 prompt cache hit rate 而进行的 context organization,以及 context engineering。我们将 session 中可恢复的 context storage 与 harness 中任意的 context management 分离,因为我们无法预测未来模型需要什么具体的 context engineering。这些接口把 context management 推给 harness,并且只保证 session 是持久的、可供查询的。
Many brains。将 brain 与 hands 解耦,解决了我们最早期的客户抱怨之一。当团队希望 Claude 处理自己 VPC 中的 resource 时,唯一方式是将他们的网络与我们的网络 peering,因为包含 harness 的 container 假设每个 resource 都在它旁边。一旦 harness 不再位于 container 中,这个假设就消失了。同样的变化也带来了性能收益。最初我们把 brain 放在 container 中,这意味着多个 brain 需要同样数量的 container。对于每个 brain,在该 container provision 完成之前都无法进行 inference;每个 session 都要预先支付完整的 container setup cost。每个 session,即使那些永远不会触碰 sandbox 的 session,也必须 clone repo、启动 process、从我们的 server 拉取 pending event。
这种空耗时间体现在 time-to-first-token(TTFT)中,它衡量 session 从接受工作到生成第一个 response token 之间等待了多久。TTFT 是用户最直接感受到的 latency。
将 brain 与 hands 解耦意味着 container 只有在需要时才由 brain 通过 tool call(execute(name, input) → string)进行 provision。因此,一个一开始不需要 container 的 session 就不必等待 container。只要 orchestration layer 从 session log 中拉取 pending event,inference 就可以开始。使用这种架构后,我们的 p50 TTFT 下降了约 60%,p95 下降超过 90%。扩展到 many brains 只意味着启动许多 stateless harness,并且仅在需要时把它们连接到 hands。
Many hands。我们还希望能够将每个 brain 连接到 many hands。在实践中,这意味着 Claude 必须推理多个执行环境,并决定把工作发送到哪里——这比在单个 shell 中操作更难。我们一开始把 brain 放在单个 container 中,是因为早期模型还不具备这种能力。随着 intelligence 扩展,单个 container 反而变成了限制:当该 container 失败时,我们会失去 brain 正在触达的每个 hand 的状态。
将 brain 与 hands 解耦后,每个 hand 都是一个 tool,execute(name, input) → string:输入一个 name 和 input,返回一个 string。这个接口支持任何 custom tool、任何 MCP server,以及我们自己的 tool。harness 不知道 sandbox 是一个 container、一部 phone,还是一个 Pokémon emulator。而且由于没有 hand 与任何 brain 耦合,brain 可以把 hand 传递给彼此。
我们面临的挑战是一个老问题:如何为“尚未被设想出来的程序”设计系统。操作系统通过将硬件虚拟化为足够通用的抽象,支持当时还不存在的程序,因此存在了几十年。通过 Managed Agents,我们的目标是设计一个能够容纳围绕 Claude 的未来 harness、sandbox 或其他组件的系统。
Managed Agents 是同一精神下的 meta-harness,不对 Claude 未来需要的具体 harness 做规定。相反,它是一个具备通用接口的系统,允许存在许多不同的 harness。例如,Claude Code 是一个优秀的 harness,我们在各种任务中广泛使用。我们也已经证明,task-specific agent harness 在窄领域中表现出色。Managed Agents 可以容纳所有这些 harness,并随 Claude 的 intelligence 随时间匹配。
Meta-harness 设计意味着要对 Claude 周围的接口做出明确取舍:我们预计 Claude 需要操纵 state(session)和执行 computation(sandbox)的能力。我们也预计 Claude 需要具备扩展到 many brains 和 many hands 的能力。我们设计这些接口,使其能够在长时间跨度内可靠、安全地运行。但我们不对 Claude 将需要多少 brain 或 hand,也不对它们的位置做任何假设。
作者:Lance Martin、Gabe Cemaj 和 Michael Cohen。感谢 Nodir Turakulov 和 Jeremy Fox 就这些主题进行的有益讨论。特别感谢 Agents API team 和 Jake Eaton 的贡献。