@tonyclaw/eval-harness
v0.2.19
Published
Evaluation harness for configurable runner work packages
Maintainers
Readme
Eval Harness
Release: v0.2.18
可配置的 runner work/ 包评测框架。
Eval Harness 不绑定 OpenCode。它是面向 CodeX/Codex CLI、OpenCode、MiMo Code、Claude Code,以及后续更多 runner adapter 的评测平台。runner 抽象和扩展方式见 Runner Adapters。
默认配置是通用模板,不绑定具体赛题。你可以通过 config/eval.local.json、
Jenkins 参数,或者生成的 eval.effective.json,把它指向任意题目 Git 仓库
和任意 submitted work Git 仓库。
Jenkins 配置入口:
这个项目把一次性的 runner 运行,变成可复现、可比较、可持续优化的证据闭环。它不只看模型在控制台里“看起来聪明不聪明”,而是检查整个 Agent 系统是否能稳定完成以下工作:
- 理解动态题目目录和
work/INSTRUCTION.md - 修改题目工程中的代码
- 输出
work/result、work/reports、work/logs/trace等交付物 - 运行验证并记录证据
- 汇总评分、失败原因和下一轮 Skill/Agent/Instruction 优化建议
工作流
准备干净题目工程
-> 使用 submitted work/INSTRUCTION.md 启动选中的 runner
-> 收集 result、reports、trace、验证日志、代码 diff
-> 确定性评分
-> 给出 ACCEPT / INVESTIGATE / REJECT 决策
-> 生成 recommendations 和 next-action
-> CodeX/Codex 或人工据此优化一个小的 Skill/Agent/Instruction
-> 重新运行并比较当前仓库提供确定性 runner/evaluator 和 Jenkins pipeline。它会给出下一轮优化输入,但不会自动修改 Skills。
你需要配置什么
框架把评测拆成三个独立输入,方便分享给其他团队或同学:
| 配置项 | Jenkins 参数 | JSON 配置字段 | 说明 |
| --- | --- | --- | --- |
| 题目仓库 | TASK_REPO_* | source_repo | 原始题目、工程或待修复项目。每个 worker 会复制为 workspace/project。 |
| work 仓库 | WORK_REPO_* | submission_repo | 参赛或实验用的 work 包,包含 INSTRUCTION.md、Skills、Agents、helper scripts。 |
| 执行矩阵 | MODEL_MATRIX、CONCURRENCY、TIMEOUT_MINUTES 等 | execution | 控制模型、并发、超时、provider 容错、验证、容器等。 |
每次 Jenkins 构建都会生成实际生效配置:
<external-runs>/eval.effective.json
runs/eval.effective.json这个文件记录本轮使用的 task/work 仓库 URL、SSH URL、ref、work 目录和 fallback URL,方便复现。
输入契约
为了换题后仍然可用,建议输入满足下面的契约:
| 输入 | 要求 | 说明 |
| --- | --- | --- |
| Task repository | 一个 Git 仓库,包含需要被 agent 修复的题目工程。 | Git 元数据会尽量保留,用于生成代码 diff;没有 .git 时会 fallback 到文件树比较。 |
| Work repository | 一个 Git 仓库,里面有提交的 work 包。 | submission_repo.work_dir 或 Jenkins WORK_REPO_WORK_DIR 必须指向包含 INSTRUCTION.md 的目录。 |
| Work assets | Skills、Agents、helper scripts、结果和日志约定。 | 默认支持 work/work/skills/* 和 work/skills/* 两种布局。 |
| Result artifacts | work/result/output.md、work/reports/FINAL_RESULT.json、work/reports/FINAL_RESULT.md、work/reports/final-consistency-report.md、work/logs/trace/**。 | 缺失会被当成证据缺口评分,而不是被控制台日志掩盖。 |
本地调试时也可以不用 Git 仓库,直接传:
PROJECT_SOURCE=<本地题目目录>
WORK_SOURCE=<本地 work 目录>共享 Jenkins job 时更推荐使用 Git URL 参数。
当前默认值
- 题目仓库:
config/eval.example.json中的占位 URL - work 仓库:
config/eval.example.json中的占位 URL - work 目录:
work - 默认模型:不写死;使用所选 runner 当前默认,或通过配置/Jenkins
MODEL_MATRIX指定 - 默认 reasoning variant:
high - 默认 worker 超时:
90分钟 - 默认执行契约:runner 会通过 prompt/env 告知 worker
AGENT_EVAL_TIMEOUT_MINUTES、预留AGENT_EVAL_FINALIZE_BEFORE_MINUTES=5分钟收尾,并要求即使失败或部分完成也写出work/result/output.md、work/reports/FINAL_RESULT.json、work/reports/FINAL_RESULT.md、work/reports/final-consistency-report.md - 默认 idle timeout:
10分钟无 agent 输出或 artifact 活动 - 默认 fetch timeout:
15分钟 - Jenkins 并发:默认
1,可配置1~5 - Jenkins runner matrix:逗号或换行分隔的 runner adapter,按 worker 顺序分配;留空则所有 worker 使用 config 中的
agent.type - Jenkins 配置文件:
EVAL_CONFIG可留空;会优先用本地OEVAL_CONFIG,再 fallback 到config/eval.example.json - Jenkins 定时运行:仓库默认不注册 cron;需要时在 Jenkins 环境中自行配置 timer
- work 版本检查:默认
REQUIRE_WORK_VERSION=true,简单接入时可设为false
这些只是默认值,不是框架限制。
快速开始
平台无关。推荐 Python 3.10+,真实评分环境已经按 Python 3.12 兼容。外部工具需要在 PATH 或 Jenkins EXTRA_PATHS / OEVAL_EXTRA_PATHS 中可用:
gitnode- 选中的 agent CLI:
opencode、mimo、codex或claude agent-inspectormvn和 Java,启用验证时需要
Ubuntu 评测基线示例:
Ubuntu 24.04.4
Python 3.12
Node v24.13.0
OpenJDK 21
Maven 3.9.11
Maven home: /usr/local/maven3Linux/macOS:
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
eval-harness --helpWindows PowerShell:
python -m venv .venv
.\.venv\Scripts\python -m pip install -e .
.\.venv\Scripts\eval-harness --helpNPM 包装入口:
npx @tonyclaw/eval-harness --help
npx @tonyclaw/eval-harness runner-presetsnpm 主包会优先使用当前平台对应的 optional native 包。Windows x64 上
@tonyclaw/eval-harness-win32-x64 已内置 Python runtime,因此用户只运行
harness CLI 时不需要单独安装 Python。其他平台当前会 fallback 到 npm 包内置
Python 源码,仍需要 PATH 中有 Python 3.10+,也可以通过
EVAL_HARNESS_PYTHON 指定 Python 可执行文件。设置
EVAL_HARNESS_FORCE_PYTHON=1 可以强制绕过 native binary。
不安装包也可以运行:
PYTHONPATH=src python -m eval --help$env:PYTHONPATH = "src"
python -m eval --help配置自己的题目仓库和 work 仓库
复制配置文件:
cp config/eval.example.json config/eval.local.jsonCopy-Item config/eval.example.json config/eval.local.json修改 config/eval.local.json:
{
"source_repo": {
"url": "https://example.com/course/task.git",
"ssh_url": "[email protected]:course/task.git",
"ref": "main",
"fallback_urls": []
},
"submission_repo": {
"url": "https://example.com/team/work.git",
"ssh_url": "[email protected]:team/work.git",
"ref": "main",
"work_dir": "work",
"fallback_urls": []
}
}也可以不修改文件,临时生成 effective config:
PYTHONPATH=src python -m eval write-effective-config \
--config config/eval.example.json \
--output /tmp/oeval/eval.effective.json \
--task-repo-url https://example.com/course/task.git \
--task-repo-ref main \
--work-repo-url https://example.com/team/work.git \
--work-repo-ref main \
--work-repo-work-dir work生成后按普通配置使用:
PYTHONPATH=src python -m eval fetch-task --config /tmp/oeval/eval.effective.json --output /tmp/oeval/inputs/task --replace-existing
PYTHONPATH=src python -m eval fetch-work --config /tmp/oeval/eval.effective.json --output /tmp/oeval/inputs/work --replace-existing
PYTHONPATH=src python -m eval prepare --config /tmp/oeval/eval.effective.json --run-id demo-01 --project-source /tmp/oeval/inputs/task --work-source /tmp/oeval/inputs/work --replace-existing本地运行
准备:
python -m eval prepare --config config/eval.local.json执行配置的 agent runner:
python -m eval execute --run runs/<run-id>评分:
python -m eval score --run runs/<run-id>一条命令跑完整生命周期:
python -m eval run --config config/eval.local.json并发运行:
python -m eval batch --config config/eval.local.json --count 5 --parallel 5本地一次性运行 Jenkins 类似流程。该入口会生成有效配置、拉取 task/work 输入、并行执行 worker、汇总批次、导出产物,并可选校验导出的 JSON:
python -m eval run-once `
--config config/eval.local.json `
--batch-id local-three-model `
--count 3 `
--parallel 3 `
--prefer-ssh `
--model agent-inspector/MiniMax-M3 `
--model agent-inspector/deepseek-v4-pro `
--model agent-inspector/glm-5.2 `
--timeout-minutes 90 `
--idle-timeout-minutes deepseek-v4-pro=30,default=10 `
--variant high `
--replace-existing `
--validate-artifacts可以用 --task-repo-url、--task-repo-ref、--work-repo-url、--work-repo-ref、--work-repo-work-dir 覆盖本轮 task/work 仓库参数,私有配置不用提交到仓库。首次验证流程时可加 --dry-run。
短链路真实调度可以使用 --smoke。它会强制 DRY_RUN=false、timeout=5、idle_timeout=2,并关闭 validation、work version 和发布门禁,适合先确认 runner、模型路由和 Agent Inspector 链路:
python -m eval run-once `
--config config/eval.local.json `
--smoke `
--count 3 `
--parallel 3 `
--model agent-inspector/MiniMax-M3 `
--model agent-inspector/deepseek-v4-pro `
--model agent-inspector/glm-5.2Agent Runner
当前支持的 agent.type:
codex或codex-cli:CodeX/Codex CLI runneropencode:OpenCode CLI runnermimocode或mimo:MiMo Code runnerclaude或claude-code:Claude Code runner
每个 runner 都有自己的默认命令、日志目录、隔离运行时 profile 和 work asset 安装目录。需要覆盖命令时,可使用 execution.agent.command、execution.<runner>.command 或 execution.<runner>_command。
查看 runner 默认参数:
python -m eval runner-presets
python -m eval runner-presets --format json
python -m eval runner-version-check --config config/eval.local.json --markdown-output runs/preflight/runner-version-check.mdCodeX/Codex CLI 示例:
{
"agent": {
"type": "codex"
},
"execution": {
"codex_command": [
"{agent_executable}",
"exec",
"--json",
"--skip-git-repo-check",
"--dangerously-bypass-approvals-and-sandbox",
"--cd",
"{workspace}",
"Read and execute work/INSTRUCTION.md. Work autonomously, do not ask for human input, and write all required result artifacts."
]
}
}Windows Jenkins 服务账号下,Codex CLI 会先使用 agent.executable、codex.executable、execution.codex_executable、execution.codex.executable 或 OEVAL_CODEX_EXECUTABLE,然后自动发现当前用户的 %LOCALAPPDATA%\OpenAI\Codex\bin\*\codex.exe,最后才回退到 PATH 中的 codex。本机路径建议放在未提交的 local config 或 Jenkins 全局环境变量中,不要写入共享配置。
CodeX/Codex CLI 同样支持通用 --model 覆盖:
python -m eval execute --run runs/<run-id> --model gpt-5Claude Code 示例:
{
"agent": {
"type": "claude-code",
"executable": "claude"
},
"execution": {
"claude_command": [
"{agent_executable}",
"-p",
"--output-format",
"stream-json",
"--verbose",
"--dangerously-skip-permissions",
"Read and execute work/INSTRUCTION.md. Work autonomously, do not ask for human input, and write all required result artifacts."
]
}
}使用 Agent Inspector 路由时,agent-inspector/MiniMax-M3 会传给 Claude Code
为 --model MiniMax-M3;runner 会在隔离 worker 环境中注入
ANTHROPIC_BASE_URL 和 ANTHROPIC_API_KEY。
Codex CLI 也会去掉 agent-inspector/ 前缀,并会把 agent-inspector/MiniMax M3 这类显示名规范化成 --model MiniMax-M3。
使用 agent-inspector/* 模型路由时,先确认 Agent Inspector 已经启动:
agent-inspector
Invoke-RestMethod http://127.0.0.1:25947/api/health健康检查期望返回 {"status":"ok"}。http://127.0.0.1:25947/proxy 是注入给 OpenCode/MiMo 类 runner 的 LLM_BASE_URL,也是注入给 Claude Code 的 ANTHROPIC_BASE_URL,不是浏览器页面;浏览器 UI 使用 http://127.0.0.1:25947/。真实调度成功后,可以在选中 runner 的日志目录里查看,例如 runs/<run-id>/<agent-log-dir>/opencode.log。
选中的 agent CLI 不可用时,可以先 dry-run 验证调度:
python -m eval batch --config config/eval.local.json --count 5 --parallel 5 --dry-runJenkins 使用
配置参考:
Jenkinsfile 暴露了可配置参数。核心参数:
EVAL_CONFIG=
CONCURRENCY=1
TIMEOUT_MINUTES=90
IDLE_TIMEOUT_MINUTES=10
DRY_RUN=false
SMOKE_MODE=false
ISOLATED_ENV=true
STREAM_OUTPUT=true
CONSOLE_LOG_MODE=important
RUNNER_MATRIX=
MODEL_MATRIX=
REASONING_VARIANT=high
PROVIDER_API_FAILURE_GRACE=5
REQUIRE_WORK_VERSION=true
TASK_REPO_URL=<题目仓库 HTTPS URL>
TASK_REPO_SSH_URL=<题目仓库 SSH URL,可选>
TASK_REPO_REF=main
WORK_REPO_URL=<work 仓库 HTTPS URL>
WORK_REPO_SSH_URL=<work 仓库 SSH URL,可选>
WORK_REPO_REF=main
WORK_REPO_WORK_DIR=workDeepSeek 这类模型在子代理或子进程阶段可能长时间没有输出,建议只给 DeepSeek worker 放宽 idle timeout,其他模型仍保持默认 10 分钟:
IDLE_TIMEOUT_MINUTES=deepseek-v4-pro=30,default=10一个 Jenkins 任务也可以同时跑多个 runner adapter。RUNNER_MATRIX 控制每个 worker 使用哪个 runner,MODEL_MATRIX 控制每个 worker 使用哪个模型,二者都按 worker 顺序对齐,数量不足时复用最后一个值:
CONCURRENCY=4
RUNNER_MATRIX=codex,opencode,mimocode,claude
MODEL_MATRIX=agent-inspector/MiniMax-M3,agent-inspector/deepseek-v4-pro,agent-inspector/glm-5.2,agent-inspector/MiniMax-M3
DRY_RUN=false混合 runner 模式下,task/work 输入仍然只拉取一次;每个 worker 会在 <external-runs>/configs/ 下生成自己的 runner-specific effective config,再进入 prepare/execute/score。
Jenkins 执行时会先生成本轮有效配置:
python -m eval write-effective-config --config <EVAL_CONFIG> --output <external-runs>/eval.effective.json [repo overrides]
python -m eval fetch-task --config <external-runs>/eval.effective.json --output <external-runs>/inputs/task --replace-existing
python -m eval fetch-work --config <external-runs>/eval.effective.json --output <external-runs>/inputs/work --replace-existing然后每个 worker 进入独立空间:
<external-runs>/<run-id>/
inputs/task/
inputs/work/
workspace/project/
workspace/work/
runtime/home/
runtime/tmp/
runtime/cache/
runtime/config/
runtime/m2-repository/
control/timeout.json每个 worker 仍然作为 Jenkins parallel 分支运行,分支名称包含 worker 序号、runner 和模型短名,例如 01 codex MiniMax-M3。分支内部的 config、准备、Inspector create/attach/complete、资产安装、执行和打分会作为日志段打印,不再拆成一堆 Jenkins 子节点:
worker 01 codex MiniMax-M3: effective config
worker 01 codex MiniMax-M3: prepare
worker 01 codex MiniMax-M3: inspector create
worker 01 codex MiniMax-M3: install agent assets
worker 01 codex MiniMax-M3: execute
worker 01 codex MiniMax-M3: inspector attach
worker 01 codex MiniMax-M3: score
worker 01 codex MiniMax-M3: inspector completeAgent Asset 安装
默认安装规则由 runner adapter 决定。OpenCode 的默认安装规则示例:
work/work/skills/* -> .opencode/skills
work/work/skills/*.md -> .opencode/agents
work/work/skills/versions.json -> .opencode/skills
work/skills/* -> .opencode/skills
work/skills/*.md -> .opencode/agents这样可以兼容嵌套布局和扁平布局,且不依赖用户机器上的全局 runner 配置目录。其他 runner 会使用各自的默认目录,例如 MiMo Code 使用 .mimocode/*,Codex CLI 使用 .codex/*,Claude Code 使用 .claude/*。
如果需要额外安装目录,可以使用 Jenkins AGENT_ASSET_INSTALLS,目标目录需要匹配当前 runner:
work/commands=>.opencode/command
work/references=>.opencode/references评分与产物
确定性评分总分 100:
artifacts/30:必需结果文件、trace、final marker、报告产物boundary/20:保护路径没有被修改validation/30:验证摘要和 pass 证据final_status/20:最终状态和 finality marker
验证摘要支持两种 helper schema:命令维度的 results[],以及阶段维度的 phases[] 加顶层 status。如果 phases[] 中所有阶段都是 PASS,harness 会把它当作正式的机器可读验证证据;只写在 work/result/output.md 里的成功描述仍然只是弱证据,不能替代 work/logs/trace/validation/**/summary.json。
最终报告也会被检查。work/reports/final-consistency-report.md 如果仍然是 helper 自动创建的 placeholder,score 会记录 final-artifact finding,并在 evaluation.json / judge-input.md 中输出 final_report_evidence,不会因为一个 PASS marker 就把它当成完整交付。
每个 run 会输出:
runs/<run-id>/evaluation/evaluation.json
runs/<run-id>/evaluation/changed-files.md
runs/<run-id>/evaluation/judge-input.md
runs/<run-id>/evaluation/recommendations.md
runs/<run-id>/evaluation/next-action.json
runs/<run-id>/evaluation/skill-improvement-input.json
runs/<run-id>/<agent-log-dir>/opencode.log
runs/<run-id>/workspace/work/result/output.md
runs/<run-id>/workspace/work/reports/**skill-improvement-input.json is the machine-readable input for the next Skill/Agent/helper/Instruction optimization. It includes the primary failure category, failure taxonomy, target candidates, evidence paths, final report evidence, and recommended change scope.
<agent-log-dir> 由当前 runner 决定,例如 opencode、mimocode、codex 或 claude。每个 run 会写入 <agent-log-dir>/sessions.json 和 .md;启用 Agent Inspector 时,如果 agent 日志没有解析出足够 session 信息,平台会尝试从 Inspector /api/logs 回填 evidence 链接。批次 summary.md 和 HTML 报告会包含 Root Cause Digest,集中展示失败原因、诊断文件、Inspector 证据和下一步建议。
批次汇总会输出:
runs/batches/<batch-id>/summary.json
runs/batches/<batch-id>/summary.md
runs/batches/<batch-id>/model-report.json
runs/batches/<batch-id>/model-report.md
runs/batches/<batch-id>/experiment.json
runs/batches/<batch-id>/comparison.json
runs/batches/<batch-id>/comparison.md
runs/history/**
runs/eval.effective.json校验归档 JSON:
python -m eval validate-artifacts --root runs --batch-id <batch-id> --count <count>常用运维命令:
python -m eval runner-presets
python -m eval runner-version-check --config config/eval.local.json --markdown-output runs/preflight/runner-version-check.md
python -m eval clean-runs --config config/eval.local.json --keep 10 --dry-run
python -m eval release-notes --from-ref <previous-tag> --to-ref HEAD --output docs/release-notes-draft.mdProvider 与超时容错
框架会尽早识别并分类以下问题:
- provider/model 配置错误
- provider 不可达
- invalid credentials
- rate limit
- provider API stream error
- agent idle timeout
- worker 总超时
PROVIDER_API_FAILURE_GRACE 默认是 5,表示普通 provider API stream error 可以容忍 5 次再终止。rate limit、鉴权错误、未知模型等 fatal 问题会提前终止,并保留部分产物供分析。
Provider failure 检测现在按 runner 做结构化识别。对于 JSON/JSONL 日志,harness 只扫描明确的错误载体,例如顶层 error、request/response/session/turn failed、Claude result.is_error=true 或 error subtype、system API error、level=ERROR/FATAL/CRITICAL、以及明确的 runtime error payload。assistant/model 分析、user tool result、tool-use 事件、Codex command_execution 输出和 agent message 会被忽略,所以业务域里的 401、403、FORBIDDEN、UNAUTHORIZED、/api/v1、errorCode 等内容会被当作任务证据,而不会误判为 provider outage。普通 stderr/stdout 仍会扫描真实 provider token,例如连接失败、无效凭据、rate limit、未知模型和 retry exhaustion。
当 worker 因 idle timeout、总 timeout、provider fail-fast 或手动中断被清理时,runner 会先写入 <agent-log-dir>/process-diagnostics-*.json 和 .md。诊断文件包含进程树、命令行、elapsed/idle 时间、监听的 artifact 路径、最近日志尾部等信息;Jenkins 归档 runs/** 后可以直接查看,用来定位 DeepSeek 这类子代理/子进程静默时到底卡在哪个进程或阶段。
如果 worker 在写完验证日志或修改代码后超时,但没有写完最终产物,score 阶段会先尝试 execution.final_result_recovery_helpers 中的 work helper,并兼容旧版 helper 参数;如果 helper 不存在或不兼容,harness 会内置生成 PARTIAL 的 output.md、FINAL_RESULT.json、FINAL_RESULT.md,同时保留失败原因和原始输出备份。这样 Jenkins 仍能 aggregate/export Evidence,但不会把失败验证掩盖成成功。
测试
python -m unittest可选开发工具:
python -m pip install -e ".[dev]"
python -m pytest
python -m coverage run -m unittest
python -m ruff check .测试覆盖:
- clone URL 顺序和 fallback
- task/work 输入物化
- agent worker 执行包装
- provider failure 分类
- timeout control
- artifact export
- schema validation
- model report 和 batch summary
演进策略
推荐的优化粒度:
- 看
runs/batches/<batch-id>/summary.md和model-report.md - 找到最小的 Skill、Agent、知识包、helper 或 Instruction 问题
- 修改并做版本控制
- 重新运行同一 task/work 输入
- 通过
comparison.md和 history 判断是否接受
这个仓库的目标不是让一次模型运行偶然成功,而是让 Agent 系统可以持续进化、可审计、可复盘。
