May 29, 2026 6 min read Hyr1sky

Agent Harness 工程认知

从 Task、Environment、Tools、Trace 与 Grader 出发,梳理 Agent Harness 如何把 Agent 开发从手动试用推进到可复现、可观察、可评估的工程体系。

Agent Harness 工程认知:把 Agent 从“能跑”带到“可复现、可观察、可评估”

最近看到篇讲 Agent Harness 的文章,不过聚焦的层次比较浅:Task、Environment、Tools、Trace、Grader 五个模块串起来,就能形成一个最小可用的 Agent 评测环境。这个视角很重要,因为很多 Agent 系统一开始只重视 model、prompt、tool calling、workflow,却没有认真建设 Trace 和 Grader。

结果就是:一次 demo 看起来不错,但下一版到底变好了还是变差了,很难回答;一个 benchmark 跑失败了,也很难定位是任务理解、工具选择、工具参数、环境状态、上下文管理,还是评分逻辑本身出了问题。

我的理解是,Harness 不是附属脚本,而是 Agent 工程里的实验台。它把 Agent 放进一个固定任务、固定环境、固定工具接口和固定评分规则中,让每一次运行都留下结构化证据。没有 harness,Agent 开发更像手动试用;有了 harness,Agent 开发才开始接近软件工程里的测试、回归和性能分析。

1. Harness 到底是什么

一个实用定义是:

Agent Harness 是把 Agentic model 接入任务环境、工具系统、执行记录和评分器的一层工程框架。

这里要区分两个概念:

  • Agent harness:负责让模型像 Agent 一样工作,包括接收任务、管理上下文、调用工具、处理工具结果、决定何时结束。
  • Eval harness:负责批量运行 eval case、隔离环境、收集 trace、调用 grader、汇总报告。

真实系统里二者经常交织在一起。比如一个 coding agent 的 harness 既要提供 read_fileedit_filerun_tests 这些工具,又要记录每次工具调用,还要在任务结束后跑测试并生成报告。但在设计上最好分开想:前者关心“Agent 怎么做事”,后者关心“我们如何知道它做得好不好”。

最开始我们提到的 5 个模块可以作为最小骨架:

  • Task:任务输入,例如用户问题、issue、工单、目标状态。
  • Environment:任务环境,例如文件系统、代码仓库、浏览器、数据库、API mock。
  • Tools:Agent 可调用的动作集合,例如读文件、查库、运行命令、检索文档。
  • Trace:每一步发生了什么,例如模型输出、工具名、参数、返回、耗时、错误。
  • Grader:如何判断结果,例如规则、测试脚本、人工标注、LLM judge 或组合评分。

这 5 个模块的关键不是“都有类名”,而是每个模块都能被固定、复现和比较。

2. 为什么 Trace 和 Grader 是核心

很多 Agent 应用失败不是因为最终答案完全错,而是因为过程不可解释。

例如用户问“这个项目是否支持插件系统”,Agent 回答“README 没有提到,不能确认支持”。最终答案看起来合理,但你还需要知道:

  • 它有没有读取 README?
  • 它有没有只读 README,而忽略了 docs/plugins.md
  • 它有没有调用了无关工具?
  • 它有没有把工具结果里没有的信息写进答案?
  • 它是不是因为上下文被截断才漏掉证据?

Trace 解决“发生了什么”。Grader 解决“这件事算不算成功”。二者一起才能让 benchmark 有分析价值。

一个没有 Trace 的 benchmark 只能告诉你 pass/fail;一个没有 Grader 的 Trace 只能让你事后肉眼翻日志。真正有用的是二者结合:失败时能看到失败类别,成功时能知道成功是否来自正确路径。

3. 一个可用 Trace 应该记录什么

最小 Trace 不要只存模型最终答案。建议至少记录这些字段:

{
  "run_id": "2026-05-29-exp-001",
  "case_id": "plugin_support_001",
  "agent_version": "agent@8f3c1a2",
  "model": "gpt-5.4",
  "messages": [],
  "steps": [
    {
      "index": 1,
      "type": "tool_call",
      "tool": "read_file",
      "arguments": { "path": "README.md" },
      "result": "本项目支持本地启动、基础登录和配置管理。",
      "started_at": "2026-05-29T10:00:00Z",
      "duration_ms": 24,
      "error": null
    }
  ],
  "final_answer": "当前 README 没有插件系统相关说明,不能确认支持插件系统。",
  "usage": {
    "input_tokens": 1200,
    "output_tokens": 180,
    "tool_calls": 1
  }
}

工程上可以把 Trace 分成四层:

  • Conversation trace:系统提示词、用户任务、模型消息、工具调用消息。
  • Tool trace:工具名、参数、返回、错误、耗时、重试次数。
  • Environment trace:初始文件、容器镜像、依赖版本、环境变量、随机种子。
  • Artifact trace:最终答案、patch、生成文件、测试日志、截图、报告。

其中 Tool trace 和 Artifact trace 最容易被低估。它们决定了你是否能在失败后回答:“Agent 是没找到证据,还是找到了但没用上?”

Trace 还应该尽量结构化。纯文本日志可以给人看,但很难做聚合分析。更好的方式是:机器读 JSONL,人读 Markdown/HTML report。每个 step 一行 JSONL,最后再聚合成 case report。

4. Grader 不只是判断答案对错

Agent Grader 通常不应该只有一个 success: true/false。更实用的是多维评分:

{
  "success": false,
  "scores": {
    "final_answer": 0,
    "evidence_used": 1,
    "tool_efficiency": 0.5,
    "instruction_following": 1
  },
  "failure_type": "unsupported_claim",
  "reason": "Agent 读取了 README,但最终回答包含 README 中没有的插件支持结论。"
}

常见 Grader 类型可以分层使用:

  • Rule grader:检查是否包含/不包含某些字段、是否调用指定工具、是否引用证据。
  • Test grader:运行单元测试、集成测试、端到端测试,适合 coding agent。
  • State grader:检查数据库、文件、浏览器页面、API 状态是否达到目标。
  • Trace grader:检查过程,例如是否先检索再回答、是否避免危险命令、是否没有重复调用。
  • LLM-as-judge:用于开放式答案质量判断,但要配合 rubrics、样例和抽检。
  • Human review:用于校准规则和 judge,尤其是新任务集早期。

一个成熟 benchmark 往往不是单一 grader,而是组合:

final_score =
  0.5 * task_success
+ 0.2 * evidence_grounding
+ 0.2 * safety_constraints
+ 0.1 * efficiency

但早期不需要过度复杂。最先要做的是把“成功条件”写清楚,哪怕只是规则和测试脚本。

5. Eval Case 应该怎么组织

一个 eval case 至少应该包含:

{
  "id": "plugin_support_001",
  "instruction": "判断项目是否支持插件系统,并给出依据。",
  "environment": {
    "files": {
      "README.md": "本项目支持本地启动、基础登录和配置管理。",
      "config.md": "配置项包括 port、theme、log_level。"
    }
  },
  "tools": ["list_files", "read_file"],
  "expected_behavior": {
    "must_read": ["README.md"],
    "answer_should_include": ["不能确认支持插件系统"],
    "answer_should_not_include": ["支持插件系统"]
  }
}

更工程化一点,可以把 case 拆成目录:

evals/
  plugin_support/
    case_001/
      task.yaml
      files/
        README.md
        config.md
      grader.py
      expected.json

task.yaml 描述任务,files/ 是环境夹具,grader.py 负责评分,expected.json 保存可比较结果。这样 case 可以进版本控制,也可以在 CI 或 nightly benchmark 中稳定运行。

设计 case 时要注意覆盖失败模式,而不是只覆盖用户故事:

  • 工具未调用:Agent 没有读取必要文件就回答。
  • 工具误用:读错文件、路径错、参数错。
  • 证据遗漏:读到了关键信息但没有使用。
  • 幻觉结论:答案包含环境里没有的事实。
  • 冗余步骤:大量重复读取或无关搜索。
  • 约束违反:调用禁用工具、泄露隐藏测试、修改只读文件。
  • 回归风险:旧版本能过,新版本因为 prompt 或 tool schema 改动失败。

6. 一个最小 Harness 的工程结构

如果从零做一个小型 Agent harness,可以先按下面的结构搭:

agent_harness/
  cases/
    plugin_support_001.json
  harness/
    runner.py
    environment.py
    tools.py
    trace.py
    grader.py
    report.py
  runs/
    2026-05-29-exp-001/
      plugin_support_001/
        trace.jsonl
        answer.txt
        grade.json
        report.md

核心流程:

def run_case(case, agent, run_dir):
    env = Environment.from_case(case)
    trace = TraceRecorder(run_dir / "trace.jsonl")
    tools = make_tools(env, trace)
 
    answer = agent.run(
        instruction=case["instruction"],
        tools=tools,
        trace=trace,
    )
 
    grade = grade_case(case=case, trace=trace.read(), answer=answer, env=env)
    write_report(run_dir, case, trace, answer, grade)
    return grade

这个结构有三个工程原则:

  • 工具调用必须经过 wrapper,这样才能统一记录 trace。
  • Grader 读取 trace 和 artifact,而不是只读取 final answer。
  • Run output 必须按 run_id/case_id/ 落盘,方便版本对比。

一个工具 wrapper 可以很简单:

def traced_tool(name, fn, trace):
    def wrapped(**kwargs):
        start = time.time()
        try:
            result = fn(**kwargs)
            trace.write({
                "type": "tool_call",
                "tool": name,
                "arguments": kwargs,
                "result": result,
                "duration_ms": int((time.time() - start) * 1000),
                "error": None,
            })
            return result
        except Exception as exc:
            trace.write({
                "type": "tool_call",
                "tool": name,
                "arguments": kwargs,
                "result": None,
                "duration_ms": int((time.time() - start) * 1000),
                "error": repr(exc),
            })
            raise
    return wrapped

早期不要追求平台化。先让单个 case 能稳定复现,再让 20 个 case 能批量跑,再做 dashboard。

7. 从开源项目借鉴工程实践

7.1 SWE-bench:用测试日志和 report 判断 patch 是否解决 issue

SWE-bench 是 coding agent 评测里最值得借鉴的 harness 之一。它的核心流程是:

  1. 加载一个真实 issue 对应的测试规格。
  2. 把模型生成的 patch 应用到隔离环境。
  3. 在容器里运行 eval script。
  4. 保存测试输出。
  5. 解析日志,判断 fail-to-pass 和 pass-to-pass。
  6. 生成每个 instance 的 report.json 和整体 run report。

关键文件树大致是:

swebench/
  harness/
    run_evaluation.py
    grading.py
    reporting.py
    docker_build.py
    docker_utils.py
    log_parsers/
    test_spec/

run_evaluation.py 负责环境准备、应用 patch、运行测试和写日志。它会为每个 instance 创建日志目录,保存 patch.diffeval.shtest_output.txtreport.json。这就是非常典型的 artifact trace。

grading.py 里不是简单看测试命令 exit code,而是解析测试日志,得到每个测试用例的状态,再计算两类指标:

  • fail-to-pass:原本失败的测试是否因为 patch 变为通过,用来衡量问题是否被解决。
  • pass-to-pass:原本通过的测试是否仍然通过,用来衡量是否引入回归。

这个思路对真实 Agent 应用很有价值:Grader 不只判断“任务有没有做完”,还要判断“有没有破坏原有能力”。

7.2 Terminal-Bench:把任务、终端环境和测试脚本绑定

Terminal-Bench 的任务设计强调终端环境里的可操作性。一个任务通常包含自然语言 instruction、隔离环境、测试脚本和运行配置。Agent 在终端中完成任务,harness 在结束后运行测试来判断状态。

可以借鉴的地方是:不要把 grader 写成只检查文本答案。对于能落到环境状态的任务,应该优先检查状态。例如:

  • 文件是否被创建。
  • 命令是否能运行。
  • 服务是否启动。
  • 输出格式是否符合要求。
  • 测试脚本是否通过。

这类任务的目录可以设计成:

tasks/
  fix_csv_parser/
    task.yaml
    Dockerfile
    docker-compose.yaml
    solution.sh
    tests/
      test_outputs.py

真实产品中的 Agent 也可以照这个思路做:把用户任务转成“环境初始状态 + 目标状态 + 验证脚本”,而不是只保存一段 prompt。

7.3 Inspect AI:把 Task、Solver、Tool、Scorer、Sandbox 作为一等对象

Inspect AI 是一个开源 LLM eval framework。它的抽象很适合参考:

  • Task 定义数据集、solver、scorer、sandbox。
  • Solver 定义模型如何解题,可以组合 prompt、tool use、generate。
  • Tool 定义模型可调用动作。
  • Scorer 定义评分逻辑。
  • Sandbox 提供隔离执行环境。
  • Eval log 保存任务配置、solver、sandbox、样本、分数等信息。

一个官方风格的最小任务大概是:

from inspect_ai import Task, task
from inspect_ai.dataset import Sample
from inspect_ai.scorer import includes
from inspect_ai.solver import generate, use_tools
 
@task
def file_probe():
    return Task(
        dataset=[
            Sample(
                input='Is there a file named "bar.txt" in the current directory?',
                target="Yes",
                files={"bar.txt": "hello"},
            )
        ],
        solver=[use_tools([list_files()]), generate()],
        sandbox="docker",
        scorer=includes(),
    )

它的工具可以通过 sandbox 执行真实命令:

from inspect_ai.tool import ToolError, tool
from inspect_ai.util import sandbox
 
@tool
def list_files():
    async def execute(dir: str):
        result = await sandbox().exec(["ls", dir])
        if result.success:
            return result.stdout
        raise ToolError(result.stderr)
 
    return execute

这个设计给自研 harness 的启发是:不要让工具直接散落在业务代码里。工具应该是可注册、可包装、可记录、可替换的对象。否则 Trace 很难统一,Grader 也很难知道 Agent 到底做了什么。

7.4 SWE-agent:Agent-Computer Interface 影响结果

SWE-agent 的核心观点之一是 Agent-Computer Interface。对于 coding agent,能力不只来自模型,还来自外部接口:怎样查看文件、怎样搜索、怎样编辑、怎样运行测试、错误信息怎样反馈给模型。

这对 harness 的启发是:工具接口本身也应该被评估。两个 Agent 使用同一个模型,但一个工具返回 2 万行日志,另一个工具返回结构化摘要,它们的表现会完全不同。因此 benchmark 结果应该记录:

  • tool schema 版本。
  • 工具返回是否截断。
  • 搜索排序策略。
  • 文件编辑接口形态。
  • 命令超时和权限策略。

否则你以为在比较模型,其实在比较不同 harness。

8. 工程实践:在真实 Agent 应用里怎么融入 Harness

真实业务系统里,不建议把 harness 做成一个完全独立、上线后才运行的评测项目。更好的方式是让它嵌入开发生命周期。

推荐结构:

app/
  agent/
    runtime.py
    tools/
    memory/
    prompts/
  observability/
    trace_recorder.py
    run_store.py
    redaction.py
  evals/
    cases/
      retrieval/
      tool_use/
      regression/
    graders/
      rule_graders.py
      trace_graders.py
      llm_judges.py
    runner.py
    reports.py

运行时链路:

User Request
  -> Agent Runtime
  -> Tool Registry
  -> Trace Recorder
  -> Artifact Store
  -> Optional Online Grader
  -> Response

离线评测链路:

Eval Case
  -> Fixed Environment
  -> Same Agent Runtime
  -> Same Tool Registry
  -> Trace Recorder
  -> Grader Suite
  -> Run Report
  -> Compare With Baseline

关键是线上和离线尽量复用同一套 Agent runtime、tool registry 和 trace schema。否则 benchmark 里的 Agent 和生产里的 Agent 不是同一个系统,评测价值会下降。

Trace 在真实应用里通常分两类:

  • Online trace:每个真实用户请求都记录,但要做脱敏、采样、权限控制和保留周期管理。
  • Eval trace:对 benchmark 全量记录,保留完整工具返回和 artifact,方便复盘和回归。

Grader 也可以分两类:

  • Online grader:轻量规则,例如工具错误率、是否引用来源、是否违反安全策略、是否超时。
  • Offline grader:完整评分,例如测试脚本、LLM judge、人工抽检、跨版本对比。

一个现实可行的落地路线是:

  1. 先统一 Trace schema,把每次模型调用、工具调用、最终回答都落到 JSONL。
  2. 从线上失败案例中挑 20 个高价值 case,沉淀成 evals/cases/
  3. 给每个 case 写一个最小 grader,先用规则或测试脚本,不急着上 LLM judge。
  4. 每次改 prompt、工具、模型、memory 策略时跑一次 regression eval。
  5. 报告里同时展示 success rate、failure type、平均工具调用数、平均耗时、token 成本。
  6. 把新发现的线上失败继续回填到 benchmark,形成回归集。

一个简单 report 可以长这样:

{
  "run_id": "2026-05-29-agent-v12",
  "baseline": "2026-05-20-agent-v11",
  "summary": {
    "cases": 80,
    "success_rate": 0.8125,
    "baseline_success_rate": 0.775,
    "avg_tool_calls": 4.2,
    "avg_latency_ms": 8300
  },
  "regressions": [
    {
      "case_id": "retrieval_014",
      "old": "pass",
      "new": "fail",
      "failure_type": "missed_evidence"
    }
  ]
}

这时 Harness 的作用就很明确了:它不是为了写论文式 benchmark,而是让 Agent 工程拥有回归测试、可观测性和版本比较能力。Trace 让你能复盘过程,Grader 让你能量化结果,Benchmark 让你能持续迭代而不靠感觉。

参考资料