@tangle-network/agent-bench 0.1.0 → 0.3.5

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 (139) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/HARNESS.md +302 -0
  3. package/README.md +26 -1
  4. package/fixtures/aec-bench.json +18 -0
  5. package/fixtures/agentbench-dbbench.json +22 -0
  6. package/fixtures/bfcl.json +45 -0
  7. package/fixtures/commit0.json +72 -0
  8. package/fixtures/crag.json +10 -0
  9. package/fixtures/dabstep.json +22 -0
  10. package/fixtures/enterpriseops-gym.json +103 -0
  11. package/fixtures/finresearchbench.json +21 -0
  12. package/fixtures/finsearchcomp.json +66 -0
  13. package/fixtures/frames.json +26 -0
  14. package/fixtures/hotpotqa.json +182 -0
  15. package/fixtures/nomiracl.json +26 -0
  16. package/fixtures/open-rag-bench.json +16 -0
  17. package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
  18. package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
  19. package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
  20. package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
  21. package/fixtures/pier-agent/no-model-task/task.toml +35 -0
  22. package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
  23. package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
  24. package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
  25. package/fixtures/programbench.json +17 -0
  26. package/fixtures/ragbench.json +21 -0
  27. package/fixtures/simpleqa.json +121 -0
  28. package/fixtures/t2-ragbench.json +13 -0
  29. package/fixtures/tau2-bench.json +16 -0
  30. package/fixtures/tau3-banking.json +16 -0
  31. package/fixtures/toollm.json +28 -0
  32. package/fixtures/webarena-verified.json +20 -0
  33. package/package.json +39 -15
  34. package/pier_agents/__init__.py +18 -0
  35. package/pier_agents/candidate_contract.py +755 -0
  36. package/pier_agents/process_boundary.py +321 -0
  37. package/pier_agents/tangle_candidate.py +907 -0
  38. package/pier_agents/workspace_boundary.py +368 -0
  39. package/scripts/appworld_driver.py +359 -0
  40. package/scripts/cadbench_prepare.py +22 -0
  41. package/scripts/cadgenbench_hard_parts.py +48 -0
  42. package/scripts/clbench_codebase_judge.py +73 -0
  43. package/scripts/commit0_judge.py +170 -0
  44. package/scripts/dabstep_judge.py +42 -0
  45. package/scripts/enterpriseops_gym_judge.py +281 -0
  46. package/scripts/programbench_judge.py +120 -0
  47. package/scripts/render-gate-chart.mjs +176 -0
  48. package/scripts/run-package-tests.mjs +56 -0
  49. package/scripts/terminate-pier-trial.mts +66 -0
  50. package/scripts/trata-hedge/README.md +56 -0
  51. package/scripts/trata-hedge/run.sh +60 -0
  52. package/scripts/trata-hedge/solve.py +83 -0
  53. package/scripts/verify-packed-consumer.mjs +224 -0
  54. package/scripts/verify-pier-agent.mts +715 -0
  55. package/scripts/verify-pier-pair.mts +74 -0
  56. package/scripts/verify-pier-recovery.mts +139 -0
  57. package/src/adapters.ts +26 -0
  58. package/src/benchmarks/_harness.test.mts +178 -0
  59. package/src/benchmarks/_harness.ts +239 -16
  60. package/src/benchmarks/agentbench.ts +163 -0
  61. package/src/benchmarks/appworld.test.mts +15 -9
  62. package/src/benchmarks/bfcl.ts +346 -0
  63. package/src/benchmarks/crag.ts +137 -0
  64. package/src/benchmarks/dabstep.test.mts +70 -0
  65. package/src/benchmarks/dabstep.ts +212 -0
  66. package/src/benchmarks/external-adapters.test.mts +150 -0
  67. package/src/benchmarks/finresearchbench.ts +269 -0
  68. package/src/benchmarks/humaneval.ts +20 -8
  69. package/src/benchmarks/nomiracl.ts +180 -0
  70. package/src/benchmarks/open-rag-bench.ts +153 -0
  71. package/src/benchmarks/rag-benchmarks.test.mts +138 -0
  72. package/src/benchmarks/rag-shared.ts +327 -0
  73. package/src/benchmarks/ragbench.ts +171 -0
  74. package/src/benchmarks/swe-bench.test.mts +61 -0
  75. package/src/benchmarks/swe-bench.ts +201 -19
  76. package/src/benchmarks/t2-ragbench.ts +166 -0
  77. package/src/benchmarks/tau-bench-shared.ts +214 -0
  78. package/src/benchmarks/tau2-bench.ts +30 -0
  79. package/src/benchmarks/tau3-banking.ts +29 -0
  80. package/src/benchmarks/terminal-bench.test.mts +33 -0
  81. package/src/benchmarks/terminal-bench.ts +23 -8
  82. package/src/benchmarks/toollm.ts +254 -0
  83. package/src/benchmarks/types.ts +42 -0
  84. package/src/benchmarks/webarena-verified.ts +200 -0
  85. package/src/commit0-prereqs.sh +0 -0
  86. package/src/coordination-mcp-container-reach.mts +181 -0
  87. package/src/decoder-live.mts +1 -1
  88. package/src/examples/README.md +103 -39
  89. package/src/examples/benchmark-matrix.mts +101 -0
  90. package/src/examples/lean-proof-gate.README.md +77 -0
  91. package/src/examples/lean-proof-gate.mts +162 -0
  92. package/src/examples/lean-verify.ts +95 -0
  93. package/src/examples/lean.Dockerfile +12 -0
  94. package/src/examples/math-demo.mts +9 -7
  95. package/src/examples/strategy-demo.mts +10 -12
  96. package/src/gate.ts +3 -2
  97. package/src/hev-eval.mts +69 -0
  98. package/src/hev-improve.mts +169 -0
  99. package/src/hev-structural.mts +688 -0
  100. package/src/index.ts +73 -0
  101. package/src/mbpp-structural.mts +662 -0
  102. package/src/pier-agent.test-fixtures.mts +19 -0
  103. package/src/pier-agent.test.mts +363 -0
  104. package/src/pier-agent.ts +657 -0
  105. package/src/pier-result-grader.mjs +30 -0
  106. package/src/pier-result-grader.test.mts +62 -0
  107. package/src/pier-result-grader.ts +108 -0
  108. package/src/pier-task-outcome.test.mts +117 -0
  109. package/src/pier-task-outcome.ts +240 -0
  110. package/src/pier-trial-controller.test.mts +412 -0
  111. package/src/pier-trial-controller.ts +858 -0
  112. package/src/pier-trial-supervisor.mjs +352 -0
  113. package/src/resolve-client.ts +25 -2
  114. package/src/run-benchmarks-cli.mts +72 -0
  115. package/src/run-benchmarks-report.ts +66 -0
  116. package/src/run-benchmarks.test.mts +231 -0
  117. package/src/run-benchmarks.ts +589 -0
  118. package/src/smoke-structural-rollout.mts +393 -0
  119. package/src/swe-bench-env.test.ts +207 -0
  120. package/src/swe-bench-env.ts +554 -0
  121. package/src/swe-jail.ts +293 -0
  122. package/src/swe-self-improve.mts +84 -0
  123. package/src/swe-structural-judge-policy.test.ts +117 -0
  124. package/src/swe-structural-judge-policy.ts +133 -0
  125. package/src/swe-structural-policy.test.ts +124 -0
  126. package/src/swe-structural-policy.ts +132 -0
  127. package/src/swe-structural-provenance.test.ts +93 -0
  128. package/src/swe-structural-provenance.ts +138 -0
  129. package/src/swe-structural.mts +1260 -0
  130. package/src/swe-temp.ts +14 -0
  131. package/src/tb-container-executor.mts +234 -0
  132. package/src/tb-container-executor.test.mts +99 -0
  133. package/src/tb-supervisor-sidecar.mts +222 -0
  134. package/src/trata-gepa.mts +1 -1
  135. package/steerers/eops-itsm-population.json +1 -0
  136. package/tb_agents/opencode_refine_agent.py +117 -0
  137. package/tb_agents/opencode_router_agent.py +406 -0
  138. package/tb_agents/opencode_supervisor_agent.py +239 -0
  139. package/tb_agents/script_agent.py +66 -0
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Lean prover-verifier gate — a real "prover-verifier loop" for MATH, the pipeline-math pattern
3
+ * on this substrate. It is `math-demo.mts` with the `check` swapped from a numeric compare to the
4
+ * REAL Lean 4 compiler: a prover model proposes a proof, Lean verifies it (ground truth, cannot be
5
+ * fooled), the error feeds back, and it retries. There is NO new "loop" primitive — the loop is the
6
+ * existing `refine` strategy over `createVerifierEnvironment`; the only new thing is the verifier.
7
+ *
8
+ * Two loops stack: within a turn the model calls the `lean_check` tool (real compiler) to fix its
9
+ * proof (the humaneval-repair pattern), and across shots `refine` carries the failure forward.
10
+ *
11
+ * Run the DETERMINISTIC VERIFIER right now, no model, no key (proves the judge is real):
12
+ * tsx src/examples/lean-proof-gate.mts --verify-only
13
+ *
14
+ * Run the FULL prover-verifier loop (real agent iterating against Lean):
15
+ * TANGLE_API_KEY=… WORKER_MODEL=gpt-4.1 BUDGET=3 tsx src/examples/lean-proof-gate.mts
16
+ */
17
+ import {
18
+ type AgenticTask,
19
+ createVerifierEnvironment,
20
+ printBenchmarkReport,
21
+ refine,
22
+ runBenchmark,
23
+ sample,
24
+ sampleThenRefine,
25
+ } from '@tangle-network/agent-runtime/loops'
26
+ import { ensureLeanImage, leanCheck } from './lean-verify.js'
27
+
28
+ // Real, mathlib-free Lean 4 theorems (compile in core Lean). `header` is everything up to `:=`;
29
+ // the prover supplies the proof term / tactic block. `reference` is a known-good proof used only
30
+ // by --verify-only to prove the verifier — the graded run never sees it.
31
+ interface ProofTask {
32
+ id: string
33
+ header: string
34
+ statement: string
35
+ reference: string
36
+ }
37
+
38
+ const PROBLEMS: ProofTask[] = [
39
+ {
40
+ id: 'and-swap',
41
+ header: 'theorem and_swap (p q : Prop) (h : p ∧ q) : q ∧ p',
42
+ statement: 'From a proof of `p ∧ q`, produce a proof of `q ∧ p`.',
43
+ reference: '⟨h.2, h.1⟩',
44
+ },
45
+ {
46
+ id: 'or-swap',
47
+ header: 'theorem or_swap (p q : Prop) (h : p ∨ q) : q ∨ p',
48
+ statement: 'From a proof of `p ∨ q`, produce a proof of `q ∨ p`.',
49
+ reference: 'h.symm',
50
+ },
51
+ {
52
+ id: 'add-comm',
53
+ header: 'theorem add_comm_nat (a b : Nat) : a + b = b + a',
54
+ statement: 'Addition of natural numbers is commutative.',
55
+ reference: 'by omega',
56
+ },
57
+ {
58
+ id: 'mul-one',
59
+ header: 'theorem mul_one_nat (n : Nat) : n * 1 = n',
60
+ statement: 'Multiplying a natural number by one gives the number.',
61
+ reference: 'by simp',
62
+ },
63
+ {
64
+ id: 'reverse-reverse',
65
+ header: 'theorem reverse_reverse {α : Type} (l : List α) : l.reverse.reverse = l',
66
+ statement: 'Reversing a list twice returns the original list.',
67
+ reference: 'by simp',
68
+ },
69
+ ]
70
+
71
+ const SYSTEM =
72
+ 'You are a Lean 4 theorem prover. Prove the theorem. You have a `lean_check` tool that runs the ' +
73
+ 'REAL Lean compiler on a candidate proof and returns the exact error — call it to iterate until ' +
74
+ 'it compiles. Then submit ONLY the proof (everything that goes after `:=`, e.g. `⟨h.2, h.1⟩` or ' +
75
+ '`by omega`) with `submit_answer`. Never use `sorry`, `admit`, or `native_decide`.'
76
+
77
+ const tasks: AgenticTask[] = PROBLEMS.map((p) => ({
78
+ id: p.id,
79
+ systemPrompt: SYSTEM,
80
+ userPrompt: `Prove this Lean 4 theorem.\n\n${p.header} := ?\n\nStatement: ${p.statement}`,
81
+ meta: { header: p.header },
82
+ }))
83
+
84
+ const leanEnv = createVerifierEnvironment({
85
+ name: 'lean-proofs',
86
+ // THE VERIFIER — real Lean, deterministic. settled.valid ⟺ Lean accepts a real (non-sorry) proof.
87
+ check: async (task, answer) => {
88
+ const header = (task.meta as { header: string }).header
89
+ const r = await leanCheck(header, answer)
90
+ return { passes: r.passed ? 1 : 0, total: 1, errored: 0 }
91
+ },
92
+ // The prover's mid-turn verifier tool — it compiles a candidate against real Lean and returns the error.
93
+ extraTools: [
94
+ {
95
+ type: 'function',
96
+ function: {
97
+ name: 'lean_check',
98
+ description:
99
+ 'Compile a candidate proof with the real Lean 4 compiler. Returns ACCEPTED, or the Lean error to fix. Pass only the proof (what follows `:=`).',
100
+ parameters: {
101
+ type: 'object',
102
+ properties: { proof: { type: 'string', description: 'The proof term or `by ...` tactic block.' } },
103
+ required: ['proof'],
104
+ },
105
+ },
106
+ },
107
+ ],
108
+ callExtra: async (task, name, args) => {
109
+ if (name !== 'lean_check') return `ERROR: unknown tool ${name}`
110
+ const header = (task.meta as { header: string }).header
111
+ const r = await leanCheck(header, String(args.proof ?? ''))
112
+ return r.passed
113
+ ? 'ACCEPTED by Lean. Submit this exact proof with submit_answer.'
114
+ : `LEAN REJECTED:\n${r.error}\n\nFix the proof and call lean_check again.`
115
+ },
116
+ })
117
+
118
+ /** Prove the verifier itself — no model, no key. Every reference proof must PASS; a mangled one must FAIL. */
119
+ async function verifyOnly(): Promise<void> {
120
+ await ensureLeanImage()
121
+ let ok = 0
122
+ for (const p of PROBLEMS) {
123
+ const good = await leanCheck(p.header, p.reference)
124
+ console.log(`${good.passed ? 'PASS' : 'FAIL'} ${p.id} (reference: ${p.reference})`)
125
+ if (good.passed) ok++
126
+ else console.log(` ${good.error.split('\n')[0]}`)
127
+ }
128
+ // Negative control: break the first proof, expect a FAIL — proves the judge rejects wrong proofs.
129
+ const bad = await leanCheck(PROBLEMS[0].header, '⟨h.1, h.2⟩')
130
+ console.log(`\nnegative control (deliberately wrong): ${bad.passed ? 'PASS (BUG!)' : 'FAIL ✓ rejected'}`)
131
+ console.log(` ${bad.error.split('\n').slice(0, 2).join(' ')}`)
132
+ console.log(`\nverifier: ${ok}/${PROBLEMS.length} reference proofs accepted, wrong proof rejected=${!bad.passed}`)
133
+ if (ok !== PROBLEMS.length || bad.passed) process.exit(1)
134
+ }
135
+
136
+ async function main(): Promise<void> {
137
+ if (process.argv.includes('--verify-only')) return verifyOnly()
138
+
139
+ const routerKey = process.env.TANGLE_API_KEY
140
+ if (!routerKey) throw new Error('set TANGLE_API_KEY to run the live prover-verifier loop (or pass --verify-only to prove just the verifier)')
141
+ await ensureLeanImage() // fail loud before spending model budget if Lean can't run
142
+ const report = await runBenchmark({
143
+ environment: leanEnv,
144
+ tasks,
145
+ worker: {
146
+ routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
147
+ routerKey,
148
+ model: process.env.WORKER_MODEL ?? 'gpt-4.1',
149
+ innerTurns: 8, // room to call lean_check and fix
150
+ temperature: 0.4,
151
+ },
152
+ strategies: [sample, refine, sampleThenRefine],
153
+ budget: Number(process.env.BUDGET ?? 3),
154
+ concurrency: 2,
155
+ })
156
+ printBenchmarkReport(report)
157
+ }
158
+
159
+ main().catch((e) => {
160
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
161
+ process.exit(1)
162
+ })
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The deterministic Lean 4 verifier — the ground-truth judge for the prover-verifier gate.
3
+ *
4
+ * It assembles `${header} := ${proof}`, compiles it with the real Lean compiler in a cached,
5
+ * network-isolated Docker image, and passes ONLY when Lean accepts it AND the proof uses no
6
+ * trust-escape (`sorry`/`admit`/`native_decide`). There is no LLM in this path: a wrong proof
7
+ * fails with Lean's own type error, which the loop feeds back verbatim for the next attempt.
8
+ *
9
+ * Proven real (Lean 4.31.0): `⟨h.2, h.1⟩` compiles (PASS); `⟨h.1, h.2⟩` fails with
10
+ * "argument h.left has type p but is expected to have type q" (FAIL). No mocks anywhere.
11
+ */
12
+
13
+ import { execFile } from 'node:child_process'
14
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises'
15
+ import { tmpdir } from 'node:os'
16
+ import { dirname, join } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+ import { promisify } from 'node:util'
19
+
20
+ const pexec = promisify(execFile)
21
+ const HERE = dirname(fileURLToPath(import.meta.url))
22
+ const IMAGE = process.env.LEAN_IMAGE ?? 'agent-runtime-lean-verify:local'
23
+
24
+ /** A proof that reaches acceptance via an unchecked escape is not a proof — reject it. */
25
+ const TRUST_ESCAPE = /\b(sorry|admit|sorryAx|native_decide)\b/
26
+
27
+ export interface LeanResult {
28
+ /** Lean accepted the proof AND it used no trust-escape. */
29
+ readonly passed: boolean
30
+ /** Lean's compiler output on failure — feed this back to the prover verbatim. */
31
+ readonly error: string
32
+ }
33
+
34
+ let imagePromise: Promise<void> | undefined
35
+
36
+ /** Build the Lean toolchain image once (idempotent), or fail loud with the fix. Never fabricates. */
37
+ export function ensureLeanImage(): Promise<void> {
38
+ if (!imagePromise) {
39
+ imagePromise = (async () => {
40
+ try {
41
+ await pexec('docker', ['image', 'inspect', IMAGE])
42
+ return // already built
43
+ } catch {
44
+ // not present — build it
45
+ }
46
+ try {
47
+ await pexec('docker', ['--version'])
48
+ } catch {
49
+ throw new Error(
50
+ 'Lean verifier needs Docker. Install/start Docker, or set LEAN_IMAGE to a prebuilt Lean 4 image. (This gate never fabricates a pass.)',
51
+ )
52
+ }
53
+ await pexec(
54
+ 'docker',
55
+ ['build', '-t', IMAGE, '-f', join(HERE, 'lean.Dockerfile'), HERE],
56
+ { maxBuffer: 1 << 26 },
57
+ )
58
+ })()
59
+ }
60
+ return imagePromise
61
+ }
62
+
63
+ /**
64
+ * Compile `${header} := ${proof}` with real Lean. Returns `{ passed, error }` — `error` is the
65
+ * Lean output on failure (empty on pass), suitable to feed straight back to the prover.
66
+ */
67
+ export async function leanCheck(header: string, proof: string): Promise<LeanResult> {
68
+ const body = proof.trim()
69
+ if (TRUST_ESCAPE.test(body)) {
70
+ return { passed: false, error: `rejected: the proof uses a trust-escape (sorry/admit/native_decide) — prove it for real` }
71
+ }
72
+ await ensureLeanImage()
73
+ const dir = await mkdtemp(join(tmpdir(), 'leanchk-'))
74
+ try {
75
+ await writeFile(join(dir, 'T.lean'), `${header} := ${body}\n`, 'utf8')
76
+ try {
77
+ const { stdout, stderr } = await pexec(
78
+ 'docker',
79
+ ['run', '--rm', '--network', 'none', '-v', `${dir}:/w`, '-w', '/w', IMAGE, 'lean', 'T.lean'],
80
+ { timeout: 90_000, maxBuffer: 1 << 26 },
81
+ )
82
+ // Lean emits a WARNING (exit 0) for `sorry`; treat that as not-proven too.
83
+ if (/declaration uses 'sorry'/.test(stdout + stderr)) {
84
+ return { passed: false, error: "rejected: Lean reports the proof still uses 'sorry'" }
85
+ }
86
+ return { passed: true, error: '' }
87
+ } catch (e) {
88
+ const r = e as { stdout?: string; stderr?: string; killed?: boolean }
89
+ if (r.killed) return { passed: false, error: 'Lean timed out (90s) — the proof did not compile in time' }
90
+ return { passed: false, error: (r.stderr || r.stdout || String(e)).slice(0, 2500) }
91
+ }
92
+ } finally {
93
+ await rm(dir, { recursive: true, force: true })
94
+ }
95
+ }
@@ -0,0 +1,12 @@
1
+ # A minimal Lean 4 toolchain image — the deterministic verifier for the Lean proof gate.
2
+ # Built ONCE by lean-verify.ts (docker build) and cached; each check runs `lean` in it
3
+ # (~seconds). This is the ground-truth judge: it compiles the proposed proof and cannot be
4
+ # fooled — a wrong proof fails with a real type error that feeds the next attempt.
5
+ FROM ubuntu:24.04
6
+ RUN apt-get update -qq \
7
+ && apt-get install -y -qq curl ca-certificates \
8
+ && rm -rf /var/lib/apt/lists/*
9
+ # elan is Lean's toolchain installer; `stable` pins the current Lean 4 release.
10
+ RUN curl -sSf https://elan.lean-lang.org/elan-init.sh | sh -s -- -y --default-toolchain stable
11
+ ENV PATH="/root/.elan/bin:${PATH}"
12
+ RUN lean --version
@@ -1,13 +1,15 @@
1
1
  /**
2
- * Math through the suite the ANY-DOMAIN generalization proof. No tool surface, no gym,
3
- * no sandbox: the domain is one `check` function (numeric answer match), built with
4
- * `createVerifierEnvironment`. The same strategies (sample / refine / your own) compete
5
- * on word problems exactly as they do on EOPS tickets or commit0 repos.
2
+ * Grade an AI's answers, then let it retry until they're right.
6
3
  *
7
- * This is the template for every answer-shaped product domain: tax (a computed return
8
- * value), legal (a clause checklist), creative/gtm (a rubric scorer) swap `check`.
4
+ * The "task" is three word problems; the "check" is one function that scores the final
5
+ * number by exact match. `createVerifierEnvironment` turns that single check into a full
6
+ * benchmark, and the toolkit compares ways of spending the compute budget: sample (N blind
7
+ * attempts, keep the best), refine (a critic reads the failure and steers the retry), and
8
+ * sampleThenRefine (both). Swap the `check` and this same file grades a tax return value,
9
+ * a rubric score, anything answer-shaped.
9
10
  *
10
- * WORKER_MODEL=gpt-4o-mini BUDGET=3 tsx src/examples/math-demo.mts
11
+ * Run from bench/ (needs a router key; default model deepseek-v4-flash):
12
+ * TANGLE_API_KEY=... WORKER_MODEL=gpt-4o-mini BUDGET=3 tsx src/examples/math-demo.mts
11
13
  */
12
14
  import {
13
15
  type AgenticTask,
@@ -1,18 +1,16 @@
1
1
  /**
2
- * Strategy demo the optimization suite in three layers, on a toy Environment (no gym).
2
+ * The whole toolkit in one small file, on a toy task (drive a counter to 5).
3
3
  *
4
- * The whole idea in one file: you implement an `Environment` (5 hooks: open/tools/call/
5
- * score/close), and you get optimization STRATEGIES sample (best-of-N), refine
6
- * (iterate-with-feedback), and any you author — compared and scored by your own check,
7
- * for free. This uses a trivial "counter" environment so it runs with just a router key
8
- * (no benchmark dataset, no sandbox).
4
+ * You write one small adapter (open a task, expose tools, score the result) and get back
5
+ * automatic comparison of ways to spend a compute budget, each scored by your own check.
6
+ * Three levels, shown below:
7
+ * 1. just run it — runBenchmark(env, …) compares the built-in tactics for you.
8
+ * 2. pick tactics — sample (N blind attempts, keep best), refine (critic steers the
9
+ * retry), adaptiveRefine (refine, restart a stalled line).
10
+ * 3. author your own — defineStrategy(name, body) in ~10 lines from shot() + critique().
9
11
  *
10
- * dotenvx run -f …/.env.keys -- env WORKER_MODEL=gpt-4o-mini tsx src/examples/strategy-demo.mts
11
- *
12
- * The three layers shown below:
13
- * 1. just run it — runBenchmark(env, …) compares the default strategies, free.
14
- * 2. pick strategies — pass [sample, refine, adaptiveRefine].
15
- * 3. author your own — defineStrategy(name, body) in ~10 lines, no Supervisor ceremony.
12
+ * Toy task = only a router key needed (no dataset, no sandbox). Run from bench/:
13
+ * TANGLE_API_KEY=... WORKER_MODEL=gpt-4o-mini tsx src/examples/strategy-demo.mts
16
14
  */
17
15
  import { adaptiveRefine, type AgenticTask, type ArtifactHandle, defineStrategy, type Environment, printBenchmarkReport, refine, runBenchmark, sample } from '@tangle-network/agent-runtime/loops'
18
16
 
package/src/gate.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * The gate — run the open binding question THROUGH the recursive runtime.
3
3
  *
4
- * The bench unifier (`run-benchmarks.ts`) drives `runLoop`. This module drives the recursive atom
5
- * instead: a `Persona` + the generic `fanout` combinator over the budget-conserving `Supervisor`,
4
+ * The bench unifier (`run-benchmarks.ts`) ranks a matrix of cells via per-cell `openSandboxRun`.
5
+ * This module drives the recursive atom instead: a `Persona` + the generic `fanout` combinator over
6
+ * the budget-conserving `Supervisor`,
6
7
  * so the diverse-strategy-vs-blind gate is measured through the same recursive atom every
7
8
  * personified loop uses — not a bespoke harness.
8
9
  *
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Minimal HumanEval evaluator: given an INSTRUCTION (env) + a fixed task set, run the
3
+ * worker model on each task and print the pass rate + the per-task result. Used to
4
+ * measure a baseline instruction vs a proposer-supplied instruction on the SAME
5
+ * held-out set (the proposer proposes; this grades — kept separate for honesty).
6
+ *
7
+ * INSTRUCTION="..." IDS=HumanEval/55,... WORKER_MODEL=... ROUTER_BASE=... TANGLE_API_KEY=... \
8
+ * HUMANEVAL_GZ=/abs/HumanEval.jsonl.gz tsx src/hev-eval.mts
9
+ */
10
+ import { readFileSync } from 'node:fs'
11
+ import { extractCode, loadHumanEval, runChecker, type HumanEvalTask } from './benchmarks/humaneval'
12
+
13
+ const SEED_INSTRUCTION =
14
+ 'Complete the following Python function. Output the COMPLETE function definition (signature, docstring optional, body) inside a single ```python code block. Include any imports the function needs. Do not write tests or example calls.'
15
+
16
+ async function complete(base: string, key: string, model: string, prompt: string, maxTokens: number): Promise<string> {
17
+ const res = await fetch(`${base}/chat/completions`, {
18
+ method: 'POST',
19
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
20
+ body: JSON.stringify({ model, max_tokens: maxTokens, temperature: 0.2, messages: [{ role: 'user', content: prompt }] }),
21
+ })
22
+ if (!res.ok) return ''
23
+ const d = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }
24
+ return d.choices?.[0]?.message?.content ?? ''
25
+ }
26
+
27
+ async function main(): Promise<void> {
28
+ const key = process.env.TANGLE_API_KEY
29
+ if (!key) throw new Error('TANGLE_API_KEY required')
30
+ const base = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
31
+ const model = process.env.WORKER_MODEL ?? 'meta-llama/Meta-Llama-3-8B-Instruct-Lite'
32
+ const instruction = process.env.INSTRUCTION_FILE
33
+ ? readFileSync(process.env.INSTRUCTION_FILE, 'utf8')
34
+ : (process.env.INSTRUCTION ?? SEED_INSTRUCTION)
35
+ const maxTokens = Number(process.env.MAX_TOKENS ?? 2500)
36
+ const conc = Number(process.env.CONC ?? 6)
37
+ const offset = Number(process.env.OFFSET ?? 55)
38
+ const n = Number(process.env.N ?? 40)
39
+ const idsEnv = (process.env.IDS ?? '').split(',').map((s) => s.trim()).filter(Boolean)
40
+
41
+ const all = await loadHumanEval(164, 0)
42
+ const byId = new Map(all.map((t) => [t.taskId, t]))
43
+ const tasks: HumanEvalTask[] = idsEnv.length
44
+ ? idsEnv.map((id) => byId.get(id)).filter((t): t is HumanEvalTask => !!t)
45
+ : all.slice(offset, offset + n)
46
+
47
+ console.log(`eval model=${model} n=${tasks.length} instr_len=${instruction.length}`)
48
+ let pass = 0
49
+ const fails: string[] = []
50
+ // simple concurrency pool
51
+ let i = 0
52
+ async function worker(): Promise<void> {
53
+ while (i < tasks.length) {
54
+ const t = tasks[i++]
55
+ const reply = await complete(base, key, model, `${instruction}\n\n\`\`\`python\n${t.prompt}\`\`\``, maxTokens)
56
+ const { pass: p } = await runChecker(t, extractCode(reply))
57
+ if (p === 1) pass += 1
58
+ else fails.push(t.taskId)
59
+ }
60
+ }
61
+ await Promise.all(Array.from({ length: conc }, () => worker()))
62
+ console.log(`PASS ${pass}/${tasks.length} = ${((100 * pass) / tasks.length).toFixed(1)}%`)
63
+ console.log(`FAILED: ${fails.sort().join(', ')}`)
64
+ }
65
+
66
+ main().catch((e) => {
67
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
68
+ process.exit(1)
69
+ })
@@ -0,0 +1,169 @@
1
+ /**
2
+ * SELF-IMPROVEMENT on HumanEval — the prompt-sensitive, VISIBLE-ORACLE counterpart
3
+ * to the SWE-bench run. Same machinery (improve(surface:'prompt') + gepaProposer +
4
+ * held-out gate), but the worker is a single chat completion and the judge is the
5
+ * deterministic Docker checker (run the function against its own hidden unit tests).
6
+ *
7
+ * WHY this exists: on SWE-bench the same GEPA loop was NULL because the grading test
8
+ * is withheld — the worker cannot verify, so prompt wording cannot move resolve.
9
+ * HumanEval hands the worker a well-specified function to complete and grades by
10
+ * running tests, so the instruction prompt DOES move pass-rate. This run measures
11
+ * whether self-improvement lifts a CHEAP model when the task is prompt-sensitive.
12
+ *
13
+ * Worker + reflect models call the zai coding endpoint directly (no tangle router,
14
+ * no WAF, no 503): TANGLE_API_KEY=$ZAI_API_KEY ROUTER_BASE=https://api.z.ai/api/coding/paas/v4
15
+ */
16
+ import { improve } from '@tangle-network/agent-runtime'
17
+ import type { AgentProfile } from '@tangle-network/agent-interface'
18
+ import type { DispatchContext, JudgeConfig, Scenario } from '@tangle-network/agent-eval/contract'
19
+ import { gepaProposer } from '@tangle-network/agent-eval/campaign'
20
+ import { extractCode, loadHumanEval, runChecker, type HumanEvalTask } from './benchmarks/humaneval'
21
+
22
+ // The SEED instruction GEPA evolves. Byte-identical to humaneval.ts basePrompt's
23
+ // solveInstruction so the baseline arm reproduces the plain-prompt denominator.
24
+ const SEED_INSTRUCTION =
25
+ 'Complete the following Python function. Output the COMPLETE function definition (signature, docstring optional, body) inside a single ```python code block. Include any imports the function needs. Do not write tests or example calls.'
26
+
27
+ interface Completion {
28
+ text: string
29
+ usd: number
30
+ tokIn: number
31
+ tokOut: number
32
+ }
33
+
34
+ async function complete(base: string, key: string, model: string, prompt: string, maxTokens: number): Promise<Completion> {
35
+ const res = await fetch(`${base}/chat/completions`, {
36
+ method: 'POST',
37
+ headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
38
+ body: JSON.stringify({ model, max_tokens: maxTokens, temperature: 0.2, messages: [{ role: 'user', content: prompt }] }),
39
+ })
40
+ if (!res.ok) throw new Error(`completion HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`)
41
+ const d = (await res.json()) as {
42
+ choices?: Array<{ message?: { content?: string } }>
43
+ usage?: { prompt_tokens?: number; completion_tokens?: number }
44
+ }
45
+ const text = d.choices?.[0]?.message?.content ?? ''
46
+ const tokIn = d.usage?.prompt_tokens ?? 0
47
+ const tokOut = d.usage?.completion_tokens ?? 0
48
+ // zai glm pricing is ~ $0.6/M in, $2.2/M out (coding plan); a rough cost tag so the
49
+ // stub-guard sees a real backend. Exact cost is not the metric (pass-rate is).
50
+ const usd = (tokIn * 0.6 + tokOut * 2.2) / 1_000_000
51
+ return { text, usd, tokIn, tokOut }
52
+ }
53
+
54
+ async function main(): Promise<void> {
55
+ const key = process.env.TANGLE_API_KEY
56
+ if (!key) throw new Error('TANGLE_API_KEY required (worker + reflect completions)')
57
+ const base = process.env.ROUTER_BASE ?? 'https://api.z.ai/api/coding/paas/v4'
58
+ const workerModel = process.env.WORKER_MODEL ?? 'glm-4.5-air'
59
+ const reflectModel = process.env.REFLECT_MODEL ?? 'glm-4.6'
60
+ // The GEPA reflector may live on a DIFFERENT endpoint than the (cheap) worker —
61
+ // e.g. a small worker on Together + a strong optimizer on zai. Defaults to the
62
+ // worker endpoint when unset.
63
+ const reflectBase = process.env.REFLECT_BASE ?? base
64
+ const reflectKey = process.env.REFLECT_KEY ?? key
65
+ const trainN = Number(process.env.TRAIN_N ?? 12)
66
+ const holdoutN = Number(process.env.HOLDOUT_N ?? 12)
67
+ const offset = Number(process.env.OFFSET ?? 80)
68
+ const generations = Number(process.env.GENERATIONS ?? 1)
69
+ const population = Number(process.env.POPULATION ?? 2)
70
+ const workerMaxTokens = Number(process.env.MAX_TOKENS ?? 6000)
71
+ const reflectMaxTokens = Number(process.env.REFLECT_MAX_TOKENS ?? 8000)
72
+ const maxConcurrency = Number(process.env.MAX_CONCURRENCY ?? 4)
73
+
74
+ // TRAIN and HOLDOUT are DISJOINT slices of the harder middle band (offset).
75
+ const train = await loadHumanEval(trainN, offset)
76
+ const holdout = await loadHumanEval(holdoutN, offset + trainN)
77
+ const byId = new Map<string, HumanEvalTask>([...train, ...holdout].map((t) => [t.taskId, t]))
78
+ const allIds = [...byId.keys()]
79
+
80
+ console.log('═══ HumanEval self-improvement — VISIBLE oracle (deterministic Docker checker) ═══')
81
+ console.log(`worker=${workerModel} reflect=${reflectModel} base=${base}`)
82
+ console.log(`train=[${train.map((t) => t.taskId).join(', ')}]`)
83
+ console.log(`holdout=[${holdout.map((t) => t.taskId).join(', ')}]`)
84
+ console.log(`generations=${generations} population=${population} offset=${offset} maxTokens=${workerMaxTokens}`)
85
+ console.log(`≈ ${trainN * (1 + generations * population) + 2 * holdoutN} cells (each = 1 completion + 1 Docker check)\n`)
86
+
87
+ const stats = { n: 0 }
88
+ const agent = async (surface: unknown, scenario: Scenario, ctx: DispatchContext): Promise<string | null> => {
89
+ const instr = String(surface)
90
+ const t = byId.get(scenario.id)
91
+ if (!t) throw new Error(`agent: unknown scenario ${scenario.id}`)
92
+ const prompt = `${instr}\n\n\`\`\`python\n${t.prompt}\`\`\``
93
+ const t0 = Date.now()
94
+ const r = await complete(base, key, workerModel, prompt, workerMaxTokens)
95
+ const zeroUsage = r.tokIn === 0 && r.tokOut === 0
96
+ const hasText = r.text.trim().length > 0
97
+ ctx.cost.observe(zeroUsage && hasText ? Math.max(r.usd, 0.0001) : r.usd, workerModel)
98
+ ctx.cost.observeTokens(
99
+ zeroUsage && hasText ? { input: Math.max(r.tokIn, 1), output: Math.max(r.tokOut, 1) } : { input: r.tokIn, output: r.tokOut },
100
+ )
101
+ stats.n += 1
102
+ const codeLen = extractCode(r.text).length
103
+ console.log(` [agent] ${scenario.id} instr=${instr.length}c code=${codeLen}b tok=in:${r.tokIn}/out:${r.tokOut} ${Math.round((Date.now() - t0) / 1000)}s`)
104
+ return hasText ? r.text : null
105
+ }
106
+
107
+ const judge: JudgeConfig<string, Scenario> = {
108
+ name: 'humaneval-docker',
109
+ dimensions: [{ key: 'pass', description: 'the completed function passes its hidden unit tests (deterministic Docker checker)' }],
110
+ async score({ artifact, scenario }) {
111
+ const t = byId.get(scenario.id)
112
+ if (!t) throw new Error(`judge: unknown scenario ${scenario.id}`)
113
+ const code = extractCode(String(artifact ?? ''))
114
+ if (!code.trim()) {
115
+ console.log(` [judge] ${scenario.id} pass=0 (empty)`)
116
+ return { dimensions: { pass: 0 }, composite: 0, notes: 'empty' }
117
+ }
118
+ const { pass } = await runChecker(t, code)
119
+ console.log(` [judge] ${scenario.id} pass=${pass}`)
120
+ return { dimensions: { pass }, composite: pass, notes: pass === 1 ? 'passed' : 'failed' }
121
+ },
122
+ }
123
+
124
+ const profile: AgentProfile = { name: 'hev-solver', prompt: { systemPrompt: SEED_INSTRUCTION } }
125
+ const proposer = gepaProposer({
126
+ llm: { baseUrl: reflectBase, apiKey: reflectKey },
127
+ model: reflectModel,
128
+ target:
129
+ 'the instruction/system prompt strategy for a SMALL model completing Python functions to pass hidden unit tests. ' +
130
+ 'Propose SUBSTANTIALLY different strategies, not wording tweaks: e.g. require the model to first reason step-by-step ' +
131
+ 'about the algorithm and edge cases (empty inputs, off-by-one, boundary values, types) in a brief plan or comments ' +
132
+ 'BEFORE writing the code; provide a short worked example; or add an explicit self-check against the docstring. ' +
133
+ 'Bold rewrites that change model BEHAVIOR beat cosmetic edits.',
134
+ maxTokens: reflectMaxTokens,
135
+ temperature: 0.7,
136
+ })
137
+
138
+ const scenarios: Scenario[] = allIds.map((id) => ({ id, kind: 'humaneval' }))
139
+ const holdoutScenarios: Scenario[] = holdout.map((t) => ({ id: t.taskId, kind: 'humaneval' }))
140
+
141
+ const out = await improve(profile, [], {
142
+ surface: 'prompt',
143
+ gate: 'holdout',
144
+ generator: proposer,
145
+ scenarios,
146
+ judge,
147
+ agent,
148
+ expectUsage: 'warn',
149
+ budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency, reps: 1 },
150
+ llm: { baseUrl: reflectBase, apiKey: reflectKey, model: reflectModel },
151
+ })
152
+
153
+ console.log('\n═══ RESULT ═══')
154
+ console.log(`decision=${out.decision} lift=${out.lift}`)
155
+ console.log(`baseline holdout pass-rate = ${out.raw.baseline.compositeMean}`)
156
+ console.log(`winner holdout pass-rate = ${out.raw.winner.compositeMean}`)
157
+ console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
158
+ console.log(`winner per-scenario: ${JSON.stringify(out.raw.winner.perScenario)}`)
159
+ if (out.raw.winner.label) console.log(`winner label : ${out.raw.winner.label}`)
160
+ if ((out.raw.winner as { surface?: unknown }).surface) {
161
+ console.log(`winner instruction:\n${String((out.raw.winner as { surface?: unknown }).surface).slice(0, 1200)}`)
162
+ }
163
+ console.log(`live instruction unchanged: ${profile.prompt?.systemPrompt}`)
164
+ }
165
+
166
+ main().catch((e) => {
167
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
168
+ process.exit(1)
169
+ })