@tonyclaw/eval-harness 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,597 @@
1
+ # Eval Harness
2
+
3
+ [English documentation](README.md)
4
+
5
+ Release: `v0.2.18`
6
+
7
+ 可配置的 runner `work/` 包评测框架。
8
+
9
+ Eval Harness 不绑定 OpenCode。它是面向 CodeX/Codex CLI、OpenCode、MiMo Code、Claude Code,以及后续更多 runner adapter 的评测平台。runner 抽象和扩展方式见 [Runner Adapters](docs/runners.md)。
10
+
11
+ 默认配置是通用模板,不绑定具体赛题。你可以通过 `config/eval.local.json`、
12
+ Jenkins 参数,或者生成的 `eval.effective.json`,把它指向任意题目 Git 仓库
13
+ 和任意 submitted work Git 仓库。
14
+
15
+ Jenkins 配置入口:
16
+
17
+ - [Jenkins 使用指南](docs/jenkins.md)
18
+ - [Jenkins 参数对比](docs/jenkins-parameter-comparison.md)
19
+
20
+ 这个项目把一次性的 runner 运行,变成可复现、可比较、可持续优化的证据闭环。它不只看模型在控制台里“看起来聪明不聪明”,而是检查整个 Agent 系统是否能稳定完成以下工作:
21
+
22
+ - 理解动态题目目录和 `work/INSTRUCTION.md`
23
+ - 修改题目工程中的代码
24
+ - 输出 `work/result`、`work/reports`、`work/logs/trace` 等交付物
25
+ - 运行验证并记录证据
26
+ - 汇总评分、失败原因和下一轮 Skill/Agent/Instruction 优化建议
27
+
28
+ ## 工作流
29
+
30
+ ```text
31
+ 准备干净题目工程
32
+ -> 使用 submitted work/INSTRUCTION.md 启动选中的 runner
33
+ -> 收集 result、reports、trace、验证日志、代码 diff
34
+ -> 确定性评分
35
+ -> 给出 ACCEPT / INVESTIGATE / REJECT 决策
36
+ -> 生成 recommendations 和 next-action
37
+ -> CodeX/Codex 或人工据此优化一个小的 Skill/Agent/Instruction
38
+ -> 重新运行并比较
39
+ ```
40
+
41
+ 当前仓库提供确定性 runner/evaluator 和 Jenkins pipeline。它会给出下一轮优化输入,但不会自动修改 Skills。
42
+
43
+ ## 你需要配置什么
44
+
45
+ 框架把评测拆成三个独立输入,方便分享给其他团队或同学:
46
+
47
+ | 配置项 | Jenkins 参数 | JSON 配置字段 | 说明 |
48
+ | --- | --- | --- | --- |
49
+ | 题目仓库 | `TASK_REPO_*` | `source_repo` | 原始题目、工程或待修复项目。每个 worker 会复制为 `workspace/project`。 |
50
+ | work 仓库 | `WORK_REPO_*` | `submission_repo` | 参赛或实验用的 `work` 包,包含 `INSTRUCTION.md`、Skills、Agents、helper scripts。 |
51
+ | 执行矩阵 | `MODEL_MATRIX`、`CONCURRENCY`、`TIMEOUT_MINUTES` 等 | `execution` | 控制模型、并发、超时、provider 容错、验证、容器等。 |
52
+
53
+ 每次 Jenkins 构建都会生成实际生效配置:
54
+
55
+ ```text
56
+ <external-runs>/eval.effective.json
57
+ runs/eval.effective.json
58
+ ```
59
+
60
+ 这个文件记录本轮使用的 task/work 仓库 URL、SSH URL、ref、work 目录和 fallback URL,方便复现。
61
+
62
+ ## 输入契约
63
+
64
+ 为了换题后仍然可用,建议输入满足下面的契约:
65
+
66
+ | 输入 | 要求 | 说明 |
67
+ | --- | --- | --- |
68
+ | Task repository | 一个 Git 仓库,包含需要被 agent 修复的题目工程。 | Git 元数据会尽量保留,用于生成代码 diff;没有 `.git` 时会 fallback 到文件树比较。 |
69
+ | Work repository | 一个 Git 仓库,里面有提交的 work 包。 | `submission_repo.work_dir` 或 Jenkins `WORK_REPO_WORK_DIR` 必须指向包含 `INSTRUCTION.md` 的目录。 |
70
+ | Work assets | Skills、Agents、helper scripts、结果和日志约定。 | 默认支持 `work/work/skills/*` 和 `work/skills/*` 两种布局。 |
71
+ | 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/**`。 | 缺失会被当成证据缺口评分,而不是被控制台日志掩盖。 |
72
+
73
+ 本地调试时也可以不用 Git 仓库,直接传:
74
+
75
+ ```text
76
+ PROJECT_SOURCE=<本地题目目录>
77
+ WORK_SOURCE=<本地 work 目录>
78
+ ```
79
+
80
+ 共享 Jenkins job 时更推荐使用 Git URL 参数。
81
+
82
+ ## 当前默认值
83
+
84
+ - 题目仓库:`config/eval.example.json` 中的占位 URL
85
+ - work 仓库:`config/eval.example.json` 中的占位 URL
86
+ - work 目录:`work`
87
+ - 默认模型:不写死;使用所选 runner 当前默认,或通过配置/Jenkins `MODEL_MATRIX` 指定
88
+ - 默认 reasoning variant:`high`
89
+ - 默认 worker 超时:`90` 分钟
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`
91
+ - 默认 idle timeout:`10` 分钟无 agent 输出或 artifact 活动
92
+ - 默认 fetch timeout:`15` 分钟
93
+ - Jenkins 并发:默认 `1`,可配置 `1~5`
94
+ - Jenkins runner matrix:逗号或换行分隔的 runner adapter,按 worker 顺序分配;留空则所有 worker 使用 config 中的 `agent.type`
95
+ - Jenkins 配置文件:`EVAL_CONFIG` 可留空;会优先用本地 `OEVAL_CONFIG`,再 fallback 到 `config/eval.example.json`
96
+ - Jenkins 定时运行:仓库默认不注册 cron;需要时在 Jenkins 环境中自行配置 timer
97
+ - work 版本检查:默认 `REQUIRE_WORK_VERSION=true`,简单接入时可设为 `false`
98
+
99
+ 这些只是默认值,不是框架限制。
100
+
101
+ ## 快速开始
102
+
103
+ 平台无关。推荐 Python 3.10+,真实评分环境已经按 Python 3.12 兼容。外部工具需要在 `PATH` 或 Jenkins `EXTRA_PATHS` / `OEVAL_EXTRA_PATHS` 中可用:
104
+
105
+ - `git`
106
+ - `node`
107
+ - 选中的 agent CLI:`opencode`、`mimo`、`codex` 或 `claude`
108
+ - `agent-inspector`
109
+ - `mvn` 和 Java,启用验证时需要
110
+
111
+ Ubuntu 评测基线示例:
112
+
113
+ ```text
114
+ Ubuntu 24.04.4
115
+ Python 3.12
116
+ Node v24.13.0
117
+ OpenJDK 21
118
+ Maven 3.9.11
119
+ Maven home: /usr/local/maven3
120
+ ```
121
+
122
+ Linux/macOS:
123
+
124
+ ```bash
125
+ python3 -m venv .venv
126
+ . .venv/bin/activate
127
+ python -m pip install -e .
128
+ eval-harness --help
129
+ ```
130
+
131
+ Windows PowerShell:
132
+
133
+ ```powershell
134
+ python -m venv .venv
135
+ .\.venv\Scripts\python -m pip install -e .
136
+ .\.venv\Scripts\eval-harness --help
137
+ ```
138
+
139
+ NPM 包装入口:
140
+
141
+ ```bash
142
+ npx @tonyclaw/eval-harness --help
143
+ npx @tonyclaw/eval-harness runner-presets
144
+ ```
145
+
146
+ npm 包是一个很薄的 Node.js 启动器,会调用包内自带的 Python 源码。
147
+ 运行机器仍然需要 `PATH` 中有 Python 3.10+,也可以通过
148
+ `EVAL_HARNESS_PYTHON` 指定 Python 可执行文件。从仓库外运行时,它会
149
+ fallback 到 npm 包内置的 `config/eval.example.json` 和 `schemas/`。
150
+
151
+ 不安装包也可以运行:
152
+
153
+ ```bash
154
+ PYTHONPATH=src python -m eval --help
155
+ ```
156
+
157
+ ```powershell
158
+ $env:PYTHONPATH = "src"
159
+ python -m eval --help
160
+ ```
161
+
162
+ ## 配置自己的题目仓库和 work 仓库
163
+
164
+ 复制配置文件:
165
+
166
+ ```bash
167
+ cp config/eval.example.json config/eval.local.json
168
+ ```
169
+
170
+ ```powershell
171
+ Copy-Item config/eval.example.json config/eval.local.json
172
+ ```
173
+
174
+ 修改 `config/eval.local.json`:
175
+
176
+ ```json
177
+ {
178
+ "source_repo": {
179
+ "url": "https://example.com/course/task.git",
180
+ "ssh_url": "git@example.com:course/task.git",
181
+ "ref": "main",
182
+ "fallback_urls": []
183
+ },
184
+ "submission_repo": {
185
+ "url": "https://example.com/team/work.git",
186
+ "ssh_url": "git@example.com:team/work.git",
187
+ "ref": "main",
188
+ "work_dir": "work",
189
+ "fallback_urls": []
190
+ }
191
+ }
192
+ ```
193
+
194
+ 也可以不修改文件,临时生成 effective config:
195
+
196
+ ```bash
197
+ PYTHONPATH=src python -m eval write-effective-config \
198
+ --config config/eval.example.json \
199
+ --output /tmp/oeval/eval.effective.json \
200
+ --task-repo-url https://example.com/course/task.git \
201
+ --task-repo-ref main \
202
+ --work-repo-url https://example.com/team/work.git \
203
+ --work-repo-ref main \
204
+ --work-repo-work-dir work
205
+ ```
206
+
207
+ 生成后按普通配置使用:
208
+
209
+ ```bash
210
+ PYTHONPATH=src python -m eval fetch-task --config /tmp/oeval/eval.effective.json --output /tmp/oeval/inputs/task --replace-existing
211
+ PYTHONPATH=src python -m eval fetch-work --config /tmp/oeval/eval.effective.json --output /tmp/oeval/inputs/work --replace-existing
212
+ 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
213
+ ```
214
+
215
+ ## 本地运行
216
+
217
+ 准备:
218
+
219
+ ```powershell
220
+ python -m eval prepare --config config/eval.local.json
221
+ ```
222
+
223
+ 执行配置的 agent runner:
224
+
225
+ ```powershell
226
+ python -m eval execute --run runs/<run-id>
227
+ ```
228
+
229
+ 评分:
230
+
231
+ ```powershell
232
+ python -m eval score --run runs/<run-id>
233
+ ```
234
+
235
+ 一条命令跑完整生命周期:
236
+
237
+ ```powershell
238
+ python -m eval run --config config/eval.local.json
239
+ ```
240
+
241
+ 并发运行:
242
+
243
+ ```powershell
244
+ python -m eval batch --config config/eval.local.json --count 5 --parallel 5
245
+ ```
246
+
247
+ 本地一次性运行 Jenkins 类似流程。该入口会生成有效配置、拉取 task/work 输入、并行执行 worker、汇总批次、导出产物,并可选校验导出的 JSON:
248
+
249
+ ```powershell
250
+ python -m eval run-once `
251
+ --config config/eval.local.json `
252
+ --batch-id local-three-model `
253
+ --count 3 `
254
+ --parallel 3 `
255
+ --prefer-ssh `
256
+ --model agent-inspector/MiniMax-M3 `
257
+ --model agent-inspector/deepseek-v4-pro `
258
+ --model agent-inspector/glm-5.2 `
259
+ --timeout-minutes 90 `
260
+ --idle-timeout-minutes deepseek-v4-pro=30,default=10 `
261
+ --variant high `
262
+ --replace-existing `
263
+ --validate-artifacts
264
+ ```
265
+
266
+ 可以用 `--task-repo-url`、`--task-repo-ref`、`--work-repo-url`、`--work-repo-ref`、`--work-repo-work-dir` 覆盖本轮 task/work 仓库参数,私有配置不用提交到仓库。首次验证流程时可加 `--dry-run`。
267
+
268
+ 短链路真实调度可以使用 `--smoke`。它会强制 `DRY_RUN=false`、`timeout=5`、`idle_timeout=2`,并关闭 validation、work version 和发布门禁,适合先确认 runner、模型路由和 Agent Inspector 链路:
269
+
270
+ ```powershell
271
+ python -m eval run-once `
272
+ --config config/eval.local.json `
273
+ --smoke `
274
+ --count 3 `
275
+ --parallel 3 `
276
+ --model agent-inspector/MiniMax-M3 `
277
+ --model agent-inspector/deepseek-v4-pro `
278
+ --model agent-inspector/glm-5.2
279
+ ```
280
+
281
+ ## Agent Runner
282
+
283
+ 当前支持的 `agent.type`:
284
+
285
+ - `codex` 或 `codex-cli`:CodeX/Codex CLI runner
286
+ - `opencode`:OpenCode CLI runner
287
+ - `mimocode` 或 `mimo`:MiMo Code runner
288
+ - `claude` 或 `claude-code`:Claude Code runner
289
+
290
+ 每个 runner 都有自己的默认命令、日志目录、隔离运行时 profile 和 work asset 安装目录。需要覆盖命令时,可使用 `execution.agent.command`、`execution.<runner>.command` 或 `execution.<runner>_command`。
291
+
292
+ 查看 runner 默认参数:
293
+
294
+ ```powershell
295
+ python -m eval runner-presets
296
+ python -m eval runner-presets --format json
297
+ python -m eval runner-version-check --config config/eval.local.json --markdown-output runs/preflight/runner-version-check.md
298
+ ```
299
+
300
+ CodeX/Codex CLI 示例:
301
+
302
+ ```json
303
+ {
304
+ "agent": {
305
+ "type": "codex"
306
+ },
307
+ "execution": {
308
+ "codex_command": [
309
+ "{agent_executable}",
310
+ "exec",
311
+ "--json",
312
+ "--skip-git-repo-check",
313
+ "--dangerously-bypass-approvals-and-sandbox",
314
+ "--cd",
315
+ "{workspace}",
316
+ "Read and execute work/INSTRUCTION.md. Work autonomously, do not ask for human input, and write all required result artifacts."
317
+ ]
318
+ }
319
+ }
320
+ ```
321
+
322
+ 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 全局环境变量中,不要写入共享配置。
323
+
324
+ CodeX/Codex CLI 同样支持通用 `--model` 覆盖:
325
+
326
+ ```powershell
327
+ python -m eval execute --run runs/<run-id> --model gpt-5
328
+ ```
329
+
330
+ Claude Code 示例:
331
+
332
+ ```json
333
+ {
334
+ "agent": {
335
+ "type": "claude-code",
336
+ "executable": "claude"
337
+ },
338
+ "execution": {
339
+ "claude_command": [
340
+ "{agent_executable}",
341
+ "-p",
342
+ "--output-format",
343
+ "stream-json",
344
+ "--verbose",
345
+ "--dangerously-skip-permissions",
346
+ "Read and execute work/INSTRUCTION.md. Work autonomously, do not ask for human input, and write all required result artifacts."
347
+ ]
348
+ }
349
+ }
350
+ ```
351
+
352
+ 使用 Agent Inspector 路由时,`agent-inspector/MiniMax-M3` 会传给 Claude Code
353
+ 为 `--model MiniMax-M3`;runner 会在隔离 worker 环境中注入
354
+ `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_API_KEY`。
355
+
356
+ Codex CLI 也会去掉 `agent-inspector/` 前缀,并会把 `agent-inspector/MiniMax M3` 这类显示名规范化成 `--model MiniMax-M3`。
357
+
358
+ 使用 `agent-inspector/*` 模型路由时,先确认 Agent Inspector 已经启动:
359
+
360
+ ```powershell
361
+ agent-inspector
362
+ Invoke-RestMethod http://127.0.0.1:25947/api/health
363
+ ```
364
+
365
+ 健康检查期望返回 `{"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`。
366
+
367
+ 选中的 agent CLI 不可用时,可以先 dry-run 验证调度:
368
+
369
+ ```powershell
370
+ python -m eval batch --config config/eval.local.json --count 5 --parallel 5 --dry-run
371
+ ```
372
+
373
+ ## Jenkins 使用
374
+
375
+ 配置参考:
376
+
377
+ - [Jenkins 使用指南](docs/jenkins.md)
378
+ - [Jenkins 参数对比](docs/jenkins-parameter-comparison.md)
379
+
380
+ Jenkinsfile 暴露了可配置参数。核心参数:
381
+
382
+ ```text
383
+ EVAL_CONFIG=
384
+ CONCURRENCY=1
385
+ TIMEOUT_MINUTES=90
386
+ IDLE_TIMEOUT_MINUTES=10
387
+ DRY_RUN=false
388
+ SMOKE_MODE=false
389
+ ISOLATED_ENV=true
390
+ STREAM_OUTPUT=true
391
+ CONSOLE_LOG_MODE=important
392
+ RUNNER_MATRIX=
393
+ MODEL_MATRIX=
394
+ REASONING_VARIANT=high
395
+ PROVIDER_API_FAILURE_GRACE=5
396
+ REQUIRE_WORK_VERSION=true
397
+ TASK_REPO_URL=<题目仓库 HTTPS URL>
398
+ TASK_REPO_SSH_URL=<题目仓库 SSH URL,可选>
399
+ TASK_REPO_REF=main
400
+ WORK_REPO_URL=<work 仓库 HTTPS URL>
401
+ WORK_REPO_SSH_URL=<work 仓库 SSH URL,可选>
402
+ WORK_REPO_REF=main
403
+ WORK_REPO_WORK_DIR=work
404
+ ```
405
+
406
+ DeepSeek 这类模型在子代理或子进程阶段可能长时间没有输出,建议只给 DeepSeek worker 放宽 idle timeout,其他模型仍保持默认 10 分钟:
407
+
408
+ ```text
409
+ IDLE_TIMEOUT_MINUTES=deepseek-v4-pro=30,default=10
410
+ ```
411
+
412
+ 一个 Jenkins 任务也可以同时跑多个 runner adapter。`RUNNER_MATRIX` 控制每个 worker 使用哪个 runner,`MODEL_MATRIX` 控制每个 worker 使用哪个模型,二者都按 worker 顺序对齐,数量不足时复用最后一个值:
413
+
414
+ ```text
415
+ CONCURRENCY=4
416
+ RUNNER_MATRIX=codex,opencode,mimocode,claude
417
+ MODEL_MATRIX=agent-inspector/MiniMax-M3,agent-inspector/deepseek-v4-pro,agent-inspector/glm-5.2,agent-inspector/MiniMax-M3
418
+ DRY_RUN=false
419
+ ```
420
+
421
+ 混合 runner 模式下,task/work 输入仍然只拉取一次;每个 worker 会在 `<external-runs>/configs/` 下生成自己的 runner-specific effective config,再进入 prepare/execute/score。
422
+
423
+ Jenkins 执行时会先生成本轮有效配置:
424
+
425
+ ```powershell
426
+ python -m eval write-effective-config --config <EVAL_CONFIG> --output <external-runs>/eval.effective.json [repo overrides]
427
+ python -m eval fetch-task --config <external-runs>/eval.effective.json --output <external-runs>/inputs/task --replace-existing
428
+ python -m eval fetch-work --config <external-runs>/eval.effective.json --output <external-runs>/inputs/work --replace-existing
429
+ ```
430
+
431
+ 然后每个 worker 进入独立空间:
432
+
433
+ ```text
434
+ <external-runs>/<run-id>/
435
+ inputs/task/
436
+ inputs/work/
437
+ workspace/project/
438
+ workspace/work/
439
+ runtime/home/
440
+ runtime/tmp/
441
+ runtime/cache/
442
+ runtime/config/
443
+ runtime/m2-repository/
444
+ control/timeout.json
445
+ ```
446
+
447
+ 每个 worker 仍然作为 Jenkins `parallel` 分支运行,分支名称包含 worker 序号、runner 和模型短名,例如 `01 codex MiniMax-M3`。分支内部的 config、准备、Inspector create/attach/complete、资产安装、执行和打分会作为日志段打印,不再拆成一堆 Jenkins 子节点:
448
+
449
+ ```text
450
+ worker 01 codex MiniMax-M3: effective config
451
+ worker 01 codex MiniMax-M3: prepare
452
+ worker 01 codex MiniMax-M3: inspector create
453
+ worker 01 codex MiniMax-M3: install agent assets
454
+ worker 01 codex MiniMax-M3: execute
455
+ worker 01 codex MiniMax-M3: inspector attach
456
+ worker 01 codex MiniMax-M3: score
457
+ worker 01 codex MiniMax-M3: inspector complete
458
+ ```
459
+
460
+ ## Agent Asset 安装
461
+
462
+ 默认安装规则由 runner adapter 决定。OpenCode 的默认安装规则示例:
463
+
464
+ ```text
465
+ work/work/skills/* -> .opencode/skills
466
+ work/work/skills/*.md -> .opencode/agents
467
+ work/work/skills/versions.json -> .opencode/skills
468
+ work/skills/* -> .opencode/skills
469
+ work/skills/*.md -> .opencode/agents
470
+ ```
471
+
472
+ 这样可以兼容嵌套布局和扁平布局,且不依赖用户机器上的全局 runner 配置目录。其他 runner 会使用各自的默认目录,例如 MiMo Code 使用 `.mimocode/*`,Codex CLI 使用 `.codex/*`,Claude Code 使用 `.claude/*`。
473
+
474
+ 如果需要额外安装目录,可以使用 Jenkins `AGENT_ASSET_INSTALLS`,目标目录需要匹配当前 runner:
475
+
476
+ ```text
477
+ work/commands=>.opencode/command
478
+ work/references=>.opencode/references
479
+ ```
480
+
481
+ ## 评分与产物
482
+
483
+ 确定性评分总分 100:
484
+
485
+ - `artifacts` `/30`:必需结果文件、trace、final marker、报告产物
486
+ - `boundary` `/20`:保护路径没有被修改
487
+ - `validation` `/30`:验证摘要和 pass 证据
488
+ - `final_status` `/20`:最终状态和 finality marker
489
+
490
+ 验证摘要支持两种 helper schema:命令维度的 `results[]`,以及阶段维度的 `phases[]` 加顶层 `status`。如果 `phases[]` 中所有阶段都是 `PASS`,harness 会把它当作正式的机器可读验证证据;只写在 `work/result/output.md` 里的成功描述仍然只是弱证据,不能替代 `work/logs/trace/validation/**/summary.json`。
491
+
492
+ 最终报告也会被检查。`work/reports/final-consistency-report.md` 如果仍然是 helper 自动创建的 placeholder,score 会记录 final-artifact finding,并在 `evaluation.json` / `judge-input.md` 中输出 `final_report_evidence`,不会因为一个 `PASS` marker 就把它当成完整交付。
493
+
494
+ 每个 run 会输出:
495
+
496
+ ```text
497
+ runs/<run-id>/evaluation/evaluation.json
498
+ runs/<run-id>/evaluation/changed-files.md
499
+ runs/<run-id>/evaluation/judge-input.md
500
+ runs/<run-id>/evaluation/recommendations.md
501
+ runs/<run-id>/evaluation/next-action.json
502
+ runs/<run-id>/evaluation/skill-improvement-input.json
503
+ runs/<run-id>/<agent-log-dir>/opencode.log
504
+ runs/<run-id>/workspace/work/result/output.md
505
+ runs/<run-id>/workspace/work/reports/**
506
+ ```
507
+
508
+ `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.
509
+
510
+ `<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 证据和下一步建议。
511
+
512
+ 批次汇总会输出:
513
+
514
+ ```text
515
+ runs/batches/<batch-id>/summary.json
516
+ runs/batches/<batch-id>/summary.md
517
+ runs/batches/<batch-id>/model-report.json
518
+ runs/batches/<batch-id>/model-report.md
519
+ runs/batches/<batch-id>/experiment.json
520
+ runs/batches/<batch-id>/comparison.json
521
+ runs/batches/<batch-id>/comparison.md
522
+ runs/history/**
523
+ runs/eval.effective.json
524
+ ```
525
+
526
+ 校验归档 JSON:
527
+
528
+ ```powershell
529
+ python -m eval validate-artifacts --root runs --batch-id <batch-id> --count <count>
530
+ ```
531
+
532
+ 常用运维命令:
533
+
534
+ ```powershell
535
+ python -m eval runner-presets
536
+ python -m eval runner-version-check --config config/eval.local.json --markdown-output runs/preflight/runner-version-check.md
537
+ python -m eval clean-runs --config config/eval.local.json --keep 10 --dry-run
538
+ python -m eval release-notes --from-ref <previous-tag> --to-ref HEAD --output docs/release-notes-draft.md
539
+ ```
540
+
541
+ ## Provider 与超时容错
542
+
543
+ 框架会尽早识别并分类以下问题:
544
+
545
+ - provider/model 配置错误
546
+ - provider 不可达
547
+ - invalid credentials
548
+ - rate limit
549
+ - provider API stream error
550
+ - agent idle timeout
551
+ - worker 总超时
552
+
553
+ `PROVIDER_API_FAILURE_GRACE` 默认是 `5`,表示普通 provider API stream error 可以容忍 5 次再终止。rate limit、鉴权错误、未知模型等 fatal 问题会提前终止,并保留部分产物供分析。
554
+
555
+ 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。
556
+
557
+ 当 worker 因 idle timeout、总 timeout、provider fail-fast 或手动中断被清理时,runner 会先写入 `<agent-log-dir>/process-diagnostics-*.json` 和 `.md`。诊断文件包含进程树、命令行、elapsed/idle 时间、监听的 artifact 路径、最近日志尾部等信息;Jenkins 归档 `runs/**` 后可以直接查看,用来定位 DeepSeek 这类子代理/子进程静默时到底卡在哪个进程或阶段。
558
+
559
+ 如果 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,但不会把失败验证掩盖成成功。
560
+
561
+ ## 测试
562
+
563
+ ```powershell
564
+ python -m unittest
565
+ ```
566
+
567
+ 可选开发工具:
568
+
569
+ ```powershell
570
+ python -m pip install -e ".[dev]"
571
+ python -m pytest
572
+ python -m coverage run -m unittest
573
+ python -m ruff check .
574
+ ```
575
+
576
+ 测试覆盖:
577
+
578
+ - clone URL 顺序和 fallback
579
+ - task/work 输入物化
580
+ - agent worker 执行包装
581
+ - provider failure 分类
582
+ - timeout control
583
+ - artifact export
584
+ - schema validation
585
+ - model report 和 batch summary
586
+
587
+ ## 演进策略
588
+
589
+ 推荐的优化粒度:
590
+
591
+ 1. 看 `runs/batches/<batch-id>/summary.md` 和 `model-report.md`
592
+ 2. 找到最小的 Skill、Agent、知识包、helper 或 Instruction 问题
593
+ 3. 修改并做版本控制
594
+ 4. 重新运行同一 task/work 输入
595
+ 5. 通过 `comparison.md` 和 history 判断是否接受
596
+
597
+ 这个仓库的目标不是让一次模型运行偶然成功,而是让 Agent 系统可以持续进化、可审计、可复盘。
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { spawnSync } = require("child_process");
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ const packageRoot = path.resolve(__dirname, "..");
9
+ const srcDir = path.join(packageRoot, "src");
10
+ const defaultConfig = path.join(packageRoot, "config", "eval.example.json");
11
+ const schemaDir = path.join(packageRoot, "schemas");
12
+
13
+ const env = { ...process.env };
14
+ env.PYTHONPATH = [srcDir, env.PYTHONPATH].filter(Boolean).join(path.delimiter);
15
+ if (!env.EVAL_HARNESS_DEFAULT_CONFIG && fs.existsSync(defaultConfig)) {
16
+ env.EVAL_HARNESS_DEFAULT_CONFIG = defaultConfig;
17
+ }
18
+ if (!env.EVAL_HARNESS_SCHEMA_DIR && fs.existsSync(schemaDir)) {
19
+ env.EVAL_HARNESS_SCHEMA_DIR = schemaDir;
20
+ }
21
+
22
+ function pythonCandidates() {
23
+ const candidates = [];
24
+ if (env.EVAL_HARNESS_PYTHON) {
25
+ candidates.push({ command: env.EVAL_HARNESS_PYTHON, args: [] });
26
+ }
27
+ if (env.PYTHON) {
28
+ candidates.push({ command: env.PYTHON, args: [] });
29
+ }
30
+ if (process.platform === "win32") {
31
+ candidates.push({ command: "py", args: ["-3"] });
32
+ }
33
+ candidates.push({ command: "python3", args: [] });
34
+ candidates.push({ command: "python", args: [] });
35
+ return candidates;
36
+ }
37
+
38
+ const cliArgs = process.argv.slice(2);
39
+ let lastError = null;
40
+ for (const candidate of pythonCandidates()) {
41
+ const result = spawnSync(
42
+ candidate.command,
43
+ [...candidate.args, "-m", "eval", ...cliArgs],
44
+ {
45
+ cwd: process.cwd(),
46
+ env,
47
+ stdio: "inherit"
48
+ }
49
+ );
50
+ if (result.error && result.error.code === "ENOENT") {
51
+ lastError = result.error;
52
+ continue;
53
+ }
54
+ if (result.error) {
55
+ console.error(`eval-harness failed to start Python: ${result.error.message}`);
56
+ process.exit(1);
57
+ }
58
+ process.exit(result.status === null ? 1 : result.status);
59
+ }
60
+
61
+ console.error("eval-harness requires Python 3.10+ on PATH.");
62
+ if (lastError) {
63
+ console.error(lastError.message);
64
+ }
65
+ process.exit(1);