2026 年 2 月 5 日,Mitchell Hashimoto 写了一篇长文,回顾自己从怀疑 AI 到天天用它写代码的过程。他是 Terraform 和 Ghostty 的作者,这篇文章按阶段写,第五个阶段叫 Engineer the Harness。
那一节的意思很简单。智能体犯了错,别只在对话里纠正,花点时间做个东西,让它以后再也不犯这个错。他提到两种做法。简单的问题,比如智能体总跑错命令、总找错 API,就去改 AGENTS.md,补一句说明。复杂一些的,就写成真正的工具,比如截图脚本、只跑相关测试的脚本,再在 AGENTS.md 里告诉智能体这些工具存在。
六天后,OpenAI 发了一篇《Harness engineering》,讲一个内部实验。按 InfoQ 的报道,一个小团队花五个月做出一个内部测试版产品,大约一百万行代码,没有一行是人手写的。人负责设计环境、说明意图、搭反馈回路,代码全部由 Codex 生成。这两篇文章前后脚出来,harness 这个词就流行开了。
harness 原意是马具,套在马身上,让马的力气变成拉车的力。用在这里,指套在模型外面、让它能干活的那套系统。Claude Code 的官方文档就这么称呼自己,模型负责推理,工具负责行动,Claude Code 是包在模型外面、提供工具并管理上下文的那一层。Codex、Cursor、Gemini CLI 也都是 harness。
为什么等不来更好的模型
智能体犯错,很多人的第一反应是等下一代模型。可很多错误跟能力关系不大,是信息不够。它不知道团队约定字段用下划线命名,不知道某个目录是生成的、不能手改,不知道跑测试前要先起 Docker。模型再聪明也猜不到这些。
项目自己也一直在变,约定会改,依赖会升级,每周都可能冒出新坑。与其等模型适应,不如让环境主动告诉它,或者干脆让它做错时立刻被拦下来。
harness 由哪几部分组成
拆开看,一个编程智能体的 harness 大致有六块。用现成产品时,前几块厂商已经做好,你主要是配置和扩展。
最核心的是执行循环。接到任务后调用模型,模型决定调哪个工具,执行工具,把结果交回模型,再调用模型,直到它认为做完了。循环本身不复杂,难在细节,工具失败怎么重试,上下文快满了怎么压缩,用户中途打断怎么办。这部分一般不用自己写,但后面所有扩展都是插在这个循环的某个环节上的。
然后是工具,决定智能体能做什么。基础的有读写文件、搜索代码、执行命令、访问网页,再往上可以通过 MCP 接数据库、工单、监控和设计稿。工具少一点、精一点更好用,每个工具的定义都占上下文,用途重叠的工具会让模型犹豫。工具的输出也要为模型设计,出错时返回能指导下一步的信息,别只给一个错误码。很多时候不用新写工具,模型对 git、gh、npm、curl 这些命令行工具很熟,告诉它可以用就行。
权限和沙箱决定它被允许做什么,这块最容易被忽略,出了事后果也最重。
| 层次 | 作用 | 举例 |
|---|---|---|
| 确认 | 危险操作前问人 | 执行不在白名单里的命令前弹窗 |
| 规则 | 按模式允许或禁止 | 允许 pnpm test,禁止 git push --force |
| 沙箱 | 从操作系统层面限制 | 只能写项目目录,网络只能访问指定域名 |
| 隔离环境 | 整个放到别处运行 | 容器、云端虚拟机、独立的 worktree |
提示词里写的不要做某事算不上安全措施。模型可能误解,可能被上下文里的恶意内容带偏,也可能在某个边界情况下觉得做一下没问题。要让它做不了,只能靠系统层面的限制。权限也不是越严越好,确认弹得太多,人会开始不看就点允许,比不弹还危险。常见的安全操作直接放行,危险操作直接禁止,只把真正需要判断的留给人。
第四块是项目知识,让智能体知道那些没写下来就没人知道的事。项目说明文件是最基础的形式。OpenAI 那个团队的做法是把仓库里一个结构化的 docs 目录当作智能体唯一的信息来源,里面放架构说明、执行计划和设计规格。讨论记录在聊天软件里、设计决策只在某人脑子里,对智能体来说就等于不存在。流程性的知识,比如发版步骤、评审清单,可以做成 Skill,平时只占一行描述,用到时才加载全文。
第五块是验证。智能体最常见的毛病之一是太早说做完了。Anthropic 在 2025 年 11 月的《Effective harnesses for long-running agents》里写过,Claude 经常跑完单元测试,或者用 curl 试一下接口,就把功能标成已完成,没有做端到端验证。后来他们让它用浏览器自动化工具像真实用户一样测试,结果明显变好。
验证手段从快到慢大概是类型检查和 lint,单元测试,构建,端到端测试,最后是让另一个智能体评审。OpenAI 那个团队还用自定义 linter 和结构测试来守架构约束,比如依赖只能按固定的层次方向走,智能体越界,检查就不过。错误信息写得越具体越好,只报违反了某条规则,智能体得猜半天;写清楚这里该改成调用哪个模块,它一次就能改对。
最后一块是钩子,在执行循环的固定时刻插入你自己的逻辑,比如工具调用前后、会话开始、模型准备结束回合的时候。钩子的好处是确定。提示词里写改完代码跑一下格式化,模型大部分时候会照做,偶尔会忘;写成每次编辑文件后自动运行格式化的钩子,就每次都会执行。在结束回合前检查测试有没有通过,没通过就让它继续,这个用法特别实用,相当于把完成的标准变成了一道关卡。
每个错误只犯一次,怎么落地
回到 Hashimoto 那条原则。它的好处是把 harness 工程变成了一个日常小习惯,不用先规划一个大工程。智能体每犯一次错,就问一句怎样让它下次不犯,再按错误的类型挑办法。
| 错误类型 | 例子 | 对应的改进 |
|---|---|---|
| 不知道某个事实 | 不知道本地测试要先起数据库 | 补进项目说明 |
| 不知道某个流程 | 发版时漏了更新 changelog | 做成 Skill |
| 违反架构约定 | 在组件里直接查数据库 | 写 lint 规则,报错里写明正确做法 |
| 引入了 bug | 改分页时弄坏了第一页 | 补测试 |
| 做了危险操作 | 想强制推送 | 加权限规则禁止 |
| 没验证就说做完了 | 没跑测试就说改好了 | 加结束前检查的钩子 |
| 拿不到需要的信息 | 看不到线上报错的详情 | 接入对应的 MCP 或命令行工具 |
能用一行说明解决的,就别写工具。越确定的手段越可靠,维护成本也越高,挑够用的最轻的那种。
harness 用久了也要清理。说明会越写越长,规则会过时,测试会变慢。Martin Fowler 网站上有篇评论把 OpenAI 那套做法归成三类,其中一类叫垃圾回收。做法是定期派智能体去查,文档有没有前后不一致,代码有没有违反约束。
这些改进对人一样有用。清楚的文档、明确的约定、带修复说明的 lint、完善的测试,新同事来了照样受益。以前大家总说以后再补,现在补上马上就能看到效果。
在现成的智能体上能做什么
用 Claude Code、Codex、Cursor 这类产品,骨架已经有了,能做的扩展大致是这些。
| 扩展方式 | 作用 | 文档 |
|---|---|---|
| 项目说明文件 | 放每次都要知道的事实 | Claude Code 记忆 |
| Skill | 按需加载的流程和知识 | Agent Skills 是怎么设计的 |
| MCP Server | 接入外部系统 | MCP 是怎么设计的 |
| 权限规则和沙箱 | 限制能做什么 | Claude Code 权限 |
| Hooks | 在固定时刻执行确定的逻辑 | Claude Code Hooks |
| 子智能体 | 隔离上下文,分工 | Claude Code 子智能体 |
| 项目自己的测试、lint、脚本 | 让智能体能自己检查 | 你的项目 |
最后一行最容易被忽略。产品提供的扩展是通用的。放到你的项目里,智能体表现好不好,很大程度上要看项目本身有没有像样的测试、lint 和脚本。
harness 做好以后,智能体在一次会话里就能比较可靠地完成任务。接下来的问题是每个任务还得有人来发起、有人来看结果,这是 Loop 工程要处理的。
参考资料