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_file、edit_file、run_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.jsontask.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 之一。它的核心流程是:
- 加载一个真实 issue 对应的测试规格。
- 把模型生成的 patch 应用到隔离环境。
- 在容器里运行 eval script。
- 保存测试输出。
- 解析日志,判断 fail-to-pass 和 pass-to-pass。
- 生成每个 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.diff、eval.sh、test_output.txt、report.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、人工抽检、跨版本对比。
一个现实可行的落地路线是:
- 先统一 Trace schema,把每次模型调用、工具调用、最终回答都落到 JSONL。
- 从线上失败案例中挑 20 个高价值 case,沉淀成
evals/cases/。 - 给每个 case 写一个最小 grader,先用规则或测试脚本,不急着上 LLM judge。
- 每次改 prompt、工具、模型、memory 策略时跑一次 regression eval。
- 报告里同时展示 success rate、failure type、平均工具调用数、平均耗时、token 成本。
- 把新发现的线上失败继续回填到 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 让你能持续迭代而不靠感觉。
参考资料
- Datawhale:《关于Agent Harness,我整理了一个最小版!》
- Anthropic Engineering:Demystifying evals for AI agents,https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents
- SWE-bench GitHub,https://github.com/SWE-bench/SWE-bench
- SWE-bench Evaluation Guide,https://www.swebench.com/SWE-bench/guides/evaluation/
- Terminal-Bench Docs,https://www.tbench.ai/docs/task-overview
- Terminal-Bench GitHub,https://github.com/harbor-framework/terminal-bench
- Inspect AI Docs,https://inspect.aisi.org.uk/
- SWE-agent GitHub,https://github.com/SWE-agent/SWE-agent