跳到主要内容

第 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

这条链需要分成四个对象理解:

对象回答的问题不应承担的职责
instructionAgent 被允许看见什么、必须达到什么终态不提供实例答案或测试实现
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 完成或脚本退出状态。

实践中可以把“可解”再拆成三层,避免团队围绕同一个词争论:

  1. 动作可达:从给定初态出发,确实存在一组允许的文件、进程或工具操作能达到目标。Oracle 主要提供这一层证据。
  2. 信息可求:只看 instruction 与 Agent 可见状态,求解者有足够信息发现那组动作。硬编码答案的 Solution 不能提供这一层证据。
  3. 重复可达:在新的 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 TaskWindows Task
宿主入口solution/solve.shsolution/solve.bat
容器目录/solutionC:/solution
执行方式直接执行 .shcmd /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_port8000 只用作防止夹具漂移的断言。第三,写完配置后显式重启服务,不把“文件正确”误当成“进程已加载”。第四,使用有限重试等待就绪,不依赖脆弱的固定长睡眠。

先在 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 缩进与字段顺序,可以先 stopstart,也可以用等价的进程管理命令;只要它确实修复公开配置、让目标端点健康且没有破坏禁止项,就应当是合法解。若 Verifier 比较 config.json 与 Solution 生成文件的逐字节结果,它实际上在考察序列化风格,而不是服务修复。

多合法解的评审可以使用一个简单问题:删除 Solution 后,能否只从 instruction 写出结果不相同、但语义同样正确的实现? 若答案是“不能,因为测试偷偷要求某条命令、某种空白或某个临时文件”,应先修改公开契约或评分标准。Solution 可以选择最短、最确定、最容易审计的一条路径;Verifier 应验证结果不变量。这一分离也是为什么官方 Cookbook 的最小任务把 instruction、solution/solve.shtests/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 -rtest -w 和实际重启命令验证读、写、执行权限,不要把 Oracle 的 root chmod 误解为脚本全程 root。
  • 程序:列出 bashpythonjqcurl 等命令来源。本例不用镜像没有声明的 jqcurl
  • 网络:本 Task 的运行基线是 no-network,Solution 不应临时安装包或获取配置。一次因作者机器缓存而成功的下载不是可解性证据。
  • 环境变量[solution.env] 可给 Oracle 声明变量,${VAR} 会从宿主解析;非 Oracle Agent 不扫描这一节。9 因此,只有参考解知道的密钥必须登记为额外前提,不能据此宣称公开任务对普通 Agent 可解。
  • 资源与时间:等待必须有上限,错误必须保留在 oracle.txt。过紧的 timeout 会把慢启动误报成不可解,过长的无限循环则会掩盖死锁。

这些检查区分了“动作上可达”和“信息上可求”。即使一个硬编码 Solution 能直接写出正确结果,若 instruction 与 Agent 可见文件不足以推出它,任务仍是信息上不可解。解决办法是补足公开证据或改变题目,不是把硬编码脚本当作证明。

当 Oracle 失败时,先按最早可能破坏参考路径的边界排查:

  1. 在宿主确认固定版本、Task 路径、[environment].os 与入口扩展名;找不到 solve.sh 时还没有进入脚本逻辑。
  2. 查看 Environment 是否完成启动和初态健康检查;如果错误服务都未运行,Solution 无法证明修复过程。
  3. 读取 oracle.txtexit-code.txt;前者回答脚本执行到哪里,后者区分非零退出与正常结束。
  4. 在同一用户下分别执行只读探针、写权限探针与控制脚本,不要一开始就改成 root。
  5. 最后才检查终态 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 采用四道门,而不是把一个命令叫作“可解性测试”:

  1. 静态门:Task 可加载,Linux 入口可发现,脚本通过 bash -n,无明显 Solution 泄露;
  2. 负向门:Nop 在已知故障初态得到 Reward 0;
  3. 正向门:Oracle 在三个独立 Trial 中均无异常、无 Solution 非零退出记录并得到 Reward 1;
  4. 敏感性门: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.json11 下面的合成测试不需要 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.txtagent/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 练习

  1. 删除标准 Solution 的健康探针,只保留配置写入和重启。构造一个启动延迟,解释为什么脚本退出 0 仍不足以证明终态已达到。
  2. python-port-repair 配置非 root 的 [agent].user。列出配置文件、PID 文件与进程重启所需权限,在不让整个工作区全局可写的前提下修正镜像。
  3. 编写第二条合法 Solution:显式 stop 后修改配置再 start,输出不同格式的 JSON。确认结果验收接受它,但 6.6 节的“只改文件不重启”仍失败。
  4. 把 Task 改为 Windows 目标,写 solve.bat 包装 PowerShell 脚本。使用 v0.18.0 的路径发现单元测试证明 .bat 被选择、单独的 solve.ps1 不会被发现。
  5. 设计一个“Oracle 为 1、Nop 也为 1”的偶然可解故障,写出你会保存的初态证据,并说明如何修复 Task 而不是提高难度标签。

参考资料

Footnotes

  1. Harbor Framework Team,OracleAgent,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/agents/oracle.py#L18-L151,访问于 2026-07-16。 2 3

  2. Harbor Framework Team,SingleStepTrial 执行顺序,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/trial/single_step.py#L18-L113,访问于 2026-07-16。

  3. Harbor Framework Team,create-task Skill 的 Solution 与 Oracle 门禁,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/skills/create-task/SKILL.md#L182-L192https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/skills/create-task/SKILL.md#L302-L314,访问于 2026-07-16。

  4. Harbor Framework Team,Trial 的 Agent 用户作用域,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/trial/single_step.py#L75-L86https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/trial/trial.py#L430-L455,访问于 2026-07-16。

  5. Harbor Framework Team,TaskPaths、跨平台脚本工具与 Oracle Windows 单元测试,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/paths.py#L67-L101https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/scripts.py#L1-L25https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/tests/unit/agents/test_oracle.py#L147-L185,访问于 2026-07-16。

  6. Harbor Framework Team,Task 结构、特殊路径与 Solution,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/docs/content/docs/tasks/index.mdx#L436-L467,访问于 2026-07-16。 2

  7. Harbor Framework Team,simple-task 的 instruction、Solution 与 tests,Harbor Cookbook 提交 e093c9a860b988d9d74901010ddddb9c7f124f92https://github.com/harbor-framework/harbor-cookbook/blob/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task/instruction.md#L1https://github.com/harbor-framework/harbor-cookbook/blob/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task/solution/solve.sh#L1-L3https://github.com/harbor-framework/harbor-cookbook/blob/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task/tests/test.sh#L1-L19,访问于 2026-07-16。该独立仓库示例不属于 Harbor v0.18.0 标签,本章只用它说明职责分层。

  8. Harbor Framework Team,Adapter Review 的 Oracle/Gold Solution 隔离检查,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/adapter_review.py#L614-L625,访问于 2026-07-16。

  9. Harbor Framework Team,SolutionConfig、Oracle 的环境变量解析与 CLI 宿主变量检查,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/task/config.py#L330-L332https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/agents/oracle.py#L119-L136https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/cli/jobs.py#L87-L142,访问于 2026-07-16。

  10. Harbor Framework Team,JobConfig.n_attempts 与 Trial 配置展开,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/models/job/config.py#L311-L329https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/job.py#L364-L387,访问于 2026-07-16。

  11. Harbor Framework Team,Job 根结果写入、结束阶段与 Job/Trial 磁盘布局,Harbor v0.18.0,https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/job.py#L472-L480https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/job.py#L831-L857https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/docs/content/docs/run-jobs/run-evals.mdx#L58-L80,访问于 2026-07-16。

  12. 本章写作 Agent,第 6 章写作与验证报告的“版本与源码核验”“实际验证”和“未覆盖边界”,2026-07-16。