跳到主要内容

第 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_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 的 root chmod 误解为脚本全程 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 失败时,先按最早可能破坏参考路径的边界排查:

  1. 在宿主确认固定版本、Task 路径、[environment].os 与入口扩展名;找不到 solve.sh 时还没有进入脚本逻辑。
  2. 查看 Environment 是否完成启动和初态健康检查;如果错误服务都未运行,Solution 无法证明修复过程。
  3. 读取 oracle.txt 与 exit-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.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 练习​

  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-L192、https://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-L86、https://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-L101、https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/utils/scripts.py#L1-L25、https://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 提交 e093c9a860b988d9d74901010ddddb9c7f124f92,https://github.com/harbor-framework/harbor-cookbook/blob/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task/instruction.md#L1、https://github.com/harbor-framework/harbor-cookbook/blob/e093c9a860b988d9d74901010ddddb9c7f124f92/harbor_cookbook/recipes/simple-task/solution/solve.sh#L1-L3、https://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-L332、https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/agents/oracle.py#L119-L136、https://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-L329、https://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-L480、https://github.com/harbor-framework/harbor/blob/527d50deb63a5d279e8c20593c18a2cbc7f61f9e/src/harbor/job.py#L831-L857、https://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。 ↩