OmX autoresearch 候选交接(candidate.json)契约:thin-supervisor 决策边界与 Parity 测试实战
【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex
导读
本文围绕 OmX(oh-my-codex)仓库中missions/candidate-handoff这一 autoresearch 试点任务,系统拆解其核心目标:实现并验证以仓库根目录为锚点的candidate.json候选交接(handoff)机制,让"薄监督器(thin-supervisor)"能够依据一个显式的、可测试的候选工件做出 keep / discard / reset 决策。读完本文,你将掌握 candidate 工件契约的全部字段与取值语义、supervisor 决策状态机的分支规则、score_improvement与pass_only两种保留策略的区别,以及如何用仓库中的 parity 测试验证整条链路。
一、任务背景:thin-supervisor autoresearch 循环需要什么样的交接?
missions/candidate-handoff/mission.md描述了一项明确的工程任务:
Implement and validate repo-root
candidate.jsonhandoff for the thin-supervisor autoresearch cycle.
其两个主要目标分别是"每次运行的候选工件契约(per-run candidate artifact contract)"和"keep/discard/reset 决策入口(keep/discard/reset decision entrypoint)";成功标准有三条:
- 候选交接工件是显式(explicit)且被测试覆盖的;
- 运行时能够区分
candidate / noop / abort / interrupted四种状态; - parity 运行时测试全部通过。
所谓"thin-supervisor",指的是监督者本身不负责实现实验逻辑,它只负责编排:准备 git worktree、注入指令、等待实验会话写回候选工件、运行 evaluator、根据结果决定保留或回滚。会话与监督者之间的唯一信息通道,就是candidate.json这个候选工件——这正是一个典型的"控制面与执行面解耦"设计:执行会话在一个隔离的 git worktree 中工作,监督者在仓库根目录下读取并裁决结果。
二、运行产物结构:candidate.json 在哪个位置、与哪些文件协同
从src/autoresearch/runtime.ts的prepareAutoresearchRuntime(runtime.ts)可以看到,每次运行会在仓库根目录下建立如下目录与文件(runId形如missions-demo-20260314t000000z,由 mission slug 与运行时间戳拼接而成):
| 文件 | 路径(相对仓库根目录) | 作用 |
|---|---|---|
| manifest.json | .omx/logs/autoresearch/<run-id>/manifest.json | 运行清单:baseline/last_kept commit、keep_policy、evaluator 契约、运行状态 |
| bootstrap-instructions.md | .omx/logs/autoresearch/<run-id>/bootstrap-instructions.md | 注入给执行会话的指令,含候选工件契约与 supervisor 语义 |
| candidate.json | .omx/logs/autoresearch/<run-id>/candidate.json | 核心交接工件:会话写回,监督者读取裁决 |
| iteration-ledger.json | .omx/logs/autoresearch/<run-id>/iteration-ledger.json | 迭代账本:baseline 与每次迭代的决策记录 |
| latest-evaluator-result.json | .omx/logs/autoresearch/<run-id>/latest-evaluator-result.json | 最近一次 evaluator 的原始结果 |
| results.tsv | worktree 根目录下 | 面向人读的 TSV 汇总:iteration commit pass score status description |
missions/README.md也明确建议运行完成后检查.omx/logs/autoresearch/<run-id>/下的manifest.json、candidate.json、iteration-ledger.json三个文件来观察 supervisor 的 keep/discard/stop 决策——这与源码中的落盘路径完全一致。
三、candidate.json 工件契约:字段与取值语义
候选工件的类型定义位于 runtime.ts 的AutoresearchCandidateArtifact:
export interface AutoresearchCandidateArtifact { status: AutoresearchCandidateStatus; // 'candidate' | 'noop' | 'abort' | 'interrupted' candidate_commit: string | null; base_commit: string; description: string; notes: string[]; created_at: string; }各字段语义如下:
| 字段 | 类型 | 必填 | 语义与约束 |
|---|---|---|---|
status | 枚举字符串 | 是 | 必须是candidate、noop、abort、interrupted之一 |
candidate_commit | string | null | 是 | 当status=candidate时必须为非空 commit;须能在 git 中解析且与退出时 worktree HEAD 一致 |
base_commit | string | 是 | 会话开始编辑前的基线 commit;必须与 supervisor 提供的 last_kept_commit 一致 |
description | string | 是 | 一行简短摘要 |
notes | string[] | 是 | 短字符串数组,用于携带额外说明 |
created_at | string | 是 | ISO 时间戳 |
prepareAutoresearchRuntime在启动时会先写入一个占位工件(runtime.ts),保证文件在会话真正写入前就存在:
{ "status": "noop", "candidate_commit": null, "base_commit": "<baseline-short-commit>", "description": "not-yet-written", "notes": ["candidate artifact will be overwritten by the launched session"], "created_at": "<ISO timestamp>" }这份占位 JSON 会被注入指令文件,明确告知执行会话"候选工件稍后将被覆盖"。
四、四种候选状态与 supervisor 语义
buildAutoresearchInstructions(runtime.ts)把候选工件的契约逐条写入bootstrap-instructions.md,其中 supervisor 侧语义如下:
status=candidate→ 运行 evaluator,随后 supervisor 决定keep 或 discard,discard 时可能对 worktree 执行 reset;status=noop→ supervisor 记录一次 noop 迭代并重新启动下一轮;status=abort→ supervisor停止整个运行;status=interrupted→ supervisor先检查 worktree 安全性,再决定如何继续。
这四种状态在processAutoresearchCandidate(runtime.ts)中分派:
noop:写入 ledger,更新 manifest,重新生成指令,返回noop,循环继续(countTrailingAutoresearchNoops会统计末尾连续 noop 次数,供上层决定何时收敛);abort:写入 ledger 后调用finalizeRun将 manifest 置为stopped,stop_reason='candidate abort',返回abort;interrupted:先调用assertResetSafeWorktree校验 worktree 干净程度——若被脏文件阻塞,则运行进入failed(stop_reason='interrupted dirty worktree requires operator intervention');若干净则记录一次interrupted迭代并继续等待下一轮。
五、工件完整性校验:防伪造、防错位
监督者不会盲信会话写回的 JSON。在processAutoresearchCandidate中,读取工件后先经过两层校验:
第一层:结构解析。parseAutoresearchCandidateArtifact(runtime.ts)要求工件必须是合法 JSON 对象,status必须是四种枚举之一,candidate_commit为 string|null,base_commit、description、created_at为非空字符串,notes必须是字符串数组;任何一项不满足都会抛错并使本轮进入error失败态。
第二层:git 完整性。validateAutoresearchCandidate(runtime.ts)做三重核对:
base_commit必须在 git 中可解析(git rev-parse --verify);- 解析后的
base_commit必须等于 manifest 中的last_kept_commit——防止会话从错误基线出发; - 当
status=candidate时,candidate_commit必须非空、可解析,且必须等于 worktree 当前 HEAD——防止会话虚报 commit。
任何一项失败都会调用failAutoresearchIteration:记录error行到 results.tsv 与 ledger,并将整个运行置为failed,stop_reason给出可行动的诊断信息(如candidate base_commit does not match last kept commit、candidate status requires a non-null candidate_commit等)。测试runtime-parity-extra.test.ts专门覆盖了"缺失候选文件、候选 commit 为空、base_commit 不匹配"三类失败路径。
六、决策入口:decideAutoresearchOutcome 的完整分支
决策核心函数是decideAutoresearchOutcome(runtime.ts),它把"候选状态 + evaluator 结果 + keep_policy"折叠为一个最终决策。完整分支如下:
| 条件 | 决策 | keep? | 决策理由示例 |
|---|---|---|---|
status=abort | abort | 否 | candidate requested abort |
status=noop | noop | 否 | candidate reported noop |
status=interrupted | interrupted | 否 | candidate session was interrupted |
evaluator 缺失或status=error | discard | 否 | evaluator error / 崩溃或解析失败 |
pass=false | discard | 否 | evaluator reported failure |
keep_policy=pass_only且 pass=true | keep | 是 | pass_only 策略直接接受 |
score_improvement且无可比分数 | ambiguous | 否 | pass 但无数值分数,无法比较 |
score_improvement且新分数更高 | keep | 是 | score improved over last kept score |
score_improvement且分数未提升 | discard | 否 | score did not improve |
关键设计在于score_improvement策略对"可比分数"的坚持:只有当last_kept_score与本次score都是数值时才可比较(comparableScore),否则即便 evaluator 报了pass=true也会判为ambiguous并丢弃——这避免了无基准的分数被误认为改进。
keep_policy 的两种取值
AutoresearchKeepPolicy定义于 contracts.ts,由 sandbox.md frontmatter 的evaluator.keep_policy声明,缺省值为score_improvement(见 runtime.ts):
score_improvement(默认):要求新分数严格高于最近保留分数,强调可量化改进;pass_only:只要 evaluatorpass=true即保留,适合无连续分数语义的布尔验证。
contracts.ts的parseKeepPolicy会做大小写与空白归一化,非法取值直接报错(keep_policy must be one of: score_improvement, pass_only)。
keep 之后的 reset 语义
决策为discard或ambiguous时,监督者调用resetToLastKeptCommit(runtime.ts)执行git reset --hard <last_kept_commit>;但在 reset 前必须通过assertResetSafeWorktree(runtime.ts),它只容忍四类未跟踪的运行时文件(results.tsv、run.log、node_modules、.omx/,见AUTORESEARCH_WORKTREE_EXCLUDES),任何其它脏文件都会以autoresearch_reset_requires_clean_worktree:<worktree>:<文件列表>抛错,防止静默丢失会话产物。
七、evaluator 契约:sandbox.md 与结果解析
候选是否被保留,最终由 mission 的sandbox.md中声明的 evaluator 说了算。missions/candidate-handoff/sandbox.md的 frontmatter 即是一个规范示例:
--- evaluator: command: node scripts/eval-candidate-handoff.js format: json ---contracts.ts 的parseSandboxContract对 frontmatter 有硬性校验:
- 必须以 YAML frontmatter 开头(
---包裹); evaluator块必须存在;evaluator.command必填;evaluator.format必填且 v1 中只能是json;evaluator.keep_policy可选,取值限定为score_improvement | pass_only。
evaluator 的输出由parseEvaluatorResult(contracts.ts)解析:
- 必须是合法 JSON 对象;
pass必须为布尔值;score可选,出现时必须是数值。
{ "pass": true, "score": 87.5 }运行时通过runAutoresearchEvaluator(runtime.ts)以 shell 方式在 worktree 内执行 evaluator 命令,捕获 stdout/stderr 与退出码;退出码非 0、输出非 JSON、pass缺失、score非数值都会归一化为status='error'的记录,并最终映射为discard(evaluator error)决策。
八、测试覆盖:三份测试如何锁定契约
任务成功标准的第三条"parity runtime tests pass"对应src/autoresearch/__tests__/下的三份测试:
runtime.test.ts—— 运行时主链路:
- 验证 bootstrap 指令包含"exactly one experiment cycle"、evaluator 契约(
required output field: pass、optional output field: score)与迭代状态快照; - 验证
.omx运行时文件被视为 reset-safe; - 验证
prepareAutoresearchRuntime落盘全部产物(mission/sandbox/manifest/ledger/results/instructions)、manifest 字段(mission_slug、branch_name、worktree_path)以及 baseline 行写入; - keep/discard 端到端:构造分数从 1 提升到 2 的候选 → 决策
keep且last_kept_commit前移;再构造分数回落的候选 → 决策discard且 worktree HEAD 被 reset 回改进 commit;ledger 顺序为baseline → keep → discard。
runtime-parity-extra.test.ts—— 边界与异常分支:
- 并发锁:第二次
prepareAutoresearchRuntime因autoresearch_active_run_exists被拒; - resume:
resumeAutoresearchRuntime可恢复 running 态 manifest,缺失 worktree 报autoresearch_resume_missing_worktree,终态 manifest 报autoresearch_resume_terminal_run; ambiguousvskeep:无基线分数 +score_improvement→ambiguous;pass_only→keep;noop/abort分支分别记录并最终停运行;- 三例完整性失败(candidate_commit 为空、base_commit 不匹配、候选文件缺失)均进入
failed状态并带可读 stop_reason; interrupted、evaluatorpass=false、evaluator 输出非 JSON(解析错误)三条分支的 results.tsv / ledger 记录断言。
contracts.test.ts—— 契约解析单元测试:
slugifyMissionName的确定性;sandbox frontmatter 的解析与各类非法输入(无 frontmatter、缺 command、缺 format、format 非 json、非法 keep_policy)的拒绝;- evaluator 结果解析:接受
{pass:true}与{pass:false,score:61},拒绝缺pass或score非数值; - mission 目录必须位于 git 仓库内、必须同时存在
mission.md与sandbox.md。
这三份测试共同构成"工件契约显式化 + 状态可区分 + parity 通过"的可执行证据。
九、从 mission 到实战:如何查看与运行
missions/README.md说明这些 mission 目录是"autoresearch-ready pilots",每个目录包含mission.md(目标、范围与预期交付物)与sandbox.md(evaluator 契约与运行边界)。运行后可在.omx/logs/autoresearch/<run-id>/下观察manifest.json、candidate.json、iteration-ledger.json来核对 supervisor 的每次决策。
需要说明当前仓库 CLI 的约束:src/cli/autoresearch.ts表明omx autoresearch命令面已进入hard-deprecated状态,直接 CLI 启动 / resume / run 均会刻意失败,迁移路径是改用$autoresearchskill(hook 原生持久循环,见 skills/autoresearch/SKILL.md)以及$deep-interview --autoresearch(用于在编写 mission 工件前澄清目标)。因此,阅读本仓库时建议以 src/autoresearch/runtime.ts 与 src/autoresearch/contracts.ts 的纯运行时逻辑为准理解契约,以三份测试文件作为可执行的行为规范。
十、小结:候选交接契约的要点清单
- 交接载体:仓库根目录
.omx/logs/autoresearch/<run-id>/candidate.json,会话写、监督者读,是执行面与控制面的唯一信息通道。 - 四态语义:
candidate进入 evaluator 评估;noop记录后重开;abort停运行;interrupted先验 worktree 安全性。 - 双重校验:结构校验(字段类型与枚举)+ git 完整性校验(base_commit 必须等于 last_kept_commit,candidate_commit 必须等于 HEAD)。
- 决策收敛:
score_improvement(默认,需要可比分数提升)与pass_only(布尔通过即保留)两种保留策略;discard 前必须 reset-safe。 - 可测试性:基线、keep、discard、noop、abort、interrupted、evaluator 失败/解析错误、完整性伪造,全部有端到端测试断言,构成契约的活文档。
理解了candidate.json这份契约,就等于掌握了 OmX autoresearch 运行时"如何让一个自治实验会话与一个薄监督者安全协作"的核心机制——它把不可信的会话输出转化为可验证、可裁决、可回滚的状态机输入。
【免费下载链接】oh-my-codexOmX - Oh My codeX: Your codex is not alone. Add hooks, agent teams, HUDs, and so much more.项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-codex
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考