@tangle-network/agent-bench 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/package.json +35 -0
- package/src/adapters.ts +60 -0
- package/src/aec-gate.mts +217 -0
- package/src/atom-humaneval.mts +197 -0
- package/src/atom-mcp-e2e.mts +223 -0
- package/src/benchmarks/_harness.ts +206 -0
- package/src/benchmarks/aec-bench.test.mts +53 -0
- package/src/benchmarks/aec-bench.ts +319 -0
- package/src/benchmarks/appworld.test.mts +45 -0
- package/src/benchmarks/appworld.ts +426 -0
- package/src/benchmarks/cad-design.ts +429 -0
- package/src/benchmarks/cadbench.ts +135 -0
- package/src/benchmarks/cadgenbench.ts +121 -0
- package/src/benchmarks/commit0.test.mts +71 -0
- package/src/benchmarks/commit0.ts +266 -0
- package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
- package/src/benchmarks/enterpriseops-gym.ts +343 -0
- package/src/benchmarks/finsearchcomp.ts +371 -0
- package/src/benchmarks/frames.ts +520 -0
- package/src/benchmarks/hotpotqa.ts +320 -0
- package/src/benchmarks/humaneval.ts +251 -0
- package/src/benchmarks/mind2web.ts +311 -0
- package/src/benchmarks/programbench.test.mts +60 -0
- package/src/benchmarks/programbench.ts +210 -0
- package/src/benchmarks/simpleqa.ts +390 -0
- package/src/benchmarks/swe-bench.ts +152 -0
- package/src/benchmarks/terminal-bench.ts +182 -0
- package/src/benchmarks/trata-hedge.ts +496 -0
- package/src/benchmarks/types.ts +58 -0
- package/src/browser/adapters/bad-design-audit.ts +153 -0
- package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
- package/src/browser/adapters/bad.ts +165 -0
- package/src/browser/agent-adapter.ts +145 -0
- package/src/browser/process-adapter.ts +146 -0
- package/src/browser/run-to-spans.ts +105 -0
- package/src/browser/run-to-spans.verify.ts +89 -0
- package/src/browser/ui-reviewer.ts +200 -0
- package/src/browser/ui-reviewer.verify.ts +94 -0
- package/src/browser/verify.ts +96 -0
- package/src/clbench-codebase-gate.mts +314 -0
- package/src/clbench-context-gate.mts +305 -0
- package/src/cloud-loop.mts +138 -0
- package/src/coding-skills/minimal-diff.md +9 -0
- package/src/coding-skills/read-before-edit.md +9 -0
- package/src/coding-skills/reproduce-first.md +10 -0
- package/src/coding-skills/run-tests-after-edit.md +9 -0
- package/src/coding-skills/trace-the-failure.md +9 -0
- package/src/commit0-env-run.mts +59 -0
- package/src/commit0-env.ts +173 -0
- package/src/commit0-gate.mts +529 -0
- package/src/commit0-prereqs.sh +48 -0
- package/src/corpus-replay.mts +300 -0
- package/src/corpus-report.mts +504 -0
- package/src/corpus.test.mts +273 -0
- package/src/corpus.ts +325 -0
- package/src/decoder-live.mts +133 -0
- package/src/directives.ts +84 -0
- package/src/diverse-gate.mjs +112 -0
- package/src/egress-probe.mts +26 -0
- package/src/eops-skills/address-every-subtask.md +5 -0
- package/src/eops-skills/exact-tools-and-args.md +5 -0
- package/src/eops-skills/full-sequence-to-goal.md +5 -0
- package/src/eops-skills/ground-every-value.md +5 -0
- package/src/eops-skills/honor-the-policies.md +5 -0
- package/src/examples/README.md +58 -0
- package/src/examples/math-demo.mts +110 -0
- package/src/examples/strategy-demo.mts +119 -0
- package/src/fleet.mts +121 -0
- package/src/gate-cli.mts +101 -0
- package/src/gate.test.mts +129 -0
- package/src/gate.ts +460 -0
- package/src/generate-eval/certify.ts +178 -0
- package/src/generate-eval/schema.ts +78 -0
- package/src/humaneval-gate.mts +204 -0
- package/src/humaneval-repair-gate.mts +143 -0
- package/src/index.ts +19 -0
- package/src/mcp-mount-probe.mts +126 -0
- package/src/profile-coordinates.ts +134 -0
- package/src/profiles.ts +128 -0
- package/src/refine-loop.test.mts +106 -0
- package/src/refine-loop.ts +106 -0
- package/src/research-gate.mts +132 -0
- package/src/research-shot.ts +134 -0
- package/src/resolve-client.ts +58 -0
- package/src/router-executor.ts +51 -0
- package/src/run-pool.ts +48 -0
- package/src/runtime-hook-recorder.ts +137 -0
- package/src/sandbox-run.ts +125 -0
- package/src/search-bench/bridge.ts +124 -0
- package/src/search-bench/export.mts +0 -0
- package/src/search-bench/parametric-check.mts +63 -0
- package/src/search-bench/profiles.ts +98 -0
- package/src/search-bench/run.mts +287 -0
- package/src/search-bench/tasks-fresh.ts +688 -0
- package/src/search-bench/tasks.ts +129 -0
- package/src/search-tool.ts +95 -0
- package/src/selector.test.mts +189 -0
- package/src/selector.ts +366 -0
- package/src/skill-sandbox-smoke.mts +100 -0
- package/src/stats.mts +90 -0
- package/src/terminal-compare.ts +519 -0
- package/src/trajectory-assemble.mjs +130 -0
- package/src/trata-gate.mts +243 -0
- package/src/trata-gepa.mts +434 -0
- package/src/worker-blender.ts +230 -0
- package/src/worker-browser.ts +102 -0
- package/src/worker-build123d.ts +143 -0
- package/src/worker-cad.ts +451 -0
- package/src/worker.ts +136 -0
- package/src/workspace-loop.mts +133 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline replay / score substrate over the learning-flywheel corpus
|
|
3
|
+
* (docs/learning-flywheel.md, layer 1: "an offline replay + reward-model layer so
|
|
4
|
+
* the controller space can be searched WITHOUT a live rollout per candidate").
|
|
5
|
+
*
|
|
6
|
+
* Two jobs, both READ-ONLY against the corpus:
|
|
7
|
+
*
|
|
8
|
+
* 1. REPLAY - for each RunRecord, take the recorded FINAL output and either
|
|
9
|
+
* (a) recompute the structural verdict from the stored per-attempt verdicts
|
|
10
|
+
* (default, zero model tokens) - proves the corpus is structurally
|
|
11
|
+
* replayable: the stored `resolved` matches the stored attempt verdicts; or
|
|
12
|
+
* (b) re-run the benchmark's OWN judge on that final output (--judge) and
|
|
13
|
+
* compare to the recorded `resolved` - proves the corpus is FAITHFULLY
|
|
14
|
+
* replayable and surfaces judge nondeterminism. For router-LLM judges
|
|
15
|
+
* (finsearchcomp) this costs JUDGE calls but NOT agent rollouts - that is
|
|
16
|
+
* the entire point of the offline layer. Deterministic judges (hotpotqa)
|
|
17
|
+
* cost nothing.
|
|
18
|
+
*
|
|
19
|
+
* 2. scoreCandidateOffline - the typed seam a future offline controller-search
|
|
20
|
+
* (GEPA / meta-harness over the corpus) consumes to score an ALTERNATIVE
|
|
21
|
+
* controller's output against a recorded instance without re-running the agent.
|
|
22
|
+
*
|
|
23
|
+
* Run:
|
|
24
|
+
* tsx src/corpus-replay.mts [corpusPath] [BENCH=finsearchcomp] [--judge]
|
|
25
|
+
* tsx src/corpus-replay.mts [corpusPath] --selector [--condition=random]
|
|
26
|
+
*
|
|
27
|
+
* Fail-loud: an empty or unreadable corpus is an error, never a silent zero.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { readFile } from 'node:fs/promises'
|
|
31
|
+
import { argv } from 'node:process'
|
|
32
|
+
import { fileURLToPath } from 'node:url'
|
|
33
|
+
import type { BenchmarkAdapter, BenchScore, BenchTask } from './benchmarks/types'
|
|
34
|
+
import { ADAPTERS } from './adapters'
|
|
35
|
+
import type { RunRecord } from './corpus'
|
|
36
|
+
import { selfConsistencySelect, summarizeSelector, summarizeVerifierSelector } from './selector'
|
|
37
|
+
|
|
38
|
+
/** The benchmark's own judge - the EXTERNAL, write-only anchor (learning-flywheel.md). */
|
|
39
|
+
export type Judge = (task: BenchTask, artifact: string) => Promise<BenchScore>
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The recorded FINAL output of a controller-run: the last attempt that actually
|
|
43
|
+
* produced an output. Returns undefined when no attempt produced one (every round
|
|
44
|
+
* errored / blank) - the caller treats that as an unreplayable record, not a pass.
|
|
45
|
+
*/
|
|
46
|
+
export function finalOutput(record: RunRecord): string | undefined {
|
|
47
|
+
for (let i = record.attempts.length - 1; i >= 0; i -= 1) {
|
|
48
|
+
const out = record.attempts[i]?.output
|
|
49
|
+
if (typeof out === 'string' && out.length > 0) return out
|
|
50
|
+
}
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The stored structural verdict for the final attempt (its `valid` flag). */
|
|
55
|
+
function storedFinalValid(record: RunRecord): boolean {
|
|
56
|
+
for (let i = record.attempts.length - 1; i >= 0; i -= 1) {
|
|
57
|
+
const a = record.attempts[i]
|
|
58
|
+
if (a?.output !== undefined && a.output.length > 0) return a.valid === true
|
|
59
|
+
}
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The OFFLINE SCORING SEAM. Score a counterfactual controller output against a
|
|
65
|
+
* recorded instance by replaying the benchmark's own judge on it - NOT by
|
|
66
|
+
* re-running the agent. This is how a future optimizer (GEPA / meta-harness over
|
|
67
|
+
* the corpus) prices a candidate steer cheaply: it proposes an alternative final
|
|
68
|
+
* answer for the recorded instance, calls this, and reads back {resolved, score}
|
|
69
|
+
* at the cost of one judge call (zero for deterministic judges, zero agent
|
|
70
|
+
* rollouts) instead of a full live k-attempt rollout.
|
|
71
|
+
*
|
|
72
|
+
* The judge is the EXTERNAL anchor: it scores the candidate output and never sees
|
|
73
|
+
* the recorded outcome, so the score stays write-only (no oracle leakage).
|
|
74
|
+
*
|
|
75
|
+
* `record` supplies the instanceId the caller must resolve to a BenchTask (the
|
|
76
|
+
* judge needs the task's gold/judge-template metadata); pass the SAME judge that
|
|
77
|
+
* was used to score the corpus so offline and online scores are comparable.
|
|
78
|
+
*/
|
|
79
|
+
export async function scoreCandidateOffline(
|
|
80
|
+
record: RunRecord,
|
|
81
|
+
candidateOutput: string,
|
|
82
|
+
judge: Judge,
|
|
83
|
+
task: BenchTask,
|
|
84
|
+
): Promise<{ resolved: boolean; score: number }> {
|
|
85
|
+
if (task.id !== record.instanceId) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`scoreCandidateOffline: task ${JSON.stringify(task.id)} does not match record.instanceId ${JSON.stringify(record.instanceId)}`,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
const verdict = await judge(task, candidateOutput)
|
|
91
|
+
return { resolved: verdict.resolved === true, score: verdict.score }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Parse the corpus JSONL; fail loud on empty/unreadable. */
|
|
95
|
+
async function readCorpus(corpusPath: string): Promise<RunRecord[]> {
|
|
96
|
+
let raw: string
|
|
97
|
+
try {
|
|
98
|
+
raw = await readFile(corpusPath, 'utf8')
|
|
99
|
+
} catch (err) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`corpus-replay: cannot read corpus ${corpusPath}: ${err instanceof Error ? err.message : String(err)}\n` +
|
|
102
|
+
`Run a bench loop first (it appends RunRecords), or pass an existing corpus path.`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
const lines = raw.split('\n').filter((l) => l.trim().length > 0)
|
|
106
|
+
if (lines.length === 0) throw new Error(`corpus-replay: corpus ${corpusPath} is empty - nothing to replay`)
|
|
107
|
+
return lines.map((l, i) => {
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(l) as RunRecord
|
|
110
|
+
} catch (err) {
|
|
111
|
+
throw new Error(`corpus-replay: corpus ${corpusPath} line ${i + 1} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve every recorded instanceId for one benchmark to its BenchTask in a single
|
|
118
|
+
* loadTasks call (the judge needs the task's gold/judge metadata). Fail loud when a
|
|
119
|
+
* recorded instance is absent from the live/fixture dataset - that is corpus/dataset
|
|
120
|
+
* drift, not a replayable record.
|
|
121
|
+
*/
|
|
122
|
+
async function loadTaskMap(adapter: BenchmarkAdapter, ids: string[]): Promise<Map<string, BenchTask>> {
|
|
123
|
+
const tasks = await adapter.loadTasks({ ids })
|
|
124
|
+
const byId = new Map<string, BenchTask>()
|
|
125
|
+
for (const t of tasks) byId.set(t.id, t)
|
|
126
|
+
return byId
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function main(): Promise<void> {
|
|
130
|
+
const args = process.argv.slice(2)
|
|
131
|
+
const reJudge = args.includes('--judge')
|
|
132
|
+
const positional = args.filter((a) => !a.startsWith('--'))
|
|
133
|
+
const corpusPath = positional[0] ?? '/home/drew/code/agent-runtime/bench/corpus/finsearch.jsonl'
|
|
134
|
+
const benchOverride = positional[1] ?? process.env.BENCH
|
|
135
|
+
|
|
136
|
+
const records = await readCorpus(corpusPath)
|
|
137
|
+
|
|
138
|
+
// Group by benchmark so each adapter judges only its own records.
|
|
139
|
+
const byBench = new Map<string, RunRecord[]>()
|
|
140
|
+
for (const r of records) {
|
|
141
|
+
const bench = benchOverride ?? r.benchmark
|
|
142
|
+
const list = byBench.get(bench)
|
|
143
|
+
if (list) list.push(r)
|
|
144
|
+
else byBench.set(bench, [r])
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --selector[=METHOD]: score a deployable, non-oracle selector OFFLINE over the
|
|
148
|
+
// corpus. Each condition-run's k attempts are the candidates; the pick's stored
|
|
149
|
+
// verdict is read AFTER selection (zero new calls). Default to the random@k
|
|
150
|
+
// condition (independent attempts = the honest "can a selector beat a blind draw?").
|
|
151
|
+
// --selector / --selector=self-consistency → cluster by OUTPUT TEXT (Wang 2022);
|
|
152
|
+
// picks blind to the checker. The right baseline for free-text answers.
|
|
153
|
+
// --selector=verifier → rank by each attempt's DEPLOYABLE-CHECKER score (commit0
|
|
154
|
+
// pytest pass-rate / aec verify.py partial credit), reported on the CONTINUOUS
|
|
155
|
+
// graded reward with a paired bootstrap CI. The right selector for graded domains
|
|
156
|
+
// where text doesn't cluster (numeric JSON, diffs).
|
|
157
|
+
const selectorArg = args.find((a) => a === '--selector' || a.startsWith('--selector='))
|
|
158
|
+
if (selectorArg) {
|
|
159
|
+
const method = selectorArg.includes('=') ? selectorArg.slice('--selector='.length) : 'self-consistency'
|
|
160
|
+
if (method !== 'self-consistency' && method !== 'verifier') {
|
|
161
|
+
throw new Error(`corpus-replay --selector: unknown method "${method}" (use self-consistency | verifier)`)
|
|
162
|
+
}
|
|
163
|
+
const condArg = args.find((a) => a.startsWith('--condition='))
|
|
164
|
+
const condFilter = condArg ? condArg.slice('--condition='.length) : 'random'
|
|
165
|
+
const pct = (x: number) => `${(x * 100).toFixed(1)}%`
|
|
166
|
+
const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
|
|
167
|
+
let any = false
|
|
168
|
+
for (const [bench, recs] of byBench) {
|
|
169
|
+
const slice = recs.filter((r) => r.condition.includes(condFilter))
|
|
170
|
+
if (slice.length === 0) continue
|
|
171
|
+
any = true
|
|
172
|
+
if (method === 'verifier') {
|
|
173
|
+
const rep = summarizeVerifierSelector(slice)
|
|
174
|
+
const sig = rep.ci.p < 0.05 ? 'SIGNIFICANT' : 'n.s.'
|
|
175
|
+
console.log(
|
|
176
|
+
`\n[${bench}] selector=verifier-grounded (continuous graded reward) · condition~="${condFilter}" · n=${rep.n}` +
|
|
177
|
+
(rep.skipped > 0 ? ` (${rep.skipped} unscoreable)` : ''),
|
|
178
|
+
)
|
|
179
|
+
console.log(` blind (one-shot reward): ${pct(rep.blindReward)}`)
|
|
180
|
+
console.log(` random@k (mean-of-k reward): ${pct(rep.randomReward)} ← blind compute control`)
|
|
181
|
+
console.log(` selector@k (verifier-pick): ${pct(rep.selectorReward)}`)
|
|
182
|
+
console.log(` oracle@k (max-of-k ceiling): ${pct(rep.oracleReward)}`)
|
|
183
|
+
console.log(` resolve rates — blind ${pct(rep.blindResolveRate)} · selector ${pct(rep.selectorResolveRate)} · oracle ${pct(rep.oracleResolveRate)}`)
|
|
184
|
+
console.log(
|
|
185
|
+
` ► selector − random: ${pp(rep.rewardVsRandom)} CI[${pp(rep.ci.lo)}, ${pp(rep.ci.hi)}] p=${rep.ci.p.toFixed(3)} (${sig}, ${rep.ci.discordant}/${rep.n} discordant)` +
|
|
186
|
+
` ← THE GATE: does a deployable checker beat a blind draw at equal k?`,
|
|
187
|
+
)
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
const rep = summarizeSelector(slice, selfConsistencySelect)
|
|
191
|
+
console.log(
|
|
192
|
+
`\n[${bench}] selector=self-consistency · condition~="${condFilter}" · n=${rep.n}` +
|
|
193
|
+
(rep.skipped > 0 ? ` (${rep.skipped} unscoreable)` : ''),
|
|
194
|
+
)
|
|
195
|
+
console.log(` blind (pass@1): ${pct(rep.blindRate)}`)
|
|
196
|
+
console.log(` random@k (control): ${pct(rep.randomRate)}`)
|
|
197
|
+
console.log(` selector@k (deployable): ${pct(rep.selectorRate)}`)
|
|
198
|
+
console.log(` oracle@k (ceiling): ${pct(rep.oracleRate)}`)
|
|
199
|
+
console.log(` ► selector − random: ${pp(rep.dVsRandom)} ← Phase-1 gate: does picking beat a blind draw at equal k?`)
|
|
200
|
+
console.log(` ► oracle − selector: ${pp(rep.gapToOracle)} (ceiling left on the table)`)
|
|
201
|
+
}
|
|
202
|
+
if (!any) {
|
|
203
|
+
const conds = [...new Set(records.map((r) => r.condition))]
|
|
204
|
+
throw new Error(
|
|
205
|
+
`corpus-replay --selector: no records match condition~="${condFilter}". Available conditions: ${conds.join(', ') || '(none)'}`,
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let totalRecords = 0
|
|
212
|
+
let totalReplayable = 0
|
|
213
|
+
let totalJudged = 0
|
|
214
|
+
let totalAgree = 0
|
|
215
|
+
|
|
216
|
+
for (const [bench, recs] of byBench) {
|
|
217
|
+
const factory = ADAPTERS[bench]
|
|
218
|
+
if (!factory) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`corpus-replay: no adapter for benchmark ${JSON.stringify(bench)} (have: ${Object.keys(ADAPTERS).join(', ')}). ` +
|
|
221
|
+
`Add it to ADAPTERS or pass the correct BENCH override.`,
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
const adapter = factory()
|
|
225
|
+
|
|
226
|
+
// Replayable = has a recorded final output to re-score. Records where every
|
|
227
|
+
// round errored / went blank carry no output and cannot be offline-scored.
|
|
228
|
+
const replayable = recs.filter((r) => finalOutput(r) !== undefined)
|
|
229
|
+
totalRecords += recs.length
|
|
230
|
+
totalReplayable += replayable.length
|
|
231
|
+
|
|
232
|
+
if (!reJudge) {
|
|
233
|
+
// Default: recompute the STRUCTURAL verdict from stored attempt verdicts and
|
|
234
|
+
// compare to record.resolved. Zero model tokens; proves the corpus's stored
|
|
235
|
+
// outcome is internally consistent with its stored per-attempt verdicts.
|
|
236
|
+
let structAgree = 0
|
|
237
|
+
for (const r of replayable) {
|
|
238
|
+
if (storedFinalValid(r) === (r.resolved === true)) structAgree += 1
|
|
239
|
+
}
|
|
240
|
+
const rate = replayable.length > 0 ? structAgree / replayable.length : 0
|
|
241
|
+
console.log(
|
|
242
|
+
`[${bench}] structural replay: replayable n=${replayable.length}/${recs.length} ` +
|
|
243
|
+
`stored-verdict<->resolved agreement ${(rate * 100).toFixed(1)}% (${structAgree}/${replayable.length})`,
|
|
244
|
+
)
|
|
245
|
+
totalJudged += replayable.length
|
|
246
|
+
totalAgree += structAgree
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --judge: preflight, resolve recorded instances -> BenchTask, re-run the judge
|
|
251
|
+
// on each recorded final output, compare the replayed verdict to record.resolved.
|
|
252
|
+
await adapter.preflight()
|
|
253
|
+
const ids = [...new Set(replayable.map((r) => r.instanceId))]
|
|
254
|
+
const taskMap = await loadTaskMap(adapter, ids)
|
|
255
|
+
|
|
256
|
+
let agree = 0
|
|
257
|
+
let judged = 0
|
|
258
|
+
let missing = 0
|
|
259
|
+
for (const r of replayable) {
|
|
260
|
+
const out = finalOutput(r)
|
|
261
|
+
if (out === undefined) continue // unreachable (replayable filter), kept explicit
|
|
262
|
+
const task = taskMap.get(r.instanceId)
|
|
263
|
+
if (!task) {
|
|
264
|
+
missing += 1
|
|
265
|
+
console.warn(`[${bench}] instance ${r.instanceId} not in dataset - corpus/dataset drift; skipping`)
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
const verdict = await adapter.judge(task, out)
|
|
269
|
+
judged += 1
|
|
270
|
+
if ((verdict.resolved === true) === (r.resolved === true)) agree += 1
|
|
271
|
+
}
|
|
272
|
+
const rate = judged > 0 ? agree / judged : 0
|
|
273
|
+
console.log(
|
|
274
|
+
`[${bench}] judge replay: re-judged n=${judged}/${replayable.length} ` +
|
|
275
|
+
`replayed<->recorded agreement ${(rate * 100).toFixed(1)}% (${agree}/${judged})` +
|
|
276
|
+
(missing > 0 ? ` [${missing} instance(s) absent from dataset]` : ''),
|
|
277
|
+
)
|
|
278
|
+
totalJudged += judged
|
|
279
|
+
totalAgree += agree
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const overall = totalJudged > 0 ? totalAgree / totalJudged : 0
|
|
283
|
+
console.log('')
|
|
284
|
+
console.log(
|
|
285
|
+
`replayable n=${totalReplayable}/${totalRecords} ` +
|
|
286
|
+
`${reJudge ? 'replayed<->recorded' : 'stored-verdict<->resolved'} agreement ${(overall * 100).toFixed(1)}% ` +
|
|
287
|
+
`(${totalAgree}/${totalJudged})${reJudge ? '' : ' [--judge to re-run the real judge]'}`,
|
|
288
|
+
)
|
|
289
|
+
console.log(
|
|
290
|
+
'note: this is the offline replay/score substrate the controller-search ' +
|
|
291
|
+
'(GEPA / meta-harness over the corpus) consumes - scoreCandidateOffline() lets it ' +
|
|
292
|
+
'price a candidate steer with one judge call (zero agent rollouts) against a recorded instance.',
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Run the CLI only when invoked directly (tsx src/corpus-replay.mts ...), so the
|
|
297
|
+
// scoreCandidateOffline seam can be imported as a library without firing main().
|
|
298
|
+
if (argv[1] && fileURLToPath(import.meta.url) === argv[1]) {
|
|
299
|
+
await main()
|
|
300
|
+
}
|