Scaling Managed Agents: Decoupling the brain from the hands

规模化管理的代理:将大脑与手分离

https://www.anthropic.com/engineering/managed-agents

Harnesses encode assumptions that go stale as models improve. Managed Agents—our hosted service for long-horizon agent work—is built around interfaces that stay stable as harnesses change.

随着模型改进,测试框架中的假设会逐渐过时。而"托管代理"——我们为长期智能体任务提供的托管服务——其接口设计确保在测试框架变更时仍能保持稳定。

https://platform.claude.com/docs/en/managed-agents/overview

摘要:
托管代理服务通过解耦“大脑”(Claude模型)与“执行层”(工具/沙箱),解决了长期任务中模型假设过时、容器耦合及安全边界等问题。核心设计包括:

1)将会话日志、工具调用等抽象为独立接口,确保组件可独立替换;

2)采用无状态架构,容器故障时可快速重启;

3)通过安全代理隔离敏感凭证,防止沙箱越权访问。该架构使任务启动延迟降低60%-90%,并支持动态扩展多模型实例与工具环境,适应未来需求变化。

-----------------------------------------

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.

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.

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无法独立完成任务的假设。但随着模型能力的提升,这些假设需要被不断质疑,因为它们可能变得过时。

举例来说,在早期工作中我们发现Claude Sonnet 4.5会在感知到上下文限制临近时过早结束任务——这种行为有时被称为"上下文焦虑"。我们通过在管理框架中添加上下文重置来解决这个问题。但当我们在Claude Opus 4.5上使用相同框架时,发现该行为已消失。这些重置操作反而成了冗余负担。

我们预计管理框架将持续进化。因此我们构建了"托管代理"服务:这是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.

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.

构建托管代理意味着要解决计算领域的一个古老问题:如何为“尚未设想的程序”设计系统。几十年前,操作系统通过将硬件虚拟化为足够通用的抽象概念(如进程、文件)来解决这个问题,这些抽象适用于当时尚未存在的程序。这些抽象概念比硬件本身更持久。无论是访问20世纪70年代的磁盘组还是现代固态硬盘,read()命令都不关心具体硬件。上层的抽象保持稳定,而下层的实现可以自由变化。

托管代理遵循相同的模式。我们将代理的组件虚拟化:会话(记录所有事件的仅追加日志)、控制框架(调用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.

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.

我们最初将所有代理组件都放在同一个容器中,这意味着会话、代理框架和沙箱都共享同一个环境。这种做法有其优势,包括文件编辑可以直接进行系统调用,也无需设计服务边界。

但将所有东西都耦合到一个容器里,我们遇到了一个老生常谈的基础设施问题:我们养了一只"宠物"。在"宠物与牲畜"的比喻中,"宠物"是需要精心照料、不能承受丢失的有名个体,而"牲畜"则是可以互相替换的。在我们的案例中,服务器就成了那只"宠物";如果容器崩溃,会话就会丢失。如果容器无响应,我们就得手动"救治"它。

"救治"容器意味着要调试无响应的卡死会话。我们唯一的入口是WebSocket事件流,但这无法告诉我们故障究竟出在哪里——框架中的错误、事件流中的数据包丢失或是容器离线,表现出来的症状都一模一样。要找出问题所在,工程师必须进入容器内部打开shell,但由于该容器通常还存储着用户数据,这种做法本质上意味着我们缺乏有效的调试手段。

第二个问题是框架假设Claude处理的所有内容都与它同处一个容器。当客户要求我们将Claude接入他们的虚拟私有云时,他们要么需要将网络与我们对接,要么就得在他们自己的环境中运行我们的框架。当我们想把框架连接到不同基础设施时,这个内置于框架的假设就成了问题。

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.

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.

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.

我们最终采用的解决方案是将“大脑”(克劳德及其控制系统)与“手部”(执行操作的沙盒和工具)及“会话”(会话事件日志)进行解耦。每个组件都成为接口,彼此间减少假设依赖,各自可独立故障或替换。

控制系统脱离容器。大脑与手部解耦意味着控制系统不再驻留于容器内部。它调用容器的方式与调用其他工具无异:execute(名称, 输入)→字符串。容器由此成为可替换资源。若容器崩溃,控制系统会捕获工具调用错误并反馈给克劳德。若克劳德决定重试,可通过标准流程重新初始化新容器:provision({资源})。我们不再需要修复故障容器。

控制系统故障恢复。控制系统也转变为可替换资源。由于会话日志存储于控制系统外部,其崩溃时无需保留任何状态。当系统故障时,可通过wake(会话ID)重启新实例,使用getSession(ID)取回事件日志,并从最后事件处恢复运行。在代理循环过程中,控制系统通过emitEvent(ID, 事件)写入会话日志,以此保持事件的持久化记录。

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.

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.

安全边界。在耦合设计中,Claude生成的任何不可信代码都在与凭证相同的容器中运行——因此提示注入只需诱使Claude读取自身环境变量即可。一旦攻击者获取这些令牌,就能创建全新且无限制的会话并分配任务。虽然权限收窄是明显的缓解措施,但这隐含假设了Claude无法利用受限令牌执行操作——而Claude正变得越来越智能。结构性修复方案是确保令牌永远无法从运行Claude生成代码的沙箱中访问。

我们采用两种模式实现这一点:认证信息可绑定资源,或存储在沙箱外部的保险库中。对于Git操作,我们使用每个代码库的访问令牌在沙箱初始化时克隆仓库,并将其绑定到本地git远程。沙箱内的git推送和拉取操作全程无需代理接触令牌本身。针对自定义工具,我们支持MCP协议并将OAuth令牌存储于安全保险库。Claude通过专用代理调用MCP工具,该代理接收与会话关联的令牌,随后从保险库获取对应凭证并调用外部服务。整个过程中执行框架始终不接触任何凭证。

The session is not Claude’s context window

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.

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上下文窗口的长度限制,传统解决方案都涉及不可逆的内容取舍决策。我们在前期语境工程研究中探索过相关技术:例如压缩功能允许Claude保存上下文摘要,记忆工具支持将上下文写入文件实现跨会话学习。这些方法可与语境修剪配合使用——选择性移除旧工具结果或思维模块等标记内容。

但选择性保留或丢弃上下文的不可逆决策可能导致失误。我们难以预判后续对话需要哪些标记。若消息被压缩流程改写,系统会从Claude上下文窗口移除已压缩内容,这些信息仅在被存储的情况下才可恢复。先前研究尝试通过将上下文作为独立于窗口的外部对象存储来解决该问题,例如将上下文设为REPL环境中的对象,让大语言模型通过编写过滤或切片代码来程序化访问。

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.

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的上下文窗口之外。但与存储在沙盒或REPL中不同,上下文被持久保存在会话日志中。通过接口getEvents(),大脑可以通过选择事件流的位置片段来查询上下文。该接口可以灵活使用,允许大脑从上次停止阅读的地方继续,回溯到特定时刻前的几个事件以查看前导,或在特定操作前重新读取上下文。

任何获取的事件在传递到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.

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.

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.

多脑架构。将"大脑"与"手"解耦解决了我们最早收到的客户投诉之一。当团队希望Claude在其自有VPC中操作资源时,此前唯一方案是将其网络与我们的网络对等互联,因为承载控制程序的容器默认所有资源都位于其近端。当控制程序移出容器后,这个假设自然消解。这项变更还带来了性能红利——最初将"大脑"置于容器时,多个"大脑"就意味着需要部署多个容器。每个"大脑"必须等待容器就绪才能开始推理,每个会话都需预先承担完整的容器启动成本。所有会话(包括永远不会触及沙箱的会话)都必须克隆代码库、启动进程、从服务器获取待处理事件。

这种空转时间体现为"首令牌响应时间"(TTFT),即会话从接受任务到生成首个响应令牌之间的等待时长。TTFT是用户最能直接感知的延迟指标。

"大脑"与"手"的解耦意味着容器仅在被需要时才会通过工具调用(execute(name, input) → string)由"大脑"动态调配。因此不需要立即使用容器的会话就无需等待。编排层从会话日志提取待处理事件后,推理即可立即启动。采用该架构后,我们的p50首令牌响应时间下降约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.

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.

人多手杂。我们还希望实现将每个“大脑”连接到多个“手”的能力。实际上,这意味着Claude需要同时处理多个执行环境并决定任务分配路径——这比在单一环境中操作更具认知挑战。最初我们将大脑部署在单一容器中,因为早期模型不具备这种多环境处理能力。但随着智能水平提升,单一容器反而成了限制:当该容器崩溃时,所有与之连接的手部执行器都会丢失状态。

将大脑与手部解耦后,每个手部都成为标准化工具:execute(名称, 输入)→字符串:输入名称和参数后返回字符串结果。这个通用接口支持自定义工具、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.

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 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)构建能容纳未来各种约束框架、沙盒或其他组件的系统。

"受控代理"本质上是一种元约束框架——它不对克劳德未来需要何种具体约束框架做预设,而是通过通用接口系统兼容多种不同框架。例如"克劳德代码"就是我们在各类任务中广泛使用的优秀框架,而特定任务代理框架在垂直领域也表现卓越。随着克劳德智能的演进,"受控代理"能兼容所有这些框架。

这种元框架设计意味着我们要明确克劳德所需的交互接口:包括操作状态(会话)和执行计算(沙盒)的能力,以及扩展到多"大脑"和多"执行端"的扩展性。我们设计的接口能确保这些功能长期可靠安全地运行,但对克劳德所需"大脑"或"执行端"的数量及位置不做任何预设。

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.

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐