你让 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。