02 · Code structure
02 · Code structure
Harness² is one Python package, harness2, split into four subpackages: core (the method), substrate (the agent frameworks that run a solver), benchmarks (the three benchmark adapters) and cli (the command-line entry points), plus a few top-level helper modules. The whole design rests on keeping three axes apart: method, substrate and benchmark. Each axis talks to the others only through a small set of typed seams, almost all defined in core/spec.py and substrate/base.py. This chapter maps the packages, explains every type that makes up those seams (what it holds, what it checks, why it exists), and spells out the import rules that keep core free of benchmark and substrate code. It also keeps the judge, the only code that ever sees a score, out of the method. It ends with a trace of the bundled offline demo through these types.
All code links point at commit 1cfe254e8d25b15f37048c3730134686abe60335.
1. Packages at a glance
1.1 The file tree
The Python sources at the pinned commit, with line counts. Everything under third_party/ (runner overlays, patches, licences) holds benchmark-specific material that the adapters load at run time. None of it is part of the importable package.
| Path | Lines | Role |
| harness2/init.py | 15 | Docstring only: "Harness²: Recursive Agent Harnessing for an Open World." |
| harness2/config.py | 189 | Deployment settings, model ids, dependency pins, paths. Reads .env; shell values win. Imports nothing from the package. |
| harness2/install.py | 319 | Installs benchmark tools and runner overlays (PATCHES, missing_runners, codex_config_error). |
| harness2/preflight.py | 160 | Health checks before money is spent (check, require_ready). |
| harness2/demo.py | 163 | Offline demo: DemoAdapter and DemoProposer. |
harness2/core/ | "Substrate- and benchmark-independent method code" (core/init.py) | |
| core/spec.py | 277 | The benchmark seam. ChannelSpec, BenchPrompts, BenchSpec, RunView, SubstrateLike, BenchmarkAdapter. |
| core/tree.py | 427 | The c1..c8 component tree: SurfaceSpec, read/validate/write/diff. |
| core/ledger.py | 132 | Budget accounting: R = 4k rollouts, P = 3k improvement sessions. |
| core/selection.py | 505 | Blind, answer-free selection of one final, then reflection. |
| core/recursion.py | 578 | The two recursion modes (parallel = agg, sequential = seq). |
| core/schedule.py | 850 | Namespacing (Namespace, ns), stream order, drivers run_task / run_domain / run_sweep. |
| core/proposer.py | 534 | Proposer: runs improver/selector sessions on one channel, with an audit trail and a cache. |
| core/context.py | 546 | Builds the on-disk evidence tree a proposer reads, plus leak and grader-verdict guards. |
| core/library.py | 222 | The per-domain experience library. |
| core/prompts.py | 1163 | Prompt text: propose, aggregate, compose_round, select, experience. |
| core/runmark.py | 115 | Completion markers and harness fingerprints for idempotent rollouts. |
| core/judge.py | 155 | Scores the run that selection chose, after it chose it. |
| core/report.py | 165 | Turns judged rows into the figure each benchmark reports. |
harness2/substrate/ | "The two solver substrates behind one interface" (substrate/init.py) | |
| substrate/base.py | 83 | Install, RunRequest, Launcher, Substrate. |
| substrate/opencode.py | 395 | OPENCODE_SURFACE, OpencodeSubstrate (name "oc"), event renderer. |
| substrate/codex.py | 480 | CODEX_SURFACE, CodexSubstrate (name "cx"), event renderer. |
harness2/benchmarks/ | "Benchmark task discovery, execution, and grading." | |
| benchmarks/lab.py | 1005 | LabAdapter, lab_spec, LAB prompt tables. |
| benchmarks/workbuddy.py | 1029 | WbAdapter, spec_for, WorkBuddy subsets and prompt tables. |
| benchmarks/wb_render.py | 577 | Renders WorkBuddy deliverables (e.g. pptx) into readable evidence. |
| benchmarks/jobbench.py | 1175 | JobBenchAdapter, make_spec, JobBench prompt tables. |
| benchmarks/jb_opencode_runner.py | 281 | Stand-alone JobBench runner script for OpenCode (no package imports). |
| benchmarks/tasks.py | 88 | discover / select: task ids and split files. |
harness2/cli/ | "Command-line adaptation, judging, and reporting." | |
| cli/_common.py | 144 | make_adapter and shared argparse helpers. |
| cli/run_sweep.py | 239 | harness2 entry point. |
| cli/judge.py | 204 | harness2-judge. |
| cli/report.py | 114 | harness2-report. |
| cli/setup.py, check.py, demo.py, bridge.py | 120 / 49 / 58 / 108 | harness2-setup, -check, -demo, -bridge. |
The console scripts are declared in pyproject.toml: harness2 = harness2.cli.run_sweep:main, plus harness2-judge, -report, -setup, -check, -demo and -bridge, each pointing at the matching cli/*.py:main.
1.2 Package and import diagram
Arrows point from importer to imported. The diagram was built from every import line in harness2/**/*.py at the pinned commit. The dashed edges are imports made inside a function (lazy imports).
flowchart TB
subgraph CLI ["cli"]
runsweep["run_sweep.py"]
clijudge["judge.py"]
clireport["report.py"]
common["_common.py"]
clidemo["demo.py"]
clicheck["check.py / setup.py / bridge.py"]
end
subgraph BENCH ["benchmarks"]
lab["lab.py"]
wb["workbuddy.py + wb_render.py"]
jb["jobbench.py"]
tasks["tasks.py"]
end
subgraph SUB ["substrate"]
sbase["base.py"]
soc["opencode.py"]
scx["codex.py"]
end
subgraph CORE ["core (the method)"]
schedule["schedule.py"]
recursion["recursion.py"]
selection["selection.py"]
library["library.py"]
context["context.py"]
prompts["prompts.py"]
proposer["proposer.py"]
ledger["ledger.py"]
spec["spec.py"]
tree["tree.py"]
runmark["runmark.py"]
judge["judge.py"]
report["report.py"]
end
subgraph TOP ["top level"]
config["config.py"]
install["install.py"]
preflight["preflight.py"]
demo["demo.py"]
end
runsweep --> schedule
runsweep --> proposer
runsweep --> common
runsweep -.-> preflight
common --> library
common --> schedule
common --> spec
common -.-> lab
common -.-> wb
common -.-> jb
clijudge --> judge
clireport --> report
clireport --> clijudge
clidemo --> schedule
clidemo --> demo
clijudge --> common
clireport --> common
clireport --> schedule
clicheck --> preflight
clicheck --> install
clicheck --> common
lab --> spec
lab --> tree
lab --> runmark
lab --> sbase
lab --> soc
lab --> scx
wb --> spec
wb --> tree
wb --> runmark
wb --> selection
wb --> sbase
wb --> soc
wb --> scx
jb --> spec
jb --> tree
jb --> runmark
jb --> install
jb --> sbase
jb --> soc
jb --> scx
lab --> tasks
wb --> tasks
jb --> tasks
soc --> tree
soc --> runmark
soc --> sbase
scx --> tree
scx --> runmark
scx --> sbase
sbase --> tree
schedule --> recursion
schedule --> selection
schedule --> library
schedule --> runmark
recursion --> context
recursion --> prompts
recursion --> ledger
recursion --> proposer
selection --> context
selection --> prompts
selection --> proposer
library --> context
library --> tree
context --> spec
context --> tree
prompts --> spec
prompts --> tree
proposer --> spec
spec --> tree
spec --> config
report --> judge
report --> schedule
judge --> spec
demo --> spec
demo --> tree
demo --> proposer
demo --> soc
preflight --> install
preflight --> soc
click spec href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py" "core/spec.py"
click tree href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/tree.py" "core/tree.py"
click ledger href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/ledger.py" "core/ledger.py"
click selection href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/selection.py" "core/selection.py"
click recursion href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/recursion.py" "core/recursion.py"
click schedule href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/schedule.py" "core/schedule.py"
click proposer href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/proposer.py" "core/proposer.py"
click context href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/context.py" "core/context.py"
click library href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/library.py" "core/library.py"
click prompts href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/prompts.py" "core/prompts.py"
click runmark href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/runmark.py" "core/runmark.py"
click judge href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/judge.py" "core/judge.py"
click report href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/report.py" "core/report.py"
click sbase href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/base.py" "substrate/base.py"
click soc href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/opencode.py" "substrate/opencode.py"
click scx href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/codex.py" "substrate/codex.py"
click lab href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/lab.py" "benchmarks/lab.py"
click wb href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/workbuddy.py" "benchmarks/workbuddy.py"
click jb href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/jobbench.py" "benchmarks/jobbench.py"
click tasks href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/tasks.py" "benchmarks/tasks.py"
click common href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/cli/_common.py" "cli/_common.py"
click runsweep href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/cli/run_sweep.py" "cli/run_sweep.py"
click clijudge href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/cli/judge.py" "cli/judge.py"
click clireport href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/cli/report.py" "cli/report.py"
click clidemo href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/cli/demo.py" "cli/demo.py"
click config href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/config.py" "config.py"
click demo href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/demo.py" "demo.py"
click preflight href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/preflight.py" "preflight.py"
click install href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/install.py" "install.py"Several modules also import config (core/recursion.py, core/proposer.py, both substrates, every benchmark, several CLIs). Those edges are left out to keep the picture readable. config imports nothing from the package, so it is a leaf.
Reading the diagram from the bottom up:
core/tree.py** is the root type module.** It imports nothing from the package.SurfaceSpeclives here rather than insubstrate/so that bothcoreandsubstratecan depend on it withoutcoreever importingsubstrate.core/spec.py** depends only on**tree** and**config. Everything else incoredepends onspec.substrate/** imports only**core.tree,core.runmark** and**config. It never imports the method modules (recursion, selection, schedule) and never imports a benchmark.benchmarks/** imports**core** value types plus helpers (spec,**tree,runmark, andselection.order_graded_first** in WorkBuddy), the substrates and**install.cli/** sits on top.** It is the only place that picks a concrete benchmark, and it does so lazily inside make_adapter.core/judge.py* is imported only by**core/report.py** (for the**Scored** row type),**cli/judge.py** and the test suite.** No module in the method's run path (schedule, recursion, selection, context, library, proposer, prompts) imports it. The module docstring states it outright: *"Nothing in the method imports this module" (core/judge.py#L15-L19).
2. The three axes and their seams
The docstring of core/spec.py gives the organising principle:
Three axes stay separate -- method (recursion, prompts, selection, library), substrate (the agent framework a solver rollout runs under) and benchmark (aBenchSpecvalue plus an adapter).ChannelSpecdescribes the proposer/selector channel, which is independent of both.
So there are really four independent concerns. Each one has its own seam:
| Axis | Owns | Seam type(s) | Where defined |
| Method | How harnesses are proposed, composed, chosen and remembered | (consumer of the seams below) | core/recursion.py, core/selection.py, core/library.py, core/prompts.py, core/schedule.py |
| Benchmark | Tasks, execution, deliverable extraction, grading | BenchmarkAdapter protocol plus a BenchSpec value | core/spec.py |
| Substrate | The agent framework's editable surface, staging, launching, trajectory format | SubstrateLike (what core needs) and Substrate (the full interface) | core/spec.py, substrate/base.py |
| Channel | Which model the improver/selector talks to, and how | ChannelSpec, consumed by Proposer | core/spec.py, core/proposer.py |
The key structural move: the method never sees a substrate directly. It reaches the substrate only through adapter.substrate, and asks it for one thing only, surface(), which returns a SurfaceSpec value. Staging, launching and trajectory rendering all happen inside the adapter's execute and trajectory. The sequence below shows one rollout crossing all the seams. It follows recursion._roll calling LabAdapter.execute.
sequenceDiagram participant R as recursion._roll participant T as core.tree participant A as LabAdapter participant S as OpencodeSubstrate participant L as launcher _launch_oc R->>A: substrate.surface() A-->>R: SurfaceSpec (OPENCODE_SURFACE) R->>T: write_tree(surface, tree, work_dir/harnesses/label) T-->>R: harness_dir R->>A: execute(task_id, harness_dir, run_id) A->>A: runmark.is_complete? reuse : re-roll A->>S: materialise(harness_dir, spec, ctx) S-->>A: Install(staging_dir, fingerprint, extras) A->>S: execute(task_id, install, [run_id], timeout) S->>L: launcher(RunRequest, Install) L-->>S: list of run dirs S-->>A: list of run dirs A->>A: runmark.mark_complete(out) A-->>R: run_dir R->>A: delivered(run_dir) A-->>R: bool R->>R: RunView(label, run_id, run_dir, ok, delivered)
Code for each step: _roll, write_tree, LabAdapter.execute, LabAdapter._install, OpencodeSubstrate.materialise, OpencodeSubstrate.execute.
Notice the inversion in the middle. The substrate owns the call shape execute(task_ref, install, run_ids, timeout), but the benchmark supplies the body (the launcher) when it builds the substrate. That is why OpencodeSubstrate.__init__ takes launcher: Launcher | None, and why execute raises if none was given (see section 6).
3. Values versus behaviour
The first question this chapter answers is: which types are values and which hold behaviour? The code is consistent. Configuration and records are dataclasses, many of them frozen=True. Behaviour lives in protocols, their concrete implementations, and the module-level functions in core.
| Type | Kind | Frozen? | Holds behaviour? | Notes |
| ChannelSpec | dataclass | yes | validation only | One model on one channel |
| BenchPrompts | dataclass | yes | validation only | Benchmark wording for shared prompts |
| BenchSpec | dataclass | yes | validation only | "A value, not a code path." |
| RunView | dataclass | no | none | One finished rollout. "Never carries a score." |
| SurfaceSpec | dataclass | yes | none (rules are data, logic is in tree.py functions) | One agent framework's editable surface |
| Install | dataclass | yes | none | A staged harness |
| RunRequest | dataclass | yes | none | One launch request |
| Namespace | dataclass | yes | naming only | <bench>-<substrate>-<mode><k>[-s<seed>][-<tag>] |
| Entry | dataclass | no | none | One ledger line |
| Ledger | dataclass | no | accounting and the budget assertion | Mutable, append-only by convention |
| Selection | dataclass | no | none | Record of one selection |
| Stage, ModeResult, TaskResult, ContextTree, Reply, Scored, Summary | dataclasses | no | derived properties and to_dict only | Records produced by method stages |
| Library | dataclass | no | yes: file I/O | A handle on one on-disk directory |
| Proposer | dataclass | no | yes: subprocesses, cache, audit | Runs model sessions |
| BenchmarkAdapter | Protocol | yes (implemented by adapters) | The benchmark seam | |
| SubstrateLike, Substrate | Protocol | yes (implemented by substrates) | The substrate seam | |
LabAdapter, WbAdapter, JobBenchAdapter, DemoAdapter | plain classes | yes | Satisfy BenchmarkAdapter structurally; none inherits from it | |
OpencodeSubstrate, CodexSubstrate | plain classes | yes | Satisfy Substrate structurally |
Two design consequences follow:
- A benchmark cell** is data plus one object.** A
BenchSpec(frozen) plus an adapter instance fully describe a (benchmark, substrate) cell. Building a new cell means writing a new value and a new adapter, never editing method code. - Validation happens at construction.
ChannelSpec,BenchPromptsandBenchSpecall validate in__post_init__. A misconfigured cell fails when it is built, before any rollout costs money.
4. core/spec.py: the benchmark seam
4.1 Module constants
| Name | Value | Purpose |
| ModelId | str alias | Documents that a field holds a model id |
| CHANNELS | ("claude_cli", "opencode") | The only two proposer/selector channels |
| INSTALL_MODES | ("workspace_agents_md", "home_dir_jinja", "workspace_config", "codex_root") | How a substrate stages a harness. The first three belong to OpenCode, codex_root to Codex. |
4.2 ChannelSpec
ChannelSpec is "How to reach one model, on one proposer/selector channel."
| Field | Type | Default | Meaning |
kind | str | (required) | One of CHANNELS |
model | ModelId | (required) | Model id. Its format depends on kind (see resolve_channel_model). |
effort | str | "high" | Reasoning effort passed to the session |
steps | int | 120 | Step budget for the session |
timeout | int | 3600 | Wall-clock seconds per session |
__post_init__ rejects an unknown kind. Because the dataclass is frozen, it is hashable and comparable. _selector_for relies on that equality: when the selector ChannelSpec equals the proposer's, the same Proposer object is reused (which keeps fake proposers such as DemoProposer in place). Otherwise a fresh Proposer is built for the selector channel, with its audit trail under <audit_dir>/selector.
4.3 resolve_channel_model
resolve_channel_model(kind, model, *, effort, steps, timeout) is "The one channel->model mapping; a lone override of either raises."
use_default = kind is None and model is None
if (kind is None) != (model is None):
raise ValueError(
"channel and model must be overridden TOGETHER (a bare-id/provider-prefixed "
f"mismatch fails silently downstream); got kind={kind!r}, model={model!r}"
)The rules, in order:
- Override both or neither. A lone override raises, because the two id formats differ (see rule 5) and a mismatch "fails silently downstream".
- No override:
kind = config.DEFAULT_PROPOSER_CHANNEL(which is"opencode", config.py#L174) andmodel = config.PROPOSER_MODEL_BY_CHANNEL[kind](config.py#L175). Both channels map toIMPROVER_MODEL, which comes fromHARNESS2_IMPROVER_MODELand falls back toHARNESS2_SOLVER_MODEL. - An empty model is allowed only on the default path. It returns a
ChannelSpecwithmodel="", so specs can be built before the environment is configured. The error is deferred to Proposer.__post_init__, which raises "Set HARNESS2_IMPROVER_MODEL before starting model sessions". An explicit empty override raises at once. opencodeneeds a provider-prefixed id (it must contain/).claude_clineeds a bare Anthropic id (no/).
All three benchmark spec builders call it with (None, None): lab_spec, spec_for and make_spec.
4.4 BenchPrompts
BenchPrompts is "The benchmark-shaped text the shared prompts interpolate." core/prompts.py holds one shared set of prompts. A benchmark specialises them only through these fields:
| Field | Default | Used for |
work | (required) | One phrase describing the work (e.g. the demo's "reconcile signed amounts in a transaction ledger") |
domain_word | "domain" | What a grouping unit is called ("practice area", "profession", "role"…) |
inv_levers | "" | Hints for the INV (invariant) proposal dimension |
dom_levers | "" | Hints for the DOM (domain) proposal dimension |
yardstick | "" | What "good" looks like for the work |
deliverable_word | "deliverable" | What the output is called |
domain_guidance | "" | Extra domain guidance |
selection_checks | "" | What the selector should check |
selection_lead | "" | Lead-in text for the selector prompt |
selection_conformance | "full" | Conformance wording for selection |
prompt_pack | "adapt" | "adapt" or "tts". Picks between the two prompt families in prompts.py (the _tts_* builders). |
How each field is spliced into the prompt text belongs to chapter 06 Budget selection and judging. What matters structurally is the anti-leak check in __post_init__:
visible = {
"work": self.work,
"inv_levers": self.inv_levers,
"dom_levers": self.dom_levers,
"yardstick": self.yardstick,
"domain_guidance": self.domain_guidance,
"selection_checks": self.selection_checks,
}
for field_name, value in visible.items():
low = value.lower()
for bad in ("benchmark", "evaluation", "grader", "rubric", "score"):
if bad in low:
raise ValueError(
f"BenchPrompts.{field_name} must describe the WORK, not its "
f"measurement (found {bad!r}). Say what the practitioner needs."
)So a benchmark author cannot even build a prompt profile that mentions the benchmark, its evaluation, grader, rubric or score in the six fields models see. This is one of the first lines of the evidence-and-safety story (chapter 05). Note that domain_word, deliverable_word, selection_lead and selection_conformance are not scanned. The code does not say why.
4.5 BenchSpec
BenchSpec is "Static description of one (benchmark, substrate) cell. A value, not a code path."
| Field | Type | Default | Meaning and who reads it |
name | str | (required) | Benchmark name ("lab", "demo"…). The first segment of every namespace. |
runs_root | Path | (required) | Root for runs, the scheduler state (<runs_root>/adapt, runs_root_for) and the library (<runs_root>/library, library_root_for) |
install_mode | str | (required) | One of INSTALL_MODES. Read by Substrate.materialise. |
prompts | BenchPrompts | (required) | Default prompt profile. An adapter may override it per task with prompts_for. |
channels | dict[str, ChannelSpec] | (required) | Must contain "proposer" and "selector" |
domain_word | str | "domain" | Set by every adapter, but no module reads spec.domain_word at this commit. The prompts read BenchPrompts.domain_word instead. |
leak_globs | tuple[str, ...] | () | Globs that must never appear in an evidence tree (e.g. LAB ("task.json",), the demo ("reference_answer.txt",)). Enforced by context.assert_no_leak_paths. |
allow_subagents | bool | False | Passed to tree.validate by _accept_tree |
reserved_scripts | frozenset[str] | frozenset() | Script basenames that the runtime provides, so a harness may call them without shipping them. Unioned with tree.RESERVED_SCRIPTS. |
proposer_deliverables | str | "contents" | "contents" or "names": whether proposers see deliverable file contents or only their names (context.py#L144) |
state_deliverable | bool | False | If no candidate produced a file deliverable, selection may fall back to showing trajectories (selection.py#L134) |
obs_trunc | int | 50_000 | Per-observation truncation when rendering trajectories |
__post_init__ (spec.py#L150-L173) enforces five invariants:
install_modeis inINSTALL_MODES.proposer_deliverablesis"contents"or"names".channelsdefines both"proposer"and"selector".obs_trunc >= 10_000. The message calls smaller values "the range that fabricates defects": an observation cut too short looks to the improver like a solver failure.context.missionmakes the same point to the model (context.py#L534-L537).reserved_scriptsholds bare.pybasenames, not paths.
4.6 RunView
RunView is "What the method knows about one finished rollout. Never carries a score."
| Field | Type | Default | Meaning |
label | str | (required) | Stage label: base1, inv1, dom1, agg1, c1… |
run_id | str | (required) | Namespace.run_id(task_id, label), e.g. demo-oc-agg2/ledger-1/base1 |
run_dir | Path | (required) | Where the adapter left the rollout |
ok | bool | (required) | The run directory exists (the adapter returned without raising) |
delivered | bool | (required) | adapter.delivered(run_dir): the requested artifact exists |
cost_usd | float | 0.0 | Rollout cost, if known |
wall_s | float | 0.0 | Wall time |
error | str | "" | Why a rollout is dead |
This is the only view the method has of a rollout. With no score field, recursion and selection cannot condition on a grade even by accident. That is the structural half of the "blind selection" claim. The procedural half is context.assert_no_grader_verdicts, which scans every evidence tree for grader output.
4.7 SubstrateLike
SubstrateLike is a @runtime_checkable Protocol with one attribute and one method:
name: str:"oc"or"cx". It feeds namespaces. ns refuses an empty substrate name, because otherwise "the two substrates' caches [would] collide on every label".surface() -> SurfaceSpec: the editable c1..c8 tree schema.
It is deliberately a subset of Substrate. Its own docstring calls it "What core needs of a substrate", and Substrate's says "core.spec.SubstrateLike* is the subset core needs."* Defining it in core lets core type adapter.substrate without importing substrate/.
4.8 BenchmarkAdapter
BenchmarkAdapter is "Implement these and the whole method works. Nothing here is method logic." It is a @runtime_checkable Protocol with two attributes and twelve methods.
| Member | Signature | Contract (from the docstrings) | Main callers in core |
spec | BenchSpec | The cell's static description | everywhere |
substrate | SubstrateLike | Provides name and surface() | recursion, schedule, library |
| tasks | (split="all") -> list[str] | Task ids for a split. "Stable order -- library state must be reproducible." | schedule / CLI |
| domain_of | (task_id) -> str | "The grouping unit: practice area / occupation / role / sector." | run_task, streams |
| task_prompt | (task_id) -> str | The task as the agent received it, "with criteria and expected output stripped" | context.build, run_selection |
| task_files | (task_id) -> Path or None | Optional source material | context.copy_source (via getattr) |
| execute | (task_id, harness_dir, run_id, *, timeout=None) -> Path | Run one rollout, "idempotently by run_id". harness_dir=None means the unmodified base harness. | recursion._roll, schedule._roll_bases |
| delivered | (run_dir) -> bool | Did the rollout produce the requested artifact? | _roll, _roll_bases |
| trajectory | (run_dir, *, obs_trunc) -> str | Readable trajectory in the shared [ACTION]/[OBSERVATION]/[ERROR] grammar | context |
| deliverable_files | (run_dir) -> dict[str, str] | Optional. Filename to extracted text. | context (via getattr) |
| deliverable_text | (run_dir) -> str | The artifact as text, "extracted from agent-authored bytes only" | selection._render_pool |
| base_harness | () -> Path | Directory holding the unmodified c1..c8 tree | run_task, reflect_adoption |
| validate_tree | (tree) -> None | Benchmark-specific invariants. Raise ValueError listing all problems in actionable prose, because "the message is fed back verbatim as retry feedback." | recursion._accept_tree |
| score | (run_dir, task_id) -> dict or None | Judge one run. None means the judge failed: "not a zero and is never cached as one. Called only by core.judge, only after selection committed." | only core.judge.judge_one |
The score contract is the boundary between method and measurement. Everything above score in the table is answer-free. score is the one place a grade enters the program, and only core/judge.py calls it. judge_one wraps it: an exception or an empty result becomes Scored.error, never a zero.
Optional hooks (duck-typed with getattr)
Beyond the protocol, core probes for extra adapter methods with getattr(adapter, name, None). They are optional: core falls back to defaults when they are missing. Every probe at this commit:
| Hook | Probed at | Effect | Implemented by |
prompts_for(task_id) -> BenchPrompts | resolve_prompts | Per-task prompt profile. A non-BenchPrompts return raises TypeError. | Lab, WorkBuddy, JobBench |
reference_domain_order() -> list[str] | schedule.py#L175 | Reference ordering of domains for the task stream | Lab, WorkBuddy, JobBench |
artifacts_intact(run_dir) | schedule.py#L404 | Checked when resuming a cached task | Lab, JobBench |
execute_many(task_id, None, run_ids) | schedule.py#L437 | Batch the k base rollouts natively | WorkBuddy |
deliverable_listing(run_dir) | context.py#L339 | Names-only deliverable view | WorkBuddy |
deliverable_images(run_dir) | context.py#L380 | Images copied into evidence | Lab, JobBench |
deliverable_assets(run_dir) | context.py#L261, selection.py#L195 | Rendered evidence files per candidate. The adapter names them explicitly, so grader output in the run dir is never swept in. | WorkBuddy |
deliverable_assets_note, deliverable_assets_line, deliverable_assets_reference_only | selection.py#L170, #L177, #L308 | Override the wording about assets in the selector prompt | none of the shipped adapters (defaults are used) |
task_files, deliverable_files | context.py#L193, #L236 | Protocol members, but marked optional and probed the same way | all four |
preflight() is not probed by core. It exists on all three real adapters and is called by the CLI and preflight.py.
4.9 resolve_prompts
resolve_prompts(adapter, task_id) returns adapter.prompts_for(task_id) when that method exists, else adapter.spec.prompts, and checks the type. Recursion, selection and reflection all call it, so per-task prompt profiles (e.g. LAB's practice area plus work type in LabAdapter.prompts_for) reach every prompt the same way.
5. core/tree.py: the component tree and SurfaceSpec
The module docstring (tree.py#L15-L21) sets the pattern. "Everything that differs between the two agent frameworks is data on SurfaceSpec… the shared logic exists once. The edit contract is omitted-is-inherited: a proposal emits only what it changes."
5.1 Types and constants
- Tree is
dict[str, str], mapping a relative path to file content. Every harness in memory is aTree. - RESERVED_SCRIPTS is
frozenset({"read_doc.py", "pack.py", "unpack.py"}). Runtime-provided helpers a harness may invoke without shipping them. - _NAME is
[a-z0-9][a-z0-9_\-]*, the naming rule for skills, sub-agents and plugins.
5.2 SurfaceSpec
SurfaceSpec is "The editable surface of one agent framework: paths, caps and per-surface rules." The table lists each field with the two concrete values from OPENCODE_SURFACE and CODEX_SURFACE.
| Field | Meaning | OpenCode | Codex |
name | Surface name | "opencode" | "codex" |
always_on | Files injected into every turn | systemprompt.md, guardrails.md, LongTermMEMORY.md, tool_descriptions/tool_guidance.md | AGENTS.md |
always_on_max | Cap on total always-on chars | 24_000 | 8_000 |
always_on_cap_reason | Prose appended to the over-cap error | output-budget competition | injected every turn, silently truncated at 32,768 bytes |
skill_dir | Skills root | skills | .agents/skills |
skill_name_rule | Frontmatter name: policy | "must_match" | "overrides_dir" |
trigger_hint | Fix-it text for an untriggered skill | "add a trigger line … to systemprompt.md or guardrails.md" | "add a trigger line … to AGENTS.md" |
autotrigger_file | Where autotrigger appends lines | guardrails.md | AGENTS.md |
trigger_line | Template for a trigger line | - When {desc}, load the {name} skill. | - When {desc}, read .agents/skills/{name}/SKILL.md and follow it. |
subagent_requires_mode | Sub-agent needs mode: subagent | True | False |
plugin_dir | Plugin directory (c8) | "plugin" | None |
hooks_file | Hooks file (c8) | None | .codex/hooks.json |
hook_events | Allowed hook events | frozenset() | 10 events (PreToolUse, PostToolUse, PermissionRequest, PreCompact, PostCompact, SessionStart, SubagentStart, SubagentStop, UserPromptSubmit, Stop) |
compile_skill_scripts | Also compile .py under skills | False | True |
reject_empty_always_on | Empty always-on text is an error | False | True |
stubs | (path, body) README stubs describing c5..c8 | scripts/, sub_agents/, plugin/, skills/ READMEs | scripts/, sub_agents/, .codex/, .agents/skills/ READMEs |
What the c1..c8 components mean is chapter 04 The harness tree. Structurally, the point is that substrate differences are data. tree.py contains no if surface.name == "codex" branch anywhere. It reads the fields.
5.3 Path rules
- _res and _rx compile, once per surface (
lru_cache(maxsize=8)), the regexes forskill(<skill_dir>/<name>/SKILL.md),skill_script,subagent(sub_agents/<name>/AGENT.md),script(scripts/<lowercase>.py),script_ref(apython3 …scripts/x.pyinvocation inside text) and, whenplugin_diris set,plugin(<plugin_dir>/<name>.ts). - normalize_rel(rel) converts backslashes, strips leading
./and outer/, and raises on absolute paths or any..segment: "path escapes the harness".write_treecalls it on every key before joining it to the destination. - is_allowed(surface, rel) is a predicate that "never raises". A path is on the surface if it is an always-on file, the hooks file, or matches one of the skill, skill-script, sub-agent, script or plugin patterns.
def is_allowed(surface: SurfaceSpec, rel: str) -> bool:
"""Whether this path is on the editable surface. A predicate: it never raises."""
try:
rel = normalize_rel(rel)
except ValueError:
return False
if rel in surface.always_on:
return True
if surface.hooks_file is not None and rel == surface.hooks_file:
return True
r = _rx(surface)
if (
r["skill"].fullmatch(rel)
or r["skill_script"].fullmatch(rel)
or r["subagent"].fullmatch(rel)
or r["script"].fullmatch(rel)
):
return True
return "plugin" in r and bool(r["plugin"].fullmatch(rel))- skill_names and subagent_names return the sorted names found in a tree. _stub_paths is the set of stub paths.
5.4 Read, write, inspect
- read_tree(surface, d, *, rejected=None) returns every on-surface file under
d, skipping__pycache__and stub READMEs. Pass a list asrejectedto collect the paths that exist but are off the surface._run_stageuses that list to tell the proposer which files were ignored (recursion.py#L211-L224). A missing directory returns{}instead of raising. - is_tree(surface, d) is true if at least one always-on file exists. Both substrates'
materialiseuse it as a guard. - write_tree(surface, tree, dest, *, stubs=True) materialises an exact tree. It first deletes every on-surface file (and stub) already at
dest, so a file removed from theTreeis really removed. Off-surface files are preserved. It creates empty always-on files if the tree lacks them, writes the tree, and adds stubs if asked.Library.addpassesstubs=False, so library entries contain only real components. - always_on_text(surface, tree) joins the always-on files with blank lines. This is the text the cap applies to.
- autotrigger(surface, tree, *, always_on_max=None) is a repair, not a check. For each skill not named in the always-on text, it builds a trigger line from the skill's
description:frontmatter andsurface.trigger_line, then appends it tosurface.autotrigger_file, unless that would exceed the cap. It mutatestreein place and returns the names it fixed._accept_treecalls it before validation. - diff_summary(surface, before, after) returns
added/removed/modifiedpath lists andalways_on_char_delta. "Kept base" means all three lists are empty (recursion.py#L119-L120, _tree_is_base). - diversity(trees) computes the pairwise line-Jaccard over candidate trees (lines keyed
rel:line), plus the count of distinct trees, "so collapse to one body is visible".run_parallelstores it asModeResult.similarityfor the k aggregates.
5.5 validate
validate(surface, tree, *, allow_subagents=True, always_on_max=None, reserved_scripts=RESERVED_SCRIPTS) "Raise[s] ValueError listing every problem; return[s] a census of the validated tree." It collects every problem before raising and joins them with " | ". One rejected proposal therefore gets complete feedback on its retry, not one error at a time.
The checks, in order:
- Always-on text over the cap, with
always_on_cap_reasonappended. - If
reject_empty_always_on(Codex): an empty always-on body ("a harness that does not exist"). - _check_skills: every skill needs
description:(else it is "dropped SILENTLY at load"). Undermust_match,name:must equal the directory. Underoverrides_dir, a mismatchingname:is flagged because it would break trigger lines. Every skill must be named in the always-on text, else "an untriggered skill is dead weight". - _check_subagents: sub-agents present when
allow_subagentsis false is an error.mode: subagentis required whensubagent_requires_mode.description:is required. - _py_compiles on every
scripts/*.py, and on skill scripts whencompile_skill_scripts. It calls the built-incompile()only. Nothing is executed. - check_hooks if the hooks file is present: valid JSON, a non-empty top-level
hooksobject, known PascalCase events, list-of-entries shape,type: "command"only, and a non-emptycommand. The docstring explains why: a malformed hooks file "never fires rather than erroring", so the shape has to be enforced where the proposer can see the error. - _check_script_refs: every
python3 scripts/x.pyinvoked from a skill or always-on file must exist in the tree, unless its basename is reserved.
On success it returns a census: always_on_chars, n_files, skills, sub_agents, scripts, plus plugins or hooks depending on the surface.
6. substrate/base.py: the substrate seam
substrate/base.py is 83 lines and depends only on core.tree.SurfaceSpec.
@dataclass(frozen=True)
class Install:
"""A materialised harness ready for a launcher; `fingerprint` digests the SOURCE tree."""
staging_dir: Path
fingerprint: str
extras: Mapping[str, str] = field(default_factory=dict)
@dataclass(frozen=True)
class RunRequest:
"""One execution request handed to a benchmark-supplied launcher body."""
task_ref: str
run_ids: tuple[str, ...]
timeout: int
Launcher = Callable[[RunRequest, Install], list[Path]]6.1 Install
| Field | Meaning |
staging_dir | A temporary directory holding the substrate-native files |
fingerprint | Digest of the source tree, from runmark.fingerprint: first 16 hex chars of a SHA-1 over sorted relative paths and bytes, ignoring __pycache__, .pyc, .DS_Store and the completion marker. None (base) digests to "base". |
extras | Free-form strings. Substrates put install_mode here. Adapters add their own keys (LAB adds "harness": "base" or "staged", which decides whether the staging dir is deleted after the run, lab.py#L609-L610). |
Fingerprinting the source tree, not the staged output, means a rollout's identity does not depend on how a substrate renders it. Combined with runmark.is_complete, adapters reuse a cached rollout only if it was produced by the same harness. This is what makes BenchmarkAdapter.execute "idempotent by run_id" in practice.
6.2 RunRequest and Launcher
RunRequest has task_ref, run_ids (a tuple, so the frozen value stays hashable) and timeout. Launcher is the type of the benchmark-supplied body: (RunRequest, Install) -> list[Path], one run dir per run id.
6.3 Substrate
Substrate is the full, @runtime_checkable protocol:
| Method | Contract |
name: str | "oc" / "cx" |
| surface() | The SurfaceSpec |
| materialise(tree_dir, spec, ctx) | Stage the tree as native files per spec.install_mode. ctx carries benchmark-owned values: {{token}} slots and file:<rel> entries for integration files. |
| execute(task_ref, install, run_ids, timeout) | Run via the launcher. One run dir per run id. |
| read_trajectory(stream_path, *, obs_trunc, max_chars, args_trunc=400) | Render the native capture format into the shared grammar. args_trunc defaults to 400 for OpenCode and 2000 for Codex. |
| preflight() | Health checks. Raise RuntimeError with the remediation command. "Never starts any part of the model chain." |
The read_trajectory docstring says obs_trunc and max_chars "are carried on the cell's BenchSpec". At this commit BenchSpec has obs_trunc but no max_chars field. Each adapter passes its own literal: WorkBuddy TRAJ_MAX_CHARS = 2_000_000 (workbuddy.py#L872), JobBench 8_000_000 (jobbench.py#L880), LAB 10**9 (lab.py#L766).
6.4 The two implementations
| OpencodeSubstrate | CodexSubstrate | |
name | "oc" | "cx" |
surface() | OPENCODE_SURFACE | CODEX_SURFACE |
| Install modes | workspace_agents_md, home_dir_jinja, workspace_config | codex_root only |
materialise | Copies the tree, writes file: ctx entries, and for home_dir_jinja composes a prompt.j2 (checked by assert_jinja_safe) | Copies only AGENTS.md, .agents, scripts, .codex. No render step, "because the CLI injects AGENTS.md itself". Non-file: ctx goes into extras. |
| Reference argv/env | run_argv, run_env (permission fence, output-token ceiling) | exec_argv, exec_env |
preflight | OpenCode checkout and bun present | Endpoint env vars, URL shape, binary present, TCP reachability |
Both execute methods are identical in shape:
def execute(
self, task_ref: str, install: Install, run_ids: list[str], timeout: int
) -> list[Path]:
if self._launcher is None:
raise RuntimeError(
"OpencodeSubstrate was constructed without a launcher body; the benchmark "
"adapter must supply one (it owns runner choice, env wiring and read-back "
"locations)"
)
return self._launcher(
RunRequest(task_ref=task_ref, run_ids=tuple(run_ids), timeout=int(timeout)), install
)The adapter wires its own bound method in as the launcher when it builds the substrate:
self.spec = lab_spec(substrate)
if substrate == "oc":
self.substrate = OpencodeSubstrate(launcher=self._launch_oc)
self.model = model or config.SOLVER_MODEL_OC
self._results_root = config.RUNS / "lab" / "results" / "oc"
else:
self.substrate = CodexSubstrate(launcher=self._launch_cx)
self.model = model or config.SOLVER_MODEL_CXThat is the cleanest statement of the seam. The substrate knows how to stage files for its framework, and the benchmark knows which runner, which environment and where results land. Neither imports the other's internals: substrate modules import no benchmark, and the benchmark uses only the substrate's public constructor and methods. The detail of each executor is chapter 07 Executors and benchmarks.
7. core/ledger.py: budget accounting as a type
The module docstring (ledger.py#L15-L19) gives the budget: R = 4k rollouts and P = 3k improvement stages. "Base executions count toward each mode. Retries are separate invocation counts; selection and reflection are recorded outside this ledger."
| Name | What it is |
| expected_rollouts(k) | 4*k: "k shared base + 2k candidates + k finals" |
| expected_sessions(k) | 3*k: "k INV + k DOM + k compositions (parallel: agg1..aggk; sequential: c1..ck)" |
| BudgetError | Subclass of AssertionError: the ledger missed the identity |
| Entry | kind ("rollout"/"session"), label, usd=0.0, cached=False, attempts=1 |
| Ledger | mode, task_id, k, entries: list[Entry] |
Ledger methods:
| Method/property | Behaviour |
| rollout(label, usd=0.0, *, cached=False) | Append a rollout entry |
| session(label, usd=0.0, *, cached=False, attempts=1) | Append a session entry. attempts keeps retries visible without counting them as extra sessions. |
| R / P | Count of rollout / session entries |
| usd, rollout_usd, session_usd | Cost sums rounded to 6 places |
| labels(kind) | Labels of one kind, in recorded order |
| assert_identity() | Raise BudgetError unless R=4k, P=3k and no duplicate label |
| to_dict() | Serialisable record, including session_attempts and both expected values |
def assert_identity(self) -> None:
"""Raise unless the recorded entries show exactly R=4k and P=3k with no duplicate
label. Duplicates are checked before the counts, because a duplicate can satisfy them.
"""
want_r, want_p = expected_rollouts(self.k), expected_sessions(self.k)
rl, sl = self.labels("rollout"), self.labels("session")
dupes = sorted(
{label for label in rl if rl.count(label) > 1}
| {label for label in sl if sl.count(label) > 1}
)
if not dupes and self.R == want_r and self.P == want_p:
returnWhy it is shaped this way:
- Recording, not budgeting. The ledger does not stop spending. It records what happened, and both recursion modes call
assert_identity()at the very end (recursion.py#L448, #L565). A mode that cut a corner (or ran extra work) fails loudly, so the two modes are always compared at an identical budget. - Duplicates first. Two stages writing the same run id would overwrite each other while still producing the right count. The error message spells this out: "a candidate the ledger counts was never actually produced".
- Dead rollouts count. _roll records a rollout even when
executeraised. Fallback stages are still recorded as sessions. Budget spent is budget spent. - Deterministic order under concurrency. _roll_many and _record_sessions write ledger entries after a thread pool drains, in the caller's order, "so a concurrent run records the same sequence of labels as a serial one".
8. core/selection.py: blind choice and reflection
The module docstring: "Choose an existing anonymized final output, then reflect on its refinement evidence." The full selection flow is chapter 06. Here the focus is on the types and the structural guarantees.
8.1 Module-level names
| Name | Purpose |
| _CHOICE, _WHY, _EXP, _EDITS | Regexes that parse a selector reply: a fenced choice block, <why>, <experience>, <edits> |
| order_graded_first(items, graded) | Stable sort, graded items first, nothing discarded. Not used inside core. WorkBuddy uses it to rank trial directories (workbuddy.py#L736). |
| Selection | The record of one selection (table below) |
| assign_ids(labels, *, task_id, k, mode) | Anonymous id to label, by canonical sort and then a shuffle seeded from the first 12 hex digits of a SHA-1 over the task id, k and mode (joined by vertical bars). The sweep's --seed plays no part |
| _anon_id(index) | Spreadsheet-style ids: A..Z, AA..AZ, BA… |
| _is_base(label) | label.startswith("base") |
| selection_pool(mode, k, rollouts, base) | "The one home for selection-pool construction: exactly the k finals of mode." |
| _render_pool | Drops non-answers (not ok, not delivered, render failure, empty text) before anonymising, and records each in dropped. With spec.state_deliverable, falls back to trajectories as evidence. |
| _assets_note, _assets_line, _evidence_block | Wording about rendered assets (adapter-overridable). The default texts are _DEFAULT_ASSETS_NOTE and _DEFAULT_ASSETS_LINE. |
| _write_candidates | One untrimmed <anon>.md per candidate, plus explicitly named asset files |
| _fallback | Neutral stand-in after a selector failure: a delivered base, else (finals pool) the first final by sorted label |
| _persist | Writes selection.json. A disk failure is recorded on the Selection instead of being raised. |
| run_selection | The selector session |
| trivial_selection | k=1: deliver the single final without a selector session |
| reflect_adoption | Builds a reflection evidence tree after adoption and distils a lesson |
| _distil | The experience session. Returns {cue, lesson, evidence, confidence[, edits]} or {}. |
8.2 Selection fields
| Field | Default | Meaning |
task_id, mode, k | (required) | Identity |
chosen_id | "" | Anonymous id chosen ("A"…) |
chosen_label | "" | The real label (agg1, c2…) |
chosen_run_id | "" | Run id of the chosen rollout |
chosen_is_base | False | Whether the chosen label is a base |
why | "" | Selector's reasoning (up to 2000 chars) |
id_map | {} | Anonymous id to label |
dropped | [] | Candidates removed before anonymising, with causes |
n_candidates | 0 | Pool size after drops |
ok | False | The selector made a valid, parsed choice. A fallback pick leaves it False |
fallback | False | The neutral fallback was used |
error | "" | Failure detail |
usd | 0.0 | Cost of selection plus reflection |
experience | {} | Distilled lesson, if any |
trivial | False | k=1 path |
evidence | "deliverable" | "deliverable" or "trajectory" |
8.3 The pool is the finals, by construction
del base
labels = (
[f"agg{i}" for i in range(1, int(k) + 1)]
if mode == "agg"
else [f"c{t}" for t in range(1, int(k) + 1)]
)
return {lbl: rollouts[lbl] for lbl in labels if lbl in rollouts}base is accepted "and deliberately unused", so the exclusion is visible at the call site. Neither base rollouts nor intermediate candidates (inv*, dom*) can be selected. The docstring calls this "no abstention by construction".
8.4 Failure semantics
run_selection docstring: "A selector failure falls back to a delivered base and never raises; an adapter bug does raise, because it is a bug and not a selection outcome." In the code, only ProposerError and ValueError (no choice block, or a choice outside the id map) are caught, around the prop.call (selection.py#L332-L350). An exception from adapter.task_prompt or context.copy_source propagates. _render_pool does catch exceptions from deliverable_text and trajectory, but records them as drops.
One consequence of the finals-only pool: _fallback looks for a delivered base first, but selection_pool never puts bases in the pool. With pool_kind="finals" (which _run_one_mode always passes), the fallback in practice chooses sorted(pool)[0], the lowest-sorted final. _fallback never sets ok, so that selection stays ok=False: the task ends partial, and the pick is not reflected, not added to the library and not judged (chapter 06).
Before the selector session starts, context.assert_no_grader_verdicts(root) scans the selection directory for grader markers such as "pass_rate" or deterministic_eval_summary (context.py#L419-L427).
9. Neighbouring types the seams depend on
These live outside the five files in the brief, but the class diagrams below need them.
9.1 Proposer, Reply, ProposerError (core/proposer.py)
- Proposer, "Runs agentic sessions on one channel, with an audit trail and a cache." Fields:
spec: ChannelSpec,audit_dir=None,attempts=2,projectandregion(Vertex, from config) and a private_calls: list[Reply]. env() strips Anthropic credentials and sets Vertex variables per channel. call(mission, context_dir, label, *, system, timeout, max_attempts, harvest) copiescontext_dirinto a fresh temp dir ascontext/and runs the channel CLI. It retries with a 10·(n−1) s back-off. Withharvestset (e.g."context/harness"), it copies that subpath out before the temp dir is deleted, which is how an edited tree comes back. Results are cached by a fingerprint of label, mission, system prompt and context (_call_fingerprint). - Reply:
label,text,ok,cost_usd,wall_s,attempts,cached,channel,error,audit,workspace.__bool__returnsok. - ProposerError: "Never swallowed into an empty string." It carries
attemptsandcost_usd, so callers still bill failed sessions (seesel.usd += exc.cost_usd).
Proposer is a dataclass, so it can be subclassed. DemoProposer overrides only call.
9.2 Namespace and ns (core/schedule.py)
The module docstring: "ns()* is the only function that builds a run namespace;* Namespace* is the only thing that globs over one. Scheme:* <bench>-<substrate>-<mode><k>[-s<seed>][-<tag>]."
- Namespace (frozen):
benchmark,substrate,mode,k,seed=None,tag="".__str__delegates tons. run_id(task_id, label) returnsf"{self}/{task_id}/{label}". task_dir(root, task_id) returnsroot/<ns>/<task_id with / → __>. glob(...) returns the two patterns that match bare, seeded and tagged forms. - ns(...) validates the mode (must be in MODES =
("agg", "seq")), a non-empty substrate and the tag (validate_tag rejects tags that look like a seed segment such ass3, so seeded and tagged namespaces can never collide). - mode_token maps
parallel/sequentialto the on-disk tokens. MODE_NAMES maps back.
Run ids are what adapters cache on, so a single naming function means two settings can never collide on disk. The substrate name is in every run id, so OpenCode and Codex rollouts never share a cache entry.
9.3 Library (core/library.py)
Library is "One (run, domain) library: read before each task, appended after." Fields: root, domain, surface: SurfaceSpec. It needs the surface because entries are stored as real component trees (read_tree/write_tree). Its methods are dir, index, entry_dir, exists, context_dir (None until the domain has at least one EXPERIENCE.md), entries, tree_of, forget, retain, add (refuses an entry without cue, lesson and evidence, and scans for grader verdicts before writing) and rebuild_index. How entries are written, trimmed and replayed is covered in chapters 03 and 06.
9.4 Records: ContextTree, Stage, ModeResult, TaskResult, Scored, Summary
- ContextTree is what context.build actually wrote: root, rollouts, deliverables, harness file count, library flag, pool, failures. Its layout property lists only what exists, for mission().
- Stage: one improvement stage:
label,dim(INV/DOM/AGG/COMPOSE),tree,kept_base,fell_back,error,usd,attempts,cached,notes. - ModeResult: one mode on one task:
stages,trees,rollouts: dict[str, RunView],finals,ledger: Ledger,similarity,errors.okmeans finals exist and there are no errors. - TaskResult: the
summary.jsonrecord for one task across modes. - Scored: one judged selection.
gradedmeans "the judge succeeded, not whether the run did well".fracisNonewhen ungraded. - Summary: the aggregate one mode reports.
10. Class diagrams
10.1 The seam types
The types from the brief and how they depend on each other. Protocols are marked <<Protocol>>. Dashed realisation arrows mean "satisfies structurally": no class inherits from a protocol.
classDiagram
direction LR
class BenchmarkAdapter {
<<Protocol>>
+spec: BenchSpec
+substrate: SubstrateLike
+tasks(split) list
+domain_of(task_id) str
+task_prompt(task_id) str
+task_files(task_id) Path
+execute(task_id, harness_dir, run_id) Path
+delivered(run_dir) bool
+trajectory(run_dir, obs_trunc) str
+deliverable_files(run_dir) dict
+deliverable_text(run_dir) str
+base_harness() Path
+validate_tree(tree) None
+score(run_dir, task_id) dict
}
class BenchSpec {
<<frozen>>
+name: str
+runs_root: Path
+install_mode: str
+prompts: BenchPrompts
+channels: dict
+leak_globs: tuple
+allow_subagents: bool
+reserved_scripts: frozenset
+proposer_deliverables: str
+state_deliverable: bool
+obs_trunc: int
}
class BenchPrompts {
<<frozen>>
+work: str
+domain_word: str
+inv_levers: str
+dom_levers: str
+yardstick: str
+selection_checks: str
+prompt_pack: str
}
class ChannelSpec {
<<frozen>>
+kind: str
+model: str
+effort: str
+steps: int
+timeout: int
}
class RunView {
+label: str
+run_id: str
+run_dir: Path
+ok: bool
+delivered: bool
+cost_usd: float
+error: str
}
class SubstrateLike {
<<Protocol>>
+name: str
+surface() SurfaceSpec
}
class Substrate {
<<Protocol>>
+materialise(tree_dir, spec, ctx) Install
+execute(task_ref, install, run_ids, timeout) list
+read_trajectory(stream_path, obs_trunc, max_chars) str
+preflight() None
}
class SurfaceSpec {
<<frozen>>
+name: str
+always_on: tuple
+always_on_max: int
+skill_dir: str
+plugin_dir: str
+hooks_file: str
+stubs: tuple
}
class Install {
<<frozen>>
+staging_dir: Path
+fingerprint: str
+extras: Mapping
}
class RunRequest {
<<frozen>>
+task_ref: str
+run_ids: tuple
+timeout: int
}
class OpencodeSubstrate {
+name = oc
-_launcher: Launcher
}
class CodexSubstrate {
+name = cx
-_launcher: Launcher
}
class Ledger {
+mode: str
+task_id: str
+k: int
+entries: list
+rollout(label, usd)
+session(label, usd, attempts)
+R() int
+P() int
+assert_identity()
}
class Entry {
+kind: str
+label: str
+usd: float
+cached: bool
+attempts: int
}
class Proposer {
+spec: ChannelSpec
+audit_dir: Path
+attempts: int
+call(mission, context_dir, label) Reply
}
class Reply {
+label: str
+text: str
+ok: bool
+cost_usd: float
+workspace: Path
}
class Namespace {
<<frozen>>
+benchmark: str
+substrate: str
+mode: str
+k: int
+seed: int
+tag: str
+run_id(task_id, label) str
+task_dir(root, task_id) Path
}
class LabAdapter
class WbAdapter
class JobBenchAdapter
class DemoAdapter
BenchmarkAdapter --> BenchSpec : spec
BenchmarkAdapter --> SubstrateLike : substrate
BenchmarkAdapter ..> RunView : run_dir becomes
BenchSpec *-- BenchPrompts : prompts
BenchSpec *-- ChannelSpec : channels
SubstrateLike <|-- Substrate : superset
SubstrateLike ..> SurfaceSpec : surface()
Substrate ..> Install : materialise
Substrate ..> RunRequest : execute
OpencodeSubstrate ..|> Substrate
CodexSubstrate ..|> Substrate
LabAdapter ..|> BenchmarkAdapter
WbAdapter ..|> BenchmarkAdapter
JobBenchAdapter ..|> BenchmarkAdapter
DemoAdapter ..|> BenchmarkAdapter
LabAdapter --> OpencodeSubstrate : launcher
LabAdapter --> CodexSubstrate : launcher
Ledger *-- Entry
Proposer --> ChannelSpec : spec
Proposer ..> Reply : call
Namespace ..> RunView : run_id
click BenchmarkAdapter href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L200-L250" "BenchmarkAdapter"
click BenchSpec href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L132-L173" "BenchSpec"
click BenchPrompts href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L89-L126" "BenchPrompts"
click ChannelSpec href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L36-L48" "ChannelSpec"
click RunView href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L176-L187" "RunView"
click SubstrateLike href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L190-L197" "SubstrateLike"
click Substrate href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/base.py#L47-L80" "Substrate"
click SurfaceSpec href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/tree.py#L40-L60" "SurfaceSpec"
click Install href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/base.py#L26-L32" "Install"
click RunRequest href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/base.py#L35-L41" "RunRequest"
click OpencodeSubstrate href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/opencode.py#L173-L320" "OpencodeSubstrate"
click CodexSubstrate href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/substrate/codex.py#L109-L228" "CodexSubstrate"
click Ledger href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/ledger.py#L49-L132" "Ledger"
click Entry href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/ledger.py#L40-L46" "Entry"
click Proposer href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/proposer.py#L90-L387" "Proposer"
click Reply href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/proposer.py#L70-L87" "Reply"
click Namespace href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/schedule.py#L84-L108" "Namespace"
click LabAdapter href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/lab.py#L498-L1002" "LabAdapter"
click WbAdapter href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/workbuddy.py#L671-L1004" "WbAdapter"
click JobBenchAdapter href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/benchmarks/jobbench.py#L622-L1162" "JobBenchAdapter"
click DemoAdapter href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/demo.py#L41-L117" "DemoAdapter"10.2 The method's run-time records
What the method produces while it runs one task, and which record owns which.
classDiagram
direction TB
class TaskResult {
+task_id: str
+domain: str
+k: int
+status: str
+modes: dict
+selections: dict
+base: list
}
class ModeResult {
+mode: str
+k: int
+stages: list
+trees: dict
+rollouts: dict
+finals: list
+ledger: Ledger
+similarity: dict
+ok() bool
}
class Stage {
+label: str
+dim: str
+tree: dict
+kept_base: bool
+fell_back: bool
+notes: str
}
class Ledger {
+entries: list
+assert_identity()
}
class RunView {
+label: str
+ok: bool
+delivered: bool
}
class Selection {
+chosen_id: str
+chosen_label: str
+id_map: dict
+dropped: list
+fallback: bool
+experience: dict
}
class ContextTree {
+root: Path
+rollouts: list
+pool: list
+layout() str
}
class Library {
+root: Path
+domain: str
+surface: SurfaceSpec
+context_dir() Path
+add(task_id, harness)
}
class Scored {
+label: str
+n_passed: int
+n_criteria: int
+graded() bool
}
class Summary {
+mode: str
+k: int
+score: float
}
TaskResult ..> ModeResult : modes = to_dict()
TaskResult ..> Selection : selections = to_dict()
ModeResult *-- Stage
ModeResult *-- Ledger
ModeResult o-- RunView : rollouts
Selection ..> RunView : picks from finals
Stage ..> ContextTree : built per stage
Selection ..> Library : experience added
Scored ..> Selection : reads selection.json
Summary ..> Scored : aggregates
click TaskResult href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/schedule.py#L239-L262" "TaskResult"
click ModeResult href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/recursion.py#L58-L105" "ModeResult"
click Stage href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/recursion.py#L41-L55" "Stage"
click Ledger href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/ledger.py#L49-L132" "Ledger"
click RunView href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/spec.py#L176-L187" "RunView"
click Selection href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/selection.py#L46-L68" "Selection"
click ContextTree href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/context.py#L42-L102" "ContextTree"
click Library href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/library.py#L57-L181" "Library"
click Scored href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/judge.py#L32-L53" "Scored"
click Summary href "https://github.com/google-research/harness2/blob/1cfe254e8d25b15f37048c3730134686abe60335/harness2/core/report.py#L31-L65" "Summary"Scored and Summary sit at the far end. They are produced by a separate process (harness2-judge, harness2-report) that reads selection.json from disk after the run. That boundary is a file, not an import.
11. Seams and import rules
11.1 Where the seams are
| Seam | Interface | One side | Other side | What crosses |
| Method ↔ benchmark | BenchmarkAdapter + BenchSpec | core/* | benchmarks/*, demo.py | Task ids, prompts, run dirs, text, RunView |
| Method ↔ substrate | SubstrateLike.surface() → SurfaceSpec | core/tree.py, core/recursion.py, core/library.py | substrate/* | One frozen value |
| Benchmark ↔ substrate | Substrate + Install + RunRequest + Launcher | benchmarks/* | substrate/* | Staged dirs, fingerprints, launch requests. The benchmark injects the launcher. |
| Method ↔ model | ChannelSpec → Proposer.call → Reply | core/recursion.py, core/selection.py | the channel CLIs | Mission text, a context dir, harvested files |
| Method ↔ measurement | BenchmarkAdapter.score via core/judge.py | cli/judge.py | adapters | Scores, after selection, through selection.json on disk |
| CLI ↔ everything | make_adapter | cli/* | benchmarks/* | (--bench, --subset, --substrate) → adapter |
11.2 What may never import what
The code states these as rules in docstrings, and the import graph in section 1.2 confirms each one at this commit:
core* never imports**substrate,benchmarks** or**cli. Checked: no `core/.pyhas such an import.SubstrateLikeandSurfaceSpecexist incore` precisely so this holds.- No method module imports
core/judge.py. Its importers arecore/report.py,cli/judge.pyandtests/test_workflow.py. The method can't read a score because it never imports the code that produces one. substrate** never imports**benchmarks. It importscore.tree,core.runmarkandconfigonly. Benchmark specifics reach it as a launcher callable andctxmappings.benchmarks** never import method logic.** They usecorevalue types and utilities (spec,tree,runmark,selection.order_graded_first), neverrecursion,schedule,contextorprompts.- Only the CLI chooses a benchmark, lazily, in
make_adapter. Soimport harness2.cli._commondoes not pull in all three benchmark modules. config** imports nothing internal.** Every layer can read settings without creating a cycle.
The repo has no import-linter and no test that enforces these rules mechanically. They hold because of how the modules are written. The test suite (tests/test_workflow.py) imports core.judge directly, which is fine for a test.
11.3 Why it matters
The paper's comparisons depend on these boundaries. "Blind selection" holds because the selection code cannot reach a score. "Identical budget" holds because both modes record into the same Ledger type and assert the same identity. "Substrate-independent method" holds because core sees only SurfaceSpec values. Swapping OpenCode for Codex changes a value, not a code path.
12. Worked example: the offline demo through these types
harness2-demo (cli/demo.py) runs the whole method with no model service and no benchmark judge. It is the smallest complete use of every seam. The trace below follows the default --k 2 --mode both, task ledger-1.
1. Building the cell. DemoAdapter.init builds:
substrate = OpencodeSubstrate(), with no launcher, because the demo'sexecuteruns a local script itself and never callssubstrate.execute. The substrate is used only forname == "oc"andsurface() == OPENCODE_SURFACE.channel = ChannelSpec("opencode", "local/deterministic").__post_init__acceptskind. The model contains/, whichresolve_channel_modelwould require foropencode, but the demo builds theChannelSpecdirectly.spec = BenchSpec(name="demo", runs_root=root/"runs", install_mode="workspace_config", prompts=BenchPrompts(work="reconcile signed amounts in a transaction ledger"), channels={"proposer": channel, "selector": channel}, leak_globs=("reference_answer.txt",)). AllBenchSpecchecks pass: valid install mode, both roles present,obs_trunc50,000.BenchPromptspasses the anti-leak scan (no "score", "rubric"…).- A base tree written with
tree.write_tree(surface, base, root/"base", stubs=False): four always-on files (onlysystemprompt.mdnon-empty: "Reconcile the ledger using python scripts/summarize.py.") plusscripts/summarize.py, which sums only positive amounts (BASE_SCRIPT). That filter is the bug. - Source files:
ledger-1=100.25, -20.10, 5.00. The correct total is 85.15. The base script printsTotal: 105.25.
DemoProposer(adapter.spec.channels["proposer"]) passes Proposer.__post_init__ because the model is non-empty. _selector_for returns the same object, since the selector ChannelSpec equals the proposer's.
2. Namespaces and bases. run_task builds Namespace("demo", "oc", "agg", 2), which stringifies to demo-oc-agg2. Base labels are base1, base2, with run ids demo-oc-agg2/ledger-1/base1 and …/base2. Both modes share these bases: they are rolled once, in the agg namespace. DemoAdapter.execute(…, None, run_id) runs the base script and writes answer.txt = Total: 105.25. Each becomes RunView(label="base1", ok=True, delivered=True, …).
3. Parallel mode (agg). run_parallel creates Ledger(mode="agg", task_id="ledger-1", k=2) and records the two bases as rollouts. Proposal stages are created in the order inv1, dom1, inv2, dom2 (recursion.py#L368). For each one, _run_stage builds a ContextTree under context/<label> and calls prop.call(mission, tree.root, "inv1.a1", harvest="context/harness"). DemoProposer (the else branch) deletes if amount > 0 from context/harness/scripts/summarize.py and returns workspace=context/harness. read_tree(OPENCODE_SURFACE, workspace, rejected=[]) gets the tree back. _accept_tree runs autotrigger (no skills, nothing to do), diff_summary (one modified file, so kept_base=False) and validate, which passes: always-on text well under 24,000 chars, scripts/summarize.py compiles, and the python scripts/summarize.py reference in systemprompt.md matches a shipped file. Then DemoAdapter.validate_tree (scripts/summarize.py must exist) and the grader-verdict scan run. Four sessions are recorded. Four rollouts follow, each now printing Total: 85.15. Two AGG stages (agg1, agg2) do the same edit on the base and roll once each.
Ledger at the end: rollouts base1, base2, inv1, dom1, inv2, dom2, agg1, agg2 gives R = 8 = 4k. Sessions inv1, dom1, inv2, dom2, agg1, agg2 gives P = 6 = 3k. assert_identity() passes.
4. Selection. selection_pool("agg", 2, …) returns {agg1, agg2}. _render_pool keeps both. assign_ids(["agg1", "agg2"], task_id="ledger-1", k=2, mode="agg") seeds random.Random with the first 12 hex digits of sha1("ledger-1|2|agg"), which gives {"A": "agg1", "B": "agg2"}. (Computed by re-running the same three lines of logic outside the repo. For ledger-2 the same call gives {"A": "agg2", "B": "agg1"}, showing that the shuffle really depends on the task.) The selector sees only context/candidates/A.md and B.md. DemoProposer (select. branch) recomputes the expected total from context/source/ledger.csv and picks the first candidate file containing Total: 85.15. That is A, and the reply contains a choice block. Selection(chosen_id="A", chosen_label="agg1", ok=True), persisted to selection/agg2/selection.json.
5. Reflection and library. reflect_adoption builds a reflection tree with harness/ (agg1's tree) and base_harness/. DemoProposer (experience. branch) sees the files differ and returns <edits> and <experience> blocks with cue, lesson, evidence and confidence. _distil parses them. Library.add stores agg1's full tree under library/k2/agg/accounting/harnesses/ledger-1/ with EXPERIENCE.md, and rebuilds INDEX.md.
6. Sequential mode (seq). In namespace demo-oc-seq2, round 1 runs inv1, dom1, then compose c1 on the base. Round 2 runs inv2, dom2, c2 on c1's tree. In round 2 the demo's edit is a no-op, because the filter is already gone, so those stages come back with kept_base=True (identical to their fallback, c1's tree) yet still pass validation. Ledger: 2 bases + 3 rollouts per round × 2 = R = 8, and 3 sessions per round × 2 = P = 6. The identity holds again. The pool is {c1, c2}.
7. Next task. run_domain calls lib.retain(task_ids[:1]) before ledger-2. Each mode's Library.context_dir() now returns the domain directory (it contains an EXPERIENCE.md), so ledger-2's improvers find context/library/INDEX.md in their evidence.
At no point did any code path call adapter.score: DemoAdapter does not even define it. The demo runs end to end without a judge, which is the strongest proof that selection is answer-free. (Running it is chapter 08 Setup and running.)
13. What the code does not settle
BenchSpec.domain_word** is never read.** Every adapter sets it, but onlyBenchPrompts.domain_wordreaches the prompts. The code does not say whether this is vestigial or reserved.max_chars** is described as carried on**BenchSpec** but isn't.** See section 6.3. The value is a literal in each adapter.- The base-first branch of
_fallback** cannot fire under the finals-only pool.** The bases are never in the pool. The code keeps it, and thepool_kind="mixed"default ofrun_selection, but no caller in this release passes a mixed pool. deliverable_assets_note** /**_line** /**_reference_only** hooks have no implementer** among the shipped adapters.- Import rules are conventions, not checks. No tool or test fails if someone adds
from ..benchmarks import …tocore. - Why some
BenchPrompts** fields are exempt from the anti-leak scan** (domain_word,deliverable_word,selection_lead,selection_conformance) is not explained by the code.
Key takeaways
- Four concerns, small seams: method (
core), benchmark (BenchmarkAdapter+BenchSpec), substrate (Substrate, seen by core only asSubstrateLike.surface()), and channel (ChannelSpec→Proposer). - Values are frozen dataclasses that validate on construction (
ChannelSpec,BenchPrompts,BenchSpec,SurfaceSpec,Install,RunRequest,Namespace). Behaviour lives in protocols, their implementations,Proposer,Libraryandcore's module functions. - Substrate differences are data.
tree.pyhas no per-framework branches. Everything is aSurfaceSpecfield. - The benchmark injects the launcher into the substrate. The substrate owns staging, and the benchmark owns runners, environment and result locations.
RunView** has no score, and nothing in the method's run path imports**core/judge.py.BenchmarkAdapter.scoreis called only by the judge, after selection, across a file boundary.Ledger.assert_identity()** makes the R=4k / P=3k budget a checked invariant**, duplicates first, with dead rollouts and fallback stages still counted.- Naming is centralised in
ns()** /**Namespace. Substrate, mode, k, seed and tag are all in every run id, so caches never collide. tree.validate** reports every problem at once**, because its message is the proposer's retry feedback.
Where to go next
- 00 · Start here: what Harness² is and how this guide is organised.
- 01 · Architecture overview: the big picture these packages implement.
- 03 · One task, end to end:
run_task→ recursion → selection → library in execution order. - 04 · The harness tree: what c1..c8 mean,
SurfaceSpecin depth, the library. - 05 · Evidence and safety:
context.py, leak globs, grader-verdict scans, the anti-leak prompt check. - 06 · Budget, selection and judging: the ledger identity, the selector prompt,
judge.pyandreport.py. - 07 · Executors and benchmarks: the three adapters, their launchers, and both substrates' materialise and render paths.
- 08 · Setup and running: installing dependencies,
harness2-check,harness2-demo, real sweeps.