最近这半年,我一直在深度使用各种 AI Coding Agent——从 [[Claude Code]] 到 [[OpenAI Codex]],从 [[Cursor]] 到 [[Windsurf]]。在这个过程中,我越来越强烈地感受到一件事:决定一个 AI 编程工具好不好用的,早已不是背后的大模型有多聪明,而是包裹在模型外面那一层「运行时系统」做得有多好。这层系统,业界现在有了一个越来越明确的名字——Harness。
Mitchell Hashimoto([[Terraform]] 的创始人)在他那篇广为流传的博客里,把自己使用 AI 编程的演进过程总结为几个阶段,最后一个阶段就叫 Engineer the Harness。他的定义很直白:每当你发现 Agent 犯了一个错误,你就花时间去设计一个解决方案,让它永远不会再犯同样的错误。这个定义抓住了 Harness Engineering 最本质的精神——用确定性的工程手段,去驯服不确定的 AI 行为。
从 Prompt 到 Context 再到 Harness
要理解 Harness Engineering 为什么会在今天成为焦点,需要先回顾一下 AI 工程领域的三次范式迁移。
第一阶段是 Prompt Engineering(大约在 2022 到 2024 年)。那时候大家都在研究怎么写出最好的提示词,few-shot learning、chain-of-thought、角色扮演,各种技巧层出不穷。这就好比在写一封精心措辞的邮件,希望收件人能完美理解你的意图。那时候的核心信念是:只要 prompt 写得够好,模型就能给出好结果。
第二阶段是 Context Engineering( 2025 年左右)。大家逐渐意识到,光靠一条 prompt 是不够的。Andrej Karpathy 等人开始强调:重要的不只是你问了什么,而是你在提问的时候带上了哪些上下文。于是 RAG 检索增强生成、对话历史管理、动态文档注入这些技术成了热门话题。如果说 Prompt Engineering 是在写邮件,Context Engineering 就是在往邮件里附上所有相关文档。
第三阶段就是我们今天要聊的 Harness Engineering(2026 年)。它的抽象层次更高,直接涵盖了前两个阶段。关注点从「如何和模型对话」转移到了「如何为模型构建一个完整的运行环境」。用一个计算机类比来说:模型是 CPU,上下文窗口是 RAM,而 Harness 就是操作系统。操作系统负责内存管理、进程调度、设备驱动、安全权限——Harness 对 AI Agent 做的事情本质上是一样的。
这个演进并非偶然。有一个案例很能说明问题:某团队在一个编程 benchmark 上,使用完全相同的模型和 prompt,仅仅通过改进运行时环境,就把成功率从 42% 提升到了 78%。这证明了一个反直觉的事实——在很多场景下,制约 AI Agent 表现的瓶颈不在模型本身,而在它周围的基础设施。这也解释了为什么模型能力不如 Opus 的 GLM 在 Claude Code 中就能表现非常好。
Harness 到底是什么
Phil Schmid 在他的博文中给出了一个很精确的定义:Harness 是包裹 AI 模型的基础设施层,用于管理长时间运行的任务。它不是 Agent 本身,而是「管理 Agent 如何运作的软件系统,确保它保持可靠、高效和可调控」。
我觉得这个定义的关键词是「可调控」。AI 模型本质上是概率性的,它的行为有一定的随机性。而工程化的软件系统需要的是确定性和可预测性。Harness 的核心使命,就是在概率性的模型和确定性的工程需求之间架起一座桥。
具体来说,一个成熟的 Harness 通常包含以下几个层面的能力。
Harness 的核心组件
系统提示词的模块化组装
在早期的 AI 工具中,system prompt 往往是一个写死的大文本。但在成熟的 Harness 中,系统提示词是动态组装的。[[Claude Code]] 的做法就很有代表性:它有一个 Prompt Composition 引擎,会根据当前的工作目录、项目类型、用户配置、已安装的 Skill 等信息,按照优先级把不同的模块拼装成最终的系统提示词。
这意味着同一个模型,在不同的项目中、不同的任务阶段,看到的「指令集」是完全不同的。这种动态性是 Harness 相比静态 prompt 的一个本质性提升。
在实际使用中,CLAUDE.md 和 AGENTS.md 文件就是这种模块化提示词的具体体现。它们是仓库级别的配置文件,会被确定性地注入到 Agent 的系统提示词中。一个重要的经验是:这些文件要保持精简。HumanLayer 团队的实践是把 CLAUDE.md 控制在六十行以内。因为模型在长上下文中的表现会随着长度增加而下降,每一条无关的指令都是对「注意力预算」的浪费。
工具编排与约束
工具是 Agent 与外部世界交互的桥梁——读写文件、执行命令、搜索代码、调用 API,这些都需要通过工具来实现。但工具的管理远比看上去要复杂。
[[Vercel]] 团队有一个很有启发性的经验:他们把 Agent 的工具数量削减了 80%,结果不仅没有降低 Agent 的能力,反而减少了执行步骤、降低了 token 消耗、缩短了响应时间。这背后的逻辑是:过多的工具会给模型带来选择困难,它需要在每一步都从大量工具中挑选最合适的那个,这本身就是一种认知负担。
[[MCP]](Model Context Protocol)服务器是另一个需要审慎使用的工具扩展机制。MCP 让你可以为 Agent 接入各种外部能力,但每个 MCP 服务器都会往上下文中注入工具描述,如果你同时挂载了十几个 MCP 服务器,光工具描述就可能占据数千 token。一个务实的建议是:如果某个 MCP 服务器不是当前任务必需的,就关掉它。对于像 Docker、GitHub CLI 这类模型已经训练过的工具,直接用原生命令行反而比通过 MCP 效果更好。
Hook 机制:确定性的守门人
如果说系统提示词是「软约束」(模型可能遵守也可能忘记),那 Hook 就是「硬约束」。我之前写过一篇关于 [[Claude Code]] Hook 机制的详细文章,这里从 Harness Engineering 的角度再梳理一下它的定位。
Hook 的本质是在 Agent 生命周期的关键节点插入确定性的脚本执行。比如 PreToolUse 可以在 Agent 动手之前拦截危险操作(比如修改 .env 文件),PostToolUse 可以在 Agent 修改代码之后自动运行格式化和类型检查。关键在于:无论模型当时的「心情」如何,这些脚本一定会执行。
这种确定性对于工程化场景至关重要。我自己的一个典型用法是:配置一个 PostToolUse hook,让 Agent 每次修改完代码后自动运行 typecheck。如果检查通过,hook 静默成功;如果失败,错误信息会被注入回 Agent 的上下文,让它自动修复。整个过程对我来说是无感的,但它确保了 Agent 产出的代码始终符合类型系统的约束。
子 Agent:上下文的防火墙
当任务变得复杂,单个上下文窗口已经不够用的时候,子 Agent 就成了必需品。但子 Agent 的价值不仅仅在于并行执行,更在于上下文隔离。
HumanLayer 团队把子 Agent 比作「上下文防火墙」,我觉得这个比喻非常精准。主 Agent 在处理一个复杂任务的时候,可能需要同时搜索代码库、阅读文档、运行测试。如果把这些中间过程全部堆积在同一个上下文窗口里,很快就会超出模型的有效处理能力。子 Agent 让每个子任务在独立的上下文中执行,只把最终结果返回给主 Agent,这样主 Agent 的上下文始终保持干净和聚焦。
在 Claude Code 中,这体现为各种类型的 subagent——Explore agent 用于代码搜索、Plan agent 用于架构设计、专门的 frontend-developer 或 backend-architect agent 处理特定领域的任务。每个 subagent 都有自己独立的上下文窗口和工具集,互不干扰。
跨会话的状态接力
子 Agent 解决的是单次会话内的上下文管理问题,但还有一个更棘手的挑战:当任务复杂到需要跨越多个上下文窗口时,前后两个 session 的 Agent 之间如何有效地交接工作?
[[Anthropic]] 在最近发布的一篇关于长时间运行 Agent 的 Harness 设计文章中,详细拆解了这个问题。核心困境很直觉:每个新的上下文窗口启动时,Agent 对之前发生的一切毫无记忆。这就像一个轮班制的工程团队,每个新来的工程师对上一班的工作一无所知。即使有 compaction(上下文压缩)机制,也无法完美传递上一个 session 的完整意图和进度。
他们观察到两个典型的失败模式。第一个是 Agent 试图一口气完成所有功能,结果上下文耗尽时留下一堆半成品,下一个 session 的 Agent 不得不猜测之前的意图,花大量时间修复残局。第二个是后续 Agent 看到已有一些进展后,直接宣布任务完成——这种「过早宣布胜利」的倾向在长任务中非常常见。
他们的解决方案是一个两阶段架构。第一个 session 使用专门的 Initializer Agent,负责搭建整个项目的环境脚手架:生成一个包含所有功能点的结构化 JSON 文件(每个功能初始标记为 failing),创建进度日志文件 claude-progress.txt,编写启动脚本 init.sh,并提交初始 git commit。后续每个 session 则使用 Coding Agent,它在开始工作前先读取 git log 和进度文件了解现状,运行基础健康检查确认环境没有遗留问题,然后一次只推进一个功能,完成后 git commit 并更新进度记录,保证环境始终处于可交付状态。
这个模式的精妙之处在于它使用了开发者日常工作中最朴素的工具——git 历史和文本文件——来构建跨会话的「记忆」。进度文件和 git log 共同扮演了「交班记录」的角色,让每个新 Agent 能在几秒钟内了解项目全貌和下一步该做什么。这比依赖 compaction 来传递上下文可靠得多,因为文件是持久化的、结构化的、可以被精确读取的。本质上,这就是把优秀软件工程师的日常交接习惯编码进了 Harness。
权限模型与安全边界
一个好的 Harness 必须解决一个核心矛盾:Agent 需要足够的权限来完成工作,但又不能拥有无限的权限以至于造成破坏。
Claude Code 的权限系统就是一个很好的范例。它把操作分为不同的风险等级:读取文件几乎没有风险,编辑文件有中等风险,执行 shell 命令有较高风险,而像 git push 或删除文件这样的操作则需要特别谨慎。用户可以为不同的操作设置不同的审批策略——有些自动放行,有些需要逐次确认。
更重要的是,Harness 应该把安全检查内建到执行流程中,而不是依赖模型的「自觉性」。比如,通过 Hook 机制硬性拦截对敏感文件的修改,比在 system prompt 里写「不要修改 .env 文件」可靠得多。
约束悖论:限制越多,能力越强
Harness Engineering 中有一个反直觉的核心洞察,值得单独拿出来说:对 Agent 施加恰当的约束,反而能提升它的生产力。
这和人类的工作方式其实是相通的。一个没有任何限制的开放式任务(「帮我改善这个项目」),往往比一个有明确边界的具体任务(「重构这个函数,保持接口不变,确保现有测试全部通过」)更难完成。约束缩小了搜索空间,减少了 Agent 在无效路径上的探索。
Epsilla 的研究提到了一个很有意思的观点:Agent 天生无法准确评估自己的工作质量。这意味着如果你不给它外部的反馈信号——测试结果、类型检查、lint 报告——它就很容易在错误的方向上越走越远。Harness 的一个关键职责就是构建这种「反压机制」(back-pressure),在 Agent 每次行动之后给它一个客观的信号,告诉它「这步走对了」还是「需要回头」。
Stripe 内部的 Minions 系统是这种理念的一个极端案例。这个由自主 Agent 组成的系统每周合并超过一千三百个 PR,没有人工审核。它能做到这一点的关键在于一个叫 Blueprint 的编排系统,它把工作流拆分成确定性节点和 Agent 节点——确定性节点处理不需要 AI 判断的步骤(如运行测试、检查代码规范),Agent 节点只负责需要创造性思维的部分。这种混合架构让约束和自由各得其所。
Anthropic 的长时间 Agent 实践也提供了另一个生动的案例。他们发现,只给 Agent 一个宏大的目标(比如「构建一个 claude.ai 的克隆」),Agent 会试图一次性实现所有东西,结果往往是上下文耗尽时什么都没做完。而一旦引入结构化约束——用 JSON 文件明确列出每一个需要实现的功能点,并强制要求 Agent 每次只处理其中一个——完成质量和整体效率都显著提升了。他们甚至特意选择用 JSON 而非 Markdown 来存储功能列表,因为模型更不容易擅自修改或覆盖结构化的 JSON 内容。这种「数据格式本身就是约束」的思路,堪称 Harness Engineering 的一个缩影。
Anthropic 还发现了一个验证层面的约束盲区:Agent 在完成一个功能后,倾向于通过单元测试或 curl 命令快速验证一下就宣布完工,却忽略了端到端的用户体验。解决方案是给 Agent 配备浏览器自动化工具([[Puppeteer]] MCP),要求它像真实用户一样打开页面、点击按钮、填写表单来验证功能是否真正可用。提供这种端到端测试工具后,Agent 的 bug 检出率大幅提升,因为很多问题只有在整个应用实际运行时才会暴露出来。这再一次印证了约束悖论的核心:给 Agent 施加更严格的验证要求(必须用浏览器实测),反而让它产出了更高质量的代码。
可撕裂的 Harness
还有一个重要的设计原则需要注意——Harness 应该是「可撕裂的」(rippable)。
这是什么意思呢?AI 模型的能力在快速迭代。今天需要通过复杂管道才能实现的功能,明天可能在一个上下文窗口里就能搞定。如果你的 Harness 过度工程化,把大量业务逻辑硬编码在控制流中,那下一次模型升级可能会让你的整个系统变得不仅多余,而且碍事。
Manus 团队在六个月内重构了五次 Harness,LangChain 在一年内重新架构了三次研究 Agent。这些经历都指向同一个教训:不要过早地把太多「聪明」的逻辑塞进 Harness,保持模块化,让每一层都可以独立替换或移除。
这也是为什么 HumanLayer 团队强调「经验主义而非预防性设计」——只在 Agent 真正犯错之后才添加对应的约束,而不是预先安装一堆「以防万一」的配置。每一条规则都应该有一个对应的失败案例作为支撑。
我的实践体会
在过去几个月的使用中,我逐渐摸索出了一些关于 Harness 调优的个人经验。
第一个体会是:CLAUDE.md 文件是 ROI 最高的投入。花半小时写好这个文件,可以省下未来几十个小时的重复沟通。但关键是要写得精准——不是什么都往里塞,而是只放那些模型真的会搞错、且搞错了代价很大的规则。比如在我的知识库项目中,「标题不要用数字」「不要用 Markdown 加粗语法」「新文件默认放到 Draft 目录」这些规则就很有价值,因为它们违反了模型的默认习惯,如果不显式声明,模型几乎一定会犯错。
第二个体会是:Hook 应该只在失败时发声。一个好的 Hook 在成功时静默运行,只有在检测到问题时才把错误信息注入 Agent 的上下文。这样既不会浪费 Agent 的注意力,又能确保问题不会被忽略。
第三个体会是关于子 Agent 的使用场景。并不是所有任务都需要子 Agent。对于简单的、确定性高的搜索操作(比如 grep 一个函数名),直接在主 Agent 中执行反而更快。子 Agent 的价值在于那些需要大量中间推理、会产生大量中间输出的任务——比如深度代码探索、复杂的重构规划、多文件并行修改等。
第四个体会是要警惕 MCP 服务器的过度使用。我曾经同时启用接近十个 MCP 服务器,结果发现 Agent 的响应变慢了,而且有时候会选择错误的工具。后来我把使用频率低的服务器全部关掉,体验反而好了很多。这再次印证了那个原则:少即是多。
最后
Harness Engineering 代表着 AI 工程领域的一个重要转折——竞争的焦点正在从「谁的模型更强」转向「谁的运行时系统更好」。Claude Code 之所以被广泛认为是目前最好用的 AI 编程工具之一,不是因为 Claude 模型比其他模型强多少,而是因为它的 Harness 设计得足够好——权限模型、Hook 系统、子 Agent 编排、动态提示词组装,这些共同构成了一个让模型能力最大化发挥的运行环境。
对于我们这些每天使用 AI 编程工具的开发者来说,理解 Harness Engineering 的意义在于:我们不应该只是被动地使用工具,而应该主动地去「调校」工具。花时间去写好 CLAUDE.md、配置好 Hook、精心选择需要启用的 MCP 服务器、理解子 Agent 的使用场景——这些投入会以指数级的效率提升回报给你。
模型会持续进化,今天写的 prompt 明天可能就过时了。但 Harness Engineering 的核心思维——用确定性的工程手段去管理不确定的 AI 行为——这个原则会持续适用很长一段时间。
related
- [[Claude Code]]
- [[OpenAI Codex]]
- [[MCP]]
- [[Terraform]]
- [[Anthropic]]
- Effective harnesses for long-running agents