← 收藏夹

规模化 Managed Agents:把「大脑」和「双手」解耦

Anthropic 工程博客 · 中文译介
作者Lance Martin、Gabe Cemaj、Michael Cohen(Anthropic)
来源anthropic.com/engineering 发布2026-04-08 译介于2026-08-21
本文是对上述英文原文的中文译介与要点梳理,按原文章节顺序逐节展开,并保留原文全部架构图;力求忠实,但表述经过重写,不是逐句全文转载。原文与配图版权归 Anthropic 所有,细节与准确措辞请以英文原文为准。如版权方认为不妥,请联系删除。

一句话概括:Agent 的 harness(外壳/脚手架)里塞满了「模型自己做不到什么」的假设,而模型每升级一次,这些假设就过期一批。Anthropic 的 Managed Agents 因此不去赌某一个具体 harness,而是把 agent 拆成 session(会话日志)、harness(循环)、sandbox(执行环境)三样东西,只对它们之间的接口下判断——接口稳定,底下的实现可以随时换掉。

起点:harness 里的假设会过期

Anthropic 工程博客一直在谈两件事:怎么构建有效的 agent,以及怎么为长周期任务设计 harness。贯穿其中的一条线索是——harness 本质上是在编码「Claude 自己做不到什么」的假设。而这些假设需要被反复质疑,因为模型一变强,它们就会失效。

原文给了一个很具体的例子:Claude Sonnet 4.5 在感觉到上下文快用完时,会提前草草收尾任务,这种行为有时被称为「上下文焦虑」(context anxiety)。他们的对策是在 harness 里加上下文重置(context resets)。但同一套 harness 换到 Claude Opus 4.5 上,这个行为消失了——那些重置逻辑就成了纯粹的负重(dead weight)。

既然 harness 注定会继续演化,那就别把系统建在某一版 harness 上。Managed Agents 是 Claude Platform 里的一项托管服务,替你运行长周期 agent,而它对外暴露的是一小组「打算比任何具体实现活得更久」的接口——包括比他们今天自己跑的这套实现活得更久

老问题:为「尚未被想到的程序」做设计

这在计算机领域是个老问题:如何为「programs as yet unthought of」(尚未被想到的程序)设计系统。

几十年前操作系统给出的答案是虚拟化:把硬件抽象成进程文件这类足够通用的概念,通用到能服务那些当时还不存在的程序。结果是抽象活得比硬件久——read() 并不关心自己读的是 1970 年代的磁盘组还是今天的 SSD。底下的实现自由更替,上层抽象纹丝不动。

Managed Agents 走的是同一条路,把 agent 的组件也虚拟化了:

三者的实现可以互不干扰地被替换掉。用原文的话说,他们只对这些接口的形状有主见,对接口背后跑什么没有主见

Managed Agents 的组件关系图:中心是 Harness,四周连接 Session、Sandbox、Tools(含 Resources / MCP)与 Orchestration
Managed Agents 的组件划分:Harness 居中,向外分别连接 Session(会话日志)、Sandbox(执行环境)、Tools + Resources / MCP(工具)与 Orchestration(调度)。图片来源:Anthropic

第一课:别养成一只「宠物」

他们最初的做法是把 agent 的所有组件塞进同一个容器——session、harness、sandbox 共享一个环境。这么做有实打实的好处:改文件就是直接的系统调用,而且根本不需要设计任何服务边界。

但把一切耦合进一个容器,就撞上了基础设施领域的一个老毛病:他们养了一只宠物

在「宠物 vs 牲畜」(pets vs cattle)这个经典类比里,宠物是有名字、需要人工照料、丢不起的个体;牲畜则是可互换的。在这里,服务器变成了那只宠物:容器一挂,会话就没了;容器一失去响应,就得把它「救回来」。

而「救」的过程意味着调试卡死的会话——问题在于,他们唯一的观察窗口是 WebSocket 事件流,这个窗口说不清故障出在哪一层:harness 的 bug、事件流丢包、容器下线,三种情况长得一模一样。要查清楚,工程师就得在容器里开一个 shell;可那个容器里往往同时装着用户数据——于是这条路实际上等于「没有调试能力」。

第二个问题更结构性:harness 默认 Claude 要处理的东西就跟它待在同一个容器里。当客户希望 Claude 去访问他们自己 VPC 里的资源时,可选项只有两个:要么把网络和 Anthropic 对等互联,要么把这套 harness 搬到客户自己的环境里跑。一个被烘进 harness 的假设,在需要对接不同基础设施时就变成了障碍。

解法:把「大脑」和「双手」拆开

最终的解法是把三样东西彼此解耦:

每一样都变成一个对其它两样几乎不作假设的接口,因而可以独立地故障、独立地被替换

harness 搬出容器

解耦之后,harness 不再住在容器里;它调用容器的方式跟调用任何其它工具一样:execute(name, input) → string

于是容器变成了牲畜。容器死了,harness 只是接到一个工具调用错误,把它原样交还给 Claude;如果 Claude 决定重试,就用一份标准配方 provision({resources}) 起一个新容器。再也不需要把坏掉的容器抢救回来。

harness 自己也能崩

harness 同样变成了牲畜。因为会话日志坐在 harness 外面,harness 内部就没有任何东西需要在崩溃中幸存:

而在 agent 循环运行期间,harness 通过 emitEvent(id, event) 往 session 里写,保证事件有一份持久记录。

Managed Agents 各组件的接口伪代码与实现要求对照表:Session、Orchestration、Harness、Sandbox、Resources、Tools
原文列出的接口清单——每一行都写明「伪代码接口」与「什么样的东西可以满足它」:Session 可以是 Postgres / SQLite / 内存数组,Orchestration 可以是 cron / 队列消费者 / 一个 while 循环,Sandbox 可以是本地进程或远程容器。这张表是整篇文章的核心。图片来源:Anthropic

安全边界:凭证必须从沙箱里够不着

这一节是我认为原文里最值得单独拎出来的部分。

在耦合式设计里,Claude 生成的不可信代码凭证跑在同一个容器里——这意味着一次 prompt injection 只需要说服 Claude 去读一下自己的环境变量就够了。攻击者一旦拿到这些 token,就能开出全新的、不受限的会话,并把活派给它们。

把 token 权限收窄当然是个显而易见的缓解措施,但原文指出了它的软肋:收窄权限本身又是在编码一条「Claude 拿着受限 token 做不到什么」的假设,而 Claude 正在变得越来越聪明

结构性的修法是:让 token 从 Claude 生成代码运行的那个沙箱里根本够不着。他们用了两种模式——凭证要么和资源捆绑在一起,要么存放在沙箱之外的 vault 里:

session 不等于 Claude 的上下文窗口

长周期任务经常会超出 Claude 上下文窗口的长度,而现有的应对手段几乎都要求做出不可逆的取舍。原文回顾了他们此前在 context engineering 上的工作:

问题在于,「留哪些、丢哪些」这类不可逆决策会导致失败——你很难预知未来的对话轮次会需要哪些 token。一旦消息被 compaction 步骤改写,harness 就把被压缩的消息从上下文窗口里移除了;除非另行存储,否则它们再也找不回来。

已有的研究给过一个方向:把上下文当作一个活在上下文窗口之外的对象。比如让上下文成为 REPL 里的一个对象,模型通过写代码来过滤、切片,以编程方式访问它。

Session 与 Harness 之间的双向关系:Session 向 Harness 推送 Events,Harness 通过 getEvents 反向查询
Session 与 Harness 的关系:事件从 session 流向 harness,而 harness 也可以主动用 getEvents 回头查询——上下文成了一个可反复审问的外部对象。图片来源:Anthropic

Managed Agents 里,session 就扮演这个「窗口之外的上下文对象」,只不过它不是存在 sandbox 或 REPL 里,而是持久化在会话日志中。接口 getEvents() 让大脑能够按位置切片去审问上下文,用法相当灵活:

取回来的事件,还可以在 harness 里先做变换再送进 Claude 的上下文窗口——变换逻辑完全由 harness 定义,包括为了拉高 prompt 缓存命中率而做的上下文组织,以及各种 context engineering。

原文把这个分工讲得很清楚:session 只负责「可恢复的上下文存储」,harness 负责「任意的上下文管理」。之所以要拆开,是因为他们无法预测未来的模型会需要什么样的 context engineering——所以接口把这部分推给 harness,自己只保证一件事:session 是持久的,且随时可被审问

多个大脑,多双手

多个大脑(many brains)

解耦顺手解决了他们最早的一类客户投诉:想让 Claude 访问自家 VPC 里的资源,此前唯一的路就是网络对等互联,因为装着 harness 的容器假定每一样资源都紧挨着自己。harness 一旦搬出容器,这个假设就不存在了。

同一个改动还带来了性能收益。大脑在容器里时,多少个大脑就要多少个容器:容器没起来之前推理就无法开始,每个会话都要先付一笔完整的容器启动成本;哪怕这个会话压根不会用到 sandbox,也照样得 clone 仓库、把进程拉起来、去服务器拉取待处理事件。

这段死时间体现在 TTFT(time-to-first-token,从接下任务到吐出第一个响应 token 的等待时长)上——原文特意点明,这是用户感受最尖锐的那种延迟

解耦之后,容器改由大脑按需通过一次工具调用(execute(name, input) → string)来创建:用不上容器的会话就不必等它。只要编排层从会话日志里拉到了待处理事件,推理立刻就能开始。

用这套架构,他们的 p50 TTFT 下降了大约 60%,p95 下降超过 90%

而扩展到多个大脑,也就变成了「启动多个无状态 harness,只在需要时才把它们接到双手上」这么简单。

多双手(many hands)

他们还希望每个大脑能连上多双手。这在实践中意味着 Claude 必须同时推理多个执行环境、并决定把活派到哪儿去——这比在单个 shell 里操作是更难的认知任务

原文对这段历史的复盘很有意思:当初之所以把大脑放进单个容器,正是因为早期模型还做不到这件事;而随着智能水平提升,单容器反过来成了瓶颈——那个容器一挂,大脑伸出去的每一只手的状态全都丢了。

解耦之后,每一只手都只是一个工具:execute(name, input) → string,进去一个名字和输入,回来一个字符串。这个接口能容纳任何自定义工具、任何 MCP server,以及他们自己的工具。

harness 并不知道那个 sandbox 到底是一个容器、一部手机,还是一个宝可梦模拟器。

而且因为没有任何一只手被绑死在某个大脑上,大脑之间可以互相传递双手

多个 Harness 与多个 Sandbox 之间的多对多连接示意
多个无状态 harness 与多个 sandbox 之间的多对多关系:大脑与双手的数量、位置都不再被硬绑定。图片来源:Anthropic

结论:Managed Agents 是一个「元框架」

回到开头那个老问题:如何为尚未被想到的程序设计系统。操作系统之所以能撑几十年,靠的就是把硬件虚拟化成足够通用的抽象。Managed Agents 想做的是同一件事——设计一个能容纳未来的 harness、sandbox 或其它 Claude 周边组件的系统。

所以原文把它定位成一个 meta-harness(元框架)它对「Claude 未来需要哪个具体 harness」不持立场,而是提供一组通用接口,让许多不同的 harness 都能跑在上面。文中举了两类例子:Claude Code 是一个优秀的通用 harness,他们自己在各种任务上广泛使用;同时也有研究表明,针对特定任务的 agent harness 在窄领域里表现出色。Managed Agents 打算把这些都容纳进来,并随着 Claude 智能的演进持续匹配。

元框架的设计意味着——只在 Claude 周围的接口上有主见

接口的设计目标是让这些能力在长时间跨度上可靠且安全地运行。但除此之外,他们对 Claude 究竟需要多少个、以及位于何处的大脑与双手,不作任何假设


译者附记

几个我觉得对自建 agent 系统最有借鉴价值的点:

原文致谢:Written by Lance Martin, Gabe Cemaj, and Michael Cohen;感谢 Nodir Turakulov 与 Jeremy Fox 的讨论,以及 Agents API 团队和 Jake Eaton 的贡献。
中文译介:lonX · 完整原文请读 anthropic.com/engineering/managed-agents