这篇回答的是平台架构层面的问题:如何设计一个能容纳未来模型与未来 harness 的 Agent 运行系统。
最有价值的是「操作系统式」的类比:把会话、harness、沙箱虚拟化成稳定接口,就像进程与文件抽象之下硬件自由更替。具体模式包括容器牲畜化、用 wake/getSession/emitEvent 实现崩溃恢复、凭证与沙箱隔离的安全边界,以及把上下文管理推给 harness 的分层设计。最易误用之处是把接口稳定性误解为功能冻结——作者明确的立场是只对接口形状有主张。
国内多数团队还停留在把 Agent 组件塞进单容器的「宠物」阶段,这篇提供了现成的解耦路线图;TTFT 数据(p50 降 60%、p95 降 90%+)也说明解耦不只是优雅,更是可感知的用户体验收益。
建议你审视自己系统里「假设模型做不到什么」的每条代码,找出已经过时的那条并删掉。
—— FDEChina编辑部 · 架构师视角
Engineering 博客上一个反复出现的主题,是如何构建有效的 Agent,以及如何为长时间运行的工作设计 harness(执行框架,即编排模型调用与工具路由的外围程序)。这一系列工作的一个共同线索是:harness 里编码着关于「Claude 自己做不到什么」的假设。然而,这些假设需要被频繁地重新审视,因为随着模型进步,它们会过时。
仅举一个例子:在之前的工作中我们发现,Claude Sonnet 4.5 会在察觉到上下文长度接近上限时提前草草收尾——这种行为有时被称为「上下文焦虑(context anxiety)」。我们通过在 harness 中加入上下文重置来应对。但当我们把同一个 harness 用在 Claude Opus 4.5 上时,发现这种行为消失了,那些重置变成了死重。
我们预期 harness 会持续演化。因此我们构建了 Managed Agents(托管 Agent):Claude 平台上的一个托管服务,通过一小组接口替你运行长时程(long-horizon)Agent——这些接口的设计目标,是活得比任何特定实现都久,包括我们今天运行的这些实现。按照我们的文档,你可以立刻开始使用 Claude Managed Agents。
构建 Managed Agents,意味着要解决计算领域一个古老的问题:如何为「尚未被想到的程序」设计系统。几十年前,操作系统通过把硬件虚拟化成足够通用的抽象——进程、文件——解决了这个问题,这些抽象足以覆盖当时还不存在的程序。抽象比硬件活得更久:read() 命令并不关心它访问的是 1970 年代的磁盘组(disk pack)还是一块现代 SSD。上层的抽象保持稳定,底层的实现自由更替。
Managed Agents 遵循同样的模式。我们把 Agent 的各个组件虚拟化:会话(session,记录所发生一切的只追加日志)、harness(调用 Claude 并把 Claude 的工具调用路由到相应基础设施的循环),以及沙箱(sandbox,Claude 运行代码、编辑文件的执行环境)。这样一来,每一部分的实现都可以在不惊动其他部分的情况下被替换。我们对这些接口的形状有明确的主张,但对接口背后运行的是什么不做规定。
别收养宠物
我们最初把所有 Agent 组件放进单个容器,这意味着会话、Agent harness 和沙箱共享同一个环境。这种做法有实实在在的好处:文件编辑就是直接的系统调用(syscall),也不需要设计任何服务边界。
但把一切耦合进一个容器之后,我们撞上了一个古老的基础设施问题:我们收养了一只宠物。在「宠物与牲畜(pets vs. cattle)」的类比里,宠物是有名字、需要人手工照料、你舍不得失去的个体;而牲畜是可以互相替换的。在我们的场景中,那台服务器成了宠物:容器一旦故障,会话就随之丢失;容器失去响应,我们还得把它抢救回来。
抢救容器,意味着要去调试那些无响应的卡死会话。我们唯一的观察窗口是 WebSocket 事件流,但它无法告诉我们故障出在哪里——harness 里的一个 bug、事件流上的一个丢包、或者容器整个掉线,表现出来的现象完全一样。要弄清楚到底出了什么问题,工程师必须在容器内部打开一个 shell;但由于那个容器里往往还存着用户数据,这种做法实质上意味着我们丧失了调试能力。
第二个问题在于,harness 默认 Claude 要处理的一切都和它住在同一个容器里。当客户要求我们把 Claude 连接到他们自己的虚拟私有云(VPC)时,他们要么把自己的网络与我们的网络做对等互联(peering,即把两个相互隔离的私有网络在网络层面直接打通),要么在我们的环境里运行我们的 harness。harness 里烘焙进去的假设,在我们想把它接到不同基础设施的那一刻,变成了障碍。
把大脑从双手上解耦
我们最终得出的方案,是把我们所说的「大脑」(Claude 及其 harness)与「双手」(执行动作的沙箱和工具)以及「会话」(会话事件的日志)解耦开来。每一部分都成为一个对其他部分假设很少的接口,每一部分都可以独立地故障、独立地被替换。
harness 搬出容器。把大脑与双手解耦,意味着 harness 不再住在容器里。它像调用其他任何工具一样调用容器:execute(name, input) → string,一个名字和一个输入进去,一个字符串出来。容器变成了牲畜。如果容器挂了,harness 把这次失败当作一个工具调用错误捕获下来,并传回给 Claude。如果 Claude 决定重试,可以用标准配方 provision({resources}) 重新初始化一个新容器。我们再也不用把失败的容器从病床上抢救回来了。
从 harness 故障中恢复。harness 自己也变成了牲畜。因为会话日志存放在 harness 之外,harness 里没有任何东西需要在崩溃后幸存。一个 harness 实例失败时,可以用 wake(sessionId) 启动一个新的,用 getSession(id) 取回事件日志,然后从最后一个事件恢复运行。在 Agent 循环执行期间,harness 通过 emitEvent(id, event) 写入会话,以保持一份持久的事件记录。
安全边界。在耦合设计里,Claude 生成的任何不可信代码,都与凭证运行在同一个容器中——因此一次提示注入(prompt injection)只需要说服 Claude 去读它自己的环境变量。攻击者一旦拿到这些令牌,就可以生成新的、不受限制的会话,并把工作委派给它们。缩小令牌的授权范围是显而易见的缓解手段,但这等于又编码了一条「Claude 拿到受限令牌后做不成什么」的假设——而 Claude 正变得越来越聪明。结构性的修复办法,是确保令牌在 Claude 生成代码所运行的沙箱里永远不可达。
我们用两种模式来保证这一点。凭证既可以与资源捆绑在一起,也可以存放在沙箱之外的保管库(vault)中。对 Git 而言,我们在沙箱初始化时用每个仓库的访问令牌克隆仓库,并把它接入本地 git remote。此后 git push 和 git pull 在沙箱内就能正常工作,而 Agent 从头到尾都不经手令牌本身。对自定义工具,我们支持 MCP,并把 OAuth 令牌存放在安全保管库里。Claude 通过一个专用代理(proxy)调用 MCP 工具;这个代理接收与会话关联的令牌,从保管库取回对应的凭证,再向外部服务发起调用。harness 自始至终不会接触任何凭证。
会话不是 Claude 的上下文窗口
长时程任务的长度常常超过 Claude 的上下文窗口,而应对这个问题的标准方法,都要对「保留什么」做出不可逆的决定。我们在之前关于上下文工程(context engineering)的工作中探讨过这些技术。例如,压缩(compaction)让 Claude 保存一份其上下文窗口的摘要;记忆工具(memory tool)让 Claude 把上下文写入文件,从而实现跨会话学习。两者还可以搭配上下文裁剪(context trimming)使用——选择性地移除旧工具结果、思考块(thinking blocks)之类的 token。
但对上下文做选择性保留或丢弃的不可逆决定,可能导致失败。你很难知道未来的轮次会需要哪些 token。如果消息被压缩步骤改写,harness 会把被压缩的消息从 Claude 的上下文窗口里移除,而这些消息只有在被存储下来的情况下才可能恢复。先前的工作探索过一种解决思路:把上下文存成一个活在上下文窗口之外的对象。例如,上下文可以是 REPL 里的一个对象,LLM 通过编写代码来编程式地过滤它、切片访问它。
在 Managed Agents 中,会话提供的正是同样的好处:它充当一个活在 Claude 上下文窗口之外的上下文对象。但上下文不是存放在沙箱或 REPL 里,而是持久地存储在会话日志中。接口 getEvents() 让大脑可以通过选取事件流的位置切片来查询上下文。这个接口的使用方式非常灵活:大脑可以从上次停止读取的地方继续往下读,可以回退到某个时刻之前的几个事件去查看前因,也可以在执行某个具体动作之前重读相关上下文。
取回来的事件,还可以先在 harness 里做变换,再传入 Claude 的上下文窗口。这些变换由 harness 自行编码决定,包括为获得高提示缓存(prompt cache)命中率而做的上下文组织,以及各种上下文工程手法。我们把「会话中的可恢复上下文存储」与「harness 中的任意上下文管理」拆成两个关注点,原因在于我们无法预测未来的模型需要什么样的上下文工程。接口把上下文管理推给 harness 去做,而只保证一件事:会话是持久的、随时可供查询的。
许多大脑,许多双手
许多大脑。把大脑从双手上解耦,解决了我们最早收到的一批客户抱怨之一。当团队想让 Claude 操作他们自己 VPC 里的资源时,唯一的路径是把他们的网络与我们的网络对等互联,因为装着 harness 的那个容器假定每个资源都躺在它旁边。一旦 harness 不再待在容器里,这个假设就消失了。同样的改动还带来了性能上的回报。当我们最初把大脑放进容器时,这意味着有多少个大脑就需要多少个容器。对每一个大脑来说,容器没有配置好就无法开始推理;每个会话都要预先支付完整的容器启动成本。每个会话——哪怕它永远不会碰沙箱——都得克隆仓库、启动进程、从我们的服务器拉取待处理事件。
这些死时间集中体现在首 token 时间(TTFT,time-to-first-token)上,它衡量一个会话从接下工作到产出第一个响应 token 之间要等多久。TTFT 是用户感受最敏锐的那部分延迟。
把大脑从双手上解耦之后,容器只在需要时才由大脑通过一次工具调用(execute(name, input) → string)来配置。因此,不需要立刻使用容器的会话就不必等它。编排层(orchestration layer)一从会话日志拉出待处理事件,推理就能立刻开始。使用这一架构,我们的 p50 TTFT 下降了约 60%,p95 下降了超过 90%。扩展到许多大脑,只是启动许多无状态的 harness,并仅在需要时把它们连接到双手。
许多双手。我们还希望每个大脑能连接许多双手。在实践中,这意味着 Claude 必须对多个执行环境进行推理,并决定把工作派发到哪里——这比在单个 shell 里操作是更难的认知任务。我们最初把大脑放进单个容器,是因为更早的模型做不到这一点。而随着智能水平提升,单容器反而成了瓶颈:当那个容器失败时,我们失去了大脑正在触及的每一双手的状态。
把大脑从双手上解耦之后,每双手都只是一个工具:execute(name, input) → string,一个名字和一个输入进去,一个字符串返回。这个接口既支持任何自定义工具、任何 MCP 服务器,也支持我们自己的工具。harness 不知道沙箱是一个容器、一部手机,还是一个宝可梦模拟器。而且,由于没有任何一双手与任何一个大脑耦合,大脑之间可以互相转交双手。
结语
我们面对的挑战是一个老问题:如何为「尚未被想到的程序」设计系统。操作系统通过把硬件虚拟化成足够通用、能覆盖尚不存在程序的抽象,存续了几十年。有了 Managed Agents,我们的目标是为 Claude 周围的未来 harness、沙箱或其他组件,设计一个同样能容纳它们的系统。
Managed Agents 正是同一精神下的一个元 harness(meta-harness):它对 Claude 未来需要什么样的具体 harness 不做规定。相反,它是一个由通用接口组成、允许许多不同 harness 并存的系统。例如,Claude Code 是一个优秀的 harness,我们在各种任务中广泛使用它;我们也展示过,面向特定任务的 Agent harness 在窄领域中表现出色。Managed Agents 可以容纳所有这些,并随时间推移匹配 Claude 的智能水平。
元 harness 设计意味着,我们对 Claude 周围的接口有明确主张:我们预期 Claude 需要操纵状态(会话)与执行计算(沙箱)的能力;我们也预期 Claude 需要扩展到许多大脑、许多双手的能力。我们把这些接口设计成可以在长时间跨度上可靠、安全地运行。但至于 Claude 需要多少个大脑或多少双手、它们在哪里,我们不做事先假设。
致谢:本文由 Lance Martin、Gabe Cemaj 和 Michael Cohen 撰写。感谢 Nodir Turakulov 和 Jeremy Fox 就这些话题的有益讨论,特别感谢 Agents API 团队和 Jake Eaton 的贡献。
延伸阅读:关于 Agent 系统的接口与架构取舍,参见 Agent 主题页;长任务中的上下文管理与断点续做,可以对照 FDE 项目生命周期指南中的阶段划分方法;想了解 Agent 能力分层与适用边界,见 FDE 能力模型;更多工程实践可到 FDE 实战指南查阅。
本文由 FDEChina 团队翻译自原文,转载已注明出处;如需引用请以原文为准。