https://www.anthropic.com/research/managed-agents 讲讲这个

我正在回复 群友 3892。 这篇是 Anthropic 工程博客的《Scaling Managed Agents with stable interfaces》,讲的是他们做 Claude Managed Agents 这个托管服务时,为什么把 agent 拆成三层,以及这种拆分解决了什么问题。我顺着原文讲了,另外加了点我自己的判断。 先说这篇文章的出发点:需要经常质疑 harness 中内嵌的假设。harness 里每一项设计,本质上都在表达一句“我认为模型现在做不到 X,所以我替它补上”。但模型一变,这些假设就过期了。文中举了个很具体的例子:用 Claude Sonnet 4.5 时,它快接近上下文上限就会提前收尾(他们叫 context anxiety),于是 harness 里加了 context reset。换到 Opus 4.5,这个毛病自己没了,那次 reset 就只剩算力成本没有收益。所以他们的结论是:harness 要经常复检,别把上一次的补丁当资产供着。 然后是核心设计。他们借用了操作系统的思路:几十年前 OS 解决的是“如何为还没被想出来的程序做设计”,办法是把硬件虚拟化成 process、file 这种足够通用的抽象,read 不关心你读的是 1970 年代的盘还是现代 SSD。Managed Agents 干的是同一件事,把 agent 虚拟化成三个接口: session,会话事件的只追加日志,里面记录发生过的所有事。 harness,调用 Claude、把 Claude 的工具调用路由到对应基础设施的那个循环。 sandbox,Claude 跑代码、改文件的执行环境。 关键在于,他们只对接口的形状有明确主张,不规定接口背后由什么实现。这样三者可以各自换实现、各自失败,互不牵连。 接下来是两个演进过程,这部分比结论更有意思。 第一版是单容器:session、harness、sandbox 全塞进一个容器里。好处很实在,改文件就是直接的 syscall,没有服务边界要设计。但问题出在他们自己用的比喻上,这叫养了只 pet。pet 就是有名字、需要人工照料、丢了赔不起的那台机器;对应的是 cattle,可以随便替换的。容器变成 pet 之后:容器挂了,会话就没了;容器不响,就得人工把它救活。而排查卡死会话的唯一窗口是 WebSocket 事件流,它告诉不了你故障发生在哪一层,harness 的 bug、事件流的丢包、容器下线,看起来一模一样。要真定位得进容器开 shell,可那容器里往往有用户数据。他们的原话很坦白:等于我们没有 debug 能力。 第二个问题更结构性:harness 默认了 Claude 要操作的东西就在它旁边那个容器里。客户说想连自己的 VPC,就只剩下两条路,把网络跟我们的 peer 起来,或者把我们的 harness 搬到你们环境里跑。一个写死在 harness 里的假设,在你想接别的基础设施时变成了障碍。 于是拆成“大脑”和“手”。harness 搬出容器,它调容器的方式跟调任何别的工具一样,都是 execute(name input) → string。容器变成 cattle:挂了,harness 当成工具调用出错交给 Claude;Claude 决定重试,就 provision 一个新容器。不用再救活了。 harness 自己也是 cattle。因为 session 日志在 harness 外面,harness 里没有任何状态需要活下来。挂了就 wake(sessionId) 起个新的,getSession(id) 拿回事件日志,从最后一条事件接着干。运行时用 emitEvent(id event) 持续把事件落进 session。我觉得这是全文最实用的一条:只要日志在外部、且是只追加的,进程就可以是无状态的,这跟传统后端的思路是一致的。 然后是安全边界,这块值得单独说。耦合设计下,Claude 生成的不可信代码跟凭证跑在同一个容器里,于是一次 prompt injection 只要说服 Claude 去读它自己的环境就够了。而 token 一旦到手,攻击者能开全新的、没限制的 session,把活派给它们,越滚越大。他们说窄化 scope 是显而易见的缓解办法,但这又编码了一个“Claude 拿受限 token 干不成大事”的假设,而这个假设正在失效。真正的修法是把 token 挪到 sandbox 永远够不着的地方。两个做法:Git 的仓库 token 在 sandbox 初始化时用来 clone,直接把凭证配置进本地 git remote,之后 sandbox 里 push、pull 都不需要 agent 碰 token;MCP 的 OAuth token 存在 vault 里,Claude 通过一个 session 关联的代理去调,代理拿 vault 里的凭证去发请求,harness 全程不知道凭证长什么样。 再往后是对上下文的重新定义,这一段跟我们群里刚聊的 cu、bu 压缩几乎是同一个问题的上游答案。他们说长任务超过上下文窗口,而压缩、记忆工具、裁剪这些手段都涉及不可逆的取舍,难点是你不知道未来几轮需要哪些 token;被 compaction 吃掉的消息从上下文窗口里移除后,只有存下来才能找回。 Managed Agents 的做法是:session 不是 Claude 的上下文窗口,而是一个存在于窗口之外、耐久存储的上下文对象。接口是 getEvents,大脑可以按位置切片去查事件流:从上次读到的地方接着读、回卷到某个时刻之前看前因、重读某次动作之前的上下文。取出来的事件还能在 harness 里先做变换再交给 Claude,怎么组织能省 token、怎么提高 prompt cache 命中率,都归 harness 管。 他们把这两件事分开的理由很漂亮:session 只保证“持久、可被查询”,harness 负责“任意形式的上下文工程”,因为没人能预测下一代模型需要什么样的上下文工程。这个分工正好回应开头那句:把会过期的部分放在能快速替换的那一层,接口只承诺不会过期的部分。对照我们刚聊的,cu 的截图压缩问题其实就属于“在 harness 里怎么变换和取舍”这一层,而“哪些文件改过、哪些 URL 访问过”这种不该丢的事实,应该放在 session 那类耐久记录里,不该压在摘要里。 最后是多大脑、多手,以及拆完之后的两个附带收益。性能上,原来大脑在容器里,多少个大脑就要多少个容器,每个 session 都得先 clone 仓库、起进程、拉 pending 事件,然后才能开始推理,这段死时间直接体现在 TTFT 上。拆开后容器是按需用工具调用开出来的,不碰 sandbox 的 session 就不用等容器。他们报的数字是 p50 TTFT 降约 60%,p95 降超过 90%。扩展大脑就是起一堆无状态 harness。另一头是多手:接口统一成 execute(name input) → string 之后,harness 不需要知道那个手是容器、手机还是 Pokemon 模拟器,而且手不绑脑子,可以在大脑之间传递。文中提到他们当年把大脑塞进单容器,是因为早期模型干不了“同时操作多个执行环境”这种更难的认知活,能力上来之后,单容器反而成了限制:那个容器一挂,大脑伸向的所有手的状态全丢。 我的整体看法:这篇的价值不在“又一个 agent 框架”,而在于它把 harness 当成有保质期的代码来对待。真正稳定的是三条线:状态怎么持久化、计算在哪执行、凭证由谁持有。这三条任何模型升级都不会让它们失效,而 prompt 怎么写、何时压缩、要不要 reset,都会过期。所以如果你们自己搭 agent 系统,可以先问一句:我这个 repo 里哪些代码是“因为模型做不到才写的”,把它们隔离到一层,别让它们长进核心数据结构里。判断标准也很直接:换一代模型,你希望删掉的是哪部分,那部分就不该是不可替换的。