原文:Build a Long-Running Agent With an Open Source Harness
作者:Sumanth|原文发布于 2026 年 8 月 25 日
Sam Altman 最近写过一句话,几乎概括了 Agent 工程正在走向哪里:“这也是应该优先选择开源 Harness 的一个理由。”
Jul 14
also, a reason to favor open-source harnesses.
如今关于 AI 的大部分注意力仍然集中在模型上:哪个模型推理更好、哪个更会写代码、上下文窗口有多大、基准测试成绩如何。但当你要求一个 Agent 连续工作几分钟,在多个来源之间搜索、执行代码、委派任务并走完几十个步骤时,模型只是不让任务中断的其中一部分。
原始模型没有持久执行循环。它不会自动知道怎样调用工具、应该保留哪些上下文、如何隔离代码执行、连接断开后怎样恢复,或者在敏感操作前暂停。所有这些能力都来自模型周围的运行时。
这个运行时,就是 Agent Harness。
这篇文章会跟随一个网页研究 Agent 走完整个执行过程,拆解长时间运行的 Agent 究竟如何工作:模型循环、MCP、Skills、沙箱执行、子 Agent、上下文压缩、审批检查点,以及让会话保持持久的事件流。
一个 Agent 实际由什么组成
语言模型在不同 API 调用之间本身是无状态的。你发送一段上下文,它生成输出,然后这次调用就结束了。模型并没有一个内建概念,知道某项任务还需要跨越二十次或五十次步骤才能完成。
Harness 会把模型包裹在一套执行系统中。最简单的形式,就是反复调用模型、向它开放工具、把工具结果重新写入上下文,并判断任务什么时候真正完成。
但一个实用的 Agent 不能只有循环。它通常还需要:负责推理的模型、连接外部系统的 MCP Server 或其他工具、描述具体工作方法的 Skills,以及一个能够运行代码和创建文件、但不与 Agent Server 共用环境的沙箱。
随着任务持续时间变长,另外一些能力会越来越重要:用子 Agent 并行工作;用上下文管理控制模型窗口;在敏感操作前设置审批检查点;让会话持久化,使任务不会随着客户端断开而消失。
为了让这套架构更具体,下面使用 TrueForge 作为完整示例,让一个网页研究 Agent 从开始到结束跑一遍。TrueForge 是 TrueFoundry 开源的 Agent Harness。
GitHub 仓库:https://github.com/truefoundry/trueforge
设置研究 Agent
我们先在 TrueForge 上构建一个小型网页研究 Agent,再观察任务执行时 Harness 内部发生了什么。
这个 Agent 只有一个目标:接收研究问题,搜索网页,把问题的不同部分并行研究,最后将结果和引用来源整理成一个交互式页面。
只需要四个基础模块:一个模型、一个用于网页搜索的 MCP 连接器、一项规定最终制品如何创建的 Skill,以及一个允许 Agent 执行代码和写入文件的沙箱。
先启动本地服务:
npx @truefoundry/trueforge
然后打开 http://localhost:8790。
接下来可以直接在界面中组装 Agent:
- 添加模型。 进入 Settings → Models,选择 Provider,填写 API Key,并启用准备使用的模型。
- 连接 Exa。 进入 Settings → Connectors,找到并连接 Exa。Agent 由此获得通过 MCP 进行网页搜索的能力。
- 启用 Skill。 在 Settings → Skills 中启用
web-artifacts-builder。它包含把研究材料整理成交互式单页 Brief 的完整流程。 - 开启沙箱。 Skill 在沙箱里执行,而不是直接运行在 Agent Server 上。在 macOS、Linux 和 WSL 中,TrueForge 可以使用内置本地沙箱;原生 Windows 则可以连接 Daytona 沙箱。
- 组合并保存 Agent。 返回聊天页,选择模型,启用 Exa 与
web-artifacts-builder,同时保持 Dynamic sub-agents 开启。发送一个测试问题,确认 Agent 能搜索、委派研究并渲染最终制品,然后把当前配置保存为 Agent。
保存后,模型、工具、Skill 与沙箱会持续绑定在这份配置上。应用程序只需引用保存好的 Agent,不必每次执行都重新组装全部组件。
至此 Agent 已经能工作。更值得研究的问题是:任务发出后,Harness 究竟做了什么?
Agent 开始运行时发生了什么
当用户发送研究问题时,Harness 会创建一个新 Turn,把任务、相关指令以及当前可用能力一起交给模型。
模型不会收到问题后就神奇地在一次响应里完成全部工作。它通常会先决定下一项动作。在这个例子里,第一步可能是通过 Exa 搜索资料。
执行过程大致如下:
- 模型接收任务;
- 模型判断需要哪一个工具或能力;
- Harness 执行这项动作;
- 执行结果返回给模型;
- 模型基于结果继续推理,决定下一步;
- 循环重复,直到任务完成。
核心仍然只是一个执行循环。
基础版本甚至可以在一个下午写完:调用模型,判断它是否请求工具,执行工具,把结果追加到上下文,然后再次调用模型。
真正困难的不是写出循环,而是让循环在真实任务中活下来。
如果模型永远不停地调用工具怎么办?一次搜索返回 20,000 Token 怎么办?三项研究可以并行时怎样调度?Agent 想执行破坏性操作时怎样阻止?浏览器在任务中途断开又该怎么办?
这些都是 Harness 要解决的问题,而我们的研究 Agent 在一次运行里就会遇到其中好几个。
MCP 把 Agent 连接到网页
模型自己不能搜索实时网页,它需要外部工具。
在这里,Exa 通过 MCP 暴露给 Agent。模型决定搜索时,Harness 发送相应的 MCP 请求,接收结果,再把信息交给下一次模型调用。
只有一两个工具时,这并不复杂。但生产环境中的 Agent 往往连接多个 MCP Server,并拥有数百个工具。
这意味着工作尚未开始,上下文问题就已经出现了。
每个工具都有说明和参数 Schema。如果 Harness 在每一次模型请求中都塞入所有工具的完整定义,那么上下文窗口很大一部分会被从未使用的工具占据。
这正是渐进式披露有价值的地方。
Agent 一开始只需要看到足以发现某项能力存在的信息;只有模型真正准备使用工具时,才加载完整定义。
同一个原则贯穿整个长任务:模型应该能够接触所有可能需要的信息,但不应该让所有信息同时占据当前上下文。
Skills 为 Agent 提供工作流程
MCP 工具描述 Agent 能做什么。
Skills 描述 Agent 应该怎样完成某类任务。
在研究 Agent 中,web-artifacts-builder 包含了整理研究、组织内容并生成最终交互制品的具体指令。
这一区分很重要,因为完整流程不需要全部写进 System Prompt。
假设一个 Agent 同时具备三十种能力:写报告、审查代码、创建 Dashboard、分析日志、搜索网页等。如果把三十套完整工作说明放进每一次模型调用,就会重现工具 Schema 带来的问题。
更好的做法,是先让 Agent 知道有哪些 Skills 可用,只有其中一项与当前任务相关时,才加载详细流程。
这样既能保持基础上下文精简,也能让同一个 Agent 完成多种工作。
在本次执行中,研究材料收集完成后,Agent 会加载制品构建 Skill,用它决定最终页面怎样生成。但生成页面不仅需要语言模型推理,还需要一个能够实际执行代码、创建文件的地方。
沙箱是实际执行发生的地方
模型可以生成代码,却不应该在承载 Agent 的同一进程里直接运行代码。
沙箱为执行提供了隔离环境。
对于研究 Agent,Skill 会在沙箱里处理搜索结果、创建文件、执行代码,并组装最终交互页面。
这种隔离对上下文管理同样有帮助。
假设 Exa 返回了一份非常大的搜索结果。最直接的办法,是把它完整贴回对话,让模型在之后每一步都继续携带几千甚至几万 Token。
短期内可行,但扩展性很差。
更好的办法,是把重数据移到其他位置。Agent 可以在沙箱中处理原始结果,把大段输出写进文件,只向模型返回简短摘要或文件引用。
信息仍然存在,需要时可以再次读取,但它不会在之后每一次模型请求中重复占用空间。
一旦 Agent 开始并行研究多个问题,上下文就会成为主要限制。
长时间运行的 Agent 从哪里开始出问题:上下文
短任务容易理解,因为历史信息不多。
长任务会在每个步骤继续积累内容:模型响应、工具输出、搜索结果、Skill 指令、文件、先前决策,以及其他 Agent 返回的结果。
最终系统必须回答一个困难问题:
模型此刻真正需要看到什么?
这里包含两类上下文。
第一类是我们主动交给模型的内容:System Prompt、工具定义、Skill 指令、文件与任务状态。
第二类是运行过程中自然产生的内容:搜索结果、中间输出、旧消息、工具响应,以及早期步骤里的推理。
第二类会持续增长。
TrueForge 使用多种技术,避免所有信息都堆积在父模型上下文中。
渐进式披露(Progressive disclosure)
工具与 Skills 不必在使用前完整加载。Agent 可以先发现能力,需要时再取回详细定义。
Code Mode
大型工具结果可以用代码处理,而不是原样发送给模型。如果搜索返回几百条记录,Agent 可以先过滤或转换,只把有用结果放进上下文。
大型结果卸载(Large-result offloading)
结果过大时,可以写入沙箱文件。模型只接收预览或引用,不需要在剩余任务中一直携带完整数据。
压缩(Compaction)
即使使用以上方法,对话历史仍会增长。当上下文超过配置阈值,系统可以把较早的会话总结成更小的表示。Agent 会失去部分早期逐字细节,但能保留继续任务所需的状态。
四项技术的目标相同:保存推理必需的信息,而不是让模型在每个步骤重新阅读全部执行历史。
还有一种阻止上下文增长的办法:从一开始就不要把工作放进父上下文。
子 Agent 隔离并行研究
网页研究 Agent 就是很好的例子。
假设用户要求比较三种不同的开发者工具。
最简单的实现,是先研究工具一,再研究工具二,最后研究工具三,全部放在同一个 Agent 上下文里。
研究结束时,父会话中会包含三条分支的每一次搜索、每一篇网页、每个工具响应和所有中间笔记。
另一种办法,是让 Harness 为每个对象创建独立子 Agent。
每个子 Agent 拥有自己的上下文,独立搜索网页、阅读材料并得出结论,最后只向父 Agent 返回一份精简结论。
父 Agent 不需要看到完整研究过程,只需要最终结果。
执行结构可以变成:
父 Agent → 子 Agent A 研究工具 A → 子 Agent B 研究工具 B → 子 Agent C 研究工具 C
每个子 Agent 在隔离上下文中工作,父 Agent 收集结果后完成最终比较。
在这个研究 Agent 中,Dynamic sub-agents 就支持这种扇出。
它带来两个优势:工作能够并行;父上下文只接收结论,不会被完整研究历史撑大。
但子 Agent 并不一定更好。
如果任务完全可以放进一个上下文窗口,把它拆成多个独立 Agent 只会增加编排成本和失败点。只有当工作真正可以并行,或子任务会给主上下文倾倒过多信息时,子 Agent 才更有价值。
审批门禁应位于推理与行动之间
到目前为止,研究 Agent 只读取信息,并在沙箱内部创建文件,没有危险的外部操作。
但如果 Exa 被替换成能够合并 GitHub PR、修改基础设施、发送 Slack 消息或更新生产数据库的工具呢?
普通执行循环是:
模型决定调用工具 → Harness 执行工具
对于敏感操作,这给了模型过多的自动权限。
Harness 需要增加一种状态:
模型决定调用工具 → Harness 暂停 → 人工审核 → Harness 继续或停止
TrueForge 可以根据工具附带的元数据强制执行审批检查点。如果工具被标记为需要批准,运行会在真正调用前停止,向用户展示准确的工具与参数。
这和在 System Prompt 中写一句“删除任何东西前先问我”并不相同。
Prompt 仍然只是模型应该遵守的一条指令。
审批门禁则是运行时规则。
模型无法通过推理绕过它,因为检查点解决之前,Harness 本身就拒绝执行工具。
研究 Agent 的外部工具都是只读的,因此不会触发门禁。如果接入一个可写的 GitHub 工具,同一个循环就可以在真正创建或修改内容前立即暂停。
运行应该能够脱离客户端继续存在
只有当 Agent 开始运行几分钟甚至更久时,最后一个问题才会变得明显。
如果用户关掉浏览器,会发生什么?
普通聊天请求通常结束得足够快,可以把客户端连接与模型响应视为同一次交互。长时间运行的 Agent 不能依赖这一假设。
执行必须独立于正在观看它的 UI。
TrueForge 会把会话步骤存储为有序事件流。随着模型推理、工具执行、子 Agent 工作、审批发生和结果返回,这些事件都会成为持久会话的一部分。
因此浏览器断开后,任务不会随之消失。
服务器可以继续执行。客户端重新连接后,可以恢复事件流,或者读取断开期间错过的事件。
同一种设计还提供了可重放的运行轨迹。
如果研究 Agent 给出了一份错误答案,可以回头检查它怎样得出结论:做过哪次搜索、哪个工具返回异常数据、子 Agent 得出什么结论,以及父 Agent 如何处理这些结果。
Agent 运行时间越长,这一点越重要。最终响应告诉你发生了什么,事件流则告诉你为什么会发生。
一次完整运行的全貌
现在可以把整条执行链放在一起。
用户发送研究问题,Harness 开始一个 Turn。
模型决定先做什么。它发现可用工具,通过 MCP 调用 Exa。搜索结果返回,但大型输出不必永久留在主模型上下文。
如果问题能够拆成独立分支,Harness 会创建子 Agent。每个子 Agent 在隔离上下文中完成自己的研究,再把精简结果交给父 Agent。
当 Agent 需要把研究整理成最终制品时,它加载相应 Skill。代码在沙箱中运行,Agent 可以在那里处理数据、创建文件,而不必直接在服务器环境中执行。
随着会话变长,未使用的工具定义保持未加载状态,大型结果被卸载,较早的对话历史可以压缩。
如果 Agent 最终请求敏感写操作,运行时可以在执行之前暂停并等待批准。
整个过程中,每个步骤都会持久化为事件。因此即使客户端断开,任务仍可继续,之后也能检查完整执行轨迹。
这就是简单的“模型—工具循环”如何变成一个长时间运行的 Agent。
模型仍然负责推理,但大部分可靠性工作发生在模型周围:决定模型看到什么、代码在哪里运行、并行工作如何隔离、执行何时暂停,以及会话能否存活到任务结束。
这也是为什么 Agent 从短演示走向真实长任务后,Harness 层会越来越重要。
TrueForge 把这些运行时组件封装成一套开源、厂商中立的 Harness,团队无需在每个 Agent 周围重复构建同样的基础设施。网页研究 Agent 只是一个例子;同一执行模型也适用于代码库入门 Agent、依赖审计器、事故分诊机器人和其他长时间工作流。
变化的是 Agent 所连接的模型、工具和 Skills。
不变的是执行循环、上下文管理、沙箱、审批检查点与持久会话。
这篇示例中的研究 Agent 是一个小型只读 Agent,但底层 Harness 同样能支撑更大的工作流。你可以为它连接不同的工具、Skills 与权限,将相同架构用于代码库入门、依赖审计或事故分诊。
本教程中的 Agent 配置保存在 TrueForge 中。作者还提供了配套 GitHub 仓库,其中包含 TypeScript Driver 与设置说明,可以从代码调用保存好的 Agent,并复现同一工作流。
TrueForge 开源仓库:https://github.com/truefoundry/trueforge
感谢 TrueFoundry 对本文的支持。