第 6 章:使用 Solution 与 Oracle 证明任务可解
第 5 章的 python-port-repair 已经能稳定构造故障:服务监听 8001,而公开契约要求修复配置并让 127.0.0.1:8000/health 返回健康状态。现在出现一个更隐蔽的问题:环境稳定地失败,不等于 Task 稳定地可解。也许修复命令依赖镜像里没有的工具,也许配置只有 root 能写,也许测试检查了指令没有公开的状态。这样的 Task 可以反复得到 0 分,却测不到 Agent 能力。
本章为它补上 solution/solve.sh,再用 Oracle 执行这条参考路径。目标不是把标准答案变成唯一解法,而是建立一条可审计的存在性证据:在与 Agent 相同的初态、用户和运行边界下,至少有一组动作能使 Verifier 接受结果。随后还要用 Nop、错误 Solution、重复 Trial 和隔离检查识别“初态已经成功”“只在作者机器成功”和“Oracle 与测试共同犯错”。
读完本章,读者应当能够:
- 解释 Solution、Oracle、instruction 和 Verifier 各自承担什么职责;
- 编写不依赖隐藏路径、权限或临时网络的
solution/solve.sh; - 在不限制其他合法解法的前提下,把参考路径与验收结果分开;
- 识别不可解、带隐藏前提和偶然可解的 Task;
- 用正向、负向和重复运行组成 solvability test(可解性测试)。
6.1 Oracle 证明的究竟是什么
Harbor v0.18.0 的单步 Trial 先运行 Agent,再收集日志与 Artifact,最后运行 Verifier。选择 -a oracle 时,OracleAgent 忽略传入的自然语言 instruction,定位与 [environment].os 匹配的 Solution,将整个 solution/ 上传到容器的专用路径,然后执行入口脚本;Verifier 仍在它之后独立评分。12
这条链需要分成四个对象理解:
| 对象 | 回答的问题 | 不应承担的职责 |
|---|---|---|
| instruction | Agent 被允许看见什么、必须达到什么终态 | 不提供实例答案或测试实现 |
| Solution | 作者知道的一条可行操作路径是什么 | 不定义唯一合法实现,不自行判分 |
| Oracle | 这条参考路径能否在真实 Trial 边界内执行 | 不模拟推理能力,不评估难度 |
| Verifier | 当前终态是否满足公开契约 | 不要求复现 Solution 的命令序列 |
因此,“Oracle 得到 Reward 1”只能说明 Solution—Environment—Verifier 这一个组合存在一条通过路径。它不能说明 instruction 信息充分,不能说明真实 Agent 拥有同样的知识,也不能排除答案泄露、Verifier 假阳性或任务过于简单。Harbor 随附的 create-task Skill 本身也把 Oracle 定位为 sanity check,而不是完整质量认证。3 由此可以推断:Oracle 成功是 Task 进入评测前的必要证据,但绝不是任务质量的充分条件。
还要避免另一个误判:脚本退出 0 也不等于 Oracle 成功。v0.18.0 在 Solution 返回非零时把返回码写入 Trial 的 agent/exit-code.txt,但 OracleAgent.run() 本身不会仅因该返回码抛出异常;后续 Verifier 仍会运行。1 可解性验收必须同时检查 Trial 无异常、没有 exit-code.txt、Verifier Reward 为 1,不能只看 Job 完成或脚本退出状态。
实践中可以把“可解”再拆成三层,避免团队围绕同一个词争论:
- 动作可达:从给定初态出发,确实存在一组允许的文件、进程或工具操作能达到目标。Oracle 主要提供这一层证据。
- 信息可求:只看 instruction 与 Agent 可见状态,求解者有足够信息发现那组动作。硬编码答案的 Solution 不能提供这一层证据。
- 重复可达:在新的 Trial、相同资源与权限边界中,参考路径不依赖残留状态或偶然外部条件,能稳定达到目标。重复 Oracle 与初态检查为这一层提供证据。
三层必须同时成立,任务才适合评测。动作可达但信息不可求,是一道“作者知道答案、参评者无法推出”的猜谜题;信息充分但动作不可达,是接口承诺超过环境能力;前两层成立但重复不可达,则 Reward 混入基础设施随机性。这样的分层也决定排错顺序:先确认动作和权限,再审查信息公开,最后追踪重复运行的差异。
6.2 Oracle 的路径、用户与权限边界
对本章 Linux Task,宿主入口是 solution/solve.sh,容器内是 /solution/solve.sh,输出与错误合并写入 /logs/agent/oracle.txt。Oracle 在执行前以 root 运行 chmod +x /solution/solve.sh,随后 Solution 仍在 Trial 为 Agent 配置的默认用户作用域中执行;若 [agent].user 指定非 root 用户,脚本不会因为自己叫“Oracle”就自动获得 root 权限。14
这一点能暴露真实的权限前提。若普通 Agent 被配置为 UID 1000,而 /workspace/app/config.json 只有 root 可写,那么有三种诚实处理方式:修改镜像权限以匹配能力目标;明确允许并提供受控提权工具;或者把任务判为在当前配置下不可解。让 Solution 偷用一个真实 Agent 没有的宿主挂载或密钥,只会制造一条不具代表性的参考路径。
Harbor v0.18.0 还支持 Windows Task,但入口不是把本章 Bash 原样搬过去。os = "windows" 时,Oracle 选择 solution/solve.bat,上传到 C:/solution,再用 cmd /c 执行;.bat 不做 chmod。直接以 solve.ps1 作为入口不在该版本的发现列表中,需要从 .bat 显式调用 PowerShell。5
| 项目 | Linux Task | Windows Task |
|---|---|---|
| 宿主入口 | solution/solve.sh | solution/solve.bat |
| 容器目录 | /solution | C:/solution |
| 执行方式 | 直接执行 .sh | cmd /c solve.bat |
| 执行位处理 | Oracle 以 root 执行 chmod +x | 不适用 |
虽然 Oracle 会在容器内补执行位,仓库里的 solve.sh 仍应保存 LF 换行并带可执行位:这有利于手工调试、打包和其他工具链。chmod 无法修复 CRLF 导致的 shebang 解析错误,也无法安装缺少的解释器。
路径映射还有一个常被忽略的方向:宿主的 solution/ 是作者材料,容器中的 /solution 是 Oracle 运行期副本;脚本修改的任务状态通常位于 /workspace、/app 或镜像定义的其他工作目录。把结果写回 /solution 只会改动临时副本,除非 instruction 本来就要求那里产生输出。相反,把 Solution 的辅助数据放在 solution/data.json 是允许的,因为整个目录会一同上传;脚本应以 /solution/data.json 明确读取,并确认这些辅助数据只是参考执行所需,不会在普通 Agent Trial 中变成隐藏的必要输入。
权限审计也不能只做 ls -l solve.sh。至少检查三条链:入口能否执行、目标文件能否修改、修改后的服务能否由同一用户停止和启动。入口的 root chmod 只解决第一条。若 PID 文件由 root 创建而 Agent 用户无法发信号,配置写入成功后仍会卡在重启;若工作区父目录不可写,所谓“原子替换配置”也可能因无法创建临时文件失败。把这些失败保留为 Oracle 日志,比在 Solution 中静默 sudo 更能揭示任务真实边界。
6.3 为贯穿项目写一条参考路径
python-port-repair 的 Solution 应当直接完成公开契约,而不是写 Reward,也不应读取 /tests。它只依赖第 5 章镜像已经提供的 Python、配置文件和控制脚本。把下面内容保存到 tasks/python-port-repair/solution/solve.sh:
#!/usr/bin/env bash
set -euo pipefail
readonly CONFIG=/workspace/app/config.json
readonly CONTROL=/workspace/app/control.py
command -v python >/dev/null
test -r "$CONFIG"
test -w "$CONFIG"
test -r "$CONTROL"
python - "$CONFIG" <<'PY'
import json
import sys
from pathlib import Path
path = Path(sys.argv[1])
config = json.loads(path.read_text(encoding="utf-8"))
expected = config["expected_port"]
if expected != 8000:
raise ValueError(f"unexpected target port: {expected!r}")
config["listen_port"] = expected
path.write_text(
json.dumps(config, sort_keys=True, separators=(",", ":")) + "\n",
encoding="utf-8",
)
PY
python "$CONTROL" restart
python - <<'PY'
import json
import time
import urllib.request
url = "http://127.0.0.1:8000/health"
last_error = None
for _ in range(20):
try:
with urllib.request.urlopen(url, timeout=1) as response:
body = json.loads(response.read())
if body == {"status": "ok"}:
print("reference solution reached the required health state")
raise SystemExit(0)
last_error = RuntimeError(f"unexpected response: {body!r}")
except Exception as exc:
last_error = exc
time.sleep(0.1)
raise SystemExit(f"service did not become healthy: {last_error}")
PY
这段脚本有意做了四件小事。第一,所有 Task 文件都用绝对路径,避免当前工作目录变化。Harbor 文档说明 Solution 从 Environment 工作目录执行,但也明确建议测试使用绝对路径;参考路径同样采用这个习惯更容易移植和排错。6 第二,修改的是 listen_port,值来自可见配置的 expected_port;8000 只用作防止夹具漂移的断言。第三,写完配置后显式重启服务,不把“文件正确”误当成“进程已加载”。第四,使用有限重试等待就绪,不依赖脆弱的固定长睡眠。
先在 Task 根目录做不需要 Docker 的静态门禁:
cd ~/harbor-lab/harbor-v0.18.0
TASK="$PWD/tasks/python-port-repair"
bash -n "$TASK/solution/solve.sh"
uv run --frozen python - "$TASK" <<'PY'
import sys
from pathlib import Path
from harbor.models.task.config import TaskOS
from harbor.models.task.task import Task
task = Task(Path(sys.argv[1]))
assert task.config.environment.os == TaskOS.LINUX
assert task.paths.discovered_solve_path_for(TaskOS.LINUX) == (
task.paths.solution_dir / "solve.sh"
)
assert task.paths.discovered_solve_path_for(TaskOS.WINDOWS) is None
print("solution entrypoint gate: PASS")
PY
这个检查可核验脚本语法、Task 可加载和 OS 入口选择;它没有启动服务,所以不能被报告成完整 Oracle Trial。
一条好的参考路径通常“无聊而明确”。不要为了展示技巧把诊断、修复和验收压成难以审计的一行管道;也不要加入与目标无关的系统升级、清理日志或包安装。Solution 越短,越容易回答“每个动作是否由 instruction 允许、每个依赖是否在 Environment 中、失败会留下什么证据”。但短不等于省略关键状态转换:本例的“修改—重启—探测”三步都不可删除。
还应让 Solution 在预期初态上是确定的,并尽量具备幂等性。上面的脚本第二次运行会再次把同一字段写成期望值、重启并探测,不会不断追加配置或产生新的端口。幂等不能替代新 Trial:它只是让手工调试更安全。正式门禁仍从第 5 章固定的错误初态启动,避免第一次运行留下的成功状态使第二次测试虚假通过。
6.4 让标准解答与评分标准保持分离
参考 Solution 是一个“存在性见证”,不是提交格式。另一个 Agent 可以用不同的 JSON 缩进与字段顺序,可以先 stop 再 start,也可以用等价的进程管理命令;只要它确实修复公开配置、让目标端点健康且没有破坏禁止项,就应当是合法解。若 Verifier 比较 config.json 与 Solution 生成文件的逐字节结果,它实际上在考察序列化风格,而不是服务修复。
多合法解的评审可以使用一个简单问题:删除 Solution 后,能否只从 instruction 写出结果不相同、但语义同样正确的实现? 若答案是“不能,因为测试偷偷要求某条命令、某种空白或某个临时文件”,应先修改公开契约或评分标准。Solution 可以选择最短、最确定、最容易审计的一条路径;Verifier 应验证结果不变量。这一分离也是为什么官方 Cookbook 的最小任务把 instruction、solution/solve.sh 与 tests/test.sh 放在三个不同层次。7
这并不意味着 Solution 可以随意偏离参评者边界。若参考脚本调用一个只给 Oracle 安装的专用二进制,或者通过 [solution.env] 获得根因,Oracle 证明的只是“特权路径存在”。除非能力目标明确允许这种差异,否则参考路径应使用普通 Agent 同样可见的文件、工具和权限。Oracle 可以知道采取什么动作,但不应额外获得动作是否可能所必需的基础设施。
标准解答也不等于“最佳解答”。它不需要模拟真实 Agent 的诊断过程,不需要故意走弯路,更不能用于估计所需 token、轮次或难度。一个两行 Solution 可能对应需要跨日志推理的难题;一个很长的 Solution 也可能只是作者写得冗余。任务难度需要真实 Agent 重复试跑和错误分析,不能从参考脚本长度推出。
隔离同样是语义要求。Harbor 只有在 Oracle 运行时才把 solution/ 上传到 /solution;普通 Agent 的运行链不需要这份目录。6 但目录约定不会阻止作者自己把答案复制进镜像。提交前至少运行:
if rg -n -S \
'/solution|solution/solve|COPY .*solution|ADD .*solution' \
"$TASK/instruction.md" "$TASK/task.toml" "$TASK/environment"
then
echo "possible Solution leakage" >&2
exit 1
fi
这只是启发式扫描。还要人工检查 Dockerfile/Compose 的构建上下文、挂载、归档、备份文件、环境变量和网络可搜索标识,确认非 Oracle Trial 既读不到 Ground Truth,也不能由它直接写入权威通过信号。v0.18.0 的 Adapter 评审清单明确要求 solution/ 不进入镜像、不被 instruction 引用。8
6.5 路径、依赖与隐藏前提
一条只在作者交互式容器里成功的命令,不是合格 Solution。逐项审计下面的依赖:
- 路径:脚本自身位于
/solution,待修状态位于 Environment 工作区;不要假设两者同目录。相对路径还会随[environment].workdir改变。 - 用户:脚本执行用户与 Agent 配置一致。分别用
test -r、test -w和实际重启命令验证读、写、执行权限,不要把 Oracle 的 rootchmod误解为脚本全程 root。 - 程序:列出
bash、python、jq、curl等命令来源。本例不用镜像没有声明的jq或curl。 - 网络:本 Task 的运行基线是
no-network,Solution 不应临时安装包或获取配置。一次因作者机器缓存而成功的下载不是可解性证据。 - 环境变量:
[solution.env]可给 Oracle 声明变量,${VAR}会从宿主解析;非 Oracle Agent 不扫描这一节。9 因此,只有参考解知道的密钥必须登记为额外前提,不能据此宣称公开任务对普通 Agent 可解。 - 资源与时间:等待必须有上限,错误必须保留在
oracle.txt。过紧的 timeout 会把慢启动误报成不可解,过长的无限循环则会掩盖死锁。
这些检查区分了“动作上可达”和“信息上可求”。即使一个硬编码 Solution 能直接写出正确结果,若 instruction 与 Agent 可见文件不足以推出它,任务仍是信息上不可解。解决办法是补足公开证据或改变题目,不是把硬编码脚本当作证明。
当 Oracle 失败时,先按最早可能破坏参考路径的边界排查:
- 在宿主确认固定版本、Task 路径、
[environment].os与入口扩展名;找不到solve.sh时还没有进入脚本逻辑。 - 查看 Environment 是否完成启动和初态健康检查;如果错误服务都未运行,Solution 无法证明修复过程。
- 读取
oracle.txt与exit-code.txt;前者回答脚本执行到哪里,后者区分非零退出与正常结束。 - 在同一用户下分别执行只读探针、写权限探针与控制脚本,不要一开始就改成 root。
- 最后才检查终态 Reward;若脚本与终态都正确但 Reward 仍为 0,记录为验收契约不一致,交给下一章的评分审计处理。
这个顺序能阻止“为通过 Oracle 而加权限”的条件反射。每一次提权、联网或额外安装都会改变待证明的命题;若修改是必要的,必须同步更新 Task 的能力目标和公开边界,而不是只改 Solution。
6.6 错误 Solution 反例
下面是一个很像正确答案的错误版本。它修改了配置,却忘记让正在运行的进程重新加载:
#!/usr/bin/env bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path
path = Path("/workspace/app/config.json")
config = json.loads(path.read_text())
config["listen_port"] = config["expected_port"]
path.write_text(json.dumps(config))
PY
# 错误:没有重启服务,也没有验证 8000/health。
这是教学性反例,不是本章机器上的 Docker 实测。脚本会退出 0,文件表面上也正确,但旧进程仍监听 8001;第 5 章的终态检查还会访问 8000/health,所以预期 Reward 为 0。这个反例说明两点:退出码不是任务得分;一个有效的 solvability test 必须验证外部可观察终态,而不是只看 Solution 运行完成。
更危险的错误是向 /logs/verifier/reward.txt 预写 1,或让 Solution 从 /tests 读取期望值。若这样的脚本通过,问题不在 Oracle“太强”,而在评分边界允许参考解影响通过信号。此时应把结果标为可解性门禁失败,不能用绿色汇总掩盖它。
6.7 不可解与偶然可解
把常见症状放进二维分类,比笼统说“Oracle 失败”更有用:
| 现象 | 典型原因 | 处理 |
|---|---|---|
| Oracle 总是 0 | 缺文件、依赖、权限或公开条件与评分条件冲突 | 按 oracle.txt、配置状态、进程状态顺序定位 |
| Oracle 偶发 1 | 固定睡眠、竞争条件、外部服务或残留状态 | 限定重试、断网、使用独立 Trial 重复运行 |
| Oracle 1,人工仅看 instruction 无法完成 | Solution 硬编码隐藏事实 | 补公开证据或删除隐藏断言 |
| Nop 也为 1 | 初态已满足目标、Verifier 假阳性或复用了污染状态 | 修正初态,确保新 Trial 从已知故障开始 |
| Oracle 1,错误 Solution 也为 1 | 验收条件过弱或可被 Solution 篡改 | 收紧结果不变量与可信边界 |
“偶然可解”尤其容易被一次成功掩盖。例如,作者先手工把端口改成 8000,随后在同一容器里运行 Solution;或外部健康服务恰好在线;或旧后台进程仍持有正确状态。单次 Oracle 1 对这些情况没有区分力。Nop 负向基线与多个独立 Trial 共同回答:不采取动作时是否失败,采取参考动作时是否稳定成功。
还可以用“最小扰动”确认隐藏前提。保持 Solution 不变,只切换一次执行用户、清空一次非必要缓存、关闭一次外网,或者把工作目录改到另一个合法值;若结果随一个未声明变量翻转,先判断这个变量是任务能力的一部分还是环境噪声。能力相关变量应进入 instruction/配置与分组记录,噪声则应被固定或消除。不要一次改变五个条件,否则成功恢复后仍不知道哪项是真正依赖。
6.8 建立 solvability test
本书为 python-port-repair 采用四道门,而不是把一个命令叫作“可解性测试”:
- 静态门:Task 可加载,Linux 入口可发现,脚本通过
bash -n,无明显 Solution 泄露; - 负向门:Nop 在已知故障初态得到 Reward 0;
- 正向门:Oracle 在三个独立 Trial 中均无异常、无 Solution 非零退出记录并得到 Reward 1;
- 敏感性门:6.6 节的错误 Solution 得到 Reward 0,并由一名评审者只看 instruction 独立完成一次。
“三个”是本书的快速工程门槛,不是 Harbor 规定,也不是统计稳定性的证明。-k 3 在 v0.18.0 中把每个 Task/Agent 组合展开为三个 Trial 配置;-n 1 只让它们串行,便于阅读证据。10
读取证据前先确认磁盘模型。Job.run() 返回的内存 JobResult 含有 trial_results,但 v0.18.0 最终写 Job 根目录 result.json 时显式排除该字段;每个 Trial 的完整结果位于同名子目录自己的 result.json。11 下面的合成测试不需要 Docker,它构造“根摘要没有明细、明细分散在直接子目录”的真实布局,并证明本章采用的枚举方式可以读取它:
python3 - <<'PY'
import json
from pathlib import Path
from tempfile import TemporaryDirectory
def load_persisted_job(job_dir: Path):
summary = json.loads((job_dir / "result.json").read_text())
trials = []
for child in sorted(job_dir.iterdir()):
result_path = child / "result.json"
if child.is_dir() and result_path.is_file():
trials.append((child, json.loads(result_path.read_text())))
return summary, trials
with TemporaryDirectory() as root:
job_dir = Path(root) / "synthetic-job"
job_dir.mkdir()
summary = {
"n_total_trials": 2,
"stats": {"n_completed_trials": 2, "n_errored_trials": 0},
}
(job_dir / "result.json").write_text(json.dumps(summary))
for name, reward in (("trial-a", 1), ("trial-b", 0)):
trial_dir = job_dir / name
trial_dir.mkdir()
result = {
"trial_name": name,
"agent_info": {"name": "oracle"},
"exception_info": None,
"verifier_result": {"rewards": {"reward": reward}},
}
(trial_dir / "result.json").write_text(json.dumps(result))
persisted_summary, persisted_trials = load_persisted_job(job_dir)
assert "trial_results" not in persisted_summary
assert persisted_summary["n_total_trials"] == 2
assert [result["trial_name"] for _, result in persisted_trials] == [
"trial-a",
"trial-b",
]
assert [result["verifier_result"]["rewards"]["reward"]
for _, result in persisted_trials] == [1, 0]
print("synthetic persisted-layout gate: PASS")
PY
这条测试只验证持久化布局与枚举逻辑,不代替 TrialResult schema、容器执行或 Reward 语义验证。它的价值在于让错误的根摘要读取在没有 Docker 时也能被稳定发现。
在 Harbor v0.18.0 源码目录运行;前置条件是第 5 章完整 Docker 门禁已通过,并且 solvability-* Job 名尚未存在:
cd ~/harbor-lab/harbor-v0.18.0
TASK="$PWD/tasks/python-port-repair"
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
export NOP_JOB="solvability-nop-$RUN_ID"
export ORACLE_JOB="solvability-oracle-$RUN_ID"
export WRONG_JOB="solvability-wrong-$RUN_ID"
WRONG_TASK="$(mktemp -d "${TMPDIR:-/tmp}/python-port-repair-wrong.XXXXXX")"
trap 'rm -rf "$WRONG_TASK"' EXIT
cp -R "$TASK/." "$WRONG_TASK/"
uv run --frozen python - "$WRONG_TASK/solution/solve.sh" <<'PY_WRITE'
import sys
from pathlib import Path
path = Path(sys.argv[1])
path.write_text('''#!/usr/bin/env bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path
path = Path("/workspace/app/config.json")
config = json.loads(path.read_text())
config["listen_port"] = config["expected_port"]
path.write_text(json.dumps(config))
PY
# 故意遗漏服务重启;脚本自身仍正常退出。
''')
path.chmod(0o755)
PY_WRITE
uv run --frozen harbor run \
-p "$TASK" -a nop -e docker \
--cpus limit --memory limit -k 1 -n 1 --yes \
--job-name "$NOP_JOB" --jobs-dir "$PWD/jobs"
uv run --frozen harbor run \
-p "$TASK" -a oracle -e docker \
--cpus limit --memory limit -k 3 -n 1 --yes \
--job-name "$ORACLE_JOB" --jobs-dir "$PWD/jobs"
uv run --frozen harbor run \
-p "$WRONG_TASK" -a oracle -e docker \
--cpus limit --memory limit -k 1 -n 1 --yes \
--job-name "$WRONG_JOB" --jobs-dir "$PWD/jobs"
rm -rf "$WRONG_TASK"
trap - EXIT
三条 Job 命令都没有模型调用,但会使用本机容器资源。错误 Solution 写入临时 Task 副本,不会覆盖标准答案;它应正常退出,却因未重启服务而得到 Reward 0。Docker 缺失、Environment 启动异常或 Verifier 没有产生 Reward 都属于门禁失败,不能补写成 0 或 1。完整运行后,用根摘要检查统计,再枚举各 Trial 子目录读取明细:
uv run --frozen python - <<'PY'
import json
import os
from pathlib import Path
jobs = Path.cwd() / "jobs"
def load_persisted_job(job_dir: Path):
summary = json.loads((job_dir / "result.json").read_text())
assert "trial_results" not in summary
trials = []
for child in sorted(job_dir.iterdir()):
result_path = child / "result.json"
if child.is_dir() and result_path.is_file():
result = json.loads(result_path.read_text())
assert result["trial_name"] == child.name
trials.append((child, result))
assert len(trials) == summary["n_total_trials"]
return summary, trials
def assert_finished(summary, expected_trials):
assert summary["n_total_trials"] == expected_trials
assert summary["stats"]["n_completed_trials"] == expected_trials
assert summary["stats"]["n_errored_trials"] == 0
nop_summary, nop_trials = load_persisted_job(jobs / os.environ["NOP_JOB"])
assert_finished(nop_summary, 1)
nop_dir, nop = nop_trials[0]
assert nop["agent_info"]["name"] == "nop"
assert nop["exception_info"] is None
assert nop["verifier_result"]["rewards"]["reward"] == 0
oracle_summary, oracle_trials = load_persisted_job(
jobs / os.environ["ORACLE_JOB"]
)
assert_finished(oracle_summary, 3)
for trial_dir, trial in oracle_trials:
assert trial["agent_info"]["name"] == "oracle"
assert trial["exception_info"] is None
assert trial["verifier_result"]["rewards"]["reward"] == 1
assert (trial_dir / "agent/oracle.txt").is_file()
assert not (trial_dir / "agent/exit-code.txt").exists()
wrong_summary, wrong_trials = load_persisted_job(jobs / os.environ["WRONG_JOB"])
assert_finished(wrong_summary, 1)
wrong_dir, wrong = wrong_trials[0]
assert wrong["agent_info"]["name"] == "oracle"
assert wrong["exception_info"] is None
assert wrong["verifier_result"]["rewards"]["reward"] == 0
assert (wrong_dir / "agent/oracle.txt").is_file()
assert not (wrong_dir / "agent/exit-code.txt").exists()
print("solvability gate: PASS")
PY
预期的最后一行是验收脚本自己打印的 solvability gate: PASS,不是本章作者伪造的 Harbor 输出。若断言失败,按“Trial 异常 → agent/oracle.txt → agent/exit-code.txt → Verifier Reward”顺序读取证据;不要先修改 Solution 让它迎合一个尚未理解的失败。
每次门禁还应保存一份简短的可解性记录:Harbor 版本与提交、Task digest/lock、Environment 类型和 OS/架构、Agent 用户、入口文件哈希、Job 名、各 Trial Reward 与异常类型。它的作用不是复制全部日志,而是让下一次失败能回答“参考路径变了、环境变了,还是结果判定变了”。如果修改了 instruction、Environment、Solution 或目标终态中的任何一项,都要把旧门禁视为失效并重新执行。
对于一组 Dataset,不要只报告“Oracle 平均 Reward 1”。平均值可能掩盖一个不可解任务和一个重复任务,也无法显示偶发失败。门禁应逐 Task 要求所有参考尝试通过,并单独列出跳过项及原因。真实 Agent 的成功率允许是研究结果;Oracle 失败首先是数据发布阻断项,必须先区分 Environment、Solution、Verifier、Provider 或运行主机故障,在完成解释与归因前将该 Task 排除出模型能力比较。
本章写作环境没有 Docker,因此没有声称这组完整 Trial 已实测。已实际核验的是 v0.18.0 固定提交、CLI 版本、Oracle/脚本发现源码,以及对应的 97 项定向单元测试;完整容器门禁必须在具备匹配 Linux Docker runtime 的主机上执行并保存 Job 证据。12
6.9 交付验收
在 Task 进入 Dataset 前,逐项签字:
-
solution/solve.sh只实现公开终态,不写 Reward、不读取 tests、不出现在 instruction 或 Environment 镜像中; - Solution 使用绝对路径,依赖已在镜像内,权限与
[agent].user一致,不依赖未声明的密钥或运行时下载; - Nop 为 0,正确 Oracle 为 1,错误 Solution 为 0;
- 三个独立 Oracle Trial 均无异常、无
exit-code.txt,并保留oracle.txt与结构化结果; - 至少一种不同操作序列也能满足公开终态,Verifier 不比较 Solution 的命令或无关字节;
- 一名评审者只凭 instruction 与 Agent 可见状态完成任务,没有使用 Solution;
- Oracle 成功只记录为“可解性必要证据”,尚未替代泄露、歧义、难度与评分质量审计。
6.10 本章小结
- Solution 是一条参考路径,Oracle 是它的执行器,Verifier 才读取终态并产生 Reward。
- v0.18.0 只在 Oracle 运行时上传
solution/;Linux 使用solve.sh,Windows 使用solve.bat。 - root
chmod只补 Linux 入口执行位,Solution 本身仍受 Agent 用户、路径、依赖、网络和 timeout 约束。 - 标准解答与评分标准必须分离,合法解不应被迫复制参考命令或字节格式。
- Nop、错误 Solution、重复 Oracle 和 instruction-only 人工求解共同区分不可解、偶然可解与隐藏前提。
- Oracle Reward 1 是 Task 质量的必要证据,而不是充分条件。
6.11 练习
- 删除标准 Solution 的健康探针,只保留配置写入和重启。构造一个启动延迟,解释为什么脚本退出 0 仍不足以证明终态已达到。
- 为
python-port-repair配置非 root 的[agent].user。列出配置文件、PID 文件与进程重启所需权限,在不让整个工作区全局可写的前提下修正镜像。 - 编写第二条合法 Solution:显式
stop后修改配置再start,输出不同格式的 JSON。确认结果验收接受它,但 6.6 节的“只改文件不重启”仍失败。 - 把 Task 改为 Windows 目标,写
solve.bat包装 PowerShell 脚本。使用 v0.18.0 的路径发现单元测试证明.bat被选择、单独的solve.ps1不会被发现。 - 设计一个“Oracle 为 1、Nop 也为 1”的偶然可解故障,写出你会保存的初态证据,并说明如何修复 Task 而不是提高难度标签。