Engineering at Anthropic

Scaling Managed Agents: Decoupling the brain from the hands扩展托管智能体:将“大脑”与“双手”解耦

Get started with Claude Managed Agents by following our docs.

A running topic on the Engineering Blog is how to build effective agents and design harnesses for long-running work. A common thread across this work is that harnesses encode assumptions about what Claude can’t do on its own. However, those assumptions need to be frequently questioned because they can go stale as models improve.
请查阅我们的文档,开始使用 Claude 托管智能体。工程博客中一个持续探讨的话题是如何构建高效的智能体并为长期运行的任务设计控制程序。这些工作的一个共同点是:控制程序往往基于 Claude 自身无法完成某些任务的假设进行编码。然而,这些假设需要被频繁质疑,因为随着模型能力的提升,它们可能会变得过时。

As just one example, in prior work we found that Claude Sonnet 4.5 would wrap up tasks prematurely as it sensed its context limit approaching—a behavior sometimes called “context anxiety.” We addressed this by adding context resets to the harness. But when we used the same harness on Claude Opus 4.5, we found that the behavior was gone. The resets had become dead weight.举个例子,在之前的工作中,我们发现 Claude Sonnet 4.5 在感知到即将达到上下文限制时会过早地结束任务——这种行为有时被称为“上下文焦虑”。我们通过在控制程序中增加上下文重置功能解决了这个问题。但当我们对 Claude Opus 4.5 使用相同的控制程序时,发现这种行为已经消失了。这些重置功能反而成了冗余的负担。

We expect harnesses to continue evolving. So we built Managed Agents: a hosted service in the Claude Platform that runs long-horizon agents on your behalf through a small set of interfaces meant to outlast any particular implementation—including the ones we run today.我们预计控制程序将持续演进。因此,我们构建了托管智能体:这是 Claude 平台的一项托管服务,通过一组旨在超越任何特定实现(包括我们目前运行的实现)的小型接口,代表您运行长周期的智能体。

Building Managed Agents meant solving an old problem in computing: how to design a system for “programs as yet unthought of.” Decades ago, operating systems solved this problem by virtualizing hardware into abstractions—process, file—general enough for programs that didn't exist yet. The abstractions outlasted the hardware. The read() command is agnostic as to whether it’s accessing a disk pack from the 1970s or a modern SSD. The abstractions on top stayed stable while the implementations underneath changed freely.构建托管智能体意味着要解决计算领域的一个老问题:如何为“尚未构想出的程序”设计系统。几十年前,操作系统通过将硬件虚拟化为抽象概念(如进程、文件)来解决这一问题,这些抽象概念对于当时还不存在的程序来说足够通用。这些抽象概念的生命周期比硬件更长。read() 命令无需关心它访问的是 20 世纪 70 年代的磁盘组还是现代 SSD。上层的抽象保持稳定,而底层的实现则可以自由更迭。

Managed Agents follow the same pattern. We virtualized the components of an agent: a session (the append-only log of everything that happened), a harness (the loop that calls Claude and routes Claude’s tool calls to the relevant infrastructure), and a sandbox (an execution environment where Claude can run code and edit files). This allows the implementation of each to be swapped without disturbing the others. We're opinionated about the shape of these interfaces, not about what runs behind them.托管智能体遵循了相同的模式。我们将智能体的组件进行了虚拟化:会话(所有发生事件的只读追加日志)、控制程序(调用 Claude 并将 Claude 的工具调用路由到相关基础设施的循环)以及沙盒(Claude 可以运行代码和编辑文件的执行环境)。这使得每个组件的实现都可以在不干扰其他组件的情况下进行替换。我们对这些接口的形态有明确的立场,但对它们背后的运行内容则保持中立。


Don’t adopt a pet不要把系统当宠物养

We started by placing all agent components into a single container, which meant the session, agent harness, and sandbox all shared an environment. There were benefits to this approach, including that file edits are direct syscalls, and there were no service boundaries to design.我们最初将所有智能体组件放入同一个容器中,这意味着会话、智能体控制程序和沙盒都共享同一个环境。这种方法有其优势,例如文件编辑可以直接通过系统调用完成,且无需设计服务边界。

But by coupling everything into one container, we ran into an old infrastructure problem: we’d adopted a pet. In the pets-vs-cattle analogy, a pet is a named, hand-tended individual you can’t afford to lose, while cattle are interchangeable. In our case, the server became that pet; if a container failed, the session was lost. If a container was unresponsive, we had to nurse it back to health.但将所有内容耦合在一个容器中,让我们遇到了一个古老的基础设施问题:我们把容器当成了“宠物”。在“宠物与牲畜”的类比中,宠物是需要悉心照料且不可或缺的个体,而牲畜则是可互换的资源。在我们的案例中,服务器变成了宠物;如果容器崩溃,会话就会丢失。如果容器无响应,我们必须对其进行“护理”以恢复正常。

Nursing containers meant debugging unresponsive stuck sessions. Our only window in was the WebSocket event stream, but that couldn’t tell us where failures arose, which meant that a bug in the harness, a packet drop in the event stream, or a container going offline all presented the same. To figure out what went wrong, an engineer had to open a shell inside the container, but because that container often also held user data, that approach essentially meant we lacked the ability to debug.护理容器意味着要调试无响应的卡死会话。我们唯一的观察窗口是 WebSocket 事件流,但它无法告诉我们故障源于何处,这意味着控制程序中的 Bug、事件流中的丢包或容器离线,表现出来的现象都是一样的。为了查明问题所在,工程师必须进入容器内部打开 Shell,但由于容器通常也包含用户数据,这种方法本质上意味着我们缺乏调试能力。

A second issue was that the harness assumed that whatever Claude worked on lived in the container with it. When customers asked us to connect Claude to their virtual private cloud, they had to either peer their network with ours, or run our harness in their own environment. An assumption baked into the harness became a problem when we wanted to connect it to different infrastructure.第二个问题是,控制程序假设 Claude 处理的所有内容都与它位于同一个容器中。当客户要求我们将 Claude 连接到他们的虚拟私有云(VPC)时,他们要么必须将网络与我们对等互联,要么必须在他们自己的环境中运行我们的控制程序。当我们想将其连接到不同的基础设施时,控制程序中预设的假设就成了问题。

Decouple the brain from the hands将“大脑”与“双手”解耦

The solution we arrived at was to decouple what we thought of as the “brain” (Claude and its harness) from both the “hands” (sandboxes and tools that perform actions) and the “session” (the log of session events). Each became an interface that made few assumptions about the others, and each could fail or be replaced independently.我们最终的解决方案是将我们所认为的“大脑”(Claude 及其控制程序)与“双手”(执行动作的沙盒和工具)以及“会话”(会话事件日志)分离开来。每一部分都成为一个对其他部分几乎不做假设的接口,且每一部分都可以独立故障或被替换。

The harness leaves the container. Decoupling the brain from the hands meant the harness no longer lived inside the container. It called the container the way it called any other tool: execute(name, input) → string. The container became cattle. If the container died, the harness caught the failure as a tool-call error and passed it back to Claude. If Claude decided to retry, a new container could be reinitialized with a standard recipe: provision({resources}). We no longer had to nurse failed containers back to health.控制程序移出容器。将大脑与双手解耦意味着控制程序不再驻留在容器内。它调用容器的方式就像调用任何其他工具一样:execute(name, input) → string。容器变成了“牲畜”。如果容器死亡,控制程序会将故障捕获为工具调用错误,并将其传回给 Claude。如果 Claude 决定重试,则可以使用标准配方重新初始化一个新容器:provision({resources})。我们不再需要费力护理故障容器。

Recovering from harness failure. The harness also became cattle. Because the session log sits outside the harness, nothing in the harness needs to survive a crash. When one fails, a new one can be rebooted with wake(sessionId), use getSession(id) to get back the event log, and resume from the last event. During the agent loop, the harness writes to the session with emitEvent(id, event) in order to keep a durable record of events.从控制程序故障中恢复。控制程序也变成了“牲畜”。由于会话日志位于控制程序之外,控制程序中没有任何内容需要在崩溃后存活。当一个控制程序失败时,可以重启一个新的,使用 wake(sessionId),通过 getSession(id) 取回事件日志,并从最后一个事件恢复。在智能体循环期间,控制程序通过 emitEvent(id, event) 写入会话,以保持事件的持久记录。



The security boundary. In the coupled design, any untrusted code that Claude generated was run in the same container as credentials—so a prompt injection only had to convince Claude to read its own environment. Once an attacker has those tokens, they can spawn fresh, unrestricted sessions and delegate work to them. Narrow scoping is an obvious mitigation, but this encodes an assumption about what Claude can't do with a limited token—and Claude is getting increasingly smart. The structural fix was to make sure the tokens are never reachable from the sandbox where Claude’s generated code runs.安全边界。在耦合设计中,Claude 生成的任何不受信任的代码都与凭据运行在同一个容器中——因此提示词注入攻击只需要说服 Claude 读取其自身的环境即可。一旦攻击者获得了这些令牌,他们就可以生成新的、不受限制的会话并将工作委派给它们。缩小作用域是一种显而易见的缓解措施,但这又引入了关于 Claude 在有限令牌下无法做什么的假设——而 Claude 正变得越来越聪明。结构上的修复方法是确保在 Claude 生成代码运行的沙盒中永远无法访问这些令牌。

We used two patterns to ensure this. Auth can be bundled with a resource or held in a vault outside the sandbox. For Git, we use each repository’s access token to clone the repo during sandbox initialization and wire it into the local git remote. Git push and pull work from inside the sandbox without the agent ever handling the token itself. For custom tools, we support MCP and store OAuth tokens in a secure vault. Claude calls MCP tools via a dedicated proxy; this proxy takes in a token associated with the session. The proxy can then fetch the corresponding credentials from the vault and make the call to the external service. The harness is never made aware of any credentials.我们使用了两种模式来确保这一点。身份验证信息可以与资源捆绑,或者保存在沙盒之外的保险库中。对于 Git,我们使用每个存储库的访问令牌在沙盒初始化期间克隆存储库,并将其连接到本地 git 远程仓库。Git 的推送和拉取操作在沙盒内部完成,而智能体本身无需处理令牌。对于自定义工具,我们支持 MCP,并将 OAuth 令牌存储在安全的保险库中。Claude 通过专用代理调用 MCP 工具;该代理接收与会话关联的令牌。随后,代理可以从保险库中获取相应的凭据并向外部服务发起调用。控制程序永远不会感知到任何凭据。

The session is not Claude’s context window会话不是 Claude 的上下文窗口

Long-horizon tasks often exceed the length of Claude’s context window, and the standard ways to address this all involve irreversible decisions about what to keep. We’ve explored these techniques in prior work on context engineering. For example, compaction lets Claude save a summary of its context window and the memory tool lets Claude write context to files, enabling learning across sessions. This can be paired with context trimming, which selectively removes tokens such as old tool results or thinking blocks.长周期任务往往超出 Claude 的上下文窗口长度,而解决此问题的标准方法都涉及关于保留哪些内容的不可逆决策。我们在之前关于上下文工程的工作中探索过这些技术。例如,压缩(compaction)允许 Claude 保存其上下文窗口的摘要,而内存工具允许 Claude 将上下文写入文件,从而实现跨会话学习。这可以与上下文修剪(context trimming)配合使用,有选择地删除旧工具结果或思考块等标记。

But irreversible decisions to selectively retain or discard context can lead to failures. It is difficult to know which tokens the future turns will need. If messages are transformed by a compaction step, the harness removes compacted messages from Claude’s context window, and these are recoverable only if they are stored. Prior work has explored ways to address this by storing context as an object that lives outside the context window. For example, context can be an object in a REPL that the LLM programmatically accesses by writing code to filter or slice it.但是,选择性保留或丢弃上下文的不可逆决策可能导致失败。很难预知未来的轮次需要哪些标记。如果消息通过压缩步骤进行了转换,控制程序就会从 Claude 的上下文窗口中移除压缩后的消息,而这些消息只有在被存储的情况下才能恢复。之前的工作已经探索了通过将上下文存储为位于上下文窗口之外的对象来解决此问题的方法。例如,上下文可以是一个 REPL 中的对象,LLM 通过编写代码来过滤或切片它,从而以编程方式访问该对象。

In Managed Agents, the session provides this same benefit, serving as a context object that lives outside Claude’s context window. But rather than be stored within the sandbox or REPL, context is durably stored in the session log. The interface, getEvents(), allows the brain to interrogate context by selecting positional slices of the event stream. The interface can be used flexibly, allowing the brain to pick up from wherever it last stopped reading, rewinding a few events before a specific moment to see the lead up, or rereading context before a specific action.在托管智能体中,会话提供了同样的优势,它作为一个位于 Claude 上下文窗口之外的上下文对象。但上下文不是存储在沙盒或 REPL 中,而是持久地存储在会话日志中。getEvents() 接口允许大脑通过选择事件流的位置切片来查询上下文。该接口的使用非常灵活,允许大脑从上次停止读取的位置继续,回溯特定时刻之前的几个事件以查看前因,或在特定操作之前重新读取上下文。

Any fetched events can also be transformed in the harness before being passed to Claude’s context window. These transformations can be whatever the harness encodes, including context organization to achieve a high prompt cache hit rate and context engineering. We separated the concerns of recoverable context storage in the session and arbitrary context management in the harness because we can’t predict what specific context engineering will be required in future models. The interfaces push that context management into the harness, and only guarantee that the session is durable and available for interrogation.任何获取的事件在传给 Claude 的上下文窗口之前,也可以在控制程序中进行转换。这些转换可以是控制程序中编码的任何内容,包括为了实现高提示词缓存命中率而进行的上下文组织,以及上下文工程。我们将可恢复的上下文存储(在会话中)与任意的上下文管理(在控制程序中)分离开来,因为我们无法预测未来模型需要什么样的特定上下文工程。这些接口将上下文管理推向了控制程序,并仅保证会话是持久的且可供查询。


Many brains, many hands
多个大脑,多双手

Many brains. Decoupling the brain from the hands solved one of our earliest customer complaints. When teams wanted Claude to work against resources in their own VPC, the only path was to peer their network with ours, because the container holding the harness assumed every resource sat next to it. Once the harness was no longer in the container, that assumption went away. The same change had a performance payoff. When we initially put the brain in a container, it meant that many brains required as many containers. For each brain, no inference could happen until that container was provisioned; every session paid the full container setup cost up front. Every session, even ones that would never touch the sandbox, had to clone the repo, boot the process, fetch pending events from our servers.多个大脑。将大脑与双手解耦解决了我们最早的客户投诉之一。当团队希望 Claude 在他们自己的 VPC 中处理资源时,唯一的途径是将他们的网络与我们的网络对等互联,因为容纳控制程序的容器假设所有资源都位于它旁边。一旦控制程序不再位于容器内,该假设就不复存在了。同样的改动也带来了性能提升。当我们最初将大脑放入容器时,意味着多个大脑需要同样多的容器。对于每个大脑,在容器配置完成之前无法进行任何推理;每个会话都要预先支付完整的容器设置成本。每个会话,即使是那些永远不会触及沙盒的会话,也必须克隆存储库、启动进程、从我们的服务器获取待处理事件。

That dead time is expressed in time-to-first-token (TTFT), which measures how long a session waits between accepting work and producing its first response token. TTFT is the latency the user most acutely feels.这种停滞时间体现在首个 Token 延迟(TTFT)上,它衡量的是会话从接受任务到产生第一个响应 Token 之间等待的时间。TTFT 是用户最直观感受到的延迟。

Decoupling the brain from the hands means that containers are provisioned by the brain via a tool call (execute(name, input) → string) only if they are needed. So a session that didn't need a container right away didn't wait for one. Inference could start as soon as the orchestration layer pulled pending events from the session log. Using this architecture, our p50 TTFT dropped roughly 60% and p95 dropped over 90%. Scaling to many brains just meant starting many stateless harnesses, and connecting them to hands only if needed.将大脑与双手解耦意味着容器仅在需要时才由大脑通过工具调用(execute(name, input) → string)进行配置。因此,不需要立即使用容器的会话无需等待。一旦编排层从会话日志中提取待处理事件,推理即可开始。使用这种架构,我们的 p50 TTFT 下降了约 60%,p95 下降了超过 90%。扩展到多个大脑仅仅意味着启动多个无状态的控制程序,并仅在需要时将它们连接到双手。

Many hands. We also wanted the ability to connect each brain to many hands. In practice, this means Claude must reason about many execution environments and decide where to send work—a harder cognitive task than operating in a single shell. We started with the brain in a single container because earlier models weren't capable of this. As intelligence scaled, the single container became the limitation instead: when that container failed, we lost state for every hand that the brain was reaching into.多双手。我们还希望能够将每个大脑连接到多双手。在实践中,这意味着 Claude 必须对多个执行环境进行推理并决定将工作发送到哪里——这比在单个 Shell 中操作是一项更艰巨的认知任务。我们最初将大脑放在单个容器中,因为早期的模型无法胜任这一点。随着智能水平的提升,单个容器反而成了限制:当该容器故障时,我们丢失了大脑正在触及的所有手的状态。

Decoupling the brain from the hands makes each hand a tool, execute(name, input) → string: a name and input go in, and a string is returned. That interface supports any custom tool, any MCP server, and our own tools. The harness doesn’t know whether the sandbox is a container, a phone, or a Pokémon emulator. And because no hand is coupled to any brain, brains can pass hands to one another.将大脑与双手解耦使得每一只手都成为一个工具,即 execute(name, input) → string:输入名称和参数,返回一个字符串。该接口支持任何自定义工具、任何 MCP 服务器以及我们自己的工具。控制程序不知道沙盒是容器、手机还是宝可梦模拟器。而且由于没有任何一只手与任何大脑耦合,大脑之间可以传递双手。


Conclusion结论

The challenge we faced is an old one: how to design a system for “programs as yet unthought of.” Operating systems have lasted decades by virtualizing the hardware into abstractions general enough for programs that didn't exist yet. With Managed Agents, we aimed to design a system that accommodates future harnesses, sandboxes, or other components around Claude.我们面临的挑战是一个古老的问题:如何为“尚未构想出的程序”设计系统。操作系统通过将硬件虚拟化为对当时还不存在的程序足够通用的抽象概念,从而延续了数十年。通过托管智能体,我们的目标是设计一个能够容纳未来 Claude 周围的控制程序、沙盒或其他组件的系统。

Managed Agents is a meta-harness in the same spirit, unopinionated about the specific harness that Claude will need in the future. Rather, it is a system with general interfaces that allow many different harnesses. For example, Claude Code is an excellent harness that we use widely across tasks. We’ve also shown that task-specific agent harnesses excel in narrow domains. Managed Agents can accommodate any of these, matching Claude’s intelligence over time.托管智能体是本着同样精神设计的元控制程序(meta-harness),它对 Claude 未来所需的特定控制程序不设预设。相反,它是一个具有通用接口的系统,允许存在多种不同的控制程序。例如,Claude Code 是我们在各项任务中广泛使用的优秀控制程序。我们也证明了特定于任务的智能体控制程序在窄领域表现出色。托管智能体可以容纳所有这些,并随着时间的推移匹配 Claude 的智能水平。

Meta-harness design means being opinionated about the interfaces around Claude: we expect that Claude will need the ability to manipulate state (the session) and perform computation (the sandbox). We also expect that Claude will require the ability to scale to many brains and many hands. We designed the interfaces so that these can be run reliably and securely over long time horizons. But we make no assumptions about the number or location of brains or hands that Claude will need.元控制程序设计意味着对 Claude 周围的接口有明确的立场:我们预计 Claude 将需要操纵状态(会话)和执行计算(沙盒)的能力。我们还预计 Claude 将需要扩展到多个大脑和多双手的需求。我们设计这些接口是为了确保它们能够在长周期内可靠且安全地运行。但我们对 Claude 所需的大脑或手的数量及位置不做任何假设。

Acknowledgements致谢

Written by Lance Martin, Gabe Cemaj, and Michael Cohen. Thanks to Nodir Turakulov and Jeremy Fox for helpful conversations on these topics. Special thanks to the Agents API team and Jake Eaton for their contributions.作者:Lance Martin, Gabe Cemaj, Michael Cohen。感谢 Nodir Turakulov 和 Jeremy Fox 在这些议题上的有益讨论。特别感谢智能体 API 团队和 Jake Eaton 的贡献。

Get the developer newsletter获取开发者通讯

Product updates, how-tos, community spotlights, and more. Delivered monthly to your inbox.产品更新、操作指南、社区精选等内容,每月发送至您的收件箱。