🏇Harness Engineer

4 阅读12分钟

Harness Engineering:为 AI Agent 构建可靠的工程环境

不再"用 AI 写代码"。 为 AI 设计一个能可靠工作的世界。

目前随着 AI coding agent 的能力快速提升,我认识的所有工程师,无一不开始使用claude code、cursor、tera等coding agent工具开始开发,从最基础的代码补全,到生成完整的工具类、完整的页面、完整的接口、单元测试等,软件工程的核心问题正在发生变化。

随着发展到2026年,我们开始面对一个新问题:

如何让 AI agent 在真实系统中可靠地工作。

问题不再只是模型,而是系统,因为AI agent执行任务的时长可以开始以小时计了。

很多团队逐渐形成一个共识:

Agent = Model + Harness

模型提供推理能力,Harness 提供运行环境、约束和反馈机制。

如果模型是那匹强壮但方向不定的马,那么 Harness 就是马具——不是为了束缚力量,而是为了让这份力量更可靠。


从 Prompt 到 Agent:问题是如何演化的

AI 编程的发展经历了几个清晰的阶段。每个阶段的变化,不只是工具升级,而是工程师思考问题的方式发生了变化

2022–2023:Prompt Engineering

核心问题是如何跟模型说话。工程师花大量时间调整 prompt 措辞、加入 few-shot 示例、设计 prompt 模板,目标是让模型一次性生成满意的结果。

2024:Context Engineering

随着 RAG、工具调用、多轮对话出现,问题变成了"模型需要什么信息"。工程师开始关注知识检索、上下文压缩、动态注入和 token 预算管理。核心目标是在有限的上下文窗口中给模型最重要的信息。

2025–:Agent Systems

当 AI 开始修改真实代码库、调试系统、调用外部 API、连续运行数小时,问题再次改变。现在真正的工程问题是:如何构建一个让 AI agent 可以安全、可预期、可审计地运行的系统。

这就是 Harness Engineering 所关注的问题。它不是单纯的 prompt 技巧,而是一套工程基础设施实践。


Harness 的系统结构

在目前一些团队的实践中,归纳总结一个完整的 Harness 通常包含五个层次。

Context Layer(上下文层)

Agent 必须理解系统。AI只能注意到它上下文中的内容,所以很多原本在我们人脑里的知识,需要被显式表达:架构决策、模块职责、接口约定、历史上做过的技术折衷。从智能体的角度看,任何它无法在运行时访问的知识,实际上不存在。ARCHITECTURE.mdAGENTS.md、API schema、测试示例——这些文件定义了 agent 如何理解系统。

Tool Layer(工具层)

Agent 的能力来自它可以调用的工具:文件系统、git、浏览器、数据库、测试套件、外部 API。工具定义了 agent 能做什么。工具的设计直接决定了 agent 的行动边界,设计不当的工具比没有工具更危险。

Runtime Layer(运行环境层)

Agent 实际执行代码的环境:sandbox、workspace、container、执行隔离。这一层决定 agent 的操作是否安全、可隔离、可复现。

Constraint Layer(约束层)

这是 Harness Engineering 的核心。

很多早期 AI 工程的约束是这样的:在 prompt 里写"请不要修改数据库 schema"。但 prompt 约束并不可靠——因为当上下文过长时,几乎不可避免的会出现上下文腐烂问题,模型可以无视建议,但无法绕过代码检查。

Harness 的做法是把规则写进系统,而不是写进 prompt。

语言约束是建议,代码约束是法律。

具体手段包括 lint rules、CI checks、pre-commit hooks、policy guards。约束分三层:

层级载体示例
不可违反层代码强制(CI/hooks)安全边界、数据隐私、层级依赖
默认遵守层文档 + 测试代码风格、模块职责
建议参考层Prompt偏好、折衷判断

Feedback Layer(反馈层)

Agent 完成任务后,系统需要自动判断它做对了吗,并把结果反馈给 agent,驱动它自我修复。反馈来源包括测试、静态分析、verification、evaluation。这一层让 agent 能够形成"失败 → 修复 → 再尝试"的自动化循环。

反馈还分两类:功能验证(代码能跑吗?逻辑对吗?)和行为验证(agent 的行为本身是否符合预期?)。行为验证是一种新型测试:

def test_agent_does_not_over_engineer():
    task = "给 User 模型加一个 email 字段"
    result = run_agent(task)

    # agent 不应该顺手重构整个 User 模型
    assert lines_changed(result) < 50
    # agent 不应该引入新的依赖
    assert new_dependencies(result) == []

一个最小可行 Harness

完整 Harness 可以很复杂,但一个最小系统只需要三件东西:

AGENTS.md      定义系统世界观
harness.yaml   定义任务与约束
guard.py       定义不可违反的规则

Harness 的最小单位不是工具,而是规则系统。

下面用一个认证服务的实际场景,展示这三个文件如何协同工作。


AGENTS.md——智能体的基础认知

这个文件放在根目录,每次 agent 开始工作前必须读它。它不是 README,是 agent 理解这个项目的完整地图,当然也可以是CLAUDE.md

# AGENTS.md

## 这个代码库的样子
auth-service/
├── src/
│   ├── api/       # HTTP 层,只做路由和参数校验
│   ├── domain/    # 业务逻辑,不知道数据库存在
│   ├── infra/     # 数据库、缓存、外部服务
│   └── shared/    # 工具函数,无业务语义
└── harness.yaml   # 你的任务来源

## 铁律(违反这些,你的 PR 会被自动拒绝)
1. domain/ 里绝对不能 import infra/
2. 新增接口必须先在 api/schemas.py 里定义 Pydantic 模型
3. 所有密码操作必须经过 shared/crypto.py,禁止裸用 hashlib
4. migration 文件禁止自动生成,必须人工审查

## 你不知道的事,你必须说出来
如果任务要求你改动 migration,在 PR description 里写:
"⚠️ 需要人工审查 migration: [原因]"

## 你的工作节奏
1. 读 harness.yaml 里分配给你的任务
2. 动手前列出你打算改的文件
3. 改完跑 make check(lint + type check + tests)
4. make check 失败,自己修,最多重试 3 次
5. 第 3 次还失败,写清楚原因,等人工介入

harness.yaml——任务调度与约束

这是 Harness 的工单系统。人类往这里写任务,agent 来取任务、执行、反馈。

project: auth-service
agent_model: claude-sonnet-4-6
context_files:
  - AGENTS.md
  - src/api/schemas.py    # 每次注入,让模型知道现有接口
  - src/domain/user.py    # 核心业务模型
  - tests/unit/           # 让模型看到测试怎么写的

tasks:
  - id: task-001
    status: pending
    title: "给 User 添加 email 验证状态"
    description: |
      在 User domain model 里增加 email_verified: bool 字段。
      修改 /auth/login,如果 email_verified=False,
      返回 403 并附带 "email_not_verified" 错误码。
      不要改 registration 流程,那是 task-002 的工作。
    constraints:
      max_files_changed: 6
      forbidden_files:
        - "migrations/*"
      required_tests: true

  - id: task-002
    status: blocked        # 等 task-001 完成后解锁
    depends_on: task-001
    title: "注册流程发送验证邮件"

注意 constraints 字段:它不是写给模型看的建议,而是写给 guard.py 执行的规则。


guard.py——不可违反的法律

在 CI 和 pre-commit hook 里自动执行,模型无法绕过。

# guard.py

import ast, yaml
from pathlib import Path
from dataclasses import dataclass

@dataclass
class Violation:
    rule: str
    file: str
    detail: str

def check_layer_violations(changed_files: list[str]) -> list[Violation]:
    """domain/ 不能引用 infra/"""
    violations = []
    for filepath in changed_files:
        if not filepath.startswith("src/domain/"):
            continue
        tree = ast.parse(Path(filepath).read_text())
        for node in ast.walk(tree):
            if isinstance(node, ast.ImportFrom):
                if node.module and "infra" in node.module:
                    violations.append(Violation(
                        rule="layer_violation",
                        file=filepath,
                        detail=f"domain 层引用了 infra: '{node.module}'"
                    ))
    return violations

def check_crypto_bypass(changed_files: list[str]) -> list[Violation]:
    """密码操作必须走 shared/crypto.py"""
    violations = []
    for filepath in changed_files:
        if "shared/crypto.py" in filepath:
            continue
        content = Path(filepath).read_text()
        for lib in ["hashlib", "bcrypt", "passlib"]:
            if f"import {lib}" in content or f"from {lib}" in content:
                violations.append(Violation(
                    rule="crypto_bypass",
                    file=filepath,
                    detail=f"绕过 crypto.py 直接使用 '{lib}'"
                ))
    return violations

def check_task_constraints(task_id: str, changed_files: list[str]) -> list[Violation]:
    """检查 harness.yaml 里的任务级约束"""
    config = yaml.safe_load(Path("harness.yaml").read_text())
    task = next(t for t in config["tasks"] if t["id"] == task_id)
    constraints = task.get("constraints", {})
    violations = []

    if len(changed_files) > constraints.get("max_files_changed", 999):
        violations.append(Violation(
            rule="too_many_files", file="(multiple)",
            detail=f"改动了 {len(changed_files)} 个文件,上限是 {constraints['max_files_changed']}"
        ))

    for pattern in constraints.get("forbidden_files", []):
        for f in changed_files:
            if Path(f).match(pattern):
                violations.append(Violation(
                    rule="forbidden_file", file=f,
                    detail=f"此文件在当前任务中禁止修改({pattern})"
                ))

    return violations

def run_guard(task_id: str, changed_files: list[str]) -> bool:
    violations = (
        check_layer_violations(changed_files) +
        check_crypto_bypass(changed_files) +
        check_task_constraints(task_id, changed_files)
    )
    if violations:
        print("\n🚨 Harness Guard 发现违规:\n")
        for v in violations:
            print(f"  [{v.rule}] {v.file}\n  → {v.detail}\n")
        return False
    print("✅ Harness Guard 通过")
    return True

把它们串起来:harness_runner.py

Runner 编排整个流程:构建 prompt、调用模型、过 guard、写文件、跑检查、更新任务状态。

def run_task(task_id: str):
    config = yaml.safe_load(Path("harness.yaml").read_text())
    task = next(t for t in config["tasks"] if t["id"] == task_id)
    client = anthropic.Anthropic()

    # 阶段 1:注入 context,调用模型
    prompt = build_prompt(task, config["context_files"])
    response = client.messages.create(
        model=config["agent_model"], max_tokens=8000,
        messages=[{"role": "user", "content": prompt}]
    )
    files_to_write = extract_files(response.content[0].text)

    # 阶段 2:过 guard(违规则把违规信息反馈给模型,最多重试 3 次)
    if not run_guard(task_id, list(files_to_write.keys())):
        return retry_with_feedback(task_id, files_to_write)

    # 阶段 3:写入文件,跑检查
    for filepath, content in files_to_write.items():
        Path(filepath).write_text(content)

    result = subprocess.run(["make", "check"], capture_output=True, text=True)
    if result.returncode != 0:
        return retry_with_error_feedback(task_id, result.stderr)

    # 阶段 4:更新任务状态
    task["status"] = "needs_review"
    print("✅ 任务完成,等待人工审查")

整个流程里,工程师没有写任何一行业务代码。写的全部是:模型的基础认知、任务边界、不可违反的规则、以及串联它们的编排逻辑。


三个值得关注的深层问题

失败是原材料,不是错误

原来我们把 CI 失败当作"需要修复的 bug"。但是在 Harness Engineering 里,失败是系统的进化信号。

每次 agent 失败,先分类再处理:

  • 幻觉型失败(调用了不存在的函数)→ 在 repo 里生成该函数的存根和注释
  • 越界型失败(修改了不该碰的层)→ 在 AGENTS.md 里强化边界描述
  • 遗忘型失败(忘记了早期的决策)→ 在 task prompt 模板里添加"历史决策回顾"节
  • 格式型失败(输出不符合 schema)→ 更新 schema 示例,增加负面案例

一段时间后,你积累的失败分类记录本身就是资产。你的 Harness 见过的失败模式,竞争对手的还没见过。这是时间上的护城河。

认知脚手架:先思考后行动

软件工程的最佳实践一定是动手写代码前会先设计,但我们在使用coding agent 通常一句话就要结果。

Harness 可以强制插入一个隔离阶段:

阶段 0(只读):agent 只能读文件,不能写
阶段 1(规划):agent 输出结构化计划,必须回答:
  - 我会改哪些文件?
  - 我依赖哪些接口?这些接口目前是否存在?
  - 这个改动会影响哪些下游模块?
阶段 2(执行):计划通过校验后,才允许写操作

AI 代码库的熵增

这是一个经常被忽视的问题:agent 生成的代码会让代码库悄悄变坏,即使每一个单独的 PR 看起来都是正确的。而且ai编码太快了,一个潜在的技术债务,会在几分钟内被复制到几百次。

典型表现:

问题具体表现
语义重复相似功能被实现了三次,agent 每次都没"看到"已有实现
依赖循环模块间循环依赖悄悄增加
抽象泄漏底层 DB 操作出现在 API handler 里
注释漂移代码改了,注释没跟上

很多 AI 生成的代码是:局部正确,整体退化。

Harness 需要一个长期健康度监控,每隔 N 个 PR 自动运行,检测语义重复率、依赖图复杂度、注释准确率、抽象泄漏情况。当指标越过阈值,自动发起一个重构任务——也由 agent 执行,目标是减少熵,而不是添加功能。


Harness Engineering 的现状

这种全新的工程模式仍然处于早期阶段,一切还是混沌,AI时代日新月异。

今天的现状大多数开发流程仍然是"手工编码 + AI 辅助",而不是"Agent 编码 + 工程师监督"。Harness Engineering 主要出现在 coding agents、autonomous agents、长时间运行的开发 agent 这些场景。

它更像是 Agent Coding 时代的基础设施实践,而不是已经完全成熟的工程范式。但随着 agent 能力不断增强,这一层基础设施的重要性会越来越明显。

如果我们想尝试这种新的方式:

第一步 :建立失败台账。从真实任务中,开始记录每次失败的类型。不一定需要自动化,手动记录也行。重要的是建立分类习惯,区分"模型能力不足"和"Harness 设计有缺陷"——这两种失败的处理方式完全不同。

第二步 :把最高频的失败变成规则。失败 3 次以上的模式,写成 linter rule 或 CI check。这是 guard.py 的第一行代码。

第三步 :加入认知脚手架。在 task prompt 前强制插入规划阶段,观察它是否减少了"做了但做错了"的比例。

第四步(持续) :熵增监控。每隔 50 个 PR 跑一次健康度扫描,看看变化。


结语

Prompt Engineering 解决的是如何与模型对话。

Context Engineering 解决的是模型需要什么信息。

Harness Engineering 关注的是:如何让 AI agent 在真实系统中可靠运行。

随着 agent 系统越来越复杂,软件工程可能会逐渐增加一层新的基础设施——Agent Infrastructure。Harness Engineering 也许正是这层基础设施的早期形态。

这三个文件(AGENTS.mdharness.yamlguard.py),加上你积累的失败分类记录,就是 Harness Engineer 的核心资产。它们与模型本身无关,跨模型迭代可以复用。

终极目标:

一个设计良好的 Harness,让工程师越来越无聊。

如果你每天还在忙着修 agent 的输出,说明 Harness 还不够好。真正成熟的 Harness,工程师的日常是:盯着健康度指标,偶尔更新一条约束规则,偶尔批准一个需要人工介入的决策。

这可能是未来。


harness-engineering from openai

agent Coding 示例