Agent Harness 工程(译)
Agent Harness 工程(译)
Agent Harness 工程(译) Agent Harness 工程(译) Modified May 12 Harness 工程的思路,恰恰反对这种默认反应。大多数失败其实都具有相当高的可解释性。如果智能体无视了某个约定,就把它写进 AGENTS.md。 如果它执行了破坏性命令,就写一个钩子把它拦住。如果它在一个 40 步任务中途迷路了,就把架构拆成规划器和执行器。如果它总是在收尾时交出一份坏掉的代码,就把类型检查的回压信号接进那条循环里。 正如 HumanLayer 所说:“这不是模型问题,而是配置问题。” 看看性能基准就知道:同一个领先模型,跑在一个现成框架里时,分数往往会比它跑在一个定制化、高度调优的 Harness 里低得多。只要把模型放进一个拥有更好代码库工具、更紧提示词和更强回压的环境,它就能展现出原始配置根本没激活出来的能力。 今天模型“理论上能做到什么”和你“实际看到它做到什么”之间的差距,在很大程度上就是 Harness 的差距。 棘轮效应:每一次错误都要变成一条规则 Harness 工程里最重要的习惯,就是把智能体的错误视为永久性信号,而不是那种重试一下就忘掉的一次性偶发问题。 如果一个智能体提交了一个 PR,里面居然把测试注释掉了,结果还被误合并了,这就是一个输入信号。下一版 AGENTS.md 里就必须明确写上:“绝不能注释掉测试;要么删除,要么修好。” 下一版 pre commit 钩子应该自动检查 diff 里是否出现 .skip(。审查子智能体也必须更新成:一旦发现被注释掉的测试,就直接拦截。 约束只应该在你观察到真实失败之后才被加入,只有在模型确实足够强、已经不再需要时才移除。一个优秀系统提示词里的每一条规则,都应该能追溯到一次具体、历史上真的发生过的失败。 也正因为如此,Harness 工程是一种方法论,而不是一个一键套用的框架。针对某个特定代码库的正确 Harness,完全是由它独有的失败历史塑造出来的。 从目标行为反推设计 设计 Harness 最有效的方式,是先从你想要的行为出发,再去构建那个能交付这种行为的组件:我们想要什么行为 → 为了实现它,应该怎样设计 Harness。 Harness 里的每一个组件,都必须承担一个明确的职责。如果你说不清某个组件具体是为了交付哪种行为而存在,它就应该被删掉。 文件系统与 Git:持久状态 文件系统是基础设施中的基础。模型只能处理能塞进上下文窗口的内容。文件系统给智能体提供了一个可读写的数据工作区、一个可以卸载中间结果的地方,以及一个让多个智能体协作的共享表面。 再加上 Git,就等于自动获得了版本控制能力。智能体可以追踪进度、为实验开分支、回滚错误。 Bash 与代码执行:通用工具能力 大多数智能体运行在 ReAct 循环上:推理、通过工具调用采取行动、观察结果、然后重复。 与其为每一个可能动作都预先造好一个工具,不如直接给智能体 Bash 权限,让它在需要时自己搭出所需工具。 智能体通常非常擅长 shell 命令,所以 Bash 和代码执行天然就会成为默认的问题求解策略。 沙箱与默认工具集 只有在 Bash 能安全运行时,它才真正有用。沙箱为智能体提供了一个隔离环境,让它能运行代码、检查文件、验证结果,同时不影响宿主机。 一个好的沙箱还应该带有强默认配置:预装语言运行时、测试 CLI 和无头浏览器,让智能体能看到自己做出来的结果,并闭合“自我验证”这条循环。 记忆与搜索:持续学习 模型除了训练权重和当前上下文之外,并没有其他知识。Harness 会用记忆文件(例如 AGENTS.md)来弥补这个缺口,把相关知识注入到每一次会话中。 至于像库版本变更、实时数据这样的动态信息,网页搜索和 MCP 工具则会被直接内建进 Harness。 对抗上下文衰减 随着上下文窗口被不断填满,模型的推理质量会持续下降。Harness 通常通过三种主要手段来管理这种稀缺性: • 上下文压缩: 智能总结并卸载较旧上下文,防止 API 报错。 • 工具调用卸载: 把巨大的工具输出(例如 2,000 行日志)存进文件系统,只把必要的开头和结尾留在上下文里。 • 渐进式披露: 只有当任务确实需要时,才暴露相应指令和工具,而不是一上来就把所有东西全塞进去。 长时任务执行 长时自主工作最容易出现的两个问题,是过早停止,以及糟糕的任务拆解。Harness 会通过结构设计来对抗这些问题: • 循环守卫: 当模型试图退出时拦截它,并在新的上下文窗口里强制它继续围绕完成目标推进。 • 规划机制: 强制模型先把目标拆成逐步执行的计划文件,并在每一步之后通过自验证钩子检查结果。 • 职责拆分: 把“生成”和“评估”分配给不同智能体,避免模型在给自己打分时天然带有的正向偏置。 钩子是你的强制执行层 钩子负责弥补“请求一个动作”和“真正强制它发生”之间的距离。它们会在特定生命周期触发:工具调用前、文件编辑后,或者提交前。钩子可以阻止破坏性命令、自动格式化代码以节省 token,以及运行测试套件。 理想状态是:成功时保持安静,失败时提供足够详细的反馈。 如果类型检查通过了,智能体什么都不用听到;如果失败了,错误就要被直接注入回循环里,让它自己继续修正。 规则手册与工具选择 仓库根目录下那份朴素的 Markdown 文件,依然是杠杆最高的配置点。但它必须被当成 飞行员检查清单,而不是风格指南。 保持简短,并确保每一条规则都是被历史错误“挣”出来的。 同样的纪律也适用于工具。十个高度聚焦的工具,一定会胜过五十个功能重叠的工具。 更进一步说,由于工具描述本身会进入提示词,那些恶意或粗糙的外部集成(比如未经验证的 MCP 服务)甚至可以在智能体开始工作前,就把坏提示词注入进去。 生产环境里真正长什么样 我目前见过的、最清楚展示成熟 Harness 的公开案例,是 Fareed Khan 对 Claude Code 架构 的一份拆解(估计版)。 前一节里的几乎每个概念,都能在这张图里找到对应的命名组件。图中的 Context injection 是知识层。Loop state 存在 memory store 和 worktree isolator 里。破坏性操作的钩子则被挂在 permission gate 后面。Subagent context firewalls 构成了整个多智能体层。Tool dispatch registry 是 MCP 服务和 Bash 接入的地方。Claude Code 的演进,至少有一半是在演进 Harness,而不仅仅是在演进底层模型。 Harness 不会消失,只会转移位置 随着模型能力提升,对 Harness 的需求并不会消失,只会发生迁移。 人们很容易以为模型变强之后,脚手架就会过时。例如,最近一轮模型升级,大幅降低了人们对“上下文焦虑”类缓解机制的需求。但地板抬高的同时,天花板也会抬高。以前根本做不到的任务,现在进入了可达范围,而这又会带来全新的失败模式。 Harness 里的每一个组件,本质上都编码了一个假设:模型凭自己还做不到这件事。当模型能力变强之后,过时的脚手架应该被拆掉,而新的脚手架则必须被造出来,用来抵达下一层任务边界。 那训练闭环呢? Harness 设计与模型训练之间,其实存在一条活跃的反馈循环。 今天很多模型在后训练时,已经把特定 Harness 放进训练闭环中了,这会带来一定程度的过拟合。模型会在 Harness 设计者重点优化的那批动作上,变得特别强,比如文件系统操作、Bash 和子智能体调度。 这也说明,Harness 是一个活系统,而不是一个静态配置文件;同时也证明了,所谓“最佳 Harness”,本质上就是那个专门针对你自己的任务和工作流做过优化的 Harness。 Harness 即服务(HaaS) 行业正在从“构建在 LLM API 之上”(只拿补全能力)转向“构建在 Harness API 之上”(直接拿运行时)。现在的 SDK,通常已经开箱提供循环、工具、上下文管理、钩子和沙箱。 与其从零搭编排层,现代默认做法正在变成:选择一个 Harness 框架,配置好它的几大核心支柱,然后只专注在领域相关的提示词和工具设计上。 这也是为什么问题排查会变得可扩展:你调优的是一个已经良好分层的配置表面,而不是每次都重新发明整套智能体架构。 未来会怎样发展? 如果你观察今天最强的编程智能体,你会发现它们彼此之间看起来,比它们各自的底层模型更像。 模型不同,但 Harness 模式正在收敛。整个行业正在快速识别那些真正承重的脚手架,它们负责把“生成文本”变成“可交付软件”。 接下来最值得兴奋的开放问题,已经开始超越单智能体:如何并行编排多个智能体,如何让智能体分析自己的执行轨迹并修复 Harness 级别的失败,以及如何构建能够按需即时组装工具的环境。 最终,这会进入这样一个阶段:Harness 不再只是静态配置文件,而会越来越像编译器。 如果你正在寻找一个优秀的智能体 Harness 框架, @FredKSchott 写了 Flue 。它很扎实,而且据说灵感就来自这篇文章的更早版本! Claude Code 架构 @FredKSchott Flue Harness 工程的思路,恰恰反对这种默认反应。大多数失败其实都具有相当高的可解释性。如果智能体无视了某个约定,就把它写进 AGENTS.md。 如果它执行了破坏性命令,就写一个钩子把它拦住。如果它在一个 40 步任务中途迷路了,就把架构拆成规划器和执行器。如果它总是在收尾时交出一份坏掉的代码,就把类型检查的回压信号接进那条循环里。 正如 HumanLayer 所说:“这不是模型问题,而是配置问题。” 看看性能基准就知道:同一个领先模型,跑在一个现成框架里时,分数往往会比它跑在一个定制化、高度调优的 Harness 里低得多。只要把模型放进一个拥有更好代码库工具、更紧提示词和更强回压的环境,它就能展现出原始配置根本没激活出来的能力。 今天模型“理论上能做到什么”和你“实际看到它做到什么”之间的差距,在很大程度上就是 Harness 的差距。 棘轮效应:每一次错误都要变成一条规则 Harness 工程里最重要的习惯,就是把智能体的错误视为永久性信号,而不是那种重试一下就忘掉的一次性偶发问题。 如果一个智能体提交了一个 PR,里面居然把测试注释掉了,结果还被误合并了,这就是一个输入信号。下一版 AGENTS.md 里就必须明确写上:“绝不能注释掉测试;要么删除,要么修好。” 下一版 pre commit 钩子应该自动检查 diff 里是否出现 .skip(。审查子智能体也必须更新成:一旦发现被注释掉的测试,就直接拦截。 约束只应该在你观察到真实失败之后才被加入,只有在模型确实足够强、已经不再需要时才移除。一个优秀系统提示词里的每一条规则,都应该能追溯到一次具体、历史上真的发生过的失败。 也正因为如此,Harness 工程是一种方法论,而不是一个一键套用的框架。针对某个特定代码库的正确 Harness,完全是由它独有的失败历史塑造出来的。 从目标行为反推设计 设计 Harness 最有效的方式,是先从你想要的行为出发,再去构建那个能交付这种行为的组件:我们想要什么行为 → 为了实现它,应该怎样设计 Harness。 Harness 里的每一个组件,都必须承担一个明确的职责。如果你说不清某个组件具体是为了交付哪种行为而存在,它就应该被删掉。 文件系统与 Git:持久状态 文件系统是基础设施中的基础。模型只能处理能塞进上下文窗口的内容。文件系统给智能体提供了一个可读写的数据工作区、一个可以卸载中间结果的地方,以及一个让多个智能体协作的共享表面。 再加上 Git,就等于自动获得了版本控制能力。智能体可以追踪进度、为实验开分支、回滚错误。 Bash 与代码执行:通用工具能力 大多数智能体运行在 ReAct 循环上:推理、通过工具调用采取行动、观察结果、然后重复。 与其为每一个可能动作都预先造好一个工具,不如直接给智能体 Bash 权限,让它在需要时自己搭出所需工具。 智能体通常非常擅长 shell 命令,所以 Bash 和代码执行天然就会成为默认的问题求解策略。 沙箱与默认工具集 只有在 Bash 能安全运行时,它才真正有用。沙箱为智能体提供了一个隔离环境,让它能运行代码、检查文件、验证结果,同时不影响宿主机。 一个好的沙箱还应该带有强默认配置:预装语言运行时、测试 CLI 和无头浏览器,让智能体能看到自己做出来的结果,并闭合“自我验证”这条循环。 记忆与搜索:持续学习 模型除了训练权重和当前上下文之外,并没有其他知识。Harness 会用记忆文件(例如 AGENTS.md)来弥补这个缺口,把相关知识注入到每一次会话中。 至于像库版本变更、实时数据这样的动态信息,网页搜索和 MCP 工具则会被直接内建进 Harness。 对抗上下文衰减 随着上下文窗口被不断填满,模型的推理质量会持续下降。Harness 通常通过三种主要手段来管理这种稀缺性: • 上下文压缩: 智能总结并卸载较旧上下文,防止 API 报错。 • 工具调用卸载: 把巨大的工具输出(例如 2,000 行日志)存进文件系统,只把必要的开头和结尾留在上下文里。 • 渐进式披露: 只有当任务确实需要时,才暴露相应指令和工具,而不是一上来就把所有东西全塞进去。 长时任务执行 长时自主工作最容易出现的两个问题,是过早停止,以及糟糕的任务拆解。Harness 会通过结构设计来对抗这些问题: • 循环守卫: 当模型试图退出时拦截它,并在新的上下文窗口里强制它继续围绕完成目标推进。 • 规划机制: 强制模型先把目标拆成逐步执行的计划文件,并在每一步之后通过自验证钩子检查结果。 • 职责拆分: 把“生成”和“评估”分配给不同智能体,避免模型在给自己打分时天然带有的正向偏置。 钩子是你的强制执行层 钩子负责弥补“请求一个动作”和“真正强制它发生”之间的距离。它们会在特定生命周期触发:工具调用前、文件编辑后,或者提交前。钩子可以阻止破坏性命令、自动格式化代码以节省 token,以及运行测试套件。 理想状态是:成功时保持安静,失败时提供足够详细的反馈。 如果类型检查通过了,智能体什么都不用听到;如果失败了,错误就要被直接注入回循环里,让它自己继续修正。 规则手册与工具选择 仓库根目录下那份朴素的 Markdown 文件,依然是杠杆最高的配置点。但它必须被当成 飞行员检查清单,而不是风格指南。 保持简短,并确保每一条规则都是被历史错误“挣”出来的。 同样的纪律也适用于工具。十个高度聚焦的工具,一定会胜过五十个功能重叠的工具。 更进一步说,由于工具描述本身会进入提示词,那些恶意或粗糙的外部集成(比如未经验证的 MCP 服务)甚至可以在智能体开始工作前,就把坏提示词注入进去。 生产环境里真正长什么样 我目前见过的、最清楚展示成熟 Harness 的公开案例,是 Fareed Khan 对 Claude Code 架构 的一份拆解(估计版)。 Claude Code 架构 前一节里的几乎每个概念,都能在这张图里找到对应的命名组件。图中的 Context injection 是知识层。Loop state 存在 memory store 和 worktree isolator 里。破坏性操作的钩子则被挂在 permission gate 后面。Subagent context firewalls 构成了整个多智能体层。Tool dispatch registry 是 MCP 服务和 Bash 接入的地方。Claude Code 的演进,至少有一半是在演进 Harness,而不仅仅是在演进底层模型。 Harness 不会消失,只会转移位置 随着模型能力提升,对 Harness 的需求并不会消失,只会发生迁移。 人们很容易以为模型变强之后,脚手架就会过时。例如,最近一轮模型升级,大幅降低了人们对“上下文焦虑”类缓解机制的需求。但地板抬高的同时,天花板也会抬高。以前根本做不到的任务,现在进入了可达范围,而这又会带来全新的失败模式。 Harness 里的每一个组件,本质上都编码了一个假设:模型凭自己还做不到这件事。当模型能力变强之后,过时的脚手架应该被拆掉,而新的脚手架则必须被造出来,用来抵达下一层任务边界。 那训练闭环呢? Harness 设计与模型训练之间,其实存在一条活跃的反馈循环。 今天很多模型在后训练时,已经把特定 Harness 放进训练闭环中了,这会带来一定程度的过拟合。模型会在 Harness 设计者重点优化的那批动作上,变得特别强,比如文件系统操作、Bash 和子智能体调度。 这也说明,Harness 是一个活系统,而不是一个静态配置文件;同时也证明了,所谓“最佳 Harness”,本质上就是那个专门针对你自己的任务和工作流做过优化的 Harness。 Harness 即服务(HaaS) 行业正在从“构建在 LLM API 之上”(只拿补全能力)转向“构建在 Harness API 之上”(直接拿运行时)。现在的 SDK,通常已经开箱提供循环、工具、上下文管理、钩子和沙箱。 与其从零搭编排层,现代默认做法正在变成:选择一个 Harness 框架,配置好它的几大核心支柱,然后只专注在领域相关的提示词和工具设计上。 这也是为什么问题排查会变得可扩展:你调优的是一个已经良好分层的配置表面,而不是每次都重新发明整套智能体架构。 未来会怎样发展? 如果你观察今天最强的编程智能体,你会发现它们彼此之间看起来,比它们各自的底层模型更像。 模型不同,但 Harness 模式正在收敛。整个行业正在快速识别那些真正承重的脚手架,它们负责把“生成文本”变成“可交付软件”。 接下来最值得兴奋的开放问题,已经开始超越单智能体:如何并行编排多个智能体,如何让智能体分析自己的执行轨迹并修复 Harness 级别的失败,以及如何构建能够按需即时组装工具的环境。 最终,这会进入这样一个阶段:Harness 不再只是静态配置文件,而会越来越像编译器。 如果你正在寻找一个优秀的智能体 Harness 框架, @FredKSchott 写了 Flue 。它很扎实,而且据说灵感就来自这篇文章的更早版本! @FredKSchott Flue 原文链接:https://x.com/addyosmani/status/2053231239721885918 一个编程智能体,是“模型”加上围绕它构建的一切。Harness 工程把这套脚手架视为一个持续演化的产物,每次智能体犯错,都会再收紧一层。 简单说:每当一个智能体失败,你就为它设计一个永久性的解决方案,让它以后不再犯完全相同的错误。 过去两年,整个行业一直在争论模型本身:谁最聪明、谁写 React 最干净、谁幻觉最少。这样的讨论当然重要,但它忽略了系统的另一半。 模型只是一个正在运行的智能体的输入之一。剩下的部分,就是 Harness:围绕模型包裹起来、让它真正能完成任务的提示词、工具、上下文策略、钩子、沙箱、子智能体、反馈回路和恢复路径。 一个普通模型配上优秀 Harness,往往能稳定胜过一个优秀模型配上糟糕 Harness。 越来越多真正有意思的工程工作,已经不再是选哪个模型,而是设计模型周围这套脚手架。 这套方法论现在已经有了名字。@Vtrivedy10 提出了 harness engineering 这个术语,并对 Harness 到底是什么、每个组成部分为什么存在,给出了一种非常清晰的拆解。行业里的其他声音也在往相似方向汇聚,比如 @dexhorthy 持续追踪这些正在浮现的模式,HumanLayer 把智能体失败理解为配置层面的“能力问题”,Anthropic 的工程团队不断发布关于长时运行应用设计的指南,而 Birgitta Böckeler 则在探索用户侧的实际体验。 @Vtrivedy10 @dexhorthy 这篇文章就是把这些线索整理到一起。 Harness 到底是什么? Trivedy 的核心定义已经解释了大部分问题: 智能体 = 模型 + Harness。只要你不是模型本身,你就是 Harness。 Harness 包含所有不属于模型本体的代码、配置和执行逻辑。一个裸模型不是智能体。只有当 Harness 为它提供状态、工具执行、反馈回路和可执行的约束时,它才真正成为智能体。 具体来说,一个 Harness 通常包括: • 系统提示词、CLAUDE.md、AGENTS.md、技能文件,以及subagent指令。 • 工具、Skills、MCP 服务,以及它们的技术描述。 • 配套基础设施,例如文件系统、沙箱和无头浏览器。 • 用于拉起子智能体、处理交接和路由模型的编排逻辑。 • 用于确定性执行的钩子和中间层,比如 lint 检查或上下文压缩。 • 用于日志、追踪、成本与延迟计量的可观测性工具。 从本质上说,智能体是一个通过工具循环去达成目标的系统。真正的能力,在于你如何设计这些工具,以及如何设计那条循环。 虽然这看起来是一个巨大的系统表面,但它是 你的 表面,而不是模型提供方的表面。Claude Code、Cursor、Codex、Aider 和 Cline,本质上都是 Harness。底层模型甚至可能完全一样,但你最终体验到的行为,大多数时候是由 Harness 决定的。 重新审视“skill Issue” 当智能体做出一些非常离谱的事时,工程师很容易第一反应就是怪模型,最后把问题归档成“等下一个版本模型出来再解决”。