HarnessDev论文解析:LLM能否自己构建并演化Agent Harness?
1. 从 HarnessDev 论文说起LLM 自建 Agent Harness 到底难在哪HarnessDev 这篇论文做的事情用一句话概括就是把问题从“模型在固定 Agent 系统里能不能完成任务”换成了“模型能不能把这个 Agent 系统本身造出来并且持续改好”。它面向的是 Agent 工程化落地场景适合已经在写 Agent、调工具链、做评测回归的开发者。论文给出的答案不算乐观也不算悲观前沿模型已经能从几乎不具备策略的种子程序出发做出可运行的 Harness在写作和机器学习实验任务上甚至能接近或超过选定的人类工程系统参考但在代码与检索研究任务上仍有明显差距更关键的是基于评测反馈的自我演化虽然能在可见任务上提分却很不稳定且常常不能迁移到未见任务或另一个执行模型。这篇论文最有价值的不是某个新架构而是一套评估 Agent 工程能力的方法论。它把 Harness 拆成六类职责Execution循环、规划、调度、停止条件、Tools工具注册、选择、输入输出约束、异常处理、Context任务、代码、历史、日志如何进入模型上下文、State目标、假设、进度、失败和工件状态的保存与恢复、Lifecycle动作前后钩子、超时、失败恢复、收尾、Verification测试、检查、结果验证、轨迹记录。然后区分四个角色创建模型 L_C 在开发环境 D 里构建出冻结 Harness H冻结后的 H 加上执行模型 L_E 跑下游任务 x最后由固定评测器 J 打分。这个分离很关键因为一个 Harness 在“作者模型”身上表现好可能只是模型与 Harness 协同适配而不是 Harness 本身成了可迁移的软件资产。论文的 RQ1 用 4 个领域、5 个 benchmark、2207 个不重复实例来测创建能力RQ2 则让 creator 反复看到 100 个 SWE-Pro 加 89 个 Terminal-Bench 任务的反馈最多提交 10 对正式版本最后在互斥的 630 个 SWE-Pro 实例上测留出表现。结果里有两个数字特别值得记住五个 Self 谱系的留出集全都比 H0 高但增益只有 1.43 到 4.44均值 3.11换成固定 Gemini 执行后只有 Opus 的留出集仍变好Qwen、DeepSeek、GPT-5.5 都回退。也就是说可见反馈集上的正增益不能直接当成跨 executor 的通用改进。如果你正在做 Agent 工程化这篇论文的实践含义很直接Harness 质量与 executor 兼容性强相关模块声明不等于机制生效可见反馈会诱导过拟合小 probe 容易误导诊断比补丁数量更重要。下面我会把论文里的评测契约翻译成一套可复制的 Harness 配置模板与演化验证步骤并说明怎么通过 TaoToken 统一 Key/API 通道接入 LLM 完成构建与迭代测试。2. 前置准备用 TaoToken 统一 Key 与 API 通道接入 LLM在复现 HarnessDev 式的“构建—冻结—评测—演化”闭环之前先把模型接入层收拢。论文里 creator 和 executor 可能是不同模型开发环境也可能是 Claude Code 或 Codex如果每个模型、每个工具都单独配一套 Key 和 Base URL后面做跨模型回归会非常痛苦。我试过把接入层统一到一个兼容 OpenAI 协议的中转入口改模型只改 Model IDBase URL 和 Key 不动回归脚本几乎不用改。TaoToken 的 API 入口是https://taotoken.net/api兼容 OpenAI 的/v1/chat/completions与/v1/models路径。你需要在控制台创建一个 API Key然后把它写进环境变量不要硬编码进仓库。下面这套环境变量命名是我在 Harness 项目里常用的creator 和 executor 分开方便后面做 Self-Eval 与 Unified-Eval 对照。export TAOTOKEN_API_KEYsk-你的TaoTokenKey export TAOTOKEN_BASE_URLhttps://taotoken.net/api/v1 # creator 模型负责写/改 Harness export CREATOR_MODELclaude-sonnet-4-5 # executor 模型Harness 冻结后真正跑下游任务 export EXECUTOR_MODELgemini-3.1-pro如果你用 Claude Code 作为 creator 的开发工作台可以在项目根目录放一个.claude/settings.json把 Base URL 和 Key 指到统一通道。注意 Claude Code 走的是 Anthropic 兼容路径TaoToken 的 Anthropic 入口在文档里有说明配置时 Base URL 不要带/v1后缀具体以接入文档为准。{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的TaoTokenKey, ANTHROPIC_MODEL: claude-sonnet-4-5 } }如果你用 Codex 作为 creator配置写在~/.codex/auth.json和~/.codex/config.toml里。auth.json 只放 Keyconfig.toml 放 Base URL 和 Model ID三件套缺一不可。下面是我实际用过的片段路径和字段名按你本地 Codex 版本对齐。{ OPENAI_API_KEY: sk-你的TaoTokenKey }model gpt-5.5 model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api/v1 env_key OPENAI_API_KEY wire_api chat配好之后先做一次最小连通性验证确认 Key、Base URL、Model ID 三件套都对。这一步不要跳过后面 401 和 model not found 大多是因为这里没对齐。curl -s https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: $CREATOR_MODEL, messages: [{role: user, content: 只回复 ok}], max_tokens: 8 }返回里能看到choices[0].message.content就说明通道通了。如果你更想先在网页里对比不同模型对同一段 Harness 代码的理解差异可以直接用模型对话入口试几轮确认模型行为符合预期再写进脚本。长期跑编码和 Agent 演化的话Coding Plan 的额度模型更适合反复冻结、反复评测这种高频调用场景。3. 可复制配置Harness 六模块模板与评测契约论文把 Harness 拆成 E/T/C/S/L/V 六类职责但真正落地时不能只写六个类名得让每个模块在运行时有可观测的触发点。下面这份harness.yaml是我按论文评测契约整理的模板字段名和结构可以直接抄进你的项目路径按你本地仓库调整。核心思路是每个模块都要声明entrypoint入口函数、events运行时事件名、assert覆盖率断言这样后面才能检查“模块是否真的走到执行路径”。harness: name: harnessdev-repro version: 0.1.0 seed: ./seed/weak_seed.py execution: entrypoint: harness.execution:run_loop events: [loop.start, loop.step, loop.stop] stop_conditions: - max_steps: 40 - no_tool_call_for: 3 - verifier_passed: true assert: min_loop_steps: 1 max_loop_steps: 40 tools: registry: harness.tools:REGISTRY events: [tool.select, tool.call, tool.error] constraints: timeout_seconds: 30 max_output_chars: 8000 assert: tool_call_rate: 0.3 error_rate: 0.2 context: builder: harness.context:build events: [context.build, context.truncate] budget_tokens: 120000 strategy: sliding_window_with_summary assert: truncate_events: 0 state: store: harness.state:FileStore events: [state.save, state.load, state.checkpoint] checkpoint_every_steps: 5 assert: checkpoint_events: 1 lifecycle: hooks: harness.lifecycle:HOOKS events: [before_action, after_action, on_timeout, on_failure] assert: hook_invocations: 1 verification: verifier: harness.verify:run_checks events: [verify.start, verify.fail, verify.pass] checks: - name: diff_non_empty - name: artifact_exists - name: self_report_matches_artifact assert: verify_invocations: 1这份模板里最容易被忽略的是assert段。论文发现 18 个代码 Harness 里 11 个定义了状态类但只有 1 个暴露保存接口、1 个实现定期 checkpoint26679 条任务轨迹里没有出现任何 checkpoint 事件。也就是说代码里写了class Memory不等于运行时真的存了状态。所以state.assert.checkpoint_events 1这种断言必须写进评测契约否则你会在静态代码审查里被骗过去。评测契约还要把“能力指标”和“效率指标”分开记录。论文里每个冻结 Harness 同时报告下游 benchmark 原生指标和执行模型消耗的 token 总数/每任务均值且不把 creator 开发 Harness 的 token 算入执行成本。下面是一个结果账本的 JSON 结构每次冻结版本提交时追加一条方便后面算 final gap。{ harness_version: v3, frozen_at: 2026-09-10T12:00:00Z, creator_model: claude-sonnet-4-5, executor_model: gemini-3.1-pro, feedback_set: { swe_pro_100: 0.687, terminal_89: 0.741 }, holdout_set: { swe_pro_630: 0.5159 }, executor_tokens: { total: 29300000, per_task_avg: 13200 }, module_coverage: { execution: true, tools: true, context: true, state: false, lifecycle: true, verification: true }, notes: state.checkpoint 未触发降级为探索性版本 }注意module_coverage这一项。论文用轨迹核验机制是否真被触发18 个 Harness 里工具、生命周期、验证完整的分别为 13/18、13/18、15/18未触发的组件全部与状态/记忆相关。所以每次冻结版本都要跑一遍覆盖率检查把没触发的模块标出来不要让它悄悄混进“正式版本”。4. 验证请求构建、冻结、评测与演化闭环配置写好后按“构建—冻结—评测—演化”四步跑闭环。第一步是让 creator 从弱种子生成 H0。弱种子不是空仓库也不是半成品 Agent它只提供稳定 CLI、模型配置入口、审计文件写入和路径/文件/搜索/进程等被动原语没有循环、规划、工具策略、上下文管理、状态、重试、恢复、验证或停止规则。你可以把种子程序理解成一个“只会被动响应”的壳原样跑下游 benchmark 应该全是 0 分。# 1. 用 creator 模型从弱种子生成 H0 python -m harnessdev.build \ --seed ./seed/weak_seed.py \ --creator-model $CREATOR_MODEL \ --base-url $TAOTOKEN_BASE_URL \ --api-key $TAOTOKEN_API_KEY \ --out ./harness/H0 \ --max-turns 60 # 2. 冻结 H0记录 commit 与配置哈希 python -m harnessdev.freeze \ --harness ./harness/H0 \ --out ./frozen/H0.json # 3. 用 executor 模型跑反馈集 python -m harnessdev.eval \ --harness ./frozen/H0.json \ --executor-model $EXECUTOR_MODEL \ --base-url $TAOTOKEN_BASE_URL \ --api-key $TAOTOKEN_API_KEY \ --tasks ./tasks/swe_pro_100.jsonl \ --out ./results/H0_feedback.json # 4. 跑留出集creator 永远看不到结果 python -m harnessdev.eval \ --harness ./frozen/H0.json \ --executor-model $EXECUTOR_MODEL \ --tasks ./tasks/swe_pro_630.jsonl \ --out ./results/H0_holdout.json跑完 H0 后把反馈集结果喂给 creator 做演化。这里要严格按论文的隔离方式creator 只能看到反馈集结果留出集结果从不返回。每次正式版本提交必须同时完成两套完整评测最多 10 对。下面这段脚本把“读结果—改代码—重评测—选版本”串起来但注意不要让它自动选版本版本选择要人工确认因为论文发现只有 2/9 个 creator 声明的最终版本恰好是该谱系的留出最优版本。import json, subprocess, pathlib FEEDBACK pathlib.Path(./results) FROZEN pathlib.Path(./frozen) MAX_PAIRS 10 def run_eval(harness, tasks, out): subprocess.run([ python, -m, harnessdev.eval, --harness, str(harness), --executor-model, gemini-3.1-pro, --tasks, tasks, --out, str(out), ], checkTrue) for i in range(MAX_PAIRS): prev FROZEN / fv{i}.json feedback FEEDBACK / fv{i}_feedback.json if not feedback.exists(): break # creator 读反馈产出候选改动 subprocess.run([ python, -m, harnessdev.evolve, --harness, str(prev), --feedback, str(feedback), --creator-model, claude-sonnet-4-5, --out, f./harness/v{i1}, ], checkTrue) # 冻结候选版本 subprocess.run([ python, -m, harnessdev.freeze, --harness, f./harness/v{i1}, --out, str(FROZEN / fv{i1}.json), ], checkTrue) # 同时跑反馈集和留出集 run_eval(FROZEN / fv{i1}.json, ./tasks/swe_pro_100.jsonl, FEEDBACK / fv{i1}_feedback.json) run_eval(FROZEN / fv{i1}.json, ./tasks/swe_pro_630.jsonl, FEEDBACK / fv{i1}_holdout.json)演化过程中要记录每次切换的“失败证据—假设—影响路径—预期观测变化—回归结果”。论文统计 64 次正式切换里8 次同时伤害两个 benchmark16 次只伤害其中一个3 次形成跨 benchmark 取舍7 次没有可测变化27 次虽涨分但落在重复运行噪声带内只有 2 次有超出噪声带的清晰正证据。同一提交的 pair score 可波动约 /-4.75 点所以任何小于这个幅度的“涨分”都要先当成噪声处理。验证成功的结果长什么样以 Opus 的正例为参照它先检查 189 个反馈案例发现 99/100 次运行自报成功但只有 48 次真正通过归因于过早完成然后加入 pre-finish completion/verification gate并对代码 diff 或终端工件做一次自检最终 Self 反馈 pair 从 71.1 到 74.1留出集从 63.02 到 67.46。这是少数同时有失败模式、机制改动、端到端验证三者闭环的案例。你的演化日志里如果能复现出“可复现失败机制 小而可解释改动 独立验证改变下游行为”这三段才算一次有效演化。5. 常见错排查401、local proxy failed、reading choices、OAuth接入和评测过程中最容易撞的几类报错我按真实日志对照整理了一遍。先看 401这通常不是 Key 错了而是 Base URL 和 Key 的配对错了。TaoToken 的 OpenAI 兼容路径是https://taotoken.net/api/v1Anthropic 兼容路径是https://taotoken.net/api两者不要混用。如果你在 Claude Code 的 settings.json 里写了带/v1的 Base URL或者在 Codex 的 config.toml 里写了不带/v1的 Base URL都会出现 401 或 404。HTTP 401: {error:{message:invalid api key,type:authentication_error}}排查顺序先echo $TAOTOKEN_API_KEY确认环境变量非空再curl直接打/v1/models看能否列出模型最后检查配置文件里的 Base URL 是否和工具协议匹配。Claude Code 用 Anthropic 协议Codex 用 OpenAI 协议Cline MCP 也走 OpenAI 协议三者的 Base URL 写法不一样。第二类是local proxy failed。这个报错通常出现在你本地起了代理进程、但代理进程没有正确转发到 TaoToken 入口的时候。先确认代理进程监听端口和配置文件里的端口一致再确认代理进程的环境变量里有TAOTOKEN_API_KEY。如果你用的是 Cline MCPMCP server 的启动命令里要显式传入 Base URL 和 Key不能只依赖全局环境变量。{ mcpServers: { harnessdev: { command: python, args: [-m, harnessdev.mcp_server], env: { OPENAI_BASE_URL: https://taotoken.net/api/v1, OPENAI_API_KEY: sk-你的TaoTokenKey, OPENAI_MODEL: claude-sonnet-4-5 } } } }第三类是reading choices相关报错典型日志是KeyError: choices或reading choices。这通常意味着返回体不是标准的 chat completion 结构可能是模型名写错导致返回了错误对象也可能是流式和非流式混用。先确认model字段是 TaoToken 支持的 Model ID再确认请求里stream字段和你的解析代码一致。如果你在 Harness 的 context builder 里做了自定义解析记得对choices缺失做兜底。resp client.chat.completions.create( modelos.environ[EXECUTOR_MODEL], messagesmessages, streamFalse, ) if not getattr(resp, choices, None): raise RuntimeError(funexpected response: {resp}) content resp.choices[0].message.content第四类是 OAuth 相关报错多见于 Claude Code 或 Codex 的登录态和 API Key 混用。如果你在 Claude Code 里既登录了账号又配了ANTHROPIC_API_KEY工具可能优先走 OAuth 而不是你的 Key导致请求打到错误端点。解决办法是在 settings.json 里显式声明ANTHROPIC_API_KEY并确认没有残留的 OAuth token 文件。Codex 同理auth.json里只放 API Key不要放 OAuth 凭据。还有一类不算报错但很坑的情况Harness 自报 success 但评测器给 0 分。论文明确说代码任务只从真实仓库 diff、终端任务只从最终环境状态取分Harness 自报 success 不会带来分数。所以你的 verification 模块必须做self_report_matches_artifact检查把自报结果和真实工件对齐否则会出现“看起来跑通了、实际没交付”的假阳性。6. 把闭环跑起来从失败轨迹到留出集确认论文给的最小闭环是失败轨迹 - 归类出可验证的 Harness 缺陷 - 小而可解释的改动 - 本地契约/回归测试 - 多任务评测 - 留出集确认 - 记录或回滚。这套流程里最容易被跳过的是“归类出可验证的 Harness 缺陷”和“留出集确认”。很多团队一看到分数下降就让另一个模型重写提示词论文显示这种局部搜索很容易追到噪声峰值真正可积累的改进需要把失败诊断、工件可审计性、跨模型回归和留出泛化纳入同一个工程流程。具体操作上我建议每次演化前先跑一遍失败轨迹归类脚本把失败样本按 E/T/C/S/L/V 六类打标统计每类缺陷的出现频次。然后只针对频次最高且可复现的那一类做改动改动幅度控制在中位最终版本的水平8 个文件、增加 476 行、删除 38 行左右。改动太大容易引入不可归因的变量改动太小又可能落在噪声带内。跨模型回归是另一个必须做的动作。论文发现 Opus 的 SWE-Pro 从 69.3 降到 33.0EQ-Bench3 从 84.6 降到 74.2原因是其检索 Harness 的重复查询率从 10.1% 升到 88.2%说明去重、复核和停止策略适配了原模型的行为。所以你的 Harness 至少要在两个不同 executor 上跑一遍如果只在原模型上涨分、换模型就回退那这个改动只能算“模型适配”而不是“Harness 改进”。最后是留出集确认。留出集必须和反馈集互斥且 creator 永远看不到结果。论文用 630 个 SWE-Pro 实例做留出你的项目可以按 80/20 切分但要注意留出集的任务类型要覆盖反馈集的主要类型否则留出结果没有参考价值。每次正式版本提交后把反馈集和留出集结果都写进结果账本算一下 final gap历史最佳留出分减去 creator 自己选择的最终版本分如果 final gap 大于 0说明版本选择不是留出最优需要回看选择逻辑。如果你想把 creator 和 executor 的调用都收拢到统一通道方便做跨模型回归可以在项目里固定TAOTOKEN_BASE_URL和TAOTOKEN_API_KEY只切换 Model ID。这样每次换模型只需要改一个环境变量回归脚本不用动。需要新建 Key 或查看额度去 API Keys 页面操作接入细节和 Anthropic 兼容路径的写法接入文档里有完整说明。验证模型行为是否符合预期可以用模型对话先试几轮长期跑编码和 Agent 演化Coding Plan 的额度模型更适合高频冻结和评测。把接入层收拢之后你就能把精力放在 Harness 本身的失败诊断和留出泛化上而不是反复调 Key 和 Base URL。