返回列表
🧠 阿头学 · 💬 讨论题

托管代理规模化:让大脑与双手解耦

这篇文章最重要的判断是:长周期 agent 的核心瓶颈已经不是单次模型能力,而是架构是否把 session、控制层和执行层解耦;但 Anthropic 把这一正确方向明显包装成了自家托管产品的必然答案,这一点不能混淆。
打开原文 ↗

2026-04-10 原文链接 ↗
阅读简报
双语对照
完整翻译
原文
讨论归档

核心观点

  • 真正会过时的是 harness 假设 作者判断得对:很多 Agent 系统不是败给模型弱,而是败给工程层把“模型做不到什么”写死了;Sonnet 需要上下文重置、Opus 反而不需要,这说明补丁式 harness 会迅速变成包袱。
  • 解耦是扎实的系统设计,不是花哨抽象 把 system 拆成 session、harness、sandbox 三层,本质上是在把“有状态、难恢复、难调试”的宠物架构改造成“无状态、可替换、可重建”的牛群架构,这个方向在分布式系统里是成熟判断,不是 AI 特供概念。
  • 安全边界必须靠结构隔离,不能靠模型自觉 文章最站得住脚的一点是:不可信代码运行环境绝不能直接接触高权限 token;把凭据放到 sandbox 外部的 vault 或代理层,比寄希望于模型“不要去读密钥”可靠得多。
  • session 作为上下文外对象是进步,但不是记忆问题终解 把 session 做成可查询的持久事件日志,确实比不可逆压缩更稳,因为信息没有被永久抹掉;但它只是把难题从“压缩什么”转成“什么时候取回什么”,并没有自动解决长期相关性检索。
  • 性能数据有吸引力,但论证还不够硬 p50 TTFT 降 60%、p95 降 90% 这个数字很强,但没有给出任务分布、样本规模、控制变量和基线,因此它更像高质量内部案例,而不是已经被外部验证的普遍规律。

跟我们的关联

  • 对 ATou 意味着什么、下一步怎么用 对 ATou 来说,这篇文章直接提示:不要再把 Agent 优化理解成 prompt 调参,而要先看架构里哪些模块是“宠物”;下一步可以用“状态是否外置、执行是否可替换、失败是否可恢复”三问审视现有流程。
  • 对 Neta 意味着什么、下一步怎么用 对 Neta 来说,这意味着产品体验里很多“AI 不够聪明”的问题,实际是系统启动链路太重;下一步应优先拆分首响应路径,把“先思考、后 provision 重型资源”作为核心设计原则。
  • 对 Uota 意味着什么、下一步怎么用 对 Uota 来说,这篇文章说明“记忆”不该只被理解为上下文堆叠,而应被理解为可查询、可回放的事件对象;下一步可以把 session log 视为外部化记忆层,讨论检索策略而不是只讨论摘要策略。
  • 对投资判断意味着什么、下一步怎么用 对投资视角来说,文章强化了一个判断:未来 Agent 基础设施的价值不会只在模型,而在 orchestration、state、security、tool runtime 这些中间层;下一步看项目时要重点问它是否只是模型壳子,还是拥有可迁移的系统抽象。

讨论引子

1. 如果模型能力持续提升,我们到底该减少 harness 约束,还是更强地把权限和流程写死? 2. session 事件日志真的能替代“记忆系统”吗,还是只是把记忆问题换成检索问题? 3. Anthropic 这套 session/harness/sandbox 抽象,会成为行业通用层,还是只会加深平台锁定?

托管代理规模化:让大脑与双手解耦

随着模型能力提升,harness 中预设的假设会逐渐过时。Managed Agents 是我们为长周期代理工作提供的托管服务,它围绕一组在 harness 演进时仍能保持稳定的接口来构建。

按我们的文档即可开始使用 Claude Managed Agents。

工程博客长期关注的话题之一,是如何构建高效代理,以及如何为长时间运行的工作设计 harness。这些文章里有一条贯穿始终的主线:harness 往往编码了关于 Claude 不能独立完成哪些事的假设。但这些假设需要被频繁质疑,因为随着模型进步,它们会逐渐过时

举一个例子:在先前的工作中,我们发现 Claude Sonnet 4.5 在感知到上下文窗口快到极限时,会倾向于提前收尾任务,这种行为有时被称为“上下文焦虑”。我们通过在 harness 中加入上下文重置来解决。但当我们把同一个 harness 用在 Claude Opus 4.5 上时,发现这种行为已经消失了。重置机制反而成了累赘。

我们预计 harness 会持续演化。因此我们构建了 Managed Agents:它是 Claude Platform 中的一项托管服务,通过一小组接口代表你运行长周期代理。这些接口的目标是超越任何特定实现的生命周期,包括我们今天正在运行的实现。

构建 Managed Agents 意味着要解决计算领域的一个老问题:如何为“尚未被设想的程序”设计系统。几十年前,操作系统通过把硬件虚拟化为足够通用的抽象(进程、文件等)来解决这个问题,从而支持那些当时还不存在的程序。这些抽象比硬件本身更长寿。read() 命令并不关心它访问的是 1970 年代的磁盘包还是现代 SSD。上层抽象保持稳定,而底层实现可以自由更替。

Managed Agents 采用同样的模式。我们把代理的关键组件虚拟化为三个部分:session(发生过的一切的只追加日志)、harness(调用 Claude 并将 Claude 的工具调用路由到相关基础设施的循环)、sandbox(Claude 可以运行代码和编辑文件的执行环境)。这样就能在不干扰其他部分的前提下替换其中任意一项实现。我们对这些接口的形状有明确偏好,但不执着于它们背后具体运行什么。

不要收养一只宠物

我们最初把所有代理组件都放进同一个容器里,这意味着 session、代理 harness 和 sandbox 共享同一套环境。这样做有一些好处,例如文件编辑就是直接的系统调用,也不需要设计服务边界。

但把一切耦合在一个容器里,我们撞上了一个经典的基础设施问题:我们等于收养了一只宠物。在“宠物 vs 牛群”的类比里,宠物是有名字、需要精心照料、丢不起的个体,而牛群是可互换的。在我们的场景里,那台服务器就成了宠物:一旦容器故障,session 就丢了;一旦容器无响应,就必须把它“护理”回来。

护理容器意味着要调试那些卡住且无响应的 session。我们唯一的观察窗口是 WebSocket 事件流,但它无法告诉我们故障发生在哪里,于是 harness 的 bug、事件流里的丢包、或容器离线,看起来都一模一样。为了弄清楚哪里出了问题,工程师不得不进入容器打开 shell;但因为这个容器往往也保存着用户数据,这种方式本质上等于我们无法进行调试。

第二个问题是,harness 默认 Claude 所处理的东西就和它在同一个容器里。当客户要求把 Claude 接入他们的虚拟私有云(VPC)时,他们要么把网络与我们做对等互联,要么在他们自己的环境里运行我们的 harness。harness 里写死的假设,在我们想把它连接到不同基础设施时就变成了问题。

让大脑与双手解耦

我们最终的解决方案,是把我们视为“大脑”(Claude 及其 harness)与“双手”(执行动作的 sandbox 与工具)以及“session”(会话事件日志)彼此解耦。它们各自成为一个对其他部分假设极少的接口,任何一部分都可以独立失败或被替换。

harness 离开容器。让大脑与双手解耦,意味着 harness 不再住在容器里。它以调用其他工具的方式调用容器:execute(name, input) → string。容器变成了牛群。如果容器死掉,harness 会把失败当作一次工具调用错误并回传给 Claude。如果 Claude 决定重试,就可以按标准配方重新初始化一个新容器:provision({resources})。不再需要把故障容器护理回健康状态。

从 harness 故障中恢复。harness 本身也变成了牛群。因为 session 日志在 harness 之外,harness 没有任何东西必须跨崩溃存活。某个 harness 挂掉时,新的 harness 可以通过 wake(sessionId) 启动,用 getSession(id) 取回事件日志,然后从最后一个事件继续。在代理循环中,harness 通过 emitEvent(id, event) 把事件写入 session,以保留一份持久的事件记录。

安全边界。在耦合设计中,Claude 生成的任何不可信代码都在与凭据同一个容器里运行,所以一次提示注入只需要说服 Claude 读取自己的环境。一旦攻击者拿到这些 token,就能启动新的、不受限制的 session 并把工作委派给它们。缩小权限范围是显而易见的缓解措施,但这会把关于 Claude 在受限 token 下做不到什么的假设写死在系统里,而 Claude 正变得越来越聪明。结构性修复是确保这些 token 永远无法从运行 Claude 生成代码的 sandbox 触达。

我们用两种模式来保证这一点。认证信息可以随资源一起打包,或者保存在 sandbox 之外的保险库中。对于 Git,我们在 sandbox 初始化时使用每个仓库的访问 token 来克隆仓库,并把它接入本地 git remote。这样在 sandbox 内部就能正常 git push 和 pull,而代理本身从不需要接触 token。对于自定义工具,我们支持 MCP,并把 OAuth token 存在安全的保险库里。Claude 通过专用代理调用 MCP 工具;该代理接收与 session 关联的 token,然后从保险库取回对应凭据,再去调用外部服务。harness 永远不会获知任何凭据信息。

session 不是 Claude 的上下文窗口

长周期任务往往会超过 Claude 上下文窗口的长度,而常见的应对方式都涉及对“保留什么”做出不可逆的决定。我们在关于上下文工程的先前工作中探讨过这些技术。例如,compaction 让 Claude 保存一份上下文窗口的摘要;memory 工具让 Claude 把上下文写入文件,从而实现跨 session 的学习。它还可以与上下文裁剪配合使用,选择性移除某些 token,比如旧的工具结果或思考块。

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

在 Managed Agents 中,session 提供了同样的好处,它充当一个存在于 Claude 上下文窗口之外的上下文对象。但它不是存放在 sandbox 或 REPL 里,而是以持久方式存放在 session 日志中。接口 getEvents() 允许大脑通过选择事件流的按位置切片来查询上下文。该接口非常灵活,大脑可以从上次停止读取的位置继续;也可以在某个特定时刻之前回退几条事件,查看前因后果;或在执行某个动作之前重读相关上下文。

任何被取回的事件,也都可以在进入 Claude 上下文窗口之前,由 harness 先做转换。这些转换可以是 harness 编码的任意逻辑,包括为提升提示缓存命中率而做的上下文组织,以及上下文工程。我们把 session 中可恢复的上下文存储,与 harness 中任意的上下文管理分离开来,因为无法预测未来模型究竟需要哪些具体的上下文工程方式。接口把上下文管理的责任推到 harness 上,同时只保证 session 是持久的,并且可被查询。

许多大脑,许多双手

许多大脑。让大脑与双手解耦,解决了我们最早收到的一类客户抱怨。当团队想让 Claude 操作他们自己 VPC 里的资源时,过去唯一的路径是把他们的网络与我们做对等互联,因为承载 harness 的容器假设每个资源都在它旁边。harness 不再在容器里之后,这个假设就消失了。同样的改变也带来了性能收益。我们最初把大脑放进容器里,意味着要有多少个大脑就要有多少个容器。对每个大脑来说,在容器完成 provisioning 之前无法进行推理;每个 session 一开始都要承担完整的容器启动成本。每个 session,即便永远不会触碰 sandbox,也得克隆仓库、启动进程、从我们的服务器拉取待处理事件。

这段空转时间体现在首次出 token 时间(TTFT)上,它衡量的是一个 session 从接收工作到产出第一个响应 token 之间等待了多久。TTFT 是用户感受最强烈的延迟。

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

许多双手。我们也希望能把每个大脑连接到许多双手。现实中,这意味着 Claude 需要对多个执行环境进行推理,并决定把工作发到哪里,这比在单一 shell 中操作更难。我们最开始把大脑放进单一容器里,是因为早期模型还做不到这一点。随着智能水平提升,单容器反而成了限制:一旦容器故障,大脑伸进的每一只手的状态都会一起丢失。

大脑与双手解耦后,每一只手都成为一个工具,execute(name, input) → string:传入名称和输入,返回一个字符串。这个接口支持任何自定义工具、任何 MCP server,以及我们自己的工具。harness 不需要知道 sandbox 是容器、手机,还是一台宝可梦模拟器。并且因为没有任何一只手与任何一个大脑绑定,大脑之间可以互相传递双手。

结论

我们面对的是一个老问题:如何为“尚未被设想的程序”设计系统。操作系统之所以能延续几十年,是因为它把硬件虚拟化成了足够通用的抽象,以便支持那些当时还不存在的程序。借助 Managed Agents,我们希望设计一套系统,能够容纳围绕 Claude 的未来 harness、sandbox,或其他组件。

Managed Agents 也是一种同样精神的元 harness,它对未来 Claude 需要的具体 harness 不持偏见。相反,它提供一组通用接口,允许存在许多不同的 harness。例如,Claude Code 是一个非常出色的 harness,我们在各类任务中广泛使用。我们也展示过,面向特定任务的代理 harness 在狭窄领域往往表现更优。Managed Agents 能够容纳其中任意一种,并随着 Claude 智能的提升而持续匹配。

元 harness 设计意味着要对 Claude 周边的接口形态有明确偏好:我们预计 Claude 需要操纵状态(session)并执行计算(sandbox)的能力。我们也预计 Claude 需要扩展到许多大脑与许多双手。我们把接口设计成能够在长时间跨度上可靠且安全地运行。但我们不会对 Claude 需要多少个大脑或双手、它们位于何处作任何假设。

致谢

由 Lance Martin、Gabe Cemaj 和 Michael Cohen 撰写。特别感谢 Agents API 团队以及 Jake Eaton 的贡献。

订阅开发者通讯

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

如果你希望接收我们每月的开发者通讯,请提供你的邮箱地址。你可以随时取消订阅。

Scaling Managed Agents: Decoupling the brain from the hands

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.

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.

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.

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 didnt 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. Were opinionated about the shape of these interfaces, not about what runs behind them.

托管代理规模化:让大脑与双手解耦

随着模型能力提升,harness 中预设的假设会逐渐过时。Managed Agents 是我们为长周期代理工作提供的托管服务,它围绕一组在 harness 演进时仍能保持稳定的接口来构建。

按我们的文档即可开始使用 Claude Managed Agents。

工程博客长期关注的话题之一,是如何构建高效代理,以及如何为长时间运行的工作设计 harness。这些文章里有一条贯穿始终的主线:harness 往往编码了关于 Claude 不能独立完成哪些事的假设。但这些假设需要被频繁质疑,因为随着模型进步,它们会逐渐过时

举一个例子:在先前的工作中,我们发现 Claude Sonnet 4.5 在感知到上下文窗口快到极限时,会倾向于提前收尾任务,这种行为有时被称为“上下文焦虑”。我们通过在 harness 中加入上下文重置来解决。但当我们把同一个 harness 用在 Claude Opus 4.5 上时,发现这种行为已经消失了。重置机制反而成了累赘。

我们预计 harness 会持续演化。因此我们构建了 Managed Agents:它是 Claude Platform 中的一项托管服务,通过一小组接口代表你运行长周期代理。这些接口的目标是超越任何特定实现的生命周期,包括我们今天正在运行的实现。

构建 Managed Agents 意味着要解决计算领域的一个老问题:如何为“尚未被设想的程序”设计系统。几十年前,操作系统通过把硬件虚拟化为足够通用的抽象(进程、文件等)来解决这个问题,从而支持那些当时还不存在的程序。这些抽象比硬件本身更长寿。read() 命令并不关心它访问的是 1970 年代的磁盘包还是现代 SSD。上层抽象保持稳定,而底层实现可以自由更替。

Managed Agents 采用同样的模式。我们把代理的关键组件虚拟化为三个部分:session(发生过的一切的只追加日志)、harness(调用 Claude 并将 Claude 的工具调用路由到相关基础设施的循环)、sandbox(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.

不要收养一只宠物

我们最初把所有代理组件都放进同一个容器里,这意味着 session、代理 harness 和 sandbox 共享同一套环境。这样做有一些好处,例如文件编辑就是直接的系统调用,也不需要设计服务边界。

但把一切耦合在一个容器里,我们撞上了一个经典的基础设施问题:我们等于收养了一只宠物。在“宠物 vs 牛群”的类比里,宠物是有名字、需要精心照料、丢不起的个体,而牛群是可互换的。在我们的场景里,那台服务器就成了宠物:一旦容器故障,session 就丢了;一旦容器无响应,就必须把它“护理”回来。

护理容器意味着要调试那些卡住且无响应的 session。我们唯一的观察窗口是 WebSocket 事件流,但它无法告诉我们故障发生在哪里,于是 harness 的 bug、事件流里的丢包、或容器离线,看起来都一模一样。为了弄清楚哪里出了问题,工程师不得不进入容器打开 shell;但因为这个容器往往也保存着用户数据,这种方式本质上等于我们无法进行调试。

第二个问题是,harness 默认 Claude 所处理的东西就和它在同一个容器里。当客户要求把 Claude 接入他们的虚拟私有云(VPC)时,他们要么把网络与我们做对等互联,要么在他们自己的环境里运行我们的 harness。harness 里写死的假设,在我们想把它连接到不同基础设施时就变成了问题。

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.

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 cant 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 及其 harness)与“双手”(执行动作的 sandbox 与工具)以及“session”(会话事件日志)彼此解耦。它们各自成为一个对其他部分假设极少的接口,任何一部分都可以独立失败或被替换。

harness 离开容器。让大脑与双手解耦,意味着 harness 不再住在容器里。它以调用其他工具的方式调用容器:execute(name, input) → string。容器变成了牛群。如果容器死掉,harness 会把失败当作一次工具调用错误并回传给 Claude。如果 Claude 决定重试,就可以按标准配方重新初始化一个新容器:provision({resources})。不再需要把故障容器护理回健康状态。

从 harness 故障中恢复。harness 本身也变成了牛群。因为 session 日志在 harness 之外,harness 没有任何东西必须跨崩溃存活。某个 harness 挂掉时,新的 harness 可以通过 wake(sessionId) 启动,用 getSession(id) 取回事件日志,然后从最后一个事件继续。在代理循环中,harness 通过 emitEvent(id, event) 把事件写入 session,以保留一份持久的事件记录。

安全边界。在耦合设计中,Claude 生成的任何不可信代码都在与凭据同一个容器里运行,所以一次提示注入只需要说服 Claude 读取自己的环境。一旦攻击者拿到这些 token,就能启动新的、不受限制的 session 并把工作委派给它们。缩小权限范围是显而易见的缓解措施,但这会把关于 Claude 在受限 token 下做不到什么的假设写死在系统里,而 Claude 正变得越来越聪明。结构性修复是确保这些 token 永远无法从运行 Claude 生成代码的 sandbox 触达。

我们用两种模式来保证这一点。认证信息可以随资源一起打包,或者保存在 sandbox 之外的保险库中。对于 Git,我们在 sandbox 初始化时使用每个仓库的访问 token 来克隆仓库,并把它接入本地 git remote。这样在 sandbox 内部就能正常 git push 和 pull,而代理本身从不需要接触 token。对于自定义工具,我们支持 MCP,并把 OAuth token 存在安全的保险库里。Claude 通过专用代理调用 MCP 工具;该代理接收与 session 关联的 token,然后从保险库取回对应凭据,再去调用外部服务。harness 永远不会获知任何凭据信息。

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.

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.

session 不是 Claude 的上下文窗口

长周期任务往往会超过 Claude 上下文窗口的长度,而常见的应对方式都涉及对“保留什么”做出不可逆的决定。我们在关于上下文工程的先前工作中探讨过这些技术。例如,compaction 让 Claude 保存一份上下文窗口的摘要;memory 工具让 Claude 把上下文写入文件,从而实现跨 session 的学习。它还可以与上下文裁剪配合使用,选择性移除某些 token,比如旧的工具结果或思考块。

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

在 Managed Agents 中,session 提供了同样的好处,它充当一个存在于 Claude 上下文窗口之外的上下文对象。但它不是存放在 sandbox 或 REPL 里,而是以持久方式存放在 session 日志中。接口 getEvents() 允许大脑通过选择事件流的按位置切片来查询上下文。该接口非常灵活,大脑可以从上次停止读取的位置继续;也可以在某个特定时刻之前回退几条事件,查看前因后果;或在执行某个动作之前重读相关上下文。

任何被取回的事件,也都可以在进入 Claude 上下文窗口之前,由 harness 先做转换。这些转换可以是 harness 编码的任意逻辑,包括为提升提示缓存命中率而做的上下文组织,以及上下文工程。我们把 session 中可恢复的上下文存储,与 harness 中任意的上下文管理分离开来,因为无法预测未来模型究竟需要哪些具体的上下文工程方式。接口把上下文管理的责任推到 harness 上,同时只保证 session 是持久的,并且可被查询。

许多大脑,许多双手

许多大脑。让大脑与双手解耦,解决了我们最早收到的一类客户抱怨。当团队想让 Claude 操作他们自己 VPC 里的资源时,过去唯一的路径是把他们的网络与我们做对等互联,因为承载 harness 的容器假设每个资源都在它旁边。harness 不再在容器里之后,这个假设就消失了。同样的改变也带来了性能收益。我们最初把大脑放进容器里,意味着要有多少个大脑就要有多少个容器。对每个大脑来说,在容器完成 provisioning 之前无法进行推理;每个 session 一开始都要承担完整的容器启动成本。每个 session,即便永远不会触碰 sandbox,也得克隆仓库、启动进程、从我们的服务器拉取待处理事件。

这段空转时间体现在首次出 token 时间(TTFT)上,它衡量的是一个 session 从接收工作到产出第一个响应 token 之间等待了多久。TTFT 是用户感受最强烈的延迟。

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

许多双手。我们也希望能把每个大脑连接到许多双手。现实中,这意味着 Claude 需要对多个执行环境进行推理,并决定把工作发到哪里,这比在单一 shell 中操作更难。我们最开始把大脑放进单一容器里,是因为早期模型还做不到这一点。随着智能水平提升,单容器反而成了限制:一旦容器故障,大脑伸进的每一只手的状态都会一起丢失。

大脑与双手解耦后,每一只手都成为一个工具,execute(name, input) → string:传入名称和输入,返回一个字符串。这个接口支持任何自定义工具、任何 MCP server,以及我们自己的工具。harness 不需要知道 sandbox 是容器、手机,还是一台宝可梦模拟器。并且因为没有任何一只手与任何一个大脑绑定,大脑之间可以互相传递双手。

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 didnt need a container right away didnt 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.

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 werent 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.

结论

我们面对的是一个老问题:如何为“尚未被设想的程序”设计系统。操作系统之所以能延续几十年,是因为它把硬件虚拟化成了足够通用的抽象,以便支持那些当时还不存在的程序。借助 Managed Agents,我们希望设计一套系统,能够容纳围绕 Claude 的未来 harness、sandbox,或其他组件。

Managed Agents 也是一种同样精神的元 harness,它对未来 Claude 需要的具体 harness 不持偏见。相反,它提供一组通用接口,允许存在许多不同的 harness。例如,Claude Code 是一个非常出色的 harness,我们在各类任务中广泛使用。我们也展示过,面向特定任务的代理 harness 在狭窄领域往往表现更优。Managed Agents 能够容纳其中任意一种,并随着 Claude 智能的提升而持续匹配。

元 harness 设计意味着要对 Claude 周边的接口形态有明确偏好:我们预计 Claude 需要操纵状态(session)并执行计算(sandbox)的能力。我们也预计 Claude 需要扩展到许多大脑与许多双手。我们把接口设计成能够在长时间跨度上可靠且安全地运行。但我们不会对 Claude 需要多少个大脑或双手、它们位于何处作任何假设。

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 didnt 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.

致谢

由 Lance Martin、Gabe Cemaj 和 Michael Cohen 撰写。特别感谢 Agents API 团队以及 Jake Eaton 的贡献。

Acknowledgements

Written by Lance Martin, Gabe Cemaj and Michael Cohen. Special thanks to the Agents API team and Jake Eaton for their contributions.

订阅开发者通讯

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

如果你希望接收我们每月的开发者通讯,请提供你的邮箱地址。你可以随时取消订阅。

Scaling Managed Agents: Decoupling the brain from the hands

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.

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.

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.

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 didnt 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. Were opinionated about the shape of these interfaces, not about what runs behind them.

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.

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.

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 cant 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.

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.

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.

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 didnt need a container right away didnt 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.

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 werent 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.

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 didnt 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.

Acknowledgements

Written by Lance Martin, Gabe Cemaj and Michael Cohen. Special thanks to the Agents API team and Jake Eaton for their contributions.

Get the developer newsletter

Product updates, how-tos, community spotlights, and more. Delivered monthly to your inbox.

Please provide your email address if youd like to receive our monthly developer newsletter. You can unsubscribe at any time.

Products

Models

Solutions

Claude Platform

Resources

Help and security

Company

Terms and policies

© 2026 Anthropic PBC

Scaling Managed Agents: Decoupling the brain from the hands \ Anthropic

📋 讨论归档

讨论进行中…