你让 AI 编码助手帮你写个函数,它几秒钟就能生成一段能跑的代码。但一个真正能上线的功能,从来不只是那一段代码。
它得知道项目里有哪些文件、找到需要更改的那一处、安全地调试命令行、跑完测试后,再告诉你结果。

这些事,模型自己一样都干不了。而负责把这些能力 "接" 到模型周围、并管住它的整套系统,就是 Harness。而设计该 Harness 的工作称为 Harness 工程。
这篇文章,我会把 Harness 拆成六层讲清楚,再用 Python 手把手带你写一个最小版本,让你直观看到它到底在做什么。
为什么光靠模型不够
语言模型本质上是个 "读文本、吐文本" 的函数。给它一段输入,它回一段输出。可一旦让它去完成真实任务,这个朴素的特性就会暴露出很多短板:
- 项目里有哪些文件?它不知道,除非你手动告诉它。
- 它没法自己运行测试、调用数据库、打开浏览器。
- 它不记得上一轮对话聊了什么。
- 活没干完,它也可能跟你自信地说 "搞定了"。
- 不加约束,它可能做出你完全预料不到的动作。
- 它一次能处理的上下文有限,太多反而变 “糊涂”。
Harness 的工作就是把这些坑一个个接住。
什么是 Harness
Harness 工程(Harness Engineering)是 2026 年兴起的 AI 工程化范式,核心公式为 Agent=Model+Harness。
模型负责 "想",Harness 负责 "做和验证",即把信息喂给模型、给它能用的工具、管住它的权限、保存它的进度、最后用测试替它验证结果。
根据系统的不同,Harness 通常包括说明与项目规则、上下文检索与记忆、终端/浏览器/数据库等工具、一个受控的执行环境、工作流步骤与审批,以及测试、评估和日志。
其实你天天都在用 Harness。Claude Code、OpenAI 的 Codex CLI 这些编码助手,本质都是 "一个模型+一套 Harness":文件访问、终端、AGENTS.md 项目说明、权限提示、测试运行……
哪怕两个产品用的同一个模型,因为搭配的 Harness 不同,表现也天差地别。
提示工程、上下文工程与 Harness 工程的区别
这三个词经常被混着提,但它们管的不是一回事。
提示工程(Prompt Engineering)管的是 "怎么说",核心问题是 "我该怎么提问?"。它能把"请审查这个功能,并说明安全风险" 这句话写得很漂亮,但它自己跑不了测试。
上下文工程(Context Engineering)管的是 "给模型看什么",核心问题是 "模型需要知道什么?"。它能准备好相关文件、编码规范、测试失败记录,但它没法自己行动或验证。
Harness 工程(Harness Engineering)管的是 "围绕模型的整个系统",核心问题是 "它必须看到什么、做什么、验证什么?"。它管理的是上下文、工具、权限、工作流、状态和检查。
打个比方更容易理解:提示工程是写指令,上下文工程是准备材料,Harness 工程是把 "人+材料+工具+验收标准" 整个工作台都搭起来。
提示可以叫模型去跑测试,但真正能跑测试、并把结果回报给模型的那套系统,得由 Harness 提供。
智能体循环
Harness 不是只调一次模型,而是跑一个智能体循环:构建上下文 → 让模型给出行动方案 → 执行这个动作并验证 → 重复操作。
当所有检查都通过,循环结束;当触达步数之类的上限,循环也会停下,此时智能体会报告它停在哪,或者向人求助。
关键在一点:每一轮循环后,上下文都会被重建,所以模型永远能看到最新结果。模型可以说 "我干完了",但 "是否通过检查",由 Harness 说了算。
Harness 的六层结构
具体怎么设计取决于场景,但大多数 Harness 都逃不开这六层:
1. 说明:定规则
说明要保持简洁明晰了,并提供指向架构、测试、安全和产品行为等相关文档的链接。
在代码库中,一段简短的 AGENTS.md 或 CLAUDE.md 是给智能体的 "入口指南",告诉它怎么浏览项目、详细文档在哪、要跑哪些检查。
2. 上下文:给正确的信息
上下文不是越多越好。常用用法是:
- 让智能体自己去搜索代码库,而不是把整个仓库预先加载进去;
- 仅保留测试日志中有用的那几行;
- 对话太长时,用摘要压缩旧历史记录;
- 把关键发现写进文件以便它下次重读;
- 需要时,派个子智能体干活,然后只把简短结论拿回来。
3. 工具与执行:让它能安全地 “动手”
Harness 决定工具跑在哪、能访问什么资源。比如让它在隔离工作区里更改文件,同时运行在生产系统之外,有外部影响的操作需要先过人工审批。
其中,工具名清晰、输入方式简单、报错信息可读,都使得工具更容易被正确使用。
4. 持久状态:让进度能跨会话保存
模型的上下文是临时的。Harness 可以把计划、决策、测试结果都保存下来,这样下次会话可以从可靠起点继续,而不是重复劳动或 “瞎猜”。
5. 编排:管工作流程的执行顺序
编排控制着智能体完成任务的工作流程,比如下一步做什么、失败要不要重试、何时停下或求助。
当任务划分清晰时,多个智能体可以协同工作,但增加智能体数量并不代表结果会自动改善。
多智能体不是越多越好,每个都要有明确角色、足够上下文,以及检查或合并工作的办法。
6. 验证与可观测:证明结果、看清故障
智能体的最后一句 "完成了" 不算数,得有测试、类型检查、构建结果或评估等当证据。比如工具调用历史、日志、trace 能定位它到底错在哪。
安全与成本限制
能跑命令、能读文件的智能体,也会因为动作太快而犯错。Harness 得限制错误造成的损失,把循环框在可控范围内。
安全上,你要把智能体当成 "不受信任的新员工":
- 沙箱:在隔离环境里跑操作,不碰生产系统和不需要的密钥。
- 最小权限:每个工具只给够用的权限,能只读就不给写权限。
- 审批关卡:删除数据、发消息、部署这类难撤销的操作,先过人审批。
- 提示注入:网页、评论、文件里的文字可能夹带指令想操纵它。应该把外部内容一律当数据,不用敏感工具去读不受信任的文本,并在风险操作前添加严格审批。
- 审计:每次工具调用都做记录,出故障时能查询。
成本上,让循环有边界:
- 步数与时间上限:在达到设定的步数或分钟数后停止,并报告进展情况。
- Token 预算:限制任务可以使用的上下文和输出量。
- 重试上限:对失败的操作进行几次重试后,进行升级处理,而不是无休止的重复执行。
- 模型分层:简单步骤使用又快又小的模型,复杂决策才上更强的模型。
假设智能体会犯错、会被误导,你按上面的规则设计权限和限制措施,可以让错误的代价范围可控、成本低。
迷你 Harness:架构与示例代码
下面,把上面的想法落实成代码。具体任务是:修复一个结账功能的 bug,折扣被重复应用了两次。要求智能体要修复好它,但不允许更改测试用例。
每一块代码正好对应六层结构中的一层:构建上下文对应说明、上下文,工具关卡对应工具与执行,验证对应验证,状态文件对应持久状态,步数限制对应编排、成本。
先建一个示例项目,里面埋好了 bug、一个测试文件、一份 AGENTS.md:
AGENTS_MD = """\
# Project rules
- Source code lives in checkout.py.
- Tests live in tests/ and are read-only. Never edit them.
- Run run_tests before you call finish.
"""
CHECKOUT_PY = """\
def apply_discount(price, percent):
return round(price * (1 - percent / 100), 2)
def checkout_total(items, discount_percent=0):
subtotal = sum(i["price"] * i["qty"] for i in items)
total = apply_discount(subtotal, discount_percent)
total = apply_discount(total, discount_percent) # BUG: applied twice
return total
"""
TEST_PY = """\
import unittest
from checkout import checkout_total
class CheckoutTests(unittest.TestCase):
def test_discount_applied_once(self):
items = [{"price": 50, "qty": 2}]
self.assertEqual(checkout_total(items, 10), 90.0)
"""再定义工具。模型需要知道自己能做什么。每个工具都有名称、描述和输入模式。
注意那个 finish 工具:模型用它表示 "任务完成",但 Harness 会在接受结果前先验证。
TOOL_SPECS = [
{"name": "list_files", "description": "List the project files.",
"input_schema": {"type": "object", "properties": {}}},
{"name": "read_file", "description": "Read one project file.",
"input_schema": {"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"]}},
{"name": "write_file", "description": "Replace a project file.",
"input_schema": {"type": "object",
"properties": {"path": {"type": "string"},
"content": {"type": "string"}},
"required": ["path", "content"]}},
{"name": "run_tests", "description": "Run the project's tests.",
"input_schema": {"type": "object", "properties": {}}},
{"name": "finish", "description": "Say done. The harness checks it first.",
"input_schema": {"type": "object",
"properties": {"summary": {"type": "string"}},
"required": ["summary"]}},
]接下来是 Harness 本体,我挑出其中最核心的三块:权限、工具、循环。
class Harness:
READ_ONLY = ("tests/", "AGENTS.md")
def resolve(self, rel, write=False):
path = (self.ws / rel).resolve()
if path != self.ws and self.ws not in path.parents:
raise PermissionDenied("path is outside the workspace")
name = path.relative_to(self.ws).as_posix()
if write and name.startswith(self.READ_ONLY):
raise PermissionDenied(f"{name} is read-only")
return path
def execute(self, name, args):
try:
if name == "run_tests":
return "ok", self.run_tests()[1]
if name in ("list_files", "read_file", "write_file"):
return "ok", getattr(self, name)(**args)
return "error", f"unknown tool: {name}"
except PermissionDenied as err:
return "blocked", f"DENIED: {err}"
except Exception as err:
return "error", f"ERROR: {err}"
def run(self, task):
system = ("You are a coding agent. Use the tools to inspect, "
"edit, and test. Call finish only when done.\n\n"
+ (self.ws / "AGENTS.md").read_text())
messages = [{"role": "user", "content": task}]
for step in range(1, self.max_steps + 1):
action = self.model.decide(system, messages, TOOL_SPECS)
name, args = action["name"], action["input"]
if name == "finish":
passed, output = self.run_tests()
if passed:
self.save_state(task, "done")
return "done"
result = "REJECTED: tests still fail.\n" + self.clip(output)
else:
outcome, result = self.execute(name, args)
result = self.clip(result)
messages.append({"role": "assistant",
"content": [{"type": "tool_use",
"id": action["id"],
"name": name,
"input": args}]})
messages.append({"role": "user",
"content": [{"type": "tool_result",
"tool_use_id": action["id"],
"content": result}]})
self.save_state(task, "stopped: step limit")
return "stopped: step limit"这段代码里,你能看到最小权限(路径锁在工作区内、tests/ 与 AGENTS.md 设置为只读)、验证(finish 前先跑测试)和步数限制。模型只需要一个带 decide 方法的对象。
离线演示用一个脚本模型回放固定动作,保证可重复运行:
class ScriptedModel:
def __init__(self, actions):
self.actions = actions
self.i = 0
def decide(self, system, messages, tools):
name, args = self.actions[min(self.i, len(self.actions) - 1)]
self.i += 1
return {"id": f"toolu_{self.i}", "name": name, "input": args}迷你 Harness 实战
代码跑起来,主要看两个场景。场景 1 是一次 "混乱但成功" 的运行:脚本模型先假装已完成、想删除测试用例、想偷读沙箱外的文件内容,最后才真正修复好。
[step 1] model -> list_files {}
[step 2] model -> read_file {"path": "checkout.py"}
[step 3] model -> finish {"summary": "Fixed."}
[step 3] check: tests FAIL, finish rejected
[step 4] model -> write_file {"path": "tests/test_checkout.py", ...}
[step 4] DENIED: tests/test_checkout.py is read-only
[step 5] model -> read_file {"path": "../secrets.txt"}
[step 5] DENIED: path is outside the workspace
[step 6] model -> write_file {"path": "checkout.py", ...}
[step 7] model -> finish {"summary": "Removed the second apply_discount call."}
[step 7] check: tests pass, finish accepted一步步看,很有意思:
第 3 步:模型什么都没有更改就说 "Fixed",Harness 自己跑测试。测试全部失败后,最终判定结果为不通过。
第 4 步:它想覆盖测试文件。工具关卡会挡下此操作,因为测试文件是只读的。
第 5 步:它想读取工作区外的文件,沙箱会直接拒绝。
第 6、7 步:真正修复,这次测试通过,Harness 接受此结果。
跑完整个测试后,进度也被存进了 .harness/state.json,任务、状态、每一步的工具调用都在里面,下次会话能接着用。
场景 2 是一个卡顿的智能体,一直在重复读同一个文件:
[step 1] model -> read_file {"path": "checkout.py"}
[step 2] model -> read_file {"path": "checkout.py"}
[step 3] model -> read_file {"path": "checkout.py"}
[step 4] model -> read_file {"path": "checkout.py"}
[step 5] model -> read_file {"path": "checkout.py"}
[step 5] step limit reached, stopping步数限制措施在第 5 步把它拦下并记录状态。没有这个限制,卡住的智能体会一直烧 Token。
如果你想换成真实模型,怎么办?Harness 的代码一行不用改,只换那个带 decide 方法的对象,模型会自己选择动作,但沙箱、检查、步数限制照旧约束它。
这正是 Harness 的意义所在:无论模型执行什么操作,都适用相同的沙箱、检查机制和约束条件。
如何改进 Harness
当智能体出错,先别急着更改提示或调换模型,顺着系统排查:
它有没有拿到对的信息?能不能找到相关文件?工具是否按预期工作?权限太宽泛还是太受限?进度有没有跨会话保存?检查有没有找到错误?
然后,再修复出问题的那一环。而且别靠感觉来。保留一小批代表性任务,每次改完 Harness 前后跑一遍,对比通过率、步数、成本和耗时,再看几份失败日志。
为什么值得这么折腾?因为这套范式已经被几个大厂的实践反复验证过:瓶颈在基础设施,不在模型智能。
- OpenAI:一个从空仓库起步的项目,5 个月攒了约 100 万行代码、合并了约 1500 个 PR,推动它的团队只有 3 名工程师,平均下来,每名工程师每天要合并三四个 PR,效率是传统模式的十倍量级。
- LangChain:在 Terminal Bench 2.0 上,模型完全不变,只优化 Harness(加自我验证循环、循环检测、上下文工程),得分从 52.8% 涨到 66.5%,排名从 30 名开外冲进前 5。
- Stripe 的 Minions:基于开源 Goose 改装的智能体系统,每周自动合并 1300 多个 PR,全部走人工审查,但没有一行人工手写代码。
- DeepSeek Harness(dsh):2026 年 8 月 13 日开源、走 MIT 协议,主打 "一切皆插件"。它的模型、工具、沙箱、调度都是插件,目标就是让 Agent 从 "能跑" 到 "敢交付"。
- 百度智能云 DuMate(搭子):内置安全沙箱+高危操作二次授权,把 "约束与执行" 落地到跨应用的办公闭环。
Harness 不能保证的事
还得泼盆冷水。很多 Harness 组件是为了弥补当前模型的短板:忘事、过早收工。模型变强后,有些补丁会变成累赘,甚至碍事。
更换模型时,记得把 Harness 也检查一遍:拆掉某个组件、重跑用例,如果拆了反而变差的才继续保留。
而且再精心设计的 Harness,也不能保证每个结果都对:测试可能漏掉关键场景,文档可能已经过时,工具可能返回残缺信息。
它带给团队的是更好的控制、更可评估、更能从失败里学习的能力,但它永远替代不了工程师专业的判断。
结论
回到开头那句话:模型负责 "想",但让它可靠工作的,是它周围那一整套系统。
我们的演示里,脚本模型连续犯了三个错:提前宣称完成、想更改测试用例、想越界读文件。但 Harness 一个都没放过去,最后照样把任务做对了。
想在这个基础上,继续完善?你可以往这个 Mini Harness 里加审批、加压缩步骤、加你自己的工具和测试,然后用量化结果判断每项改动值不值。
当底层模型越来越趋同,决定 AI 落地效果的,就会越来越偏向你手里的这套 Harness。