模型之外:拆解决定Agent 可靠性的 Harness 层

Sam Altman 最近写下的一句话概括了 Agent 工程的发展方向:“also, a reason to favor open-source harnesses.”

AI 领域的大部分关注点仍然集中在模型上。哪个模型推理能力更强,哪个模型写代码更好,上下文窗口有多大,或者在基准测试中表现如何。但一旦你让一个 Agent 工作几分钟,跨多个来源搜索、执行代码、委派任务并执行数十个步骤时,模型只是维持任务运行的一部分。
一个原始模型没有持久的执行循环。它不会自动知道如何调用工具、决定保留什么上下文、隔离代码执行、从断开的连接中恢复,或在敏感操作前暂停。所有这些都来自围绕它的运行时。
这个运行时就是 Agent Harness。
在本文中,我们将通过跟踪一个 Web 研究 Agent 的完整运行过程,来剖析一个长时间运行的 Agent 究竟是如何执行的:模型循环、MCP、Skills、沙盒执行、Subagents、上下文压缩、审批检查点,以及保持会话持久的事件流。
Agent 实际由什么构成

语言模型本身在 API 调用之间是无状态的。你发送一些上下文给它,它产生一个输出,然后调用结束。它没有内置的概念来理解一个需要跨越二十或五十个独立步骤持续运行的任务。
Harness 将模型包装在一个执行系统中。最简单的情况下,这意味着反复调用模型、让它访问工具、将工具结果反馈到上下文中,并决定任务何时完成。
但一个有用的 Agent 需要的不只是循环本身。它通常有一个用于推理的模型、用于访问外部系统的 MCP server 或其他工具、描述特定任务应如何执行的 Skills,以及一个可以运行代码和创建文件而无需共享 Agent 服务器本身的沙盒。
此外还有一些随着运行时间变长而变得越来越重要的能力:用于并行工作的 Subagents、用于控制模型窗口的上下文管理、敏感操作前的审批检查点,以及持久会话,以确保客户端断开连接时任务不会消失。
为了使架构具体化,我们将以 TrueForge 作为实例,跟踪一个 Web 研究 Agent 的完整执行过程。TrueForge 是 TrueFoundry 开源的一个 Agent Harness。
GitHub 仓库:https://github.com/truefoundry/trueforge
搭建研究 Agent
为了使后续的架构讲解更具体,我们将在 TrueForge 上构建一个小型 Web 研究 Agent,然后跟踪它运行时 Harness 内部发生的事情。
这个 Agent 只有一项工作:给它一个研究问题,它会搜索网络、并行研究问题的不同部分,并将所有内容转化为一个引用了来源的交互式页面。
对于这个 Agent,我们只需要四个构建模块:一个模型、一个用于网页搜索的 MCP 连接器、一个定义最终 artifact 应如何创建的 Skill,以及一个 Agent 可以在其中执行代码和写入文件的沙盒。
使用以下命令启动本地服务器:
npx @truefoundry/trueforge
然后打开 http://localhost:8790。

从那里开始,可以直接在 UI 中组装这个 Agent。
- 添加模型。 进入 Settings → Models,选择一个提供商,添加你的 API key,并启用你想让 Agent 使用的模型。
- 连接 Exa。 进入 Settings → Connectors,找到 Exa 并连接。这使 Agent 可以通过 MCP 进行网页搜索。
- 启用 Skill。 在 Settings → Skills 下,启用 web-artifacts-builder。其中包含 Agent 将研究结果转化为交互式单页简报所遵循的流程。
- 开启沙盒。 Skill 在沙盒内执行,而不是直接在 Agent 服务器上执行。在 macOS 或 Linux(包括 WSL)上,TrueForge 可以使用其内置的本地沙盒。在原生 Windows 上,你可以改为连接 Daytona 沙盒。
- 组装并保存 Agent。 返回聊天界面,选择模型,启用 Exa 和 web-artifacts-builder,并保持 Dynamic sub-agents 启用。发送一个测试问题,确保 Agent 能够搜索、委派研究并渲染最终的 artifact。然后将配置保存为一个 Agent。
一旦 Agent 被保存,模型、工具、Skill 和沙盒就会保持附加在该配置上。你的应用只需引用已保存的 Agent,而不必在每次运行时重新构建整个设置。
此时 Agent 已经可以工作了。更有趣的问题是,当我们向它发送任务后,Harness 实际上在做什么。
Agent 开始运行时会发生什么

当你发送一个研究问题时,Harness 会创建一个新的 turn,并用任务、相关指令以及当前可用的能力来调用模型。
模型并不是接收到问题后在一次响应中神奇地完成整个工作流。它通常先决定一个动作。在我们的例子中,这可能意味着通过 Exa 搜索信息。
因此,执行过程开始看起来像这样:
- 模型接收任务。
- 它决定需要哪个工具或能力。
- Harness 执行该动作。
- 结果返回给模型。
- 模型对结果进行推理并决定下一步做什么。
- 循环重复,直到任务完成。
从核心来看,这仍然只是一个执行循环。
你可以在一个下午自己写出一个基础版本:调用模型、检查它是否请求了工具、运行该工具、追加结果,然后再次调用模型。
困难不在于编写循环。困难在于让这个循环在真实任务中存活下来。
如果模型永远不停地调用工具会怎样?当一次搜索返回 20,000 个 Token 时会怎样?当三项研究可以并行运行时会怎样?当 Agent 想要执行破坏性操作时会怎样?如果浏览器在运行中途断开连接会怎样?
这些都是 Harness 层面的问题。
我们的研究 Agent 在一次运行中就遇到了其中的几个。
MCP 将 Agent 连接到网络
模型本身无法搜索实时网络。它需要一个外部工具。
在这个设置中,Exa 通过 MCP 暴露出来。当模型决定需要搜索时,Harness 发送相应的 MCP 请求,接收结果,并使这些信息可用于下一次模型调用。
对于只有一两个工具的小型 Agent 来说,这很简单。但生产环境的 Agent 通常有许多 MCP server 和可能数百个工具。
这在 Agent 甚至还没开始工作之前就造成了上下文问题。
每个工具都带有描述和 schema,说明它的功能以及接受哪些参数。如果 Harness 将每个完整定义插入到每个模型请求中,模型的上下文窗口就会有很大一部分被可能永远不会用到的工具消耗掉。
这正是渐进式披露发挥作用的地方。
与其预先加载所有内容,Agent 最初只需看到足够的信息来发现某个能力的存在。完整定义只在模型真正需要那个工具时才加载。
同样的原则适用于整个长时间运行的 Agent:模型应该能够访问它可能需要的一切,同时不必把所有内容都同时放在活动上下文中。
Skills 为 Agent 提供流程
MCP 工具描述 Agent 能做什么。
Skills 描述它应该如何执行某类特定任务。
对于这个研究 Agent,web-artifacts-builder 包含了获取研究内容、组织并将其转化为最终交互式 artifact 的指令。
这种区分很重要,因为整个流程不需要全部放在系统提示词中。
想象一个拥有三十种不同能力的 Agent:撰写报告、审查代码、创建仪表板、分析日志、研究网络等等。将所有三十个工作流的完整指令放入每次模型调用中,会造成我们刚刚在工具 schema 中看到的同样问题。
相反,Agent 可以知道哪些 Skills 可用,并且只在某个 Skill 变得相关时才加载详细流程。
这样既保持了基础上下文更小,又仍然允许同一个 Agent 执行许多不同类型的工作。
在我们的运行中,一旦收集完研究内容,Agent 就会加载构建 artifact 的 Skill,并用它来决定最终页面应该如何生成。
但生成该页面需要的不仅仅是语言模型推理。它需要一个地方来执行代码和创建文件。
沙盒是执行发生的地方
模型可以生成代码,但不应该在托管 Agent 的同一进程中直接运行这些代码。
沙盒为它提供了一个隔离的执行环境。
对于我们的研究 Agent,沙盒是 Skill 处理搜索结果、创建文件、执行代码和组装最终交互式页面的地方。
这种分离对上下文管理也很有用。
假设 Exa 返回一个非常大的搜索响应。一种选择是将全部内容粘贴回对话中,让模型在之后的每一步都携带那数千个 Token。
这在一段时间内可行,但扩展性很差。
更好的方法是把大量数据移到别处。Agent 可以在沙盒内处理原始结果,将大型输出写入文件,只向模型返回一个紧凑的摘要或引用。
如果 Agent 需要再次查看,信息仍然可用,但它不再占用每次模型请求中的空间。
这很重要,因为一旦 Agent 开始并行研究多个事物,上下文就成为主要约束。
长时间运行的 Agent 开始出问题的地方:上下文
短时间的 Agent 运行很容易推理,因为根本没有太多历史记录。
长时间运行的 Agent 则会在每一步不断积累信息:模型响应、工具输出、搜索结果、Skill 指令、文件、之前的决策以及其他 Agent 返回的结果。
最终系统必须回答一个难题:
模型现在真正需要看到的是什么?
这里涉及两类上下文。
第一类是我们有意提供给模型的上下文:系统提示词、工具定义、Skill 指令、文件和任务状态。
第二类是运行过程中自行产生的上下文:搜索结果、中间输出、旧消息、工具响应以及早期步骤的推理。
第二类会持续增长。
TrueForge 使用了几种技术来防止所有这些信息在父模型上下文中积累。
渐进式披露
正如我们在前面看到的,工具和 Skills 不需要在被使用之前完整加载。
Agent 可以先发现能力,之后再拉取详细定义。
Code Mode
大型工具结果可以通过程序处理,而不是直接原样发送给模型。
如果一次搜索返回数百条记录,Agent 可以在代码中对它们进行过滤或转换,只将有用的输出送回上下文。
大结果卸载
如果一个结果太大而无法保留在对话中,它可以被写入沙盒中的文件。
模型收到的是预览或引用,而不是在剩余的运行过程中携带完整的载荷。
Compaction
即使有了这些技术,对话本身最终还是会增长。
当上下文超过配置的阈值时,会话中较旧的部分可以被总结为更小的表示形式。Agent 会丢失早期步骤的一些逐字细节,但保留了足够的状态来继续任务。
这四种技术背后的目标是相同的:保留推理所需的信息,而不强迫模型在每一步都重读完整的执行历史。
还有一种阻止上下文增长的方法:一开始就不要把工作放进父上下文中。
Subagents 隔离并行研究
我们的 Web 研究 Agent 就是一个很好的例子。
假设用户要求它比较三个不同的开发者工具。
最简单的实现是在同一个 Agent 上下文中先研究工具一,再研究工具二,然后是工具三。
到研究完成时,父对话中包含了来自所有三个分支的每一次搜索查询、每一个读取的页面、每一个工具响应和每一条中间笔记。
相反,Harness 可以为每一项创建一个单独的 Subagent。
每个 Subagent 接收自己的上下文并独立工作。它搜索网络、阅读相关页面、对其发现进行推理,并最终向父级返回一个紧凑的结论。
父级不需要看到整个研究过程。
它只需要输出。
所以运行看起来更像这样:
Parent agent
→ Subagent A researches tool A
→ Subagent B researches tool B
→ Subagent C researches tool C
每个子级在隔离的上下文中工作,并将其发现返回给父级,父级随后进行最终比较。
对于我们的研究 Agent,Dynamic sub-agents 正好支持这种扇出模式。
这有两个优点。工作可以并行进行,而且父上下文保持小得多,因为它接收的是结论而不是完整的研究历史。
但 Subagents 并不一定更好。
如果一个任务能轻松容纳在一个上下文窗口内,将其拆分为多个独立的 Agent 会增加更多的编排工作和更多出错的机会。当工作确实是并行的,或者子任务否则会向主上下文倾倒过多信息时,Subagents 才变得有用。
审批门应位于推理与行动之间
到目前为止,我们的 Agent 只读取信息并在其沙盒内创建文件。没有任何危险的外部操作。
但想象一下,把 Exa 替换为能够合并 GitHub pull request、修改基础设施、发送 Slack 消息或更新生产数据库的工具。
执行循环通常看起来像这样:
模型决定调用工具 → Harness 执行工具
对于敏感操作来说,自动赋予模型这么大的权限太多了。
Harness 需要另一个状态:
模型决定调用工具 → Harness 暂停 → 人工审查 → Harness 继续或停止
TrueForge 可以基于附加在工具上的元数据强制执行审批检查点。如果一个工具被标记为需要审批,运行会在调用执行前停止,并向用户显示确切的工具和参数。
这与在系统提示词中放入类似“删除任何东西之前先问我”的做法不同。
提示词仍然是模型必须遵循的指令。
审批门是一条运行时规则。
模型无法通过推理绕过它,因为在检查点得到解决之前,Harness 本身拒绝执行该工具。
我们的研究 Agent 从未触发过审批门,因为它的外部工具是只读的。如果我们附加一个具有写入能力的 GitHub 工具,同样的执行循环就可以在创建或修改任何东西之前立即暂停。
运行应该在客户端断开后继续存在
还有最后一个问题,只有当 Agent 开始运行几分钟或更长时间时才会显现出来。
如果你关闭浏览器会发生什么?
聊天机器人的请求通常完成得足够快,以至于客户端连接和模型响应可以被当作同一次交互。长时间运行的 Agent 不能做这样的假设。
执行必须独立于监视它的 UI 而存在。
TrueForge 将会话的步骤存储为有序的事件流。随着模型推理、工具执行、Subagents 运行、审批发生以及结果返回,这些事件成为持久会话的一部分。
因此,如果浏览器断开连接,运行不会随之消失。
服务器可以继续执行任务。当客户端重新连接时,它可以恢复事件流或检索错过的那些事件。
同样的设计还为你提供了可重放的运行轨迹。
如果研究 Agent 返回了一个糟糕的答案,你可以检查它是如何得出那个答案的:它执行了哪次搜索、哪个工具返回了意外的数据、Subagent 得出了什么结论,以及父级对该结论做了什么。
随着 Agent 运行时间越来越长,这一点变得越来越重要。最终响应告诉你发生了什么。事件流告诉你为什么。
运行的完整剖析
现在我们可以把整个执行过程拼在一起了。
用户发送研究问题,Harness 开始一个 turn。
模型决定首先需要做什么。它发现可用的工具并通过 MCP 调用 Exa。搜索结果返回,但大型输出不必留在主模型上下文中。
如果问题可以拆分为独立的分支,Harness 会创建 Subagents。每个 Subagent 在隔离的上下文中研究问题的相应部分,并将紧凑的结果发回给父级。
当 Agent 需要将这些发现转化为最终 artifact 时,它会加载相关的 Skill。代码在沙盒内运行,Agent 可以在那里处理数据和创建文件,而无需直接在服务器上执行。
随着会话变长,未使用的工具定义保持未加载状态,大结果被卸载,较旧的对话历史可以被压缩。
如果 Agent 最终请求一个敏感的写入操作,运行时可以在执行前停止并等待审批。
在整个过程中,每一步都被持久化为一个事件,因此即使客户端断开连接,任务也可以继续,而且之后可以检查完整的执行过程。
这就是将简单的模型-工具循环转变为长时间运行 Agent 的关键。
模型仍然在进行推理,但大部分可靠性工作发生在它周围:决定模型看到什么、代码在哪里运行、并行工作如何隔离、执行何时停止,以及会话是否能存活足够长的时间以完成任务。
这就是为什么随着 Agent 超越短演示,Harness 层变得更加重要。
TrueForge 将这些运行时组件打包成一个开源、供应商中立的 Harness,而不是要求团队围绕每个 Agent 构建相同的基础设施。Web 研究 Agent 只是一个例子,但对于代码库入门 Agent、依赖审计器、事故分诊机器人或其他长时间运行的工作流来说,底层执行模型基本保持不变。
变化的是附加到 Agent 上的模型、工具和 Skills。
循环、上下文管理、沙盒、审批检查点和持久会话保持不变。
我们的例子是一个小型的只读 Web 研究 Agent,但其底层的 Harness 对更大规模的工作流也以同样的方式工作。你可以将相同的设置用于代码库入门 Agent、依赖审计器或事故分诊机器人。变化的是你附加到它上面的工具、Skills 和权限。执行循环、上下文处理、审批门、沙盒和轨迹基本保持不变。
在这篇演练中,Agent 本身已在 TrueForge 内配置并保存。我还分享了一个配套的 GitHub 仓库,其中包含 TypeScript driver 和设置说明,用于从代码中调用该已保存的 Agent 并亲自遵循相同的工作流。配套 driver + 设置指南。 TrueForge 是开源的,GitHub 仓库在这里:https://github.com/truefoundry/trueforge。感谢 TrueFoundry 与我合作完成这篇文章。
- 原文链接: x.com/Sumanth_077/status...
- 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
鸿途知科网
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。