
Claude Managed Agents:解耦「大脑」与「双手」

如何设计一个系统,让它能够运行「尚未想到的程序」?
几十年前,操作系统通过将硬件虚拟化为抽象——进程、文件——解决了这个问题。
这些抽象足够通用,适用于当时尚未存在的程序。
抽象比硬件更持久。
read() 命令不在乎它访问的是 1970 年代的磁盘组还是现代 SSD。
上层的抽象保持稳定,而下层的实现可以自由变化。
Managed Agents 遵循同样的模式。
我们将 Agent 的组件虚拟化:session(会话)、harness(挽具)、sandbox(沙箱)。
核心愿景
什么是 Managed Agents?
Managed Agents 是 Claude Platform 中的一个托管服务。
它代表你运行长周期的 Agent。
它通过一小部分接口运行,这些接口旨在比任何特定实现(包括我们今天运行的实现)更持久。
核心价值:
- 解耦大脑与双手 — 大脑(Claude + harness)与双手(sandbox + tools)分离
- 持久接口 — 接口比实现更持久
- 可扩展 — 支持多个大脑、多个双手
- 高可靠性 — 组件独立失败和替换
- 高安全性 — 安全边界清晰
一、问题背景
1. Harness 假设会过时
Engineering Blog 的一个持续主题是如何构建有效的 Agent 和设计 Harness。
这些工作的一个共同点是:Harness 编码了关于 Claude 自己无法做什么的假设。
但是,这些假设需要经常质疑。
因为随着模型的改进,这些假设会过时。
一个例子:
在之前的工作中,我们发现 Claude Sonnet 4.5 会在感知到上下文限制接近时过早结束任务——这种行为有时被称为「上下文焦虑」。
我们通过在 Harness 中添加上下文重置来解决这个问题。
但当我们在 Claude Opus 4.5 上使用相同的 Harness 时,我们发现这种行为消失了。
重置变成了死重。
教训: Harness 会继续演化。我们不应该假设某个特定的 Harness 设计会持久。
2. 如何设计系统?
构建 Managed Agents 意味着解决计算中的一个老问题:
如何为「尚未想到的程序」设计系统?
几十年前,操作系统通过将硬件虚拟化为抽象——进程、文件——解决了这个问题。
这些抽象足够通用,适用于当时尚未存在的程序。
抽象比硬件更持久。
read() 命令不在乎它访问的是 1970 年代的磁盘组还是现代 SSD。
上层的抽象保持稳定,而下层的实现可以自由变化。
Managed Agents 遵循同样的模式。
二、核心设计
1. 三个组件

Managed Agents 将 Agent 的组件虚拟化:
| 组件 | 说明 | 抽象 |
|---|---|---|
| Session(会话) | 所有发生的事情的仅追加日志 | getEvents() |
| Harness(挽具) | 调用 Claude 并将 Claude 的工具调用路由到相关基础设施的循环 | execute() |
| Sandbox(沙箱) | Claude 可以运行代码和编辑文件的执行环境 | provision() |
关键设计:
我们关注这些接口的形状,而不是关注它们背后运行什么。
这使得每个组件的实现都可以在不干扰其他组件的情况下交换。
2. 不要「领养宠物」
我们最初将所有 Agent 组件放在一个容器中。
这意味着 session、agent harness 和 sandbox 共享一个环境。
在 pets-vs-cattle 类比中,pet 是一个命名的、手工照看的个体,你不能承受失去它,而 cattle 是可互换的。
在我们的例子中,服务器变成了那只宠物。
如果容器失败,session 就会丢失。
如果容器无响应,我们不得不照顾它回到健康状态。
照顾容器意味着调试无响应的卡住会话。
我们唯一的窗口是 WebSocket 事件流,但它无法告诉我们失败来自哪里。
这意味着:
- Harness 中的 bug
- 事件流中的数据包丢失
- 容器离线
这三者都呈现相同的问题。
为了找出问题所在,工程师不得不在容器内打开一个 shell。
但因为那个容器通常也持有用户数据,这种方法本质上意味着我们缺乏调试能力。
3. 解耦大脑与双手
解决方案是解耦「大脑」(Claude 及其 harness)与「双手」(sandbox 和执行动作的工具)和「session」(会话事件日志)。
每个都变成了一个接口,对其他组件的假设很少,每个都可以独立失败或替换。
三、解耦的价值
1. Harness 离开容器
解耦大脑与双手意味着 harness 不再生活在容器内。
它像调用任何其他工具一样调用容器:
execute(name, input) → string容器变成了 cattle。
如果容器死亡,harness 将失败捕获为工具调用错误,并将其传回给 Claude。
如果 Claude 决定重试,可以使用标准配方重新初始化新容器:
provision({resources})我们不再需要照顾失败的容器恢复健康。
2. 从 Harness 失败中恢复
Harness 也变成了 cattle。
因为 session 日志位于 harness 之外,harness 中不需要任何东西在崩溃中幸存。
当一个失败时,可以重新启动一个新的:
wake(sessionId)
getSession(id) # 获取事件日志
# 从最后一个事件恢复在 Agent 循环期间,harness 使用 emitEvent(id, event) 写入 session,以保持事件的持久记录。
3. 安全边界
在耦合设计中,Claude 生成的任何不受信任的代码都在与凭证相同的容器中运行。
因此,提示注入只需要说服 Claude 读取自己的环境。
一旦攻击者获得这些 token,他们就可以生成新的、无限制的会话,并将工作委托给它们。
缩小范围是一个明显的缓解措施,但这编码了一个关于 Claude 在有限 token 下无法做什么的假设——而 Claude 变得越来越聪明。
结构性修复是确保 token 永远无法从 Claude 运行生成的代码的 sandbox 中访问。
我们使用两种模式来确保这一点:
1. Auth 可以与资源捆绑或在 sandbox 之外的 vault 中持有。
对于 Git,我们在 sandbox 初始化期间使用每个仓库的访问 token 克隆仓库,并将其连接到本地 git remote。
Git push 和 pull 可以在 sandbox 内工作,而 Agent 无需处理 token 本身。
2. 对于自定义工具,我们支持 MCP 并将 OAuth token 存储在安全 vault 中。
Claude 通过专用代理调用 MCP 工具;该代理接收与会话关联的 token。
然后,代理可以从 vault 中获取相应的凭证,并向外部服务发出调用。
Harness 永远不会知道任何凭证。
四、Session 不是 Claude 的上下文窗口
1. 长周期任务
长周期任务通常超过 Claude 上下文窗口的长度。
解决这个问题的标准方法都涉及关于保留什么的不可逆决定。
我们在之前的上下文工程工作中探索了这些技术:
- Compaction(压缩) — 让 Claude 保存其上下文窗口的摘要
- Memory tool(记忆工具) — 让 Claude 将上下文写入文件,实现跨会话学习
- Context trimming(上下文修剪) — 选择性删除 token,如旧的工具结果或思考块
但是,选择性保留或丢弃上下文的不可逆决定可能导致失败。
很难知道未来的轮次需要哪些 token。
如果消息被压缩步骤转换,harness 会从 Claude 的上下文窗口中删除压缩的消息。
这些消息只有在存储的情况下才能恢复。
以前的工作探索了通过将上下文存储为生活在上下文窗口之外的对象来解决这个问题。
例如,上下文可以是 REPL 中的对象,LLM 通过编写代码来过滤或切片它来编程访问。
2. Session 作为上下文对象
在 Managed Agents 中,session 提供了同样的好处,作为生活在 Claude 上下文窗口之外的上下文对象。
但是,不是存储在 sandbox 或 REPL 中,上下文被持久地存储在 session 日志中。
接口 getEvents() 允许大脑通过选择事件流的位置切片来询问上下文。
该接口可以灵活使用:
- 允许大脑从它上次停止阅读的地方继续
- 在特定时刻之前倒回几个事件,看看前奏
- 在特定动作之前重新阅读上下文
任何获取的事件也可以在传递给 Claude 的上下文窗口之前在 harness 中转换。
这些转换可以是 harness 编码的任何东西,包括:
- 上下文组织 — 实现高提示缓存命中率
- 上下文工程 — 选择性保留或丢弃
我们分离了 session 中的可恢复上下文存储和 harness 中的任意上下文管理,因为我们无法预测未来模型需要什么特定的上下文工程。
接口将上下文管理推入 harness,只保证 session 是持久的,可用于询问。
五、多个大脑,多个双手
1. 多个大脑
解耦大脑与双手解决了我们最早的客户投诉之一。
当团队希望 Claude 对自己 VPC 中的资源工作时,唯一的途径是将他们的网络与我们的对等互联。
因为持有 harness 的容器假设每个资源都坐在它旁边。
一旦 harness 不再在容器中,这个假设就消失了。
同样的变化也有性能回报。
当我们最初将大脑放入容器时,意味着许多大脑需要同样多的容器。
对于每个大脑,在容器被配置之前不能进行推理。
每个会话都在前期支付完整的容器设置成本。
每个会话,即使是那些永远不会触及 sandbox 的会话,都必须克隆仓库、启动进程、从我们的服务器获取待处理事件。
这种死时间表达为 time-to-first-token(TTFT)。
TTFT 衡量会话在接受工作和产生其第一个响应 token 之间等待的时间。
TTFT 是用户最敏锐感受到的延迟。
解耦大脑与双手意味着容器只有在需要时才通过工具调用 execute(name, input) → string 由大脑配置。
因此,不需要立即使用容器的会话不必等待一个。
只要编排层从 session 日志中提取待处理事件,推理就可以开始。
使用这种架构,我们的 p50 TTFT 下降了约 60%,p95 下降了超过 90%。
扩展到多个大脑只意味着启动许多无状态 harness,并仅在需要时将它们连接到双手。
2. 多个双手
我们还希望能够将每个大脑连接到多个双手。
在实践中,这意味着 Claude 必须推理多个执行环境,并决定将工作发送到哪里。
这是一个比在单个 shell 中操作更难的认知任务。
我们从一个容器中的大脑开始,因为早期的模型没有能力做到这一点。
随着智能的扩展,单个容器变成了限制。
当那个容器失败时,我们丢失了大脑到达的每个手的状态。
解耦大脑与双手使每个手都成为一个工具:
execute(name, input) → string一个名称和输入进去,一个字符串返回。
该接口支持任何自定义工具、任何 MCP 服务器和我们自己的工具。
Harness 不知道 sandbox 是容器、手机还是 Pokémon 模拟器。
因为没有手耦合到任何大脑,大脑可以将手传递给彼此。
六、核心对比
| 维度 | 耦合设计 | Managed Agents |
|---|---|---|
| 架构 | 所有组件在一个容器中 | Session、Harness、Sandbox 解耦 |
| 失败恢复 | 容器失败,session 丢失 | 组件独立失败和替换 |
| 调试 | 难以调试,无法进入容器 | 可以独立调试每个组件 |
| 安全性 | 凭证与代码在同一容器 | 凭证在 vault,永远无法访问 |
| 上下文管理 | 不可逆决定,压缩后丢失 | Session 持久存储,可恢复 |
| TTFT | 每个会话等待容器配置 | 按需配置,p50 降低 60% |
| 扩展性 | 一个容器,一个大脑,一个手 | 多个大脑,多个手 |
| 接口持久性 | 假设会过时 | 接口比实现更持久 |
关键洞察
- 如何为「尚未想到的程序」设计系统?
收尾金句:
- 操作系统通过虚拟化硬件解决了这个问题
- Managed Agents 通过虚拟化 Agent 组件解决了这个问题
- 这就是解耦「大脑」与「双手」
Claude Managed Agents
解耦「大脑」与「双手」
"
