﻿---
lang: zh-TW
title: "Harness 與架構演進：從 Harness² 看它和普通 harness 的區別"
description: "Harness² 的 JobBench 起點 harness 裡，系統提示、守則、記憶三個檔案都是空的，模型權重也不動，分數卻能往上走。順著這個現象往下追：harness 由哪些檔案組成、平常誰在改；任務沒見過又沒有標準答案時，憑什麼判斷一次修改是好是壞；預算 k 有哪兩種花法；改完的東西怎麼留給下一個任務；最後回頭對照，它和人手維護的普通 harness 到底差在哪、多花了什麼。"
pubDate: 2026-11-11
tags: ["AI", "Agent", "Harness", "源码分析"]
clc: "TP18"
hero: "./hero.svg"
refs:
  - id: h2-readme
    title: "Harness² README"
    url: https://github.com/google-research/harness2/blob/main/README.md
    site: GitHub · google-research/harness2
    note: "本文讀的是 2026-11-02 的提交 `6868817`；harness 與 harnessing 的定義、兩層遞歸、八個元件、`--k` 與 `--mode`"
  - id: h2-paper
    title: "Harness²: Recursive Agent Harnessing for an Open World"
    url: https://arxiv.org/abs/2610.14224
    author: "Ruiyao Xu, Yanfei Chen, Zhongying CuiZhu, Bhavana Dalvi Mishra, Yifei Ming, Han Yu, Rujun Han, Chen-Yu Lee, Tomas Pfister"
    site: arXiv
    date: 2026-10-09
    note: "arXiv:2610.14224；開放世界的設定、與離線方法的對照、主結果（Table 1）、消融（Table 3、Table 6）、經驗庫的使用統計（E.2）、局限（Appendix F）"
  - id: h2-site
    title: "Harness² 專案頁"
    url: https://harness2.github.io
    site: harness2.github.io
    note: "WorkBuddy-Bench 上 Gemini 3.5 Flash + OpenCode 的各方法平均分"
  - id: h2-baseline
    title: "harness_baseline_oc：JobBench 的起點 harness"
    url: https://github.com/google-research/harness2/tree/main/third_party/jb_runner/harness_baseline_oc
    site: GitHub · google-research/harness2
    note: "`harness.yaml` 列出可演化的元件；`systemprompt.md`、`guardrails.md`、`LongTermMEMORY.md` 在 v0 都只有一行註解"
  - id: h2-prompts
    title: "prompts.py：方法發給模型的每一段提示"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/prompts.py
    site: GitHub · google-research/harness2
    note: "`_SURFACE_OPENCODE`、`_INV_TEMPLATE`、`_DOM_TEMPLATE`、`aggregate`、`compose_round`、`select`、`experience`、`ANSWER_FREE`"
  - id: h2-tree
    title: "tree.py：元件樹的讀寫與校驗"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/tree.py
    site: GitHub · google-research/harness2
    note: "`SurfaceSpec`、`is_allowed`、`validate`、`autotrigger`；常駐文字上限在 `substrate/opencode.py`（24,000 字元）和 `substrate/codex.py`（8,000 字元）"
  - id: h2-recursion
    title: "recursion.py：並行與串行兩種任務級遞歸"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/recursion.py
    site: GitHub · google-research/harness2
    note: "`run_parallel`、`run_sequential`、`_run_stage`（提案不合格重試一次，再不行就沿用目前的 harness）"
  - id: h2-context
    title: "context.py：改進器讀到的證據目錄"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/context.py
    site: GitHub · google-research/harness2
    note: "`context/` 的佈局、`assert_no_leak_paths`、`GRADER_VERDICT_MARKERS` 與 `assert_no_grader_verdicts`"
  - id: h2-spec
    title: "spec.py：基準適配器要實作的介面"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/spec.py
    site: GitHub · google-research/harness2
    note: "`BenchPrompts.__post_init__` 拒絕 benchmark、rubric、score 等字眼；`RunView` 不帶分數；`score()` 只在選定之後由 `core.judge` 呼叫"
  - id: h2-ledger
    title: "ledger.py：預算記帳"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/ledger.py
    site: GitHub · google-research/harness2
    note: "`expected_rollouts` 為 4k，`expected_sessions` 為 3k，`assert_identity` 對不上就報錯"
  - id: h2-selection
    title: "selection.py：盲選與反思"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/selection.py
    site: GitHub · google-research/harness2
    note: "`selection_pool`、`assign_ids`、`trivial_selection`、`reflect_adoption`、`_distil`"
  - id: h2-library
    title: "library.py：按領域存放的經驗庫"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/library.py
    site: GitHub · google-research/harness2
    note: "目錄佈局、`Library.add`（沒有 cue、lesson、evidence 就不入庫）、`rebuild_index`"
  - id: h2-demo
    title: "demo.py：不需要模型的本地演示"
    url: https://github.com/google-research/harness2/blob/main/harness2/demo.py
    site: GitHub · google-research/harness2
    note: "`DemoAdapter`（兩個記帳任務）與 `DemoProposer`（寫死的編輯、選擇與反思）"
  - id: h2-schedule
    title: "schedule.py：任務流的排程"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/schedule.py
    site: GitHub · google-research/harness2
    note: "`run_task` 每個任務都從 `adapter.base_harness()` 讀起點檔案樹；`run_domain` 在同一個領域內逐個任務執行"
  - id: h2-proposer
    title: "proposer.py：改進器會話"
    url: https://github.com/google-research/harness2/blob/main/harness2/core/proposer.py
    site: GitHub · google-research/harness2
    note: "`Proposer.call` 把 `context/` 複製進臨時目錄 `harness2-prop-*`，經 `claude_cli` 或 `opencode` 通道開會話，結束後取回 `context/harness`"
  - id: h2-experiments
    title: "Harness² Experiment guide"
    url: https://github.com/google-research/harness2/blob/main/docs/EXPERIMENTS.md
    site: GitHub · google-research/harness2
    note: "Recursion modes、Web access、Results and resume 各節"
---

2026 年 10 月，Google Research 開源了 Harness²，對應的論文叫《Recursive Agent Harnessing for an Open World》[^h2-paper]。README 給的數字是這樣的：模型權重完全不動，只做一步改進，GLM-5 和 Gemini 3.5 Flash 在三個專業工作基準上都比原來的 harness 分數高，最多的一項是 WorkBuddy-Bench 上的 11.5 分[^h2-readme]。

權重不動，那動的是什麼？翻開倉庫，JobBench 用的起點 harness 是一個目錄，裡面最主要的三個檔案長這樣[^h2-baseline]：

```text
systemprompt.md      <!-- c1 system prompt. EMPTY in v0 — v0 is the BARE stock OpenCode run … -->
guardrails.md        <!-- c6 guardrails. EMPTY in v0. Evolution adds always-on rules here. -->
LongTermMEMORY.md    <!-- c3 memory. EMPTY in v0. Evolution adds durable, GENERAL domain facts here … -->
```

三個檔案都只有一行註解，`skills/`、`scripts/`、`sub_agents/` 也只放了說明檔。也就是說，起點幾乎是一個空的 harness，往裡面填東西的不是人，是另一個 agent。這就帶出幾個問題：

1. harness 具體由哪些東西組成？平常是誰在改它？
2. 這裡的任務事先沒見過，也沒有標準答案。一次修改是變好還是變壞，憑什麼判斷？
3. 命令列上的 `--k` 是改進的預算，預算多了該怎麼花？
4. 為一個任務改好的 harness，下一個任務還用得上嗎？名字裡的「平方」指的是什麼？
5. 回過頭看，它和我們平常說的 harness 到底差在哪，多花了什麼？

本文就順著這五個問題，一層一層往下讀。讀的是 2026-11-02 的提交 `6868817`。先說明一點：三個基準都要真實的模型額度，我們沒有重跑，文中的分數全部引自論文、專案頁和 README；機制部分則都對過原始碼，圖裡出現的檔名、函式名和環境變數都是倉庫裡的原樣。

## 一、普通 harness 的構成

### harness 是一棵檔案樹

README 開頭給了兩個定義：**harness** 是包在模型外面、讓它能當 agent 用的那套系統，包括提示、工具、記憶、技能、編排；**harnessing** 是優化這套系統的過程[^h2-readme]。用過 Claude Code、Codex 或 OpenCode 的人對它不陌生：`AGENTS.md`、`skills/`、hooks，就是 harness 落在磁碟上的樣子。

Harness² 把可以改的部分固定成八個元件。以 OpenCode 為例，它們掛在一次執行的不同位置上[^h2-prompts]：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 448" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-anat-title h2-anat-desc">
<title id="h2-anat-title">八個可編輯元件在一次執行裡的位置</title>
<desc id="h2-anat-desc">左邊是模型的上下文視窗：systemprompt.md、guardrails.md、LongTermMEMORY.md、tool_guidance.md 四份常駐文字每次執行都在，OpenCode 上限 24,000 字元；skills 的 SKILL.md 被叫到才載入；其餘空間留給任務、工具結果和模型自己的輸出。右邊是工具迴圈：模型提出工具呼叫，先經過 plugin，再到 bash 執行 scripts 下的腳本，或由 task 工具委派給 sub_agents 下的子 agent，結果回到上下文。最下面是凍結的底座 OpenCode 或 Codex CLI。</desc>
<defs>
<marker id="h2-anat-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-anat-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="448" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="284" height="340" rx="8"/>
<text class="t-zone" x="36" y="44">模型的上下文視窗</text>
<rect class="dd-zone" x="316" y="16" width="468" height="340" rx="8"/>
<text class="t-zone" x="336" y="44">工具迴圈</text>
<path class="dd-edge" d="M452 90 H482" marker-end="url(#h2-anat-arrow)"/>
<path class="dd-edge" d="M658 116 V158" marker-end="url(#h2-anat-arrow)"/>
<path class="dd-edge" d="M500 116 V130 Q500 138 492 138 H440 Q432 138 432 146 V158" marker-end="url(#h2-anat-arrow)"/>
<path class="dd-edge" d="M432 238 V310 Q432 318 424 318 H286" marker-end="url(#h2-anat-arrow)"/>
<rect class="dd-card" x="32" y="64" width="252" height="136" rx="6"/>
<path class="dd-card-head" d="M38 64 H278 Q284 64 284 70 V96 H32 V70 Q32 64 38 64 Z"/>
<text class="t-name" x="44" y="85">常駐文字</text>
<text class="t-note" x="272" y="85" text-anchor="end"><tspan class="t-acc">每次執行都在</tspan></text>
<text class="t-mono" x="44" y="113">systemprompt.md</text>
<text class="t-val" x="272" y="113" text-anchor="end">角色與步驟</text>
<line class="dd-hair" x1="32" y1="122" x2="284" y2="122"/>
<text class="t-mono" x="44" y="139">guardrails.md</text>
<text class="t-val" x="272" y="139" text-anchor="end">守則</text>
<line class="dd-hair" x1="32" y1="148" x2="284" y2="148"/>
<text class="t-mono" x="44" y="165">LongTermMEMORY.md</text>
<text class="t-val" x="272" y="165" text-anchor="end">領域事實</text>
<line class="dd-hair" x1="32" y1="174" x2="284" y2="174"/>
<text class="t-mono" x="44" y="191">tool_guidance.md</text>
<text class="t-val" x="272" y="191" text-anchor="end">工具用法</text>
<text class="t-note" x="36" y="222"><tspan class="t-acc">上限 24,000 字元</tspan>（OpenCode）</text>
<rect class="dd-derived" x="32" y="236" width="252" height="52" rx="6"/>
<text class="t-mono" x="44" y="257">skills/&lt;name&gt;/SKILL.md</text>
<text class="t-note" x="44" y="276">虛線：被叫到時才載入</text>
<rect class="dd-card" x="32" y="300" width="252" height="36" rx="6"/>
<text class="t-name" x="44" y="323.0" text-anchor="start">任務、工具結果、自己的輸出</text>
<rect class="dd-card" x="332" y="64" width="120" height="52" rx="6"/>
<text class="t-name" x="344" y="84">模型</text>
<text class="t-note" x="344" y="102">提出工具呼叫</text>
<rect class="dd-card" x="484" y="64" width="284" height="52" rx="6"/>
<text class="t-mono" x="496" y="84">plugin/&lt;name&gt;.ts</text>
<text class="t-note" x="496" y="102">一直註冊著，呼叫前後都經過，跳不過去</text>
<rect class="dd-card" x="332" y="160" width="200" height="84" rx="6"/>
<path class="dd-card-head" d="M338 160 H526 Q532 160 532 166 V192 H332 V166 Q332 160 338 160 Z"/>
<text class="t-name" x="344" y="181">工具</text>
<text class="t-mono" x="520" y="181" text-anchor="end">bash</text>
<text class="t-mono" x="344" y="209">scripts/&lt;name&gt;.py</text>
<line class="dd-hair" x1="332" y1="218" x2="532" y2="218"/>
<text class="t-mono" x="344" y="235">確定性的小工具</text>
<rect class="dd-card" x="548" y="160" width="220" height="84" rx="6"/>
<path class="dd-card-head" d="M554 160 H762 Q768 160 768 166 V192 H548 V166 Q548 160 554 160 Z"/>
<text class="t-name" x="560" y="181">工具</text>
<text class="t-mono" x="756" y="181" text-anchor="end">task</text>
<text class="t-mono" x="560" y="209">sub_agents/&lt;name&gt;/AGENT.md</text>
<line class="dd-hair" x1="548" y1="218" x2="768" y2="218"/>
<text class="t-mono" x="560" y="235">自己的上下文，只回傳文字</text>
<text class="t-say" x="452" y="286">同一句指示，放在左邊每一次都佔位置，</text>
<text class="t-say" x="452" y="306">放在右邊只在用到的時候才花成本。</text>
<rect class="dd-card" x="16" y="372" width="768" height="60" rx="8"/>
<text class="t-name" x="400" y="397" text-anchor="middle">底座 substrate · OpenCode / Codex CLI</text>
<text class="t-note" x="400" y="418" text-anchor="middle">CLI、工具實作、模型供應商：凍結，不在可編輯的範圍內</text>
</svg>
</div>
<figcaption>圖 1 · harness 是一棵檔案樹，八個元件掛在執行過程的不同位置：常駐文字佔上下文，skill、腳本、子 agent 用到才花成本，外掛卡在工具迴圈裡</figcaption>
</figure>

圖裡要看的是左右兩邊的成本不一樣。左邊四份常駐文字每一次執行都在上下文裡，每個字都和 agent 自己的輸出搶預算；右邊的腳本、子 agent 只在被呼叫時才花成本，外掛則卡在工具呼叫的必經之路上，模型想跳也跳不過去。這個差別後面會決定一條修改該放在哪裡。Codex 上的佈局略有不同：常駐檔案只有 `AGENTS.md` 一個，外掛的位置換成 `.codex/hooks.json`。

最下面那一條是 OpenCode 和 Codex 本身。倉庫把它們叫做 substrate，也就是「底座」，視為凍結的基礎設施；改進只發生在上面這八個元件的路徑上，寫到別處的檔案會被直接丟掉[^h2-tree]。

### 平常誰在改它

知道了 harness 是什麼，再看它平常怎麼變。

**最常見的做法是人手改。** 我們看到 agent 某件事做得不好，就去 `AGENTS.md` 補一條規則，或者寫一個 skill。這樣的 harness 是寫給「所有任務」的：部署之前定稿，之後每個任務都用同一份。它的問題也在這裡，寫的人不可能預見所有任務，補的規則只會越堆越長。

**第二種做法是把修改自動化，但放在部署之前。** 論文把這一類叫離線方法，舉的例子有 Meta-Harness、AHE 等：準備一批固定的訓練任務，讓優化器反覆改 harness、跑任務、看分數，搜出一份最好的再上線[^h2-paper]。這已經不需要人了，可它需要兩樣東西：事先拿得到的任務，和能打分的評分器。

Harness² 針對的恰好是這兩樣都沒有的情況。**任務到來時才第一次見到，沒有評分器，也沒有標準答案，還能不能改 harness？**

## 二、沒有標準答案時的修改依據

### 現象：開放世界

論文把這種設定叫開放世界（open world）：任務像一條流一樣陸續到來，設計 harness 的時候沒見過，執行的時候也沒有人告訴你做得對不對[^h2-paper]。三個基準都是這一類工作：JobBench 是各種職業的辦公任務，LAB 是法律工作，WorkBuddy-Bench 是辦公、網頁和程式碼[^h2-readme]。交付的是一份文件或一張表，沒有單元測試可以跑。

### 第一次嘗試及其不足

最直覺的辦法是讓 agent 做完之後回頭看自己的軌跡，反思哪裡沒做好，再把教訓寫進 harness。這條路的問題是只有一次執行：agent 這次漏了一步，是 harness 沒交代清楚，還是這一次抽樣剛好走偏了？一條軌跡分不出來。更麻煩的是寫進去的修改有沒有用也無從知道，因為沒有再跑一次來對照。

### 解決：Propose–Probe–Compose

Harness² 的辦法是把「同一個任務多跑幾次」當成證據來源。先介紹兩個角色：**執行器**（倉庫裡叫 solver）是真正做任務的 agent，它每完整跑一次任務稱為一次 **rollout**；**改進器**（improver）是另一個 agent 會話，它不做任務，只負責讀證據、改 harness。兩者連用哪個模型都是分開設定的。它們在倉庫裡的關係是這樣[^h2-recursion][^h2-proposer]：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 532" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-topo-title h2-topo-desc">
<title id="h2-topo-title">Harness² 的組成：兩個 agent 與中間的方法層</title>
<desc id="h2-topo-desc">左邊是執行器，在 OpenCode 或 Codex 上做任務，每個任務跑 4k 次 rollout，交出 run_dir，並裝入一棵 harness 檔案樹。右邊是改進器，透過 claude_cli 或 opencode 通道開會話，每個任務 3k 次，讀 context 目錄，帶回 context/harness。中間是 harness2/core 的方法層：recursion.py 排程，context.py 把 run_dir 擺成 context 並掃描，tree.py 讀回檔案樹並校驗，selection.py 盲選與反思，library.py 管經驗庫。下面是方法之外的三樣東西：基準適配器、凍結的底座，以及選定之後才呼叫 score 的 harness2-judge。</desc>
<defs>
<marker id="h2-topo-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-topo-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="532" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="240" height="320" rx="8"/>
<text class="t-zone" x="36" y="44">執行器 · 做任務</text>
<rect class="dd-zone" x="272" y="16" width="256" height="320" rx="8"/>
<text class="t-zone" x="292" y="44">方法 · harness2/core/</text>
<rect class="dd-zone" x="544" y="16" width="240" height="320" rx="8"/>
<text class="t-zone" x="564" y="44">改進器 · 改 harness</text>
<rect class="dd-zone" x="16" y="352" width="768" height="112" rx="8"/>
<text class="t-zone" x="36" y="380">方法之外</text>
<path class="dd-edge" d="M240 140 H286" marker-end="url(#h2-topo-arrow)"/>
<path class="dd-edge" d="M512 140 H558" marker-end="url(#h2-topo-arrow)"/>
<path class="dd-edge-acc" d="M560 192 H514" marker-end="url(#h2-topo-arrow-acc)"/>
<path class="dd-edge-acc" d="M288 192 H242" marker-end="url(#h2-topo-arrow-acc)"/>
<rect class="dd-card" x="32" y="69" width="208" height="136" rx="6"/>
<path class="dd-card-head" d="M38 69 H234 Q240 69 240 75 V101 H32 V75 Q32 69 38 69 Z"/>
<text class="t-name" x="44" y="90">rollout</text>
<text class="t-mono" x="228" y="90" text-anchor="end">base1</text>
<text class="t-mono" x="44" y="118">底座</text>
<text class="t-val" x="228" y="118" text-anchor="end">OpenCode / Codex</text>
<line class="dd-hair" x1="32" y1="127" x2="240" y2="127"/>
<text class="t-mono" x="44" y="144">交出</text>
<text class="t-val" x="228" y="144" text-anchor="end">run_dir</text>
<line class="dd-hair" x1="32" y1="153" x2="240" y2="153"/>
<text class="t-mono" x="44" y="170">每個任務</text>
<text class="t-val" x="228" y="170" text-anchor="end">4k 次</text>
<line class="dd-hair" x1="32" y1="179" x2="240" y2="179"/>
<text class="t-mono" x="44" y="196">裝入</text>
<text class="t-val" x="228" y="196" text-anchor="end"><tspan class="t-acc">harness 檔案樹</tspan></text>
<text class="t-note" x="36" y="228">模型由這個變數指定：</text>
<text class="t-small" x="36" y="246">HARNESS2_SOLVER_MODEL</text>
<text class="t-say" x="36" y="286">兩個 agent 各跑各的行程，</text>
<text class="t-say" x="36" y="306">中間只隔著磁碟上的目錄。</text>
<rect class="dd-card" x="288" y="64" width="224" height="48" rx="6"/>
<text class="t-mono" x="300" y="84">recursion.py</text>
<text class="t-note" x="300" y="102">run_parallel · run_sequential</text>
<rect class="dd-card" x="288" y="116" width="224" height="48" rx="6"/>
<text class="t-mono" x="300" y="136">context.py</text>
<text class="t-note" x="300" y="154">run_dir 擺成 context/，再掃一遍</text>
<rect class="dd-card" x="288" y="168" width="224" height="48" rx="6"/>
<text class="t-mono" x="300" y="188">tree.py</text>
<text class="t-note" x="300" y="206">讀回檔案樹，校驗，不合格退回</text>
<rect class="dd-card" x="288" y="220" width="224" height="48" rx="6"/>
<text class="t-mono" x="300" y="240">selection.py</text>
<text class="t-note" x="300" y="258">盲選一份交付，交付後反思</text>
<rect class="dd-card" x="288" y="272" width="224" height="48" rx="6"/>
<text class="t-mono" x="300" y="292">library.py</text>
<text class="t-note" x="300" y="310">INDEX.md · EXPERIENCE.md</text>
<rect class="dd-card" x="560" y="69" width="208" height="136" rx="6"/>
<path class="dd-card-head" d="M566 69 H762 Q768 69 768 75 V101 H560 V75 Q560 69 566 69 Z"/>
<text class="t-name" x="572" y="90">session</text>
<text class="t-mono" x="756" y="90" text-anchor="end">inv1.a1</text>
<text class="t-mono" x="572" y="118">通道</text>
<text class="t-val" x="756" y="118" text-anchor="end">claude_cli / opencode</text>
<line class="dd-hair" x1="560" y1="127" x2="768" y2="127"/>
<text class="t-mono" x="572" y="144">讀</text>
<text class="t-val" x="756" y="144" text-anchor="end">context/</text>
<line class="dd-hair" x1="560" y1="153" x2="768" y2="153"/>
<text class="t-mono" x="572" y="170">每個任務</text>
<text class="t-val" x="756" y="170" text-anchor="end">3k 次</text>
<line class="dd-hair" x1="560" y1="179" x2="768" y2="179"/>
<text class="t-mono" x="572" y="196">帶回</text>
<text class="t-val" x="756" y="196" text-anchor="end"><tspan class="t-acc">context/harness/</tspan></text>
<text class="t-note" x="564" y="228">模型由這個變數指定：</text>
<text class="t-small" x="564" y="246">HARNESS2_IMPROVER_MODEL</text>
<text class="t-note" x="564" y="286">工作目錄是臨時的：</text>
<text class="t-small" x="564" y="304">harness2-prop-*/context/</text>
<rect class="dd-card" x="32" y="396" width="224" height="48" rx="6"/>
<text class="t-name" x="44" y="416">基準適配器</text>
<text class="t-small" x="44" y="434">jobbench · lab · workbuddy</text>
<rect class="dd-card" x="272" y="396" width="256" height="48" rx="6"/>
<text class="t-name" x="284" y="416">底座 · 凍結</text>
<text class="t-small" x="284" y="434">substrate/opencode.py · codex.py</text>
<rect class="dd-derived" x="544" y="396" width="224" height="48" rx="6"/>
<text class="t-mono" x="556" y="416">harness2-judge</text>
<text class="t-note" x="556" y="434">虛線：選定之後才呼叫 score()</text>
<text class="t-say" x="36" y="492">強調色是 harness 檔案樹走的路：改進器在 context/harness/ 裡就地改，tree.py 校驗後裝進下一次 rollout。</text>
<text class="t-say" x="36" y="512">分數不在這個迴圈裡：harness2-judge 是另一個命令，算出來的分數不會流回上面任何一格。</text>
</svg>
</div>
<figcaption>圖 2 · 兩個 agent 不直接對話：執行器交出執行記錄，方法層擺成目錄給改進器讀，改進器改完的檔案樹經過校驗再裝回執行器</figcaption>
</figure>

兩個 agent 之間沒有任何直接的通訊。執行器跑完留下一個執行目錄，方法層的 `context.py` 把它整理成一個 `context/` 目錄；改進器在一個臨時工作目錄裡讀它，直接修改其中的 `context/harness/`，會話結束後這棵檔案樹被取回來，校驗通過才裝進下一次 rollout。方法層本身不呼叫模型，它只負責排程、擺放證據和檢查結果。

有了這張圖，一步改進的過程就好講了。它分三段，倉庫把這三段叫 Propose–Probe–Compose：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 700" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-seq-title h2-seq-desc">
<title id="h2-seq-title">k=1 時一步改進的時序</title>
<desc id="h2-seq-desc">三條生命線：執行器、recursion.py、改進器。recursion.py 先讓執行器用未修改的 harness 跑 base1，是第 1 次 rollout；再開 inv1 和 dom1 兩個改進會話；兩棵候選檔案樹回來後經過校驗，各裝進執行器再跑一次，是第 2、3 次 rollout；然後開合成會話 agg1 讀三次執行的對照，帶回合成的檔案樹和筆記；agg1 再跑一次，是第 4 次 rollout，產出就是交付物。k=1 時用 trivial_selection 不開選擇會話。最後開一次不計入預算的反思會話，把 EXPERIENCE.md 寫進經驗庫。</desc>
<defs>
<marker id="h2-seq-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-seq-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="700" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="256" height="616" rx="8"/>
<text class="t-zone" x="36" y="44"></text>
<rect class="dd-zone" x="288" y="16" width="224" height="616" rx="8"/>
<text class="t-zone" x="308" y="44"></text>
<rect class="dd-zone" x="528" y="16" width="256" height="616" rx="8"/>
<text class="t-zone" x="548" y="44"></text>
<path class="dd-edge dd-dash" d="M176 104 V612"/>
<path class="dd-edge dd-dash" d="M400 104 V612"/>
<path class="dd-edge dd-dash" d="M656 104 V612"/>
<path class="dd-edge" d="M400 148 H178" marker-end="url(#h2-seq-arrow)"/>
<text class="t-mono" x="192" y="140">base1 · 未修改的 harness</text>
<text class="t-val" x="32" y="152">R 1</text>
<path class="dd-edge" d="M400 188 H654" marker-end="url(#h2-seq-arrow)"/>
<text class="t-mono" x="416" y="180">inv1 · 只改做事的方式</text>
<text class="t-val" x="32" y="192">P 1</text>
<path class="dd-edge" d="M400 228 H654" marker-end="url(#h2-seq-arrow)"/>
<text class="t-mono" x="416" y="220">dom1 · 只補這一行的知識</text>
<text class="t-val" x="32" y="232">P 2</text>
<path class="dd-edge-acc" d="M656 268 H402" marker-end="url(#h2-seq-arrow-acc)"/>
<text class="t-mono" x="416" y="260">兩棵候選檔案樹 · 校驗</text>
<path class="dd-edge-acc" d="M400 308 H178" marker-end="url(#h2-seq-arrow-acc)"/>
<text class="t-mono" x="192" y="300">inv1 · 裝上候選再跑</text>
<text class="t-val" x="32" y="312">R 2</text>
<path class="dd-edge-acc" d="M400 348 H178" marker-end="url(#h2-seq-arrow-acc)"/>
<text class="t-mono" x="192" y="340">dom1 · 裝上候選再跑</text>
<text class="t-val" x="32" y="352">R 3</text>
<path class="dd-edge" d="M400 388 H654" marker-end="url(#h2-seq-arrow)"/>
<text class="t-mono" x="416" y="380">agg1 · 讀三次執行的對照</text>
<text class="t-val" x="32" y="392">P 3</text>
<path class="dd-edge-acc" d="M656 428 H402" marker-end="url(#h2-seq-arrow-acc)"/>
<text class="t-mono" x="416" y="420">合成的檔案樹 + notes</text>
<path class="dd-edge-acc" d="M400 468 H178" marker-end="url(#h2-seq-arrow-acc)"/>
<text class="t-mono" x="192" y="460">agg1 · 產出就是交付物</text>
<text class="t-val" x="32" y="472">R 4</text>
<rect class="dd-card" x="304" y="494" width="192" height="32" rx="6"/>
<text class="t-mono" x="400.0" y="515.0" text-anchor="middle">trivial_selection</text>
<path class="dd-edge" d="M400 556 H654" marker-end="url(#h2-seq-arrow)"/>
<text class="t-mono" x="416" y="548">reflect · 不計入 P</text>
<path class="dd-edge" d="M656 596 H402" marker-end="url(#h2-seq-arrow)"/>
<text class="t-mono" x="416" y="588">EXPERIENCE.md · 入庫</text>
<rect class="dd-card" x="96" y="52" width="160" height="52" rx="6"/>
<text class="t-name" x="108" y="72">執行器</text>
<text class="t-note" x="108" y="90">OpenCode + harness</text>
<rect class="dd-card" x="304" y="52" width="192" height="52" rx="6"/>
<text class="t-mono" x="316" y="72">recursion.py</text>
<text class="t-small" x="316" y="90">run_parallel(k=1)</text>
<rect class="dd-card" x="560" y="52" width="192" height="52" rx="6"/>
<text class="t-name" x="572" y="72">改進器</text>
<text class="t-note" x="572" y="90">讀 context/，改 harness</text>
<text class="t-note" x="764" y="212" text-anchor="end">Propose</text>
<text class="t-note" x="764" y="332" text-anchor="end">Probe</text>
<text class="t-note" x="764" y="432" text-anchor="end">Compose</text>
<text class="t-note" x="764" y="580" text-anchor="end">Reflect</text>
<text class="t-say" x="36" y="660">k=1 只有一份最終產出，不開選擇會話。帳本只認左側標出的 4 次 rollout 和 3 次改進會話，反思另算。</text>
<text class="t-say" x="36" y="680">強調色是 harness 檔案樹：每一次都由改進器寫出來，校驗後才裝進執行器。</text>
</svg>
</div>
<figcaption>圖 3 · 一步改進的完整時序（k=1）：4 次 rollout、3 次改進會話，Propose、Probe、Compose 各佔一段</figcaption>
</figure>

1. **Propose（提案）**：執行器先用原始 harness 跑任務，得到參照執行 `base1`。兩個改進器各自讀這份軌跡和產出，各提出一份修改後的 harness，標籤是 `inv1` 和 `dom1`。
2. **Probe（試探）**：兩份候選 harness 各自在**同一個任務**上再跑一次。
3. **Compose（合成）**：第三個改進器拿到全部材料，合成出最終的 harness `agg1`，再跑一次，這次的產出就是交付物。

合成者手上到底有什麼？倉庫附了一個不需要模型的本地演示：任務是把 `ledger.csv` 裡帶正負號的金額加總，起點 harness 的 `scripts/summarize.py` 多寫了一個 `if amount > 0`，把負數漏掉了[^h2-demo]。`ledger-1` 的三筆金額是 100.25、−20.10、5.00，照原始碼算，漏掉負數得到 105.25，正確的總和是 85.15。把合成者的工作目錄畫出來是這樣：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 440" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-ctx-title h2-ctx-desc">
<title id="h2-ctx-title">合成者讀到的 context 目錄</title>
<desc id="h2-ctx-desc">左邊是執行器在同一個任務 ledger-1 上的三次 rollout：base1 用未修改的 harness，答案是 Total: 105.25；inv1 和 dom1 用修改過的 harness，答案是 Total: 85.15。右邊是合成者 agg1 的工作目錄 context，裡面有任務原文、base1 的完整軌跡和產出、每個候選的 harness 檔案與產出、經驗庫索引，以及可以就地編輯的 harness 目錄。評分標準、分數和參考答案不在這個目錄裡。</desc>
<defs>
<marker id="h2-ctx-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-ctx-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="440" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="296" height="408" rx="8"/>
<text class="t-zone" x="36" y="44">執行器 · 任務 ledger-1 跑三次</text>
<rect class="dd-zone" x="328" y="16" width="456" height="408" rx="8"/>
<text class="t-zone" x="348" y="44">改進器 · 合成者的工作目錄</text>
<path class="dd-edge-acc" d="M296 135 H312 Q320 135 320 143 V153 Q320 161 328 161 H342" marker-end="url(#h2-ctx-arrow-acc)"/>
<path class="dd-edge-acc" d="M296 235 H312 Q320 235 320 227 V221 Q320 213 328 213 H342" marker-end="url(#h2-ctx-arrow-acc)"/>
<path class="dd-edge" d="M296 335 H300 Q308 335 308 327 V247 Q308 239 316 239 H342" marker-end="url(#h2-ctx-arrow)"/>
<rect class="dd-card" x="32" y="64" width="264" height="84" rx="6"/>
<path class="dd-card-head" d="M38 64 H290 Q296 64 296 70 V96 H32 V70 Q32 64 38 64 Z"/>
<text class="t-name" x="44" y="85">rollout</text>
<text class="t-mono" x="284" y="85" text-anchor="end">base1</text>
<line class="dd-hair" x1="32" y1="122" x2="296" y2="122"/>
<text class="t-mono" x="44" y="113">harness</text>
<text class="t-val" x="284" y="113" text-anchor="end">base（未修改）</text>
<text class="t-mono" x="44" y="139">answer.txt</text>
<text class="t-val" x="284" y="139" text-anchor="end"><tspan class="t-acc">Total: 105.25</tspan></text>
<rect class="dd-card" x="32" y="164" width="264" height="84" rx="6"/>
<path class="dd-card-head" d="M38 164 H290 Q296 164 296 170 V196 H32 V170 Q32 164 38 164 Z"/>
<text class="t-name" x="44" y="185">rollout</text>
<text class="t-mono" x="284" y="185" text-anchor="end">inv1</text>
<line class="dd-hair" x1="32" y1="222" x2="296" y2="222"/>
<text class="t-mono" x="44" y="213">harness</text>
<text class="t-val" x="284" y="213" text-anchor="end">base + INV 的修改</text>
<text class="t-mono" x="44" y="239">answer.txt</text>
<text class="t-val" x="284" y="239" text-anchor="end"><tspan class="t-acc">Total: 85.15</tspan></text>
<rect class="dd-card" x="32" y="264" width="264" height="84" rx="6"/>
<path class="dd-card-head" d="M38 264 H290 Q296 264 296 270 V296 H32 V270 Q32 264 38 264 Z"/>
<text class="t-name" x="44" y="285">rollout</text>
<text class="t-mono" x="284" y="285" text-anchor="end">dom1</text>
<line class="dd-hair" x1="32" y1="322" x2="296" y2="322"/>
<text class="t-mono" x="44" y="313">harness</text>
<text class="t-val" x="284" y="313" text-anchor="end">base + DOM 的修改</text>
<text class="t-mono" x="44" y="339">answer.txt</text>
<text class="t-val" x="284" y="339" text-anchor="end">Total: 85.15</text>
<text class="t-say" x="36" y="380">同一個任務、同一個模型，</text>
<text class="t-say" x="36" y="400">三次的差別只在 harness。</text>
<rect class="dd-card" x="344" y="64" width="424" height="240" rx="6"/>
<path class="dd-card-head" d="M350 64 H762 Q768 64 768 70 V96 H344 V70 Q344 64 350 64 Z"/>
<text class="t-name" x="356" y="85">context/</text>
<text class="t-mono" x="756" y="85" text-anchor="end">agg1</text>
<line class="dd-hair" x1="344" y1="122" x2="768" y2="122"/>
<line class="dd-hair" x1="344" y1="148" x2="768" y2="148"/>
<line class="dd-hair" x1="344" y1="174" x2="768" y2="174"/>
<line class="dd-hair" x1="344" y1="200" x2="768" y2="200"/>
<line class="dd-hair" x1="344" y1="226" x2="768" y2="226"/>
<line class="dd-hair" x1="344" y1="252" x2="768" y2="252"/>
<line class="dd-hair" x1="344" y1="278" x2="768" y2="278"/>
<text class="t-mono" x="356" y="113">task.md</text>
<text class="t-val" x="756" y="113" text-anchor="end">任務原文，去掉評分條件</text>
<text class="t-mono" x="356" y="139">rollouts/base1.md</text>
<text class="t-val" x="756" y="139" text-anchor="end">完整軌跡，不截斷</text>
<text class="t-mono" x="356" y="165">deliverables/base1.md</text>
<text class="t-val" x="756" y="165" text-anchor="end"><tspan class="t-acc">Total: 105.25</tspan></text>
<text class="t-mono" x="356" y="191">pool/inv1/harness/</text>
<text class="t-val" x="756" y="191" text-anchor="end">候選的檔案樹</text>
<text class="t-mono" x="356" y="217">pool/inv1/produced.md</text>
<text class="t-val" x="756" y="217" text-anchor="end"><tspan class="t-acc">Total: 85.15</tspan></text>
<text class="t-mono" x="356" y="243">pool/dom1/produced.md</text>
<text class="t-val" x="756" y="243" text-anchor="end">Total: 85.15</text>
<text class="t-mono" x="356" y="269">library/INDEX.md</text>
<text class="t-val" x="756" y="269" text-anchor="end">同領域更早任務留下的成品</text>
<text class="t-mono" x="356" y="295">harness/</text>
<text class="t-val" x="756" y="295" text-anchor="end">就地編輯，磁碟上的改動即提案</text>
<rect class="dd-derived" x="344" y="320" width="424" height="44" rx="6"/>
<text class="t-note" x="556" y="347" text-anchor="middle">rubric · 分數 · 參考答案</text>
<text class="t-say" x="348" y="392">虛線：評分材料不在 context/ 裡，</text>
<text class="t-say" x="348" y="412">目錄交給模型之前還會再掃一遍。</text>
</svg>
</div>
<figcaption>圖 4 · 合成者判斷一次修改有沒有用，靠的是同一個任務上「改之前」和「改之後」的對照，不是分數</figcaption>
</figure>

演示裡的兩個「編輯者」是同一段寫死的腳本，所以 `inv1` 和 `dom1` 改的是同一處；接上真模型時，兩者的分工是不同的，下面會講。

圖裡要看的是兩個數字。合成者不知道 85.15 才是對的，沒有人告訴它；它能看到的是**行為上的對照**：去掉那個過濾條件之後，總和把負數算進來了，而任務說的是「每一筆帶正負號的金額」。論文對這一點說得很克制：對照顯示的是行為怎麼變了，不是答案對不對[^h2-paper]。

### 兩個編輯者的分工

Propose 階段為什麼是兩個編輯者，而不是一個？提示裡把修改的方向切成了兩個互不重疊的維度[^h2-prompts]：

- **INV（invariant）管 agent 怎麼做事**：流程、工具的用法、怎樣才算做完，與題材無關。提示要求它找出軌跡裡最早偏掉的那一步，並且「不要加專業規則」。
- **DOM（domain）管這一行的人知道什麼**：交付物該有哪些部分、哪些數字要對得上、內行人不會漏掉什麼。提示要求它「不要診斷命令、重試、腳本或 hook」。

兩份候選各改一個維度，各跑一次，合成者才能分清一個變化是哪一類修改帶來的。如果只有一個編輯者把兩類修改混在一起，跑出來變好了，也說不清是誰的功勞。

### 證據怎麼算數

對照不等於真相，所以提示花了很多篇幅約束合成者怎麼讀證據[^h2-prompts]：

- 參照執行要當成一組樣本來讀。只在 k 次裡出現一次的缺陷算噪音，針對它的修改是對單一樣本過擬合。
- 每個候選只跑了一次，一次好結果只是弱證據。一條修改要留下來，必須指得出軌跡裡的**具體時刻**；「這看起來是好習慣」不算。
- 幾個候選意見一致只是支持，不是獨立的證明，因為它們出自同一個模型、同一批證據。
- 什麼都不改是合法的結論，叫 `KEEP_BASE`。提示的原話是：證據不支持的改動只花上下文，買不到東西。

合成者最後要交一份筆記，每個決定一行，格式是 `kept|revised|dropped -> 哪條修改 -> 哪個軌跡時刻`。

### 把答案擋在外面

既然整個方法的前提是「沒有標準答案」，評分材料就不能出現在改進器讀得到的任何地方。程式碼裡有三處在守這條線：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 460" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-guard-title h2-guard-desc">
<title id="h2-guard-title">擋住評分材料的三層，和繞過它們的一條路</title>
<desc id="h2-guard-desc">左邊是評分材料：RUBRICS.json、score 函式和參考答案。中間是框架設的三層：提示層在每段提示裡寫明 WHAT YOU DO NOT HAVE；資料結構層的 RunView 沒有分數欄位；檔案層按 pass_rate 等標記掃描 context 目錄，命中就拒絕。三層都過了，context 目錄才交給改進器。下面是框架管不到的一條路：JobBench 的 RUBRICS.json 公開掛在 huggingface.co，一次驗證執行裡 Codex 執行器沒有人指示就自己下載了它，內容落在這次 rollout 的軌跡裡，而軌跡會原樣進入 context/rollouts。</desc>
<defs>
<marker id="h2-guard-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-guard-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="460" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="184" height="256" rx="8"/>
<text class="t-zone" x="36" y="44">評分材料</text>
<rect class="dd-zone" x="216" y="16" width="368" height="256" rx="8"/>
<text class="t-zone" x="236" y="44">框架自己設的三層</text>
<rect class="dd-zone" x="600" y="16" width="184" height="256" rx="8"/>
<text class="t-zone" x="620" y="44">改進器讀到的</text>
<rect class="dd-zone" x="16" y="288" width="768" height="104" rx="8"/>
<text class="t-zone" x="36" y="316">框架管不到的一條路</text>
<path class="dd-edge" d="M184 119 H208"/>
<path class="dd-edge" d="M208 85 V225"/>
<path class="dd-edge" d="M208 85 H230" marker-end="url(#h2-guard-arrow)"/>
<path class="dd-edge" d="M208 155 H230" marker-end="url(#h2-guard-arrow)"/>
<path class="dd-edge" d="M208 225 H230" marker-end="url(#h2-guard-arrow)"/>
<path class="dd-edge-acc" d="M264 350 H294" marker-end="url(#h2-guard-arrow-acc)"/>
<path class="dd-edge-acc" d="M536 350 H614" marker-end="url(#h2-guard-arrow-acc)"/>
<path class="dd-edge dd-dash" d="M752 332 V176" marker-end="url(#h2-guard-arrow)"/>
<rect class="dd-card" x="32" y="64" width="152" height="110" rx="6"/>
<path class="dd-card-head" d="M38 64 H178 Q184 64 184 70 V96 H32 V70 Q32 64 38 64 Z"/>
<text class="t-name" x="44" y="85">評分器</text>
<text class="t-mono" x="44" y="113">RUBRICS.json</text>
<line class="dd-hair" x1="32" y1="122" x2="184" y2="122"/>
<text class="t-mono" x="44" y="139">score()</text>
<line class="dd-hair" x1="32" y1="148" x2="184" y2="148"/>
<text class="t-mono" x="44" y="165">參考答案</text>
<rect class="dd-card" x="232" y="56" width="336" height="58" rx="6"/>
<path class="dd-card-head" d="M238 56 H562 Q568 56 568 62 V88 H232 V62 Q232 56 238 56 Z"/>
<text class="t-name" x="244" y="77">提示層</text>
<text class="t-mono" x="556" y="77" text-anchor="end">prompts.py</text>
<text class="t-mono" x="244" y="105">WHAT YOU DO NOT HAVE</text>
<text class="t-val" x="556" y="105" text-anchor="end">每段提示都帶</text>
<rect class="dd-card" x="232" y="126" width="336" height="58" rx="6"/>
<path class="dd-card-head" d="M238 126 H562 Q568 126 568 132 V158 H232 V132 Q232 126 238 126 Z"/>
<text class="t-name" x="244" y="147">資料結構層</text>
<text class="t-mono" x="556" y="147" text-anchor="end">spec.py</text>
<text class="t-mono" x="244" y="175">RunView</text>
<text class="t-val" x="556" y="175" text-anchor="end">沒有分數欄位</text>
<rect class="dd-card" x="232" y="196" width="336" height="58" rx="6"/>
<path class="dd-card-head" d="M238 196 H562 Q568 196 568 202 V228 H232 V202 Q232 196 238 196 Z"/>
<text class="t-name" x="244" y="217">檔案層</text>
<text class="t-mono" x="556" y="217" text-anchor="end">context.py</text>
<text class="t-mono" x="244" y="245">"pass_rate" …</text>
<text class="t-val" x="556" y="245" text-anchor="end">命中就拒絕整個目錄</text>
<rect class="dd-card" x="616" y="64" width="152" height="110" rx="6"/>
<path class="dd-card-head" d="M622 64 H762 Q768 64 768 70 V96 H616 V70 Q616 64 622 64 Z"/>
<text class="t-name" x="628" y="85">context/</text>
<text class="t-mono" x="628" y="113">task.md</text>
<line class="dd-hair" x1="616" y1="122" x2="768" y2="122"/>
<text class="t-mono" x="628" y="139">rollouts/</text>
<line class="dd-hair" x1="616" y1="148" x2="768" y2="148"/>
<text class="t-mono" x="628" y="165">pool/</text>
<text class="t-note" x="620" y="200">三層都過了才交出去</text>
<rect class="dd-card" x="32" y="332" width="232" height="36" rx="6"/>
<text class="t-mono" x="44" y="355.0" text-anchor="start"><tspan class="t-acc">huggingface.co · RUBRICS.json</tspan></text>
<rect class="dd-card" x="296" y="332" width="240" height="36" rx="6"/>
<text class="t-name" x="308" y="355.0" text-anchor="start">Codex 執行器，沒人指示就自己下載</text>
<rect class="dd-card" x="616" y="332" width="152" height="36" rx="6"/>
<text class="t-name" x="628" y="355.0" text-anchor="start">這次 rollout 的軌跡</text>
<text class="t-say" x="36" y="420">三層擋的是框架自己遞出去的東西。執行器能上網時，公開的評分標準會從執行記錄這一側進來。</text>
<text class="t-say" x="36" y="440">虛線：軌跡會原樣擺進 context/rollouts/，所以下載到的內容能被改進器讀到。這一步是我們的推斷。</text>
</svg>
</div>
<figcaption>圖 5 · 三層檢查只管得住框架自己遞出去的東西；執行器若能上網，評分標準可以從執行記錄這一側繞進來</figcaption>
</figure>

**提示層**：每一段任務提示都帶著一節 `WHAT YOU DO NOT HAVE`，寫明上下文裡沒有 rubric、評分條件、參考答案或分數，也不准去猜「評分器想要什麼」[^h2-prompts]。

**資料結構層**：描述一次 rollout 的 `RunView` 沒有分數欄位；基準適配器提供的提示文字如果出現 benchmark、rubric、score 這些字眼，建構時就直接報錯；真正的打分函式 `score()` 只在最終選定之後才由獨立的 `harness2-judge` 呼叫[^h2-spec]。

**檔案層**：`context/` 目錄交給模型之前，會按一組標記字串（例如 `"pass_rate"`）掃描檔名和內容，命中就拒絕。註解特地寫明這是絆線而不是防線：觸發了要去找是哪條讀取路徑漏了，不要去加長標記清單[^h2-context]。

圖的下半部是這三層管不到的地方。實驗指南坦白記了一件事：驗證這個版本時，有一次 Codex 執行器沒有人指示，自己上網把任務的評分標準下載了下來，因為 JobBench 的 `RUBRICS.json` 就公開掛在 Hugging Face 上[^h2-experiments]。Codex 執行時沙箱是關掉的，所以文件要求評測前在網路層封掉 `huggingface.co`、`github.com` 這些網域，並且在報告結果時一併說明用了哪些限制。

### 改在哪個元件上

還剩一個問題：編輯者想加一條指示，放進八個元件的哪一個？回到圖 1 左右兩邊的成本差別。提示直接給了優先順序：有明確觸發情境的做成 skill，確定性的操作做成腳本，輸入大而答案小的調查交給子 agent，模型屢次跳過的檢查放進外掛，**常駐文字排最後**，而且加一段就得刪一段[^h2-prompts]。理由是這類工作最貴的失敗，是 agent 把輸出預算全花在推理上、最後什麼都沒交。常駐文字越長，這種失敗越容易發生。

論文附錄裡有一組數字可以對照這個排序。在 WorkBuddy-Bench 上，把改好的 harness 裡某一類元件拿掉再評分：拿掉 skill 掉 5.3 分，拿掉腳本掉 3.7 分，拿掉常駐文字那幾項只掉 1.5 分；改動的檔案裡，skill 和腳本大約佔一半[^h2-paper]。論文也提醒，這幾個數字不能直接相加，但方向是清楚的：起作用的主要是按需載入的那幾類。

提案在執行之前還要過一道校驗[^h2-tree]：每個 skill 的名字必須在常駐文字裡出現（否則 agent 不知道要載入它），提到的腳本必須真的存在且能編譯，常駐文字不能超過上限（OpenCode 是 24,000 字元，Codex 是 8,000）。不合格的提案會連同錯誤訊息退回去重試一次，再不行就沿用目前的 harness[^h2-recursion]。

到這裡，一步改進就講完了。一步要跑四次任務：一次參照、兩次試探、一次最終。**如果願意多花一些預算，該把它花在哪裡？**

## 三、遞歸預算的兩種用法

命令列上的 `--k` 就是這個預算，`--mode` 決定怎麼花[^h2-readme]。兩種模式的開頭一樣：先用原始 harness 跑 k 次參照執行。之後分成兩條路[^h2-recursion]：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 472" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-modes-title h2-modes-desc">
<title id="h2-modes-title">k=2 時並行與串行兩種遞歸</title>
<desc id="h2-modes-desc">並行模式：兩次參照執行 base1、base2 之後，同時提出四個候選 inv1、dom1、inv2、dom2 並各跑一次，再由兩個合成者各自讀全部證據，合成 agg1 和 agg2，盲選其中一個交付。串行模式：第一輪從 base 出發提出 inv1、dom1，合成檢查點 c1；第二輪在 c1 上繼續提出 inv2、dom2，合成 c2；c1 和 c2 一起進入盲選。兩種模式都是 8 次 rollout、6 次改進會話。</desc>
<defs>
<marker id="h2-modes-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-modes-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="472" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="224" rx="8"/>
<text class="t-zone" x="36" y="44">並行 · --mode parallel · 往寬處探</text>
<rect class="dd-zone" x="16" y="256" width="768" height="200" rx="8"/>
<text class="t-zone" x="36" y="284">串行 · --mode sequential · 往深處改</text>
<path class="dd-edge" d="M112 96 H152"/>
<path class="dd-edge" d="M112 176 H152"/>
<path class="dd-edge" d="M152 76 V196"/>
<path class="dd-edge" d="M152 76 H198" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M152 116 H198" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M152 156 H198" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M152 196 H198" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M272 76 H336"/>
<path class="dd-edge" d="M272 116 H336"/>
<path class="dd-edge" d="M272 156 H336"/>
<path class="dd-edge" d="M272 196 H336"/>
<path class="dd-edge" d="M336 76 V196"/>
<path class="dd-edge" d="M336 96 H398" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M336 176 H398" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M472 96 H516"/>
<path class="dd-edge" d="M472 176 H516"/>
<path class="dd-edge" d="M516 96 V176"/>
<path class="dd-edge" d="M516 136 H558" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M648 136 H686" marker-end="url(#h2-modes-arrow)"/>
<rect class="dd-card" x="40" y="80" width="72" height="32" rx="6"/>
<text class="t-mono" x="76" y="101" text-anchor="middle">base1</text>
<rect class="dd-card" x="40" y="160" width="72" height="32" rx="6"/>
<text class="t-mono" x="76" y="181" text-anchor="middle">base2</text>
<rect class="dd-card" x="200" y="60" width="72" height="32" rx="6"/>
<text class="t-mono" x="236" y="81" text-anchor="middle">inv1</text>
<rect class="dd-card" x="200" y="100" width="72" height="32" rx="6"/>
<text class="t-mono" x="236" y="121" text-anchor="middle">dom1</text>
<rect class="dd-card" x="200" y="140" width="72" height="32" rx="6"/>
<text class="t-mono" x="236" y="161" text-anchor="middle">inv2</text>
<rect class="dd-card" x="200" y="180" width="72" height="32" rx="6"/>
<text class="t-mono" x="236" y="201" text-anchor="middle">dom2</text>
<rect class="dd-card" x="400" y="80" width="72" height="32" rx="6"/>
<text class="t-mono" x="436" y="101" text-anchor="middle">agg1</text>
<rect class="dd-card" x="400" y="160" width="72" height="32" rx="6"/>
<text class="t-mono" x="436" y="181" text-anchor="middle">agg2</text>
<rect class="dd-card" x="560" y="120" width="88" height="32" rx="6"/>
<text class="t-name" x="604" y="141" text-anchor="middle">盲選</text>
<rect class="dd-card" x="688" y="120" width="72" height="32" rx="6"/>
<text class="t-name" x="724" y="141" text-anchor="middle">交付</text>
<text class="t-note" x="40" y="228">k 次參照執行</text>
<text class="t-note" x="184" y="228">2k 個候選，各跑一次</text>
<text class="t-note" x="384" y="228">k 個合成，各跑一次</text>
<path class="dd-edge" d="M112 320 H140"/>
<path class="dd-edge" d="M112 368 H140"/>
<path class="dd-edge" d="M140 320 V368"/>
<path class="dd-edge" d="M140 320 H166" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M140 368 H166" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M232 320 H256"/>
<path class="dd-edge" d="M232 368 H256"/>
<path class="dd-edge" d="M256 320 V368"/>
<path class="dd-edge" d="M256 344 H278" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge-acc" d="M344 344 H368"/>
<path class="dd-edge-acc" d="M368 320 V368"/>
<path class="dd-edge-acc" d="M368 320 H390" marker-end="url(#h2-modes-arrow-acc)"/>
<path class="dd-edge-acc" d="M368 368 H390" marker-end="url(#h2-modes-arrow-acc)"/>
<path class="dd-edge" d="M456 320 H480"/>
<path class="dd-edge" d="M456 368 H480"/>
<path class="dd-edge" d="M480 320 V368"/>
<path class="dd-edge" d="M480 344 H502" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge" d="M568 344 H606" marker-end="url(#h2-modes-arrow)"/>
<path class="dd-edge-acc" d="M312 360 V392 Q312 400 320 400 H644 Q652 400 652 392 V364" marker-end="url(#h2-modes-arrow-acc)"/>
<path class="dd-edge" d="M696 344 H710" marker-end="url(#h2-modes-arrow)"/>
<rect class="dd-card" x="40" y="304" width="72" height="32" rx="6"/>
<text class="t-mono" x="76" y="325" text-anchor="middle">base1</text>
<rect class="dd-card" x="40" y="352" width="72" height="32" rx="6"/>
<text class="t-mono" x="76" y="373" text-anchor="middle">base2</text>
<rect class="dd-card" x="168" y="304" width="64" height="32" rx="6"/>
<text class="t-mono" x="200" y="325" text-anchor="middle">inv1</text>
<rect class="dd-card" x="168" y="352" width="64" height="32" rx="6"/>
<text class="t-mono" x="200" y="373" text-anchor="middle">dom1</text>
<rect class="dd-card" x="280" y="328" width="64" height="32" rx="6"/>
<text class="t-mono" x="312" y="349" text-anchor="middle"><tspan class="t-acc">c1</tspan></text>
<rect class="dd-card" x="392" y="304" width="64" height="32" rx="6"/>
<text class="t-mono" x="424" y="325" text-anchor="middle">inv2</text>
<rect class="dd-card" x="392" y="352" width="64" height="32" rx="6"/>
<text class="t-mono" x="424" y="373" text-anchor="middle">dom2</text>
<rect class="dd-card" x="504" y="328" width="64" height="32" rx="6"/>
<text class="t-mono" x="536" y="349" text-anchor="middle">c2</text>
<rect class="dd-card" x="608" y="328" width="88" height="32" rx="6"/>
<text class="t-name" x="652" y="349" text-anchor="middle">盲選</text>
<rect class="dd-card" x="712" y="328" width="56" height="32" rx="6"/>
<text class="t-name" x="740" y="349" text-anchor="middle">交付</text>
<text class="t-say" x="40" y="436"><tspan class="t-acc">c1</tspan> 既是第 2 輪編輯的起點，也和 c2 一起進入盲選：後一輪改偏了，還能選回前一輪。</text>
</svg>
</div>
<figcaption>圖 6 · k=2 時兩種模式都是 8 次 rollout 和 6 次改進會話，差別在候選是一次鋪開，還是一輪接一輪</figcaption>
</figure>

**並行**把預算一次鋪開：k 個 INV 候選和 k 個 DOM 候選彼此看不到對方，各自從同一批參照執行出發；然後 k 個合成者各自讀全部 2k 個候選的證據，各合成一份。提示還特地要求合成者不要去湊共識，交出自己站得住腳的那一份。

**串行**把預算疊起來：每一輪只提兩個候選，合成出一個檢查點，下一輪在這個檢查點上繼續改。這個檢查點自己跑的那一次，就是下一輪最直接的證據，它顯示的是「目前正在改的這份 harness 實際做了什麼」。合成者可以把更早被否決的候選撿回來，也可以原樣重現更早的檢查點。

兩種模式的帳是一樣的。`ledger.py` 規定每個任務恰好 4k 次 rollout（k 次參照、2k 個候選、k 個最終）和 3k 次改進會話，執行結束時對不上就報錯[^h2-ledger]。這樣比較兩種模式時，花的錢是相等的。

k 大於 1 時會有 k 份最終產出，交哪一份？這一步叫盲選[^h2-selection]。選擇者只看到任務原文、原始材料和各份**產出**，候選的代號是打亂的 A、B、C，它不知道哪份產出來自哪份 harness，也看不到執行過程。評判的順序是：要求的東西有沒有交齊、形狀有沒有照任務說的來、內容對不對得上原始材料、自己和自己是否一致。k 等於 1 時只有一份，不開選擇會話，直接交付。

論文的結果是，k 從 1 加到 5，兩種模式的分數都往上走：WorkBuddy-Bench 的程式碼子集上，並行從 76.5 升到 80.8，辦公子集從 79.5 升到 83.4。串行在 k=2 時的程式碼子集上略高，到 k=5 時並行反超[^h2-paper]。

到這裡，一個任務的事情做完了。可是下一個任務到來時，起點又是那份空的 base harness。**前一個任務花四次以上執行換來的東西，就這樣丟掉嗎？**

## 四、第二層遞歸：改進 harness

### 現象：改好的 harness 不能直接沿用

最直覺的做法是讓下一個任務直接從上一個任務改好的 harness 出發。Harness² 沒有這樣做：`run_task` 每次都重新讀 base 的檔案樹，每個任務仍然從 base 開始[^h2-schedule]。論文沒有專門解釋為什麼不沿用，下面的原因是我們從提示的寫法推出來的：為一個任務改的 harness 裡寫著那個工作區的具體事實（哪兩個欄位對得上、哪個數值才是對的），提示甚至鼓勵編輯者把驗證過的事實直接寫進去。這些事實換一個任務就是錯的錨點，一路累積下去，harness 會越來越偏向前面的任務。

### 解決：把成品和教訓存進經驗庫

它留下來的是另一種東西。交付之後多開一個反思會話（Reflect），讀這個任務的完整改進記錄，寫兩樣東西[^h2-selection]：

1. **每條修改做了什麼**。把每個候選 harness 和 base 逐一比對，每個被改過的元件寫一行，給一個判定：`helped`、`hurt`、`no observed effect` 或 `confounded`。提示明說要優先用 `confounded`（分不清）：一個候選同時改了好幾處，或者背後只有一次執行，就該這樣標。
2. **最多一條可以帶走的教訓**。`cue` 寫這條教訓適用的情境形狀，`lesson` 寫要對 harness 做的操作，`evidence` 指回上面的哪幾行。記錄裡找不到具體的執行對照，就只寫一行 `NO_ENTRY`。

這裡有一個細節：盲選比的是產出，所以「被選中」只說明哪份產出讀起來最好，不說明哪條修改起了作用。提示因此要求反思時把所有候選都當證據，被否決的候選裡也可能有真正有用的修改。

反思通過了，被採用的**整棵 harness 樹**和一份 `EXPERIENCE.md` 才會存進經驗庫；三個欄位缺一個就不入庫[^h2-library]。用演示裡的任務畫出來是這樣：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 420" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-lib-title h2-lib-desc">
<title id="h2-lib-title">經驗庫在任務之間傳遞的東西</title>
<desc id="h2-lib-desc">左邊是 accounting 領域的任務流：ledger-1 從 base harness 出發，採用 agg1；交付後的反思把一條記錄寫進右邊的經驗庫。經驗庫裡 ledger-1 的 EXPERIENCE.md 記著採用的是 agg1、cue 是帳本同時有貸方和借方、lesson 是在 scripts/summarize.py 加總時兩種正負號都要算、evidence 是去掉只算正數的過濾後試探的總和包含了原先漏掉的金額、confidence 為 high；旁邊存著整棵被採用的 harness 樹和 INDEX.md。下一個任務 ledger-2 仍然從 base harness 出發，但它的編輯者會先讀這個經驗庫。</desc>
<defs>
<marker id="h2-lib-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-lib-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="420" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="232" height="388" rx="8"/>
<text class="t-zone" x="36" y="44">任務流 · accounting</text>
<rect class="dd-zone" x="264" y="16" width="520" height="388" rx="8"/>
<text class="t-zone" x="284" y="44">經驗庫 · agg/accounting/</text>
<path class="dd-edge-acc" d="M232 127 H278" marker-end="url(#h2-lib-arrow-acc)"/>
<path class="dd-edge" d="M280 352 H264 Q256 352 256 344 V295 Q256 287 248 287 H234" marker-end="url(#h2-lib-arrow)"/>
<rect class="dd-card" x="32" y="64" width="200" height="110" rx="6"/>
<path class="dd-card-head" d="M38 64 H226 Q232 64 232 70 V96 H32 V70 Q32 64 38 64 Z"/>
<text class="t-name" x="44" y="85">任務</text>
<text class="t-mono" x="220" y="85" text-anchor="end">ledger-1</text>
<line class="dd-hair" x1="32" y1="122" x2="232" y2="122"/>
<line class="dd-hair" x1="32" y1="148" x2="232" y2="148"/>
<text class="t-mono" x="44" y="113">起點</text>
<text class="t-val" x="220" y="113" text-anchor="end">base</text>
<text class="t-mono" x="44" y="139">採用</text>
<text class="t-val" x="220" y="139" text-anchor="end"><tspan class="t-acc">agg1</tspan></text>
<text class="t-mono" x="44" y="165">交付後</text>
<text class="t-val" x="220" y="165" text-anchor="end">Reflect</text>
<text class="t-note" x="36" y="198">反思通過才寫入 →</text>
<rect class="dd-card" x="32" y="232" width="200" height="110" rx="6"/>
<path class="dd-card-head" d="M38 232 H226 Q232 232 232 238 V264 H32 V238 Q32 232 38 232 Z"/>
<text class="t-name" x="44" y="253">任務</text>
<text class="t-mono" x="220" y="253" text-anchor="end">ledger-2</text>
<line class="dd-hair" x1="32" y1="290" x2="232" y2="290"/>
<line class="dd-hair" x1="32" y1="316" x2="232" y2="316"/>
<text class="t-mono" x="44" y="281">起點</text>
<text class="t-val" x="220" y="281" text-anchor="end">base</text>
<text class="t-mono" x="44" y="307">先讀</text>
<text class="t-val" x="220" y="307" text-anchor="end">context/library/</text>
<text class="t-mono" x="44" y="333">之後</text>
<text class="t-val" x="220" y="333" text-anchor="end">照常三段</text>
<text class="t-say" x="36" y="372">每個任務都從 base 出發，</text>
<text class="t-say" x="36" y="392">帶得走的只有右邊這個庫。</text>
<rect class="dd-card" x="280" y="56" width="488" height="214" rx="6"/>
<path class="dd-card-head" d="M286 56 H762 Q768 56 768 62 V88 H280 V62 Q280 56 286 56 Z"/>
<text class="t-name" x="292" y="77">EXPERIENCE.md</text>
<text class="t-mono" x="756" y="77" text-anchor="end">harnesses/ledger-1/</text>
<line class="dd-hair" x1="280" y1="114" x2="768" y2="114"/>
<line class="dd-hair" x1="280" y1="140" x2="768" y2="140"/>
<line class="dd-hair" x1="280" y1="192" x2="768" y2="192"/>
<line class="dd-hair" x1="280" y1="244" x2="768" y2="244"/>
<text class="t-mono" x="292" y="105">adopted</text>
<text class="t-val" x="388" y="105"><tspan class="t-acc">agg1</tspan></text>
<text class="t-mono" x="292" y="131">cue</text>
<text class="t-val" x="388" y="131">When a ledger includes credits and debits</text>
<text class="t-mono" x="292" y="157">lesson</text>
<text class="t-val" x="388" y="157">Include both signs when summing in</text>
<text class="t-val" x="388" y="183">scripts/summarize.py.</text>
<text class="t-mono" x="292" y="209">evidence</text>
<text class="t-val" x="388" y="209">removing the positive-only filter changed the probe</text>
<text class="t-val" x="388" y="235">total to include the credits omitted by the base …</text>
<text class="t-mono" x="292" y="261">confidence</text>
<text class="t-val" x="388" y="261">high for this observed arithmetic contrast</text>
<rect class="dd-card" x="280" y="286" width="488" height="36" rx="6"/>
<text class="t-mono" x="292" y="309">harnesses/ledger-1/harness/</text>
<text class="t-note" x="756" y="309" text-anchor="end">整棵被採用的檔案樹，不是摘要</text>
<rect class="dd-card" x="280" y="334" width="488" height="36" rx="6"/>
<text class="t-mono" x="292" y="357">INDEX.md</text>
<text class="t-note" x="756" y="357" text-anchor="end">每條記錄的 cue，供下一個任務先瀏覽</text>
<text class="t-say" x="284" y="392">找不到具體的執行對照，反思只寫 NO_ENTRY，這個任務就不入庫。</text>
</svg>
</div>
<figcaption>圖 7 · 任務之間傳遞的不是上一份 harness，而是「改過什麼、有什麼證據、什麼情境下適用」的記錄</figcaption>
</figure>

下一個任務的編輯者會在 `context/library/` 裡讀到這個庫：先看 `INDEX.md`，再打開看起來相關的幾條。庫裡的檔案只是範例，不在執行時的磁碟上，要用就得複製進自己的 harness；而且提示要求把舊任務斷言的工作區事實重新對過，因為那些在上一個任務是事實，在這裡只是假設[^h2-prompts]。

經驗庫按領域分開：同一個領域裡的任務按順序跑，只讀得到排在自己前面的任務；不同領域之間可以同時跑[^h2-experiments]。

庫裡的東西實際被用了多少？論文統計過改動的檔案裡有多少帶著經驗庫的來源：LAB 和 WorkBuddy-Bench 的辦公子集是 22%，JobBench 是 47%；而一半以上的行直接來自庫的檔案只有 10% 到 22%[^h2-paper]。也就是說，編輯者多半是參考之後改寫，很少整份照搬。

### 名字裡的「平方」

現在可以回答「平方」是什麼意思了。

改進器本身也是一個 agent：一次 OpenCode 或 Claude CLI 會話，有自己的任務說明（`MISSION.md`），有自己的工作目錄（`context/`），有自己的參考資料（經驗庫）[^h2-context]。這些東西合起來，就是改進器的 harness。論文把它叫**改進 harness**（improvement harness），和做任務用的**執行 harness** 區分開[^h2-readme]。

於是有了兩層，各改一份 harness：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 348" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-sq-title h2-sq-desc">
<title id="h2-sq-title">兩層遞歸各自改的是哪一份 harness</title>
<desc id="h2-sq-desc">左邊的執行器由不動的模型權重加上執行 harness 組成，執行 harness 是 c1 到 c8 那些檔案。右邊的改進器同樣由不動的模型權重加上改進 harness 組成，改進 harness 包含不變的任務說明與提示、每次重新擺放的 context 目錄，以及跨任務累積的 library 目錄。第一層任務級遞歸是改進器修改執行 harness；第二層 harnessing 級遞歸是任務做完後的反思把記錄寫進 library，也就是改進器自己的 harness。</desc>
<defs>
<marker id="h2-sq-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-sq-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="348" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="336" height="264" rx="8"/>
<text class="t-zone" x="36" y="44">執行器 = 模型 + 執行 harness</text>
<rect class="dd-zone" x="448" y="16" width="336" height="264" rx="8"/>
<text class="t-zone" x="468" y="44">改進器 = 模型 + 改進 harness</text>
<path class="dd-edge" d="M464 161 H338" marker-end="url(#h2-sq-arrow)"/>
<text class="t-note" x="400" y="150" text-anchor="middle">① 任務級</text>
<path class="dd-edge-acc" d="M184 226 V246 Q184 254 192 254 H608 Q616 254 616 246 V228" marker-end="url(#h2-sq-arrow-acc)"/>
<text class="t-note" x="400" y="246" text-anchor="middle"><tspan class="t-acc">② harnessing 級</tspan></text>
<rect class="dd-card" x="32" y="64" width="304" height="36" rx="6"/>
<text class="t-name" x="44" y="87.0" text-anchor="start">模型權重 · 不動</text>
<rect class="dd-card" x="32" y="116" width="304" height="110" rx="6"/>
<path class="dd-card-head" d="M38 116 H330 Q336 116 336 122 V148 H32 V122 Q32 116 38 116 Z"/>
<text class="t-name" x="44" y="137">執行 harness</text>
<text class="t-mono" x="324" y="137" text-anchor="end">c1 … c8</text>
<text class="t-mono" x="44" y="165">systemprompt.md · guardrails.md</text>
<text class="t-val" x="324" y="165" text-anchor="end">常駐</text>
<line class="dd-hair" x1="32" y1="174" x2="336" y2="174"/>
<text class="t-mono" x="44" y="191">skills/ · scripts/ · sub_agents/</text>
<text class="t-val" x="324" y="191" text-anchor="end">按需</text>
<line class="dd-hair" x1="32" y1="200" x2="336" y2="200"/>
<text class="t-mono" x="44" y="217">plugin/</text>
<text class="t-val" x="324" y="217" text-anchor="end">迴圈內</text>
<rect class="dd-card" x="464" y="64" width="304" height="36" rx="6"/>
<text class="t-name" x="476" y="87.0" text-anchor="start">模型權重 · 不動</text>
<rect class="dd-card" x="464" y="116" width="304" height="110" rx="6"/>
<path class="dd-card-head" d="M470 116 H762 Q768 116 768 122 V148 H464 V122 Q464 116 470 116 Z"/>
<text class="t-name" x="476" y="137">改進 harness</text>
<text class="t-mono" x="476" y="165">MISSION.md · prompts.py</text>
<text class="t-val" x="756" y="165" text-anchor="end">不動</text>
<line class="dd-hair" x1="464" y1="174" x2="768" y2="174"/>
<text class="t-mono" x="476" y="191">context/</text>
<text class="t-val" x="756" y="191" text-anchor="end">每次重擺</text>
<line class="dd-hair" x1="464" y1="200" x2="768" y2="200"/>
<text class="t-mono" x="476" y="217"><tspan class="t-acc">library/</tspan></text>
<text class="t-val" x="756" y="217" text-anchor="end">跨任務累積</text>
<text class="t-say" x="36" y="308">① 改進器讀 context/，改左邊的執行 harness，一個任務之內做完。</text>
<text class="t-say" x="36" y="328">② 任務做完後，反思把「改了什麼、證據在哪」寫進 library/，下一個任務的改進器手上的 harness 就多了一條。</text>
</svg>
</div>
<figcaption>圖 8 · 「平方」的意思：第一層讓改進器改執行器的 harness，第二層讓做完的任務回頭改改進器的 harness</figcaption>
</figure>

- **任務級遞歸**改的是執行 harness，一個任務之內完成，就是第二、三節講的 Propose–Probe–Compose。
- **harnessing 級遞歸**改的是改進 harness，跨任務進行，就是這一節的經驗庫。每做完一個任務，「怎麼改 harness」這件事本身多了一條有證據的經驗。

圖裡標著「不動」的幾格也要看清楚：模型權重不動，改進器的那幾段提示（也就是判斷規則）不動，會變的只有經驗庫的內容[^h2-paper]。所以這個「平方」說的是對 harnessing 這個過程再做一次 harnessing，並不是一個無限往上套的結構。

三樣設計各值多少，論文做了消融（Gemini 3.5 Flash、OpenCode、k=1）[^h2-paper]：拿掉經驗庫，WorkBuddy-Bench 和 JobBench 分別掉 3.1 和 1.8 分；拿掉 INV 與 DOM 的分工，掉 2.9 和 3.7 分；拿掉合成這一步，掉 1.6 和 1.9 分。在 WorkBuddy-Bench 上掉得最多的是經驗庫，在 JobBench 上則是分工。

## 五、與普通 harness 的區別

### 回到那三個空檔案

現在回到開頭。起點 harness 是空的，分數卻往上走，因為在 Harness² 裡，harness 已經不是一份事先寫好的檔案：它是每個任務執行時當場生成的。空的 base 只是每個任務出發的地方；真正在跑任務的那份 harness，是讀過這個任務的幾次參照執行之後才寫出來的，其中甚至寫著這個工作區驗證過的事實。

把這個差別和第一節講的兩種做法放在同一條時間線上：

<figure class="dd dd-figure">
<div class="dd-scroll">
<svg class="dd-svg" viewBox="0 0 800 516" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="h2-when-title h2-when-desc">
<title id="h2-when-title">三種做法各在什麼時候改 harness</title>
<desc id="h2-when-desc">三條泳道，中間一條虛線表示部署的時刻。人手維護：部署前由人看失敗案例、往 AGENTS.md 補規則，部署後三個任務用同一份 harness 各跑一次。離線優化：部署前用訓練任務和評分器反覆搜尋，部署後同樣是同一份 harness 各跑一次。Harness²：部署時只有幾乎是空的 base harness，部署後每個任務當場改出自己的 harness、跑 4k 次，做完把記錄寫進同領域的經驗庫，後面的任務會讀到。</desc>
<defs>
<marker id="h2-when-arrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head" d="M1 1 L7 4 L1 7"/></marker>
<marker id="h2-when-arrow-acc" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto"><path class="dd-head-acc" d="M1 1 L7 4 L1 7"/></marker>
</defs>
<rect class="dd-frame" width="800" height="516" rx="10"/>
<rect class="dd-zone" x="16" y="16" width="768" height="112" rx="8"/>
<text class="t-zone" x="36" y="44">人手維護</text>
<rect class="dd-zone" x="16" y="144" width="768" height="112" rx="8"/>
<text class="t-zone" x="36" y="172">離線優化 · Meta-Harness 一類</text>
<rect class="dd-zone" x="16" y="272" width="768" height="172" rx="8"/>
<text class="t-zone" x="36" y="300">Harness²</text>
<path class="dd-edge dd-dash" d="M336 8 V452"/>
<path class="dd-edge" d="M288 90 H366" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M288 218 H366" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M288 346 H304 Q312 346 312 338 V314 Q312 306 320 306 H704 Q712 306 712 314 V318" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M424 306 V318" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M568 306 V318" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M480 90 H510" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M624 90 H654" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M480 218 H510" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge" d="M624 218 H654" marker-end="url(#h2-when-arrow)"/>
<path class="dd-edge-acc" d="M404 372 V394" marker-end="url(#h2-when-arrow-acc)"/>
<path class="dd-edge-acc" d="M548 372 V394" marker-end="url(#h2-when-arrow-acc)"/>
<path class="dd-edge-acc" d="M692 372 V394" marker-end="url(#h2-when-arrow-acc)"/>
<path class="dd-edge-acc" d="M588 396 V374" marker-end="url(#h2-when-arrow-acc)"/>
<path class="dd-edge-acc" d="M732 396 V374" marker-end="url(#h2-when-arrow-acc)"/>
<rect class="dd-card" x="32" y="64" width="256" height="52" rx="6"/>
<text class="t-name" x="44" y="84">人看失敗案例</text>
<text class="t-note" x="44" y="102">往 AGENTS.md 補規則、寫 skill</text>
<rect class="dd-card" x="368" y="64" width="112" height="52" rx="6"/>
<text class="t-name" x="380" y="84">任務 1</text>
<text class="t-note" x="380" y="102">同一份 · 1 次</text>
<rect class="dd-card" x="512" y="64" width="112" height="52" rx="6"/>
<text class="t-name" x="524" y="84">任務 2</text>
<text class="t-note" x="524" y="102">同一份 · 1 次</text>
<rect class="dd-card" x="656" y="64" width="112" height="52" rx="6"/>
<text class="t-name" x="668" y="84">任務 3</text>
<text class="t-note" x="668" y="102">同一份 · 1 次</text>
<rect class="dd-card" x="32" y="192" width="256" height="52" rx="6"/>
<text class="t-name" x="44" y="212">訓練任務 × 評分器</text>
<text class="t-note" x="44" y="230">優化器反覆改、跑、看分數</text>
<rect class="dd-card" x="368" y="192" width="112" height="52" rx="6"/>
<text class="t-name" x="380" y="212">任務 1</text>
<text class="t-note" x="380" y="230">同一份 · 1 次</text>
<rect class="dd-card" x="512" y="192" width="112" height="52" rx="6"/>
<text class="t-name" x="524" y="212">任務 2</text>
<text class="t-note" x="524" y="230">同一份 · 1 次</text>
<rect class="dd-card" x="656" y="192" width="112" height="52" rx="6"/>
<text class="t-name" x="668" y="212">任務 3</text>
<text class="t-note" x="668" y="230">同一份 · 1 次</text>
<rect class="dd-card" x="32" y="320" width="256" height="52" rx="6"/>
<text class="t-name" x="44" y="340">base harness</text>
<text class="t-note" x="44" y="358">三個主要檔案是空的</text>
<rect class="dd-card" x="368" y="320" width="112" height="52" rx="6"/>
<text class="t-name" x="380" y="340">任務 1</text>
<text class="t-note" x="380" y="358"><tspan class="t-acc">當場改 · 4k 次</tspan></text>
<rect class="dd-card" x="512" y="320" width="112" height="52" rx="6"/>
<text class="t-name" x="524" y="340">任務 2</text>
<text class="t-note" x="524" y="358"><tspan class="t-acc">當場改 · 4k 次</tspan></text>
<rect class="dd-card" x="656" y="320" width="112" height="52" rx="6"/>
<text class="t-name" x="668" y="340">任務 3</text>
<text class="t-note" x="668" y="358"><tspan class="t-acc">當場改 · 4k 次</tspan></text>
<rect class="dd-card" x="368" y="396" width="400" height="32" rx="6"/>
<text class="t-name" x="380" y="417.0" text-anchor="start">經驗庫 · 同一個領域</text>
<text class="t-note" x="756" y="417" text-anchor="end">每做完一個任務多一條</text>
<text class="t-say" x="36" y="476">虛線是部署的時刻。上面兩種做法，harness 在虛線左邊就定稿了，右邊每個任務拿到的是同一份。</text>
<text class="t-say" x="36" y="496">Harness² 的修改全部發生在虛線右邊：每個任務從 base 出發改出自己的一份，再把記錄留給後面的任務。</text>
</svg>
</div>
<figcaption>圖 9 · 普通 harness 的修改發生在部署之前，Harness² 的修改發生在每個任務之內</figcaption>
</figure>

逐項對照是這樣：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="h2-diff-title">三種對待 harness 的方式</div>
<div class="dt-scroll">
<table class="dt dt-sticky" aria-labelledby="h2-diff-title">
<colgroup><col style="width:19%"><col><col><col class="hl"></colgroup>
<thead>
<tr><th scope="col">項目</th><th scope="col">人手維護</th><th scope="col">離線優化</th><th scope="col" class="acc">Harness²</th></tr>
</thead>
<tbody>
<tr><th scope="row">誰來改</th><td>人</td><td>優化器</td><td class="acc">改進器（另一個 agent 會話）</td></tr>
<tr><th scope="row">什麼時候改</th><td colspan="2">部署之前</td><td class="acc">任務到來之後，交付之前</td></tr>
<tr><th scope="row">改的依據</th><td>人看到的失敗案例</td><td>訓練任務上的分數</td><td class="acc">同一任務上改前改後的執行對照</td></tr>
<tr><th scope="row">需要評分器</th><td>不需要</td><td class="warn">需要</td><td class="acc">不需要</td></tr>
<tr><th scope="row">一份 harness 管多少任務</th><td colspan="2">全部任務共用一份</td><td class="acc">一個任務一份，都從 base 出發</td></tr>
<tr><th scope="row">跨任務累積的東西</th><td>越寫越長的規則</td><td>搜出來的那一份</td><td class="acc">經驗庫：成品加有證據的教訓</td></tr>
<tr><th scope="row">每個任務的執行次數</th><td colspan="2">1 次</td><td class="warn">4k 次，另加 3k 次改進會話</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">「離線優化」一欄按論文對 Meta-Harness 這一類方法的描述整理。</p>
</div>
<figcaption>圖 10 · 普通 harness 是部署前定稿的一份檔案，Harness² 把它變成每個任務執行時的中間產物</figcaption>
</figure>

### 效果與代價

效果上，論文的主結果表給了同一個模型（Gemini 3.5 Flash）、同一個底座（OpenCode）下各種方法在三個基準上的分數[^h2-paper][^h2-site]：

<figure class="dd dd-figure dt-figure">
<div class="dt-panel">
<div class="dt-title" id="h2-res-title">三個基準上的分數 · Gemini 3.5 Flash + OpenCode</div>
<div class="dt-scroll">
<table class="dt" aria-labelledby="h2-res-title">
<colgroup><col style="width:34%"><col><col><col></colgroup>
<thead>
<tr><th scope="col">方法</th><th scope="col" class="num">WorkBuddy-Bench</th><th scope="col" class="num">JobBench</th><th scope="col" class="num">LAB</th></tr>
</thead>
<tbody>
<tr><th scope="row">Base harness</th><td class="mono num">72.4</td><td class="mono num">31.5</td><td class="mono num">79.9</td></tr>
<tr><th scope="row">ReasoningBank</th><td class="mono num">73.4</td><td class="mono num bad">30.8</td><td class="mono num bad">77.3</td></tr>
<tr><th scope="row">Harness Scaling</th><td class="mono num">75.3</td><td class="mono num">32.9</td><td class="mono num bad">78.9</td></tr>
<tr><th scope="row">Meta-Harness</th><td class="mono num">76.4</td><td class="mono num">35.8</td><td class="mono num">81.6</td></tr>
<tr><th scope="row">Harness²（k=1）</th><td class="mono num acc">77.5</td><td class="mono num acc">36.0</td><td class="mono num acc">81.9</td></tr>
<tr><th scope="row">Harness²（k=5，並行）</th><td class="mono num acc">80.8</td><td class="mono num acc">39.0</td><td class="mono num acc">84.5</td></tr>
</tbody>
</table>
</div>
<p class="dt-note">紅字表示比 base 還低。k=5 一列由論文的 k=1 分數加上「+Recursion」的增量算出（+3.3、+3.0、+2.6）。</p>
</div>
<figcaption>圖 11 · 同一個模型、同一個底座，只換對待 harness 的方式</figcaption>
</figure>

表裡有兩點值得注意。其一，另外幾種方法並不總是比什麼都不做好：論文統計，Harness Scaling 在十二組「基準 × 配置」裡有七組低於 base。其二，Harness² 也不是每一格都領先：換成 Codex 底座時，LAB 上是 Meta-Harness 的 76.5 高於它的 75.6[^h2-paper]。

還有一個趨勢：比較弱的執行模型得益更大。README 裡那個最大的 11.5 分就出在 GLM-5 上，WorkBuddy-Bench 從 64.2 升到 75.7，只用了一步改進[^h2-readme][^h2-paper]。這也說得通，模型越弱，越需要有人把工作區的事實和步驟替它寫好。

代價同樣明顯，有四點要看清楚。

**執行量至少是四倍。** k=1 時每個任務要完整跑 4 次，外加 3 次改進會話和 1 次反思；k=5 時是 20 次執行和 15 次改進會話。這是拿算力換分數，交付也要等這些都跑完。論文把它列為第一條局限，並且說明並行只能縮短等待時間，總計算量不會變少[^h2-paper]。

**對照不等於正確。** 這是論文列的第二條局限：執行有隨機性，幾個候選又出自同一個模型、帶著同樣的偏見，對照只能說明行為變了[^h2-paper]。演示裡 105.25 變成 85.15 之所以一眼能判斷，是因為任務說明夠清楚；任務本身含糊時，合成者和我們一樣沒有把握。

**改進器得夠強。** 讀幾份完整軌跡、比對候選、寫出能過校驗的 skill 和腳本，這本身就是一件不輕的 agent 工作。改進器用哪個模型由 `HARNESS2_IMPROVER_MODEL` 單獨設定。

**它假設任務可以重跑。** Probe 的前提是同一個任務能在相同條件下跑好幾次。產生檔案的辦公任務可以；發郵件、下單這類有外部副作用的任務，跑四次就是做了四次。這一點是我們根據機制推出來的，論文正文沒有展開討論。

## 小結

1. **harness 是什麼，誰在改？** 包在模型外面的一棵檔案樹：常駐文字、skill、子 agent、腳本、外掛。平常由人在部署前改，或由離線優化器拿訓練任務的分數去搜。
2. **沒有標準答案，憑什麼判斷修改的好壞？** 在同一個任務上多跑幾次：一次參照，INV 和 DOM 兩個候選各試一次，合成者看改前改後的行為對照，每個決定都要指得出軌跡裡的具體時刻。評分材料在提示、資料結構、檔案三層都被擋在外面。
3. **預算 k 怎麼花？** 並行一次鋪開 2k 個候選、合成 k 份；串行一輪接一輪在檢查點上改。兩者都是 4k 次執行和 3k 次改進會話，最後從 k 份產出裡盲選一份。
4. **改完的東西怎麼留下來？** 每個任務仍從 base 出發；留下的是經驗庫裡的整棵 harness 和一條有證據的教訓，供同領域後面的任務參考。這個給改進器用的 harness 就是「平方」的來歷。
5. **和普通 harness 差在哪？** 普通 harness 是部署前定稿、所有任務共用的一份檔案；Harness² 裡它是每個任務執行時當場生成的中間產物。換來的是不需要評分器也能改進，付出的是每個任務至少四倍的執行量。

想親手看一遍流程，可以跑倉庫自帶的演示，不需要任何模型憑證[^h2-readme]：

```bash
uv venv --python 3.12 .venv
source .venv/bin/activate
uv pip install -e '.[render]'
harness2-demo --output runs/demo --k 2
```

它用寫死的編輯者把並行和串行兩種模式各走一遍，產物都在 `runs/demo/` 底下，可以對照圖 3、圖 4 和圖 7 逐個目錄打開來看。

## 參考資料
