扩展 Managed Agents:将大脑与双手解耦
Harness 会编码一些假设,而随着模型改进,这些假设会过时。Managed Agents 是我们面向长周期智能体工作的托管服务,它围绕一组接口构建,即使 harness 变化,这些接口也能保持稳定。
来源:https://www.anthropic.com/engineering/managed-agents
发布日期:2026-04-08
请按照我们的文档开始使用 Claude Managed Agents。
Engineering Blog 上一个持续讨论的主题,是如何构建有效的智能体,以及如何为长时间运行的工作设计 harness。贯穿这些工作的一个共同点是:harness 会编码关于 Claude 不能独立完成什么的假设。不过,这些假设需要经常被重新审视,因为随着模型改进,它们可能会过时。
仅举一个例子,在之前的工作中,我们发现 Claude Sonnet 4.5 在感知到上下文限制接近时,会过早收尾任务,这种行为有时被称为“context anxiety”。我们通过在 harness 中加入上下文重置来解决这个问题。但当我们把同一个 harness 用在 Claude Opus 4.5 上时,发现这种行为已经消失了。那些重置变成了无用负担。
我们预计 harness 会继续演化。因此我们构建了 Managed Agents:这是 Claude Platform 中的一项托管服务,可以代表你运行长周期智能体,并通过一小组接口工作。这些接口旨在比任何特定实现都更长寿,包括我们今天正在运行的实现。
构建 Managed Agents 意味着要解决一个计算领域的老问题:如何为“尚未被想到的程序”设计系统。几十年前,操作系统通过把硬件虚拟化为抽象来解决这个问题,例如进程、文件,这些抽象足够通用,可以支持当时还不存在的程序。抽象比硬件更长寿。read() 命令并不关心自己访问的是 20 世纪 70 年代的磁盘组,还是现代 SSD。上层抽象保持稳定,而底层实现可以自由变化。
Managed Agents 遵循同样的模式。我们把智能体的组成部分虚拟化了:session(发生过的一切的追加式日志)、harness(调用 Claude 并把 Claude 的工具调用路由到相关基础设施的循环),以及 sandbox(Claude 可以在其中运行代码和编辑文件的执行环境)。这让每个组成部分的实现都可以被替换,而不会影响其他部分。我们对这些接口的形态有明确看法,但不限定接口背后具体运行什么。

不要养一只宠物
一开始,我们把所有智能体组件都放进同一个容器,这意味着 session、agent harness 和 sandbox 都共享一个环境。这种做法有一些好处,包括文件编辑就是直接的系统调用,而且不需要设计服务边界。
但把所有东西耦合进一个容器后,我们遇到了一个老基础设施问题:我们养了一只宠物。在 pets-vs-cattle 的类比中,宠物是一个有名字、需要手工照料、你承受不起失去它的个体;而牲畜是可互换的。在我们的场景里,服务器就变成了那只宠物;如果容器失败,session 就丢了。如果容器无响应,我们就得把它救回来。
照料容器意味着要调试无响应、卡住的 session。我们唯一的观察窗口是 WebSocket 事件流,但它无法告诉我们故障出在哪里,这意味着 harness 中的 bug、事件流中的丢包,或者容器离线,看起来都一样。要弄清楚哪里出了问题,工程师必须在容器内打开 shell,但由于该容器通常也持有用户数据,这种方式本质上意味着我们缺乏调试能力。
第二个问题是,harness 假设 Claude 处理的任何东西都和它一起存在于容器中。当客户要求我们把 Claude 连接到他们的 virtual private cloud 时,他们要么必须把他们的网络与我们的网络对等互联,要么必须在自己的环境中运行我们的 harness。一个被写死在 harness 里的假设,在我们想把它连接到不同基础设施时变成了问题。
将大脑与双手解耦
我们最终采用的解决方案,是把我们所说的“大脑”(Claude 及其 harness)与“双手”(执行操作的 sandbox 和工具)以及“session”(session 事件日志)解耦。每一个都变成了对其他部分假设很少的接口,并且每一个都可以独立失败或被替换。
Harness 离开容器。 将大脑与双手解耦意味着 harness 不再住在容器里。它调用容器的方式,就像调用任何其他工具一样:execute(name, input) → string。容器变成了 cattle。如果容器死掉,harness 会把失败捕捉为工具调用错误并传回给 Claude。如果 Claude 决定重试,一个新容器可以用标准配方重新初始化:provision({resources})。我们不再需要把失败的容器救回健康状态。
从 harness 失败中恢复。 Harness 也变成了 cattle。由于 session log 位于 harness 之外,harness 中没有任何东西需要在崩溃后保留下来。当一个 harness 失败时,一个新的 harness 可以用 wake(sessionId) 重启,用 getSession(id) 取回事件日志,并从最后一个事件继续。在智能体循环期间,harness 会用 emitEvent(id, event) 写入 session,以保留一份持久事件记录。

安全边界。 在耦合设计中,Claude 生成的任何不可信代码都会在与凭证相同的容器中运行,因此一次提示注入只需要说服 Claude 读取它自己的环境即可。一旦攻击者拿到这些 token,就可以启动新的、不受限制的 session,并把工作委派给它们。窄范围作用域是一个显而易见的缓解措施,但这会编码一个关于 Claude 不能用受限 token 做什么的假设,而 Claude 正变得越来越聪明。结构性修复办法是确保 token 永远无法从 Claude 生成代码运行的 sandbox 中触达。
我们用了两种模式来确保这一点。认证可以与资源绑定,也可以保存在 sandbox 外的 vault 中。对于 Git,我们使用每个仓库的访问 token,在 sandbox 初始化期间克隆仓库,并把它接入本地 git remote。Git push 和 pull 可以在 sandbox 内工作,而智能体永远不会亲自处理 token。对于自定义工具,我们支持 MCP,并把 OAuth token 存储在安全 vault 中。Claude 通过专用代理调用 MCP 工具;该代理接收一个与 session 关联的 token。然后代理可以从 vault 中获取对应凭证,并调用外部服务。Harness 永远不会知道任何凭证。
Session 不是 Claude 的上下文窗口
长周期任务经常超过 Claude 上下文窗口的长度,而解决这个问题的标准方法都涉及不可逆的取舍:要保留什么。我们在之前关于上下文工程的工作中探索过这些技术。例如,compaction 让 Claude 保存其上下文窗口的摘要,而 memory tool 让 Claude 把上下文写入文件,从而支持跨 session 学习。这可以与 context trimming 搭配使用,后者会选择性移除旧工具结果或 thinking blocks 等 token。
但对上下文做选择性保留或丢弃的不可逆决策,可能导致失败。很难知道未来回合会需要哪些 token。如果消息经过 compaction 步骤转换,harness 会从 Claude 的上下文窗口中移除已压缩消息,而只有当这些消息被存储下来时,它们才可恢复。之前的工作探索过一种解决方式:把上下文存储为一个存在于上下文窗口之外的对象。例如,上下文可以是 REPL 中的一个对象,LLM 通过编写代码来过滤或切片,从而以编程方式访问它。

在 Managed Agents 中,session 提供了同样的好处:它作为一个存在于 Claude 上下文窗口之外的上下文对象。不过,上下文不是存储在 sandbox 或 REPL 中,而是持久存储在 session log 里。接口 getEvents(), 允许大脑通过选择事件流的位置切片来查询上下文。这个接口可以灵活使用,让大脑从上次停止阅读的地方继续;也可以在某个特定时刻之前倒回几个事件,查看前因;或者在某个特定操作之前重新阅读上下文。
任何获取到的事件也可以在 harness 中转换,然后再传入 Claude 的上下文窗口。这些转换可以是 harness 编码的任何内容,包括用于实现高 prompt cache 命中率的上下文组织,以及上下文工程。我们把 session 中可恢复的上下文存储与 harness 中任意的上下文管理分离开来,因为我们无法预测未来模型会需要哪些具体的上下文工程。接口把上下文管理推入 harness,并且只保证 session 是持久的、可供查询的。
许多大脑,许多双手
许多大脑。 将大脑与双手解耦,解决了我们最早的一项客户抱怨。当团队希望 Claude 针对他们自己 VPC 中的资源工作时,唯一的路径是把他们的网络和我们的网络对等互联,因为持有 harness 的容器假设每个资源都在它旁边。一旦 harness 不再位于容器中,这个假设就消失了。同样的改变也带来了性能收益。最初把大脑放在容器里时,许多大脑意味着同样多的容器。对每个大脑来说,在那个容器完成配置之前,推理无法发生;每个 session 都要先支付完整的容器设置成本。每个 session,哪怕永远不会触碰 sandbox,也必须克隆仓库、启动进程、从我们的服务器获取待处理事件。
这种空转时间会体现为 time-to-first-token(TTFT),它衡量一个 session 从接受工作到产出第一个响应 token 之间等待了多久。TTFT 是用户最明显感受到的延迟。
将大脑与双手解耦意味着,容器只有在需要时才由大脑通过工具调用 (execute(name, input) → string) 进行配置。所以,如果某个 session 一开始不需要容器,它就不会为容器等待。只要编排层从 session log 中取到待处理事件,推理就能开始。使用这种架构后,我们的 p50 TTFT 大约下降了 60%,p95 下降超过 90%。扩展到许多大脑只意味着启动许多无状态 harness,并且只在需要时把它们连接到双手。
许多双手。 我们还希望每个大脑都能连接到许多双手。在实践中,这意味着 Claude 必须推理许多执行环境,并决定把工作发送到哪里;这比在单个 shell 中操作更有认知难度。一开始我们把大脑放在单个容器中,是因为早期模型还不具备这种能力。随着智能提升,单个容器反而变成了限制:当那个容器失败时,我们会失去大脑伸向的每只手的状态。
将大脑与双手解耦,让每只手都成为一个工具,execute(name, input) → string:输入一个名称和 input,返回一个字符串。这个接口支持任何自定义工具、任何 MCP server,以及我们自己的工具。Harness 不需要知道 sandbox 是容器、手机,还是 Pokémon 模拟器。而且由于没有任何手与任何大脑耦合,大脑之间也可以相互传递双手。

结论
我们面临的挑战是一个老问题:如何为“尚未被想到的程序”设计系统。操作系统通过把硬件虚拟化为足够通用的抽象,支撑了几十年,而这些抽象可以支持当时还不存在的程序。对于 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 团队和 Jake Eaton 的贡献。