@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.
Files changed (111) hide show
  1. package/README.md +15 -0
  2. package/package.json +35 -0
  3. package/src/adapters.ts +60 -0
  4. package/src/aec-gate.mts +217 -0
  5. package/src/atom-humaneval.mts +197 -0
  6. package/src/atom-mcp-e2e.mts +223 -0
  7. package/src/benchmarks/_harness.ts +206 -0
  8. package/src/benchmarks/aec-bench.test.mts +53 -0
  9. package/src/benchmarks/aec-bench.ts +319 -0
  10. package/src/benchmarks/appworld.test.mts +45 -0
  11. package/src/benchmarks/appworld.ts +426 -0
  12. package/src/benchmarks/cad-design.ts +429 -0
  13. package/src/benchmarks/cadbench.ts +135 -0
  14. package/src/benchmarks/cadgenbench.ts +121 -0
  15. package/src/benchmarks/commit0.test.mts +71 -0
  16. package/src/benchmarks/commit0.ts +266 -0
  17. package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
  18. package/src/benchmarks/enterpriseops-gym.ts +343 -0
  19. package/src/benchmarks/finsearchcomp.ts +371 -0
  20. package/src/benchmarks/frames.ts +520 -0
  21. package/src/benchmarks/hotpotqa.ts +320 -0
  22. package/src/benchmarks/humaneval.ts +251 -0
  23. package/src/benchmarks/mind2web.ts +311 -0
  24. package/src/benchmarks/programbench.test.mts +60 -0
  25. package/src/benchmarks/programbench.ts +210 -0
  26. package/src/benchmarks/simpleqa.ts +390 -0
  27. package/src/benchmarks/swe-bench.ts +152 -0
  28. package/src/benchmarks/terminal-bench.ts +182 -0
  29. package/src/benchmarks/trata-hedge.ts +496 -0
  30. package/src/benchmarks/types.ts +58 -0
  31. package/src/browser/adapters/bad-design-audit.ts +153 -0
  32. package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
  33. package/src/browser/adapters/bad.ts +165 -0
  34. package/src/browser/agent-adapter.ts +145 -0
  35. package/src/browser/process-adapter.ts +146 -0
  36. package/src/browser/run-to-spans.ts +105 -0
  37. package/src/browser/run-to-spans.verify.ts +89 -0
  38. package/src/browser/ui-reviewer.ts +200 -0
  39. package/src/browser/ui-reviewer.verify.ts +94 -0
  40. package/src/browser/verify.ts +96 -0
  41. package/src/clbench-codebase-gate.mts +314 -0
  42. package/src/clbench-context-gate.mts +305 -0
  43. package/src/cloud-loop.mts +138 -0
  44. package/src/coding-skills/minimal-diff.md +9 -0
  45. package/src/coding-skills/read-before-edit.md +9 -0
  46. package/src/coding-skills/reproduce-first.md +10 -0
  47. package/src/coding-skills/run-tests-after-edit.md +9 -0
  48. package/src/coding-skills/trace-the-failure.md +9 -0
  49. package/src/commit0-env-run.mts +59 -0
  50. package/src/commit0-env.ts +173 -0
  51. package/src/commit0-gate.mts +529 -0
  52. package/src/commit0-prereqs.sh +48 -0
  53. package/src/corpus-replay.mts +300 -0
  54. package/src/corpus-report.mts +504 -0
  55. package/src/corpus.test.mts +273 -0
  56. package/src/corpus.ts +325 -0
  57. package/src/decoder-live.mts +133 -0
  58. package/src/directives.ts +84 -0
  59. package/src/diverse-gate.mjs +112 -0
  60. package/src/egress-probe.mts +26 -0
  61. package/src/eops-skills/address-every-subtask.md +5 -0
  62. package/src/eops-skills/exact-tools-and-args.md +5 -0
  63. package/src/eops-skills/full-sequence-to-goal.md +5 -0
  64. package/src/eops-skills/ground-every-value.md +5 -0
  65. package/src/eops-skills/honor-the-policies.md +5 -0
  66. package/src/examples/README.md +58 -0
  67. package/src/examples/math-demo.mts +110 -0
  68. package/src/examples/strategy-demo.mts +119 -0
  69. package/src/fleet.mts +121 -0
  70. package/src/gate-cli.mts +101 -0
  71. package/src/gate.test.mts +129 -0
  72. package/src/gate.ts +460 -0
  73. package/src/generate-eval/certify.ts +178 -0
  74. package/src/generate-eval/schema.ts +78 -0
  75. package/src/humaneval-gate.mts +204 -0
  76. package/src/humaneval-repair-gate.mts +143 -0
  77. package/src/index.ts +19 -0
  78. package/src/mcp-mount-probe.mts +126 -0
  79. package/src/profile-coordinates.ts +134 -0
  80. package/src/profiles.ts +128 -0
  81. package/src/refine-loop.test.mts +106 -0
  82. package/src/refine-loop.ts +106 -0
  83. package/src/research-gate.mts +132 -0
  84. package/src/research-shot.ts +134 -0
  85. package/src/resolve-client.ts +58 -0
  86. package/src/router-executor.ts +51 -0
  87. package/src/run-pool.ts +48 -0
  88. package/src/runtime-hook-recorder.ts +137 -0
  89. package/src/sandbox-run.ts +125 -0
  90. package/src/search-bench/bridge.ts +124 -0
  91. package/src/search-bench/export.mts +0 -0
  92. package/src/search-bench/parametric-check.mts +63 -0
  93. package/src/search-bench/profiles.ts +98 -0
  94. package/src/search-bench/run.mts +287 -0
  95. package/src/search-bench/tasks-fresh.ts +688 -0
  96. package/src/search-bench/tasks.ts +129 -0
  97. package/src/search-tool.ts +95 -0
  98. package/src/selector.test.mts +189 -0
  99. package/src/selector.ts +366 -0
  100. package/src/skill-sandbox-smoke.mts +100 -0
  101. package/src/stats.mts +90 -0
  102. package/src/terminal-compare.ts +519 -0
  103. package/src/trajectory-assemble.mjs +130 -0
  104. package/src/trata-gate.mts +243 -0
  105. package/src/trata-gepa.mts +434 -0
  106. package/src/worker-blender.ts +230 -0
  107. package/src/worker-browser.ts +102 -0
  108. package/src/worker-build123d.ts +143 -0
  109. package/src/worker-cad.ts +451 -0
  110. package/src/worker.ts +136 -0
  111. package/src/workspace-loop.mts +133 -0
@@ -0,0 +1,305 @@
1
+ /**
2
+ * CL-bench (Context Learning) deployable-selector gate — Tencent/Fudan's CL-bench
3
+ * (arXiv:2602.03587) repurposed for our verifier-grounded selector question.
4
+ *
5
+ * CL-bench asks whether a model can learn NEW knowledge from in-context material
6
+ * (a rule book, a framework doc, a conversation) at inference time: each record is
7
+ * a `messages` conversation (system + context-laden turns, the final turn the task)
8
+ * graded against expert `rubrics`. The official metric is BINARY — a task is solved
9
+ * only if the response passes EVERY rubric (avg 63 rubrics/task). We keep that binary
10
+ * as `resolved`, but the per-rubric pass-count gives a CONTINUOUS score (fraction of
11
+ * rubrics satisfied) — the within-task graded variance a selector needs, which the
12
+ * deterministic-but-pass/fail benches (aec) lacked.
13
+ *
14
+ * The CHECKER is the benchmark's own rubric judge (an LLM, per CL-bench's eval.py),
15
+ * run by us at inference time — deployable, but NOT deterministic, so treat the judge
16
+ * as a noisy verifier: we rank by the rubric FRACTION (variance-reduced over many
17
+ * rubrics), not the binary, and the judge model + temperature are pinned for
18
+ * test-retest stability. This is the LLM-judge analogue of the HumanEval Docker gate;
19
+ * read a positive result as "a deployable rubric-fraction verifier captures selection
20
+ * value on a hard context-learning domain", scoped by judge noise.
21
+ *
22
+ * Router-only (no sandbox): worker + judge are both router chat calls. Two paired arms
23
+ * over the same tasks, each "shot" = one stateless completion of the final turn:
24
+ * random@K — K completions over the unmodified conversation
25
+ * diverse@K — K completions, the i-th with a strategy lens prepended to the system turn
26
+ * verifierGroundedSelect ranks the K shots by rubric fraction. Metrics are reported on
27
+ * BOTH the continuous fraction (the gate-relevant signal) and the official binary.
28
+ * Writes a corpus RunRecord/task (condition random@k) so `corpus-replay --selector` and
29
+ * `corpus-report` consume it unchanged. Fail loud.
30
+ *
31
+ * dotenvx run -f … -- env N=20 K=4 WORKER_MODEL=deepseek-chat JUDGE_MODEL=deepseek-chat \
32
+ * CORPUS=/tmp/clbench-ctx.jsonl tsx src/clbench-context-gate.mts
33
+ */
34
+
35
+ import { execFileSync } from 'node:child_process'
36
+ import { existsSync, readFileSync } from 'node:fs'
37
+ import { composeStrategies } from './directives'
38
+ import { type AttemptRecord, appendRunRecord, buildRunRecordFromAttempts } from './corpus'
39
+ import { type RouterConfig, routerChatWithUsage } from '@tangle-network/agent-runtime/loops'
40
+ import { selfConsistencySelect, verifierGroundedSelect } from './selector'
41
+ import { type PairedLift, pairedLift, pool } from './stats.mts'
42
+
43
+ const datasetUrl = 'https://huggingface.co/datasets/tencent/CL-bench/resolve/main/CL-bench.jsonl'
44
+
45
+ function must(name: string): string {
46
+ const v = process.env[name]
47
+ if (!v) throw new Error(`env ${name} is required`)
48
+ return v
49
+ }
50
+
51
+ interface ChatMessage {
52
+ role: string
53
+ content: string
54
+ }
55
+
56
+ interface CtxTask {
57
+ id: string
58
+ messages: ChatMessage[]
59
+ rubrics: string[]
60
+ category: string
61
+ }
62
+
63
+ /** Fetch the first `count` lines of the (large, ~300MB) CL-bench JSONL via a piped
64
+ * `curl | head` so a smoke pulls only a few records, then slice [offset, offset+limit].
65
+ * A local cached file (CLBENCH_CTX_FILE) short-circuits the fetch for powered runs.
66
+ * Fail loud on a malformed record — a silently-short task set would poison the gate. */
67
+ function loadCtxTasks(limit: number, offset: number): CtxTask[] {
68
+ const need = offset + limit
69
+ // Fetch a 2-line buffer past `need`: on CL-bench's huge multi-KB records, `head`
70
+ // closing the pipe can emit a TRUNCATED final line (SIGPIPE mid-write) → invalid
71
+ // JSON. Fetching need+2 and parsing only the first `need` complete lines makes the
72
+ // truncated tail land in the discarded buffer.
73
+ const fetchN = need + 2
74
+ let raw: string
75
+ const cached = process.env.CLBENCH_CTX_FILE
76
+ if (cached) {
77
+ if (!existsSync(cached)) throw new Error(`CLBENCH_CTX_FILE not found: ${cached}`)
78
+ raw = execFileSync('bash', ['-c', `head -n ${fetchN} ${JSON.stringify(cached)}`], { maxBuffer: 1 << 30 }).toString('utf8')
79
+ } else {
80
+ // -fsSL: fail on HTTP error, follow redirects (HF resolve 302s to the CDN). curl's
81
+ // SIGPIPE (exit 23) when head closes is benign — suppress its stderr; a real fetch
82
+ // failure surfaces as 0 parsed tasks below.
83
+ raw = execFileSync('bash', ['-c', `curl -fsSL ${JSON.stringify(datasetUrl)} 2>/dev/null | head -n ${fetchN}`], {
84
+ maxBuffer: 1 << 30,
85
+ }).toString('utf8')
86
+ }
87
+ const tasks: CtxTask[] = []
88
+ // Only the first `need` lines are guaranteed complete (the +2 absorbs head's tail).
89
+ const lines = raw.split('\n').filter((l) => l.trim() !== '').slice(0, need)
90
+ for (const line of lines) {
91
+ const d = JSON.parse(line) as {
92
+ messages?: ChatMessage[]
93
+ rubrics?: unknown[]
94
+ metadata?: { task_id?: string; context_category?: string }
95
+ }
96
+ const messages = d.messages
97
+ const taskId = d.metadata?.task_id
98
+ if (!Array.isArray(messages) || messages.length === 0 || !taskId) {
99
+ throw new Error(`malformed CL-bench record: ${line.slice(0, 120)}`)
100
+ }
101
+ // Rubrics are strings or {rubric_criteria} objects (mirrors eval.py's build_rubrics_text).
102
+ const rubrics = (d.rubrics ?? []).map((r) =>
103
+ typeof r === 'string' ? r : String((r as { rubric_criteria?: string }).rubric_criteria ?? '').trim(),
104
+ ).filter((r) => r.length > 0)
105
+ if (rubrics.length === 0) throw new Error(`CL-bench record ${taskId} has no rubrics`)
106
+ tasks.push({ id: taskId, messages, rubrics, category: d.metadata?.context_category ?? 'Unknown' })
107
+ }
108
+ if (tasks.length === 0) throw new Error('CL-bench parsed to 0 tasks')
109
+ if (offset >= tasks.length) throw new Error(`OFFSET ${offset} >= fetched size ${tasks.length}`)
110
+ return tasks.slice(offset, offset + limit)
111
+ }
112
+
113
+ /** Apply a diversity strategy lens to a conversation by prepending it to the system
114
+ * turn (or, if none, inserting a system turn). The non-system context turns — which
115
+ * carry the in-context knowledge the task is about — are never mutated. */
116
+ function diversifyMessages(messages: ChatMessage[], lensSystem: string, baseSystem: string): ChatMessage[] {
117
+ if (messages[0]?.role === 'system') {
118
+ return [{ role: 'system', content: lensSystem }, ...messages.slice(1)]
119
+ }
120
+ return [{ role: 'system', content: composeStrategies(baseSystem, 1)[0] as string }, ...messages]
121
+ }
122
+
123
+ const judgePrompt = (rubricsText: string, modelOutput: string): string =>
124
+ 'You are a rigorous, strict grading teacher. Grade the student response against the 【Rubrics】, ' +
125
+ 'checking EACH requirement independently.\n\n' +
126
+ `【Rubrics】:\n${rubricsText}\n` +
127
+ `【Student Response】:\n${modelOutput}\n\n` +
128
+ 'Output ONLY this JSON (no other text):\n' +
129
+ '{\n "status": ["yes" or "no", ... one per rubric, in order],\n "all_pass": 0 or 1\n}\n'
130
+
131
+ interface RubricVerdict {
132
+ /** fraction of rubrics satisfied (0..1) — the continuous within-task signal. */
133
+ fraction: number
134
+ /** official binary: every rubric satisfied. */
135
+ allPass: boolean
136
+ /** rubric count actually graded (for diagnostics). */
137
+ graded: number
138
+ }
139
+
140
+ function parseJudge(reply: string, rubricCount: number): RubricVerdict {
141
+ let text = reply.trim()
142
+ if (text.startsWith('```json')) text = text.slice(7)
143
+ if (text.startsWith('```')) text = text.slice(3)
144
+ if (text.endsWith('```')) text = text.slice(0, -3)
145
+ const obj = JSON.parse(text.trim()) as { status?: unknown[]; all_pass?: unknown }
146
+ const status = Array.isArray(obj.status) ? obj.status : []
147
+ const yes = status.filter((s) => String(s).trim().toLowerCase() === 'yes').length
148
+ const graded = status.length > 0 ? status.length : rubricCount
149
+ const fraction = graded > 0 ? yes / graded : 0
150
+ // Trust an explicit all_pass when given; else derive from the per-rubric list.
151
+ const allPass = obj.all_pass === 1 || obj.all_pass === '1' || (status.length === rubricCount && yes === rubricCount && rubricCount > 0)
152
+ return { fraction, allPass, graded }
153
+ }
154
+
155
+ /** Grade one completion with the rubric judge. A judge API/parse failure is a real
156
+ * zero (the response could not be validated) — surfaced, never masked. */
157
+ async function judgeRubrics(cfg: RouterConfig, task: CtxTask, output: string): Promise<RubricVerdict> {
158
+ if (!output.trim()) return { fraction: 0, allPass: false, graded: 0 }
159
+ const rubricsText = task.rubrics.map((r, i) => `${i + 1}. ${r}`).join('\n')
160
+ // Fault-isolate the judge: a transient router failure (after retries) or an
161
+ // unparseable judge reply scores this attempt 0 (eval.py's convention), it must
162
+ // NOT throw — one bad grade would otherwise crash the whole N×K×2 run. graded=0
163
+ // marks it as judge-failed so it's distinguishable from a real 0/N rubric pass.
164
+ try {
165
+ const res = await routerChatWithUsage(cfg, [{ role: 'user', content: judgePrompt(rubricsText, output) }], { temperature: 0 })
166
+ return parseJudge(typeof res.content === 'string' ? res.content : '', task.rubrics.length)
167
+ } catch {
168
+ return { fraction: 0, allPass: false, graded: 0 }
169
+ }
170
+ }
171
+
172
+ const pct = (x: number) => `${(x * 100).toFixed(1)}%`
173
+ const pp = (x: number) => `${x >= 0 ? '+' : ''}${(x * 100).toFixed(1)}pp`
174
+
175
+ interface Shot {
176
+ output: string
177
+ verdict: RubricVerdict
178
+ }
179
+
180
+ async function main(): Promise<void> {
181
+ const n = Number(process.env.N ?? 20)
182
+ const k = Number(process.env.K ?? 4)
183
+ const offset = Number(process.env.OFFSET ?? 0)
184
+ const model = process.env.WORKER_MODEL ?? 'deepseek-chat'
185
+ const judgeModel = process.env.JUDGE_MODEL ?? model
186
+ const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
187
+ const routerKey = must('TANGLE_API_KEY')
188
+ const solveConcurrency = Number(process.env.CONCURRENCY ?? 6)
189
+ const corpusPath = process.env.CORPUS ?? '/tmp/clbench-ctx.jsonl'
190
+ if (!Number.isInteger(n) || n < 1) throw new Error(`N must be a positive integer, got ${process.env.N}`)
191
+ if (!Number.isInteger(k) || k < 1) throw new Error(`K must be a positive integer, got ${process.env.K}`)
192
+
193
+ const workerCfg: RouterConfig = { routerBaseUrl, routerKey, model }
194
+ const judgeCfg: RouterConfig = { routerBaseUrl, routerKey, model: judgeModel }
195
+
196
+ console.log(`=== CL-bench (Context Learning) selector gate · N=${n} K=${k} offset=${offset} ===`)
197
+ console.log(` worker=${model} judge=${judgeModel} (rubric-fraction verifier) router=${routerBaseUrl}`)
198
+ console.log(' regime: STATELESS single completions — selector no-self-correction LOWER BOUND, judge is an LLM (noisy verifier)')
199
+
200
+ const tasks = loadCtxTasks(n, offset)
201
+ console.log(`loaded ${tasks.length} task(s); rubrics/task: ${tasks.map((t) => t.rubrics.length).join(',')}`)
202
+
203
+ type Unit = { taskIdx: number; arm: 'random' | 'diverse'; shot: number; messages: ChatMessage[] }
204
+ const units: Unit[] = []
205
+ for (let ti = 0; ti < tasks.length; ti += 1) {
206
+ const task = tasks[ti] as CtxTask
207
+ const baseSystem = task.messages[0]?.role === 'system' ? (task.messages[0] as ChatMessage).content : 'You are a helpful assistant.'
208
+ const lenses = composeStrategies(baseSystem, k)
209
+ for (let s = 0; s < k; s += 1) {
210
+ units.push({ taskIdx: ti, arm: 'random', shot: s, messages: task.messages })
211
+ units.push({ taskIdx: ti, arm: 'diverse', shot: s, messages: diversifyMessages(task.messages, lenses[s] as string, baseSystem) })
212
+ }
213
+ }
214
+ console.log(`\n▶ solving ${units.length} attempts (${tasks.length} tasks × ${k} shots × 2 arms) via router, conc=${solveConcurrency}`)
215
+ const outputs = await pool(units, solveConcurrency, async (u) => {
216
+ const res = await routerChatWithUsage(workerCfg, u.messages, { temperature: Number(process.env.TEMPERATURE ?? '0.8') })
217
+ return typeof res.content === 'string' ? res.content : ''
218
+ })
219
+
220
+ console.log(`▶ grading ${outputs.length} completions with the rubric judge (${judgeModel}), conc=${solveConcurrency}`)
221
+ const verdicts = await pool(units, solveConcurrency, (u, i) => judgeRubrics(judgeCfg, tasks[u.taskIdx] as CtxTask, outputs[i] as string))
222
+
223
+ // Regroup into per-task arms, shot order preserved.
224
+ const byTask = tasks.map(() => ({ random: [] as Shot[], diverse: [] as Shot[] }))
225
+ units.forEach((u, i) => {
226
+ const shot: Shot = { output: outputs[i] as string, verdict: verdicts[i] as RubricVerdict }
227
+ const grp = byTask[u.taskIdx] as { random: Shot[]; diverse: Shot[] }
228
+ if (u.arm === 'random') grp.random[u.shot] = shot
229
+ else grp.diverse[u.shot] = shot
230
+ })
231
+
232
+ // Per-task aligned metrics, on BOTH the continuous fraction and the official binary.
233
+ const fr = { blind: [] as number[], random: [] as number[], diverse: [] as number[], oracle: [] as number[], sc: [] as number[] }
234
+ const bin = { blind: [] as number[], random: [] as number[], diverse: [] as number[], oracle: [] as number[] }
235
+ for (const grp of byTask) {
236
+ const rFr = grp.random.map((s) => s.verdict.fraction)
237
+ const dFr = grp.diverse.map((s) => s.verdict.fraction)
238
+ const rIdx = verifierGroundedSelect(rFr) // rank random shots by rubric fraction
239
+ const dIdx = verifierGroundedSelect(dFr)
240
+ const scIdx = selfConsistencySelect(grp.diverse.map((s) => s.output))
241
+ fr.blind.push((grp.random[0] as Shot).verdict.fraction)
242
+ fr.random.push((grp.random[rIdx] as Shot).verdict.fraction)
243
+ fr.diverse.push((grp.diverse[dIdx] as Shot).verdict.fraction)
244
+ fr.oracle.push(Math.max(...dFr))
245
+ fr.sc.push((grp.diverse[scIdx] as Shot).verdict.fraction)
246
+ bin.blind.push((grp.random[0] as Shot).verdict.allPass ? 1 : 0)
247
+ bin.random.push((grp.random[rIdx] as Shot).verdict.allPass ? 1 : 0)
248
+ bin.diverse.push((grp.diverse[dIdx] as Shot).verdict.allPass ? 1 : 0)
249
+ bin.oracle.push(dFr.some((_, j) => (grp.diverse[j] as Shot).verdict.allPass) ? 1 : 0)
250
+ }
251
+ const rate = (xs: number[]) => xs.reduce((s, x) => s + x, 0) / xs.length
252
+
253
+ console.log(`\n${'='.repeat(78)}`)
254
+ console.log(`RESULTS · CL-bench Context Learning · n=${tasks.length} · k=${k} · worker=${model} · judge=${judgeModel}`)
255
+ console.log('='.repeat(78))
256
+ console.log(' — rubric FRACTION (continuous within-task signal, the gate-relevant metric) —')
257
+ console.log(` blind (shot 0) ${pct(rate(fr.blind))}`)
258
+ console.log(` random@k (verifier-pick) ${pct(rate(fr.random))}`)
259
+ console.log(` diverse@k (verifier-pick) ${pct(rate(fr.diverse))}`)
260
+ console.log(` oracle@k (max fraction) ${pct(rate(fr.oracle))}`)
261
+ console.log(` self-consistency@k ${pct(rate(fr.sc))}`)
262
+ console.log(' — official BINARY all-rubrics-pass (solving rate) —')
263
+ console.log(` blind ${pct(rate(bin.blind))} random@k ${pct(rate(bin.random))} diverse@k ${pct(rate(bin.diverse))} oracle@k ${pct(rate(bin.oracle))}`)
264
+
265
+ const row = (label: string, l: PairedLift) =>
266
+ console.log(` ${label.padEnd(36)} ${pp(l.point).padStart(7)} CI [${pp(l.low)}, ${pp(l.high)}] (paired ${l.pairs}, discordant ${l.discordant})`)
267
+ console.log(`\n PAIRED LIFTS on rubric fraction (95% bootstrap CI, B=10000):`)
268
+ row('random@k − blind (compute)', pairedLift(fr.blind, fr.random))
269
+ row('diverse@k − random@k (verifier)', pairedLift(fr.random, fr.diverse))
270
+ row('diverse@k − blind (total)', pairedLift(fr.blind, fr.diverse))
271
+ row('verifier-pick − sc-pick (diverse)', pairedLift(fr.sc, fr.diverse))
272
+ const ceiling = pairedLift(fr.random, fr.oracle)
273
+ row('oracle@k − random@k (ceiling)', ceiling)
274
+
275
+ // Corpus: one RunRecord/task for the random@k arm, ranked by the rubric-fraction
276
+ // verifier — `corpus-replay --selector=verifier` + `corpus-report` consume it.
277
+ for (let ti = 0; ti < tasks.length; ti += 1) {
278
+ const task = tasks[ti] as CtxTask
279
+ const grp = byTask[ti] as { random: Shot[]; diverse: Shot[] }
280
+ const attempts: AttemptRecord[] = grp.random.map((s, round) => ({
281
+ round,
282
+ prompt: 'clbench-context',
283
+ output: s.output.slice(0, 4000),
284
+ valid: s.verdict.allPass,
285
+ score: s.verdict.fraction,
286
+ eventCount: 1,
287
+ eventTypes: { 'router.chat': 1 },
288
+ traceTail: s.output.slice(-600),
289
+ }))
290
+ const record = buildRunRecordFromAttempts(attempts, {
291
+ benchmark: 'clbench-context',
292
+ instanceId: task.id,
293
+ condition: `random@${k}`,
294
+ model,
295
+ infraError: false,
296
+ })
297
+ await appendRunRecord(corpusPath, record)
298
+ }
299
+ console.log(`\n=== wrote ${tasks.length} task(s) → ${corpusPath} · gate: tsx src/corpus-replay.mts ${corpusPath} --selector=verifier ===`)
300
+ }
301
+
302
+ main().catch((err) => {
303
+ console.error(`clbench-context-gate: ${err instanceof Error ? err.message : String(err)}`)
304
+ process.exit(1)
305
+ })
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The LIVE observe→steer loop — the hard join, on real endpoints (no mocks).
3
+ *
4
+ * The facade-postmortem's standing rule (docs/research/loop-facade-postmortem.md):
5
+ * prove the smallest real loop on LIVE paths, not mocks. This closes the join
6
+ * with BOTH ends real — a real cloud worker and a real router-backed observer:
7
+ *
8
+ * round → REAL cloud worker (openSandboxRun, opencode in a box) over the task +
9
+ * accumulated steers → its real event trace
10
+ * → observe() with a REAL router LLM reads that trace → an AnalystFinding
11
+ * → the finding's recommended_action is injected as a STEER into the next
12
+ * round's worker prompt
13
+ * → stop when the deterministic verifier passes, or budget.
14
+ *
15
+ * It reports, per round: the tools the worker actually used, the observer's
16
+ * finding (LLM-derived from the real trace), and whether the steer changed
17
+ * behavior — honestly, never as a claimed win.
18
+ *
19
+ * STATUS (2026-06-08): the join ran live end-to-end for 3 rounds earlier (real
20
+ * worker → real trace → real router-LLM finding → real steer injection). Re-runs
21
+ * are currently BLOCKED at provisioning: the sandbox egress proxy returns
22
+ * CONNECT 403 for router.tangle.tools (only that host; id/pangolin/sandbox +
23
+ * api.openai.com all pass), so the in-box agent cannot reach the model router and
24
+ * produces zero output/zero tools. This is a platform egress regression — it
25
+ * worked 2026-06-06 — tracked as ops-board #984, NOT a flaw in this loop. The
26
+ * efficacy measurement (does the steer improve behavior at equal budget) is gated
27
+ * on that unblock; until then this proves the live JOIN, not efficacy.
28
+ *
29
+ * dotenvx run -f …/.env.keys -f …/agent-state.env -- \
30
+ * env MODEL=gpt-4.1 ROUNDS=3 pnpm exec tsx src/cloud-loop.mts
31
+ */
32
+ import { createChatClient } from '@tangle-network/agent-eval'
33
+ import { observe, openSandboxRun } from '@tangle-network/agent-runtime/loops'
34
+ import { Sandbox } from '@tangle-network/sandbox'
35
+ import { answerOutput, sandboxAgentRun } from './sandbox-run'
36
+
37
+ function env(name: string, fallback?: string): string {
38
+ const v = process.env[name] ?? fallback
39
+ if (v === undefined) throw new Error(`missing env ${name}`)
40
+ return v
41
+ }
42
+
43
+ /** A task with a flaw the observer can catch from the trace: models tend to
44
+ * CLAIM the tests pass without running them. The verifier requires the exact
45
+ * proof-of-execution token, so a real run is the only way to pass. */
46
+ const task = [
47
+ 'Write a Python function `is_prime(n)` in a file `prime.py`, then write a test',
48
+ 'that exercises it on 2, 11, 15, and 97. ACTUALLY RUN the test with python3 and',
49
+ 'paste the real interpreter output. End your reply with the line ALLPASS only if',
50
+ 'every assertion really passed when you ran it. Do not claim ALLPASS without',
51
+ 'having executed the test and seen it pass.',
52
+ ].join(' ')
53
+
54
+ /** Deterministic verifier: the worker must show real execution + the proof token. */
55
+ function verify(output: string): boolean {
56
+ return /ALLPASS/.test(output) && /(passed|ok|\b4 (tests|asserts)|assert)/i.test(output)
57
+ }
58
+
59
+ /** Distinct tool names from an opencode trace (the proof the worker acted). */
60
+ function tools(events: ReadonlyArray<unknown>): string[] {
61
+ const names = new Set<string>()
62
+ for (const ev of events) {
63
+ const part = (ev as { data?: { part?: { type?: string; tool?: string } } }).data?.part
64
+ if (part?.type === 'tool' && part.tool) names.add(part.tool)
65
+ }
66
+ return [...names]
67
+ }
68
+
69
+ async function main(): Promise<void> {
70
+ const routerKey = env('TANGLE_API_KEY')
71
+ const model = env('MODEL', 'gpt-4.1')
72
+ const routerBaseUrl = env('ROUTER_BASE_URL', 'https://router.tangle.tools/v1')
73
+ const rounds = Number(env('ROUNDS', '3'))
74
+ const client = new Sandbox({ baseUrl: env('SANDBOX_BASE_URL', 'https://sandbox.tangle.tools'), apiKey: routerKey })
75
+ const chat = createChatClient({ transport: 'router', apiKey: routerKey, baseUrl: routerBaseUrl, defaultModel: model })
76
+
77
+ console.error(`\n=== LIVE observe→steer loop · ${model} · real cloud worker + real observer ===\n`)
78
+ const steers: string[] = []
79
+ let solved = false
80
+
81
+ for (let round = 1; round <= rounds && !solved; round++) {
82
+ const prompt = steers.length
83
+ ? `${task}\n\n=== CORRECTIONS FROM YOUR PRIOR ATTEMPT (apply them) ===\n${steers.map((s) => `- ${s}`).join('\n')}`
84
+ : task
85
+ console.error(`── round ${round}${steers.length ? ` (carrying ${steers.length} steer)` : ''}`)
86
+
87
+ const controller = new AbortController()
88
+ const timer = setTimeout(() => controller.abort(), 240_000)
89
+ let output = ''
90
+ let events: unknown[] = []
91
+ try {
92
+ const agentRun = sandboxAgentRun({ model, routerBaseUrl, backendType: 'opencode', name: `worker-r${round}` })
93
+ const run = await openSandboxRun<string>(
94
+ client,
95
+ { agentRun, signal: controller.signal },
96
+ { kind: 'events', fromEvents: (e) => answerOutput.parse(e as never) },
97
+ )
98
+ try {
99
+ const turn = await run.start(prompt)
100
+ output = (turn.out ?? '').trim()
101
+ events = turn.events
102
+ } finally {
103
+ await run.close().catch(() => {})
104
+ }
105
+ } catch (err) {
106
+ console.error(` worker errored: ${err instanceof Error ? err.message : String(err)}`)
107
+ continue
108
+ } finally {
109
+ clearTimeout(timer)
110
+ }
111
+
112
+ solved = verify(output)
113
+ console.error(` tools used: [${tools(events).join(', ') || 'none'}] verifier: ${solved ? 'PASS ✓' : 'fail'}`)
114
+ if (solved) break
115
+
116
+ // THE JOIN: a REAL observer reads the REAL trace → a finding → next round's steer.
117
+ const ob = await observe(
118
+ { task, output, trace: events, outcome: 'failed', runId: `r${round}` },
119
+ { chat, model },
120
+ )
121
+ const next = ob.findings.flatMap((f) => (f.recommended_action ? [f.recommended_action] : [])).slice(0, 3)
122
+ if (next.length === 0) {
123
+ console.error(' observer found nothing actionable — stopping.')
124
+ break
125
+ }
126
+ for (const s of next) console.error(` observer → steer: ${s}`)
127
+ steers.length = 0
128
+ steers.push(...next)
129
+ }
130
+
131
+ console.error(`\n=== ${solved ? '✅ SOLVED' : '✗ unsolved'} after ${steers.length ? 'steered ' : ''}rounds · observe→steer ran on LIVE endpoints ===`)
132
+ process.exit(solved ? 0 : 1)
133
+ }
134
+
135
+ main().catch((e) => {
136
+ console.error(e)
137
+ process.exit(1)
138
+ })
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: minimal-diff
3
+ description: Make the smallest change that satisfies the task; do not touch unrelated code.
4
+ ---
5
+ Keep the diff minimal:
6
+ 1. Change only what the task requires; leave unrelated code, formatting, and files alone.
7
+ 2. Do not refactor, rename, or "clean up" beyond the ask — each extra change is a chance to break a check.
8
+ 3. Prefer the local, surgical fix over a broad rewrite.
9
+ The grader is watching the whole repo state; unrequested changes are pure downside.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: read-before-edit
3
+ description: Read the target file and the code that calls it before editing, so a change doesn't break callers.
4
+ ---
5
+ Before editing a function or module:
6
+ 1. Read the full file you're about to change, not just the lines near the edit.
7
+ 2. Find and read its callers (grep for the symbol) to learn the contract you must preserve.
8
+ 3. Match the surrounding style and signatures; keep the change consistent with how the code already works.
9
+ Editing blind to callers is how a local fix becomes a regression elsewhere.
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: reproduce-first
3
+ description: Before changing any code to fix a bug or failing test, run the failing test/command first to observe the real error.
4
+ ---
5
+ When the task is to fix a bug or make a failing test pass:
6
+ 1. Run the exact failing test or command FIRST and read the actual error/traceback.
7
+ 2. Do not guess the cause from the description — confirm it from the real output.
8
+ 3. Only then make the smallest change that addresses the observed failure.
9
+ 4. Re-run the same test to confirm it now passes before moving on.
10
+ An assumed cause is the most common reason a fix doesn't work.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: run-tests-after-edit
3
+ description: After each code change, run the relevant tests and read the result before declaring the step done.
4
+ ---
5
+ After every change:
6
+ 1. Run the narrowest test that covers what you changed (then the broader suite if time allows).
7
+ 2. Read the output — a passing exit code is the only proof, not your expectation.
8
+ 3. If it fails, treat the new error as the next problem to reproduce and fix; don't pile on more edits blind.
9
+ Never report a step finished on a change you have not actually run.
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: trace-the-failure
3
+ description: When a test or program fails, read the traceback from the top error to the deepest frame in your own code, and fix at the root.
4
+ ---
5
+ On a failure with a stack trace:
6
+ 1. Read the actual exception type and message first.
7
+ 2. Walk the frames to the DEEPEST one inside the code under test — that's usually where the root cause is, not the top frame.
8
+ 3. Inspect the values at that frame (add a print/log if needed) before editing.
9
+ 4. Fix the root cause, not the symptom that surfaced higher up.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The HARD test: sample-vs-refine on Commit0 (implement entire stubbed Python libraries
3
+ * against their own test suites), through the PUBLISHED suite — runBenchmark over a
4
+ * Commit0 Environment (local Docker workspace, file tools, pytest as the check).
5
+ *
6
+ * COMMIT0_FIXTURES=1 N=3 BUDGET=3 INNER_TURNS=10 WORKER_MODEL=deepseek-v4-pro \
7
+ * tsx src/commit0-env-run.mts
8
+ *
9
+ * IDS=commit-0/wcwidth to pick repos; STRATEGIES=sample,refine,adaptiveRefine to choose arms.
10
+ */
11
+ import { readFileSync } from 'node:fs'
12
+ import { join } from 'node:path'
13
+ import { adaptiveRefine, printBenchmarkReport, refine, runBenchmark, sample, type Strategy } from '@tangle-network/agent-runtime/loops'
14
+ import { type Commit0Row, createCommit0Environment, rowToTask } from './commit0-env'
15
+
16
+ function must(name: string): string {
17
+ const v = process.env[name]
18
+ if (!v) throw new Error(`env ${name} is required`)
19
+ return v
20
+ }
21
+
22
+ async function main(): Promise<void> {
23
+ const n = Number(process.env.N ?? 2)
24
+ const budget = Number(process.env.BUDGET ?? 3)
25
+ const model = process.env.WORKER_MODEL ?? 'deepseek-v4-pro'
26
+ const rows = (JSON.parse(readFileSync(join(import.meta.dirname, '..', 'fixtures', 'commit0.json'), 'utf8')) as Commit0Row[]).filter(
27
+ (r) => !process.env.IDS || process.env.IDS.split(',').includes(r.instance_id),
28
+ )
29
+ const picked = rows.slice(0, n)
30
+ const byName: Record<string, Strategy> = { sample, refine, adaptiveRefine }
31
+ const strategies = (process.env.STRATEGIES ?? 'sample,refine').split(',').map((s) => {
32
+ const st = byName[s.trim()]
33
+ if (!st) throw new Error(`unknown strategy ${s}`)
34
+ return st
35
+ })
36
+
37
+ console.error(`=== commit0 (HARD) · ${picked.map((r) => r.instance_id).join(', ')} · ${model} · budget=${budget} · [${strategies.map((s) => s.name).join(' vs ')}] ===\n`)
38
+ const environment = createCommit0Environment(picked)
39
+ const report = await runBenchmark({
40
+ environment,
41
+ tasks: picked.map(rowToTask),
42
+ worker: {
43
+ routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
44
+ routerKey: must('TANGLE_API_KEY'),
45
+ model,
46
+ innerTurns: Number(process.env.INNER_TURNS ?? 10),
47
+ temperature: 0.4,
48
+ },
49
+ strategies,
50
+ budget,
51
+ concurrency: Number(process.env.CONCURRENCY ?? 1),
52
+ })
53
+ printBenchmarkReport(report)
54
+ }
55
+
56
+ main().catch((e) => {
57
+ console.error(`commit0-env-run: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`)
58
+ process.exit(1)
59
+ })