@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,133 @@
1
+ /**
2
+ * LIVE validation of the per-harness tool-part decoders against a REAL sandbox box.
3
+ *
4
+ * Spin a real box for HARNESS, make the harness call tools, capture the actual session part shapes,
5
+ * and prove the harness's decoder extracts the tool calls (with error status where the harness carries
6
+ * it). No mock. Exits non-zero on a decode miss so it can gate CI. The box is ALWAYS deleted.
7
+ *
8
+ * Run: HARNESS=opencode dotenvx run -f ~/company/devops/secrets/agent-state.env -- \
9
+ * pnpm exec tsx bench/src/decoder-live.mts (also: claude-code, codex, kimi-code, …)
10
+ */
11
+ import { Sandbox } from '@tangle-network/sandbox'
12
+ import { decodeToolPart } from '../../src/runtime/supervise/trace-source'
13
+
14
+ function must(k: string): string {
15
+ const v = process.env[k]
16
+ if (!v) throw new Error(`missing env ${k}`)
17
+ return v
18
+ }
19
+
20
+ function* candidateParts(node: unknown, depth = 0): Generator<unknown> {
21
+ if (!node || typeof node !== 'object' || depth > 6) return
22
+ const o = node as Record<string, unknown>
23
+ yield o
24
+ if (o.part) yield o.part
25
+ if (Array.isArray(o.parts)) for (const p of o.parts) yield p
26
+ if (o.data) yield* candidateParts(o.data, depth + 1)
27
+ if (o.message) yield* candidateParts(o.message, depth + 1)
28
+ }
29
+
30
+ const HARNESS = process.env.HARNESS ?? 'opencode'
31
+
32
+ async function main(): Promise<number> {
33
+ const client = new Sandbox({
34
+ baseUrl: process.env.SANDBOX_BASE_URL ?? 'https://sandbox.tangle.tools',
35
+ apiKey: must('TANGLE_API_KEY'),
36
+ timeoutMs: 600_000,
37
+ } as never)
38
+
39
+ console.error(`[live] creating ${HARNESS} box (${process.env.WORKER_MODEL ?? 'deepseek-v4-flash'})…`)
40
+ const box = (await client.create({
41
+ backend: {
42
+ type: HARNESS,
43
+ model: {
44
+ provider: process.env.WORKER_PROVIDER ?? 'openai',
45
+ model: process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
46
+ baseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
47
+ },
48
+ profile: { name: 'decoder-live' },
49
+ },
50
+ } as never)) as Record<string, (...a: never[]) => unknown> & { id?: string }
51
+
52
+ try {
53
+ console.error('[live] box', box.id, '— waiting for running…')
54
+ await box.waitFor('running' as never, { timeoutMs: 180_000 } as never)
55
+
56
+ // Force several distinct tool calls AND at least one tool error (to exercise error-status decode).
57
+ const prompt =
58
+ 'Use your tools, one tool call at a time: (1) list the files in the current directory; ' +
59
+ '(2) run `echo hello-from-tool`; (3) run `false` (this command fails on purpose); ' +
60
+ '(4) create notes.txt containing "hi". A separate tool call for each. Then reply "done".'
61
+
62
+ const rawEvents: unknown[] = []
63
+ const ac = new AbortController()
64
+ const timer = setTimeout(() => ac.abort(), 240_000)
65
+ try {
66
+ for await (const ev of box.streamPrompt(prompt as never, {
67
+ signal: ac.signal,
68
+ } as never) as AsyncGenerator<unknown>) {
69
+ rawEvents.push(ev)
70
+ }
71
+ } finally {
72
+ clearTimeout(timer)
73
+ }
74
+
75
+ // Vocabulary: every distinct part `type` the harness emitted (so we learn its real schema).
76
+ const types = new Map<string, number>()
77
+ for (const ev of rawEvents)
78
+ for (const part of candidateParts(ev)) {
79
+ const t = (part as Record<string, unknown>)?.type
80
+ if (typeof t === 'string') types.set(t, (types.get(t) ?? 0) + 1)
81
+ }
82
+
83
+ // Decode tool calls via the HARNESS adapter + de-dup by callId.
84
+ const decoded: Array<{ toolName: string; status?: string; callId?: string }> = []
85
+ const seen = new Set<string>()
86
+ for (const ev of rawEvents)
87
+ for (const part of candidateParts(ev)) {
88
+ const step = decodeToolPart(part, HARNESS)
89
+ if (!step) continue
90
+ if (step.callId && seen.has(step.callId)) continue
91
+ if (step.callId) seen.add(step.callId)
92
+ decoded.push({ toolName: step.toolName, ...(step.status ? { status: step.status } : {}), ...(step.callId ? { callId: step.callId } : {}) })
93
+ }
94
+
95
+ // Surface any harness/model error events (so a no-tools run isn't mistaken for a decoder miss).
96
+ const errs = rawEvents.filter((e) => JSON.stringify(e).match(/"error"/i)).slice(0, 2)
97
+ if (errs.length) console.error(`[live] harness error events:`, errs.map((e) => JSON.stringify(e).slice(0, 300)))
98
+
99
+ console.error(`\n========== LIVE DECODER RESULT — harness=${HARNESS} ==========`)
100
+ console.error(`raw stream events: ${rawEvents.length}`)
101
+ console.error(`part-type vocabulary:`, JSON.stringify(Object.fromEntries(types)))
102
+ console.error(`decoded ${decoded.length} tool calls:`, JSON.stringify(decoded))
103
+ console.error(`with error status: ${decoded.filter((d) => d.status === 'error').length}`)
104
+ // Sample raw shapes the decoder did NOT match but that mention a tool (the schema to learn from).
105
+ console.error(`\n--- raw toolish parts NOT decoded (first 4) ---`)
106
+ let shown = 0
107
+ for (const ev of rawEvents) {
108
+ if (shown >= 4) break
109
+ for (const part of candidateParts(ev)) {
110
+ if (shown >= 4) break
111
+ const s = JSON.stringify(part)
112
+ if (s.match(/tool/i) && !decodeToolPart(part, HARNESS)) {
113
+ console.error(s.slice(0, 380))
114
+ shown++
115
+ }
116
+ }
117
+ }
118
+ return decoded.length > 0 ? 0 : 2
119
+ } finally {
120
+ await (box.delete as (...a: never[]) => Promise<void>)().catch(() => {})
121
+ console.error('[live] box deleted.')
122
+ }
123
+ }
124
+
125
+ main()
126
+ .then((code) => {
127
+ console.error(code === 0 ? `\n✅ ${HARNESS}: decoder extracts real tool calls.` : `\n❌ ${HARNESS}: decoder extracted ZERO — see raw shapes above.`)
128
+ process.exit(code)
129
+ })
130
+ .catch((e) => {
131
+ console.error('[live] FAILED:', e)
132
+ process.exit(1)
133
+ })
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The steer surface (docs/architecture.md §4–§5): the refine directives a driver
3
+ * injects on rounds 2+. They live HERE, not in the worker files — a worker is a
4
+ * SUBSTRATE that runs a prompt; the directive is the DRIVER's steer, and the thing
5
+ * GEPA optimizes. Centralizing them makes the optimization surface explicit and
6
+ * stops workers from owning steer text.
7
+ *
8
+ * Honest caveat (architecture.md §11, rung-0): these hand-written / early-GEPA
9
+ * directives are the *intrinsic verify-and-revise* family, which LOSES to
10
+ * compute-matched random@k on FinSearchComp. They are kept as the optimization
11
+ * SURFACE and the baseline the real trace-fed driver must beat — NOT as a
12
+ * recommended default. The end state is a driver that supplies the steer from the
13
+ * trace+analysis; until run.ts is unified onto that path, workers take a directive
14
+ * as an optional param defaulting to the relevant constant below.
15
+ */
16
+
17
+ /**
18
+ * APPROACH-diversity strategy lenses — a DIFFERENT family from the verify-and-revise
19
+ * directives above. The caveat names why verify-and-revise loses: k identical-directive
20
+ * shots cluster on the SAME reasoning path, so they share a failure mode and a random
21
+ * pick among them gains nothing. These lenses make the k shots take DIFFERENT reasoning
22
+ * paths, so a deployable self-consistency selector can pick the consensus across
23
+ * genuinely independent attempts. That is the gate hypothesis runProgram's `parallel`
24
+ * is built to deploy: diverse@k (parallel of distinct lenses) vs random@k (blind
25
+ * sample of identical) at EQUAL k. Each lens is a PREFIX to the task; it changes HOW
26
+ * the model reasons, never the answer the judge scores. GEPA optimizes the shared base
27
+ * (`improve-prompt`); these lenses layer on top via `composeStrategies`.
28
+ */
29
+ const DIVERSE_STRATEGY_LENSES: ReadonlyArray<string> = [
30
+ 'Answer directly and decisively from what you already know. State the single best answer without hedging.',
31
+ 'Decompose the question into the sub-facts it depends on. Establish each sub-fact explicitly, then compose them into the answer.',
32
+ 'Reason from first principles. Ignore the most obvious or popular guess; derive the answer from underlying facts and relationships.',
33
+ 'Name the most plausible WRONG answer and the trap that makes it tempting. Rule it out, then commit to the answer that survives.',
34
+ ]
35
+
36
+ /** Compose the k diverse strategies for a run: each = an approach lens layered on the
37
+ * (optionally GEPA-learned) shared base. Returns exactly `k` distinct strategy prefixes
38
+ * (lenses cycle if k exceeds the lens count, but each carries its index so prompts differ). */
39
+ export function composeStrategies(base: string, k: number): string[] {
40
+ const lenses = DIVERSE_STRATEGY_LENSES
41
+ return Array.from({ length: k }, (_, i) => {
42
+ const lens = lenses[i % lenses.length] as string
43
+ const tag = i < lenses.length ? '' : ` (variant ${Math.floor(i / lenses.length) + 1})`
44
+ return `${lens}${tag}\n\n${base}`
45
+ })
46
+ }
47
+
48
+ /** Research (local opencode, model-knowledge) refine directive — hand-written. */
49
+ export const DEFAULT_RESEARCH_REFINE_DIRECTIVE =
50
+ 'Double-check it for a specific factual or reasoning error. If it is correct, restate the SAME answer unchanged. Change it ONLY if you identify a concrete, specific error — do not change a correct answer. End with the FINAL ANSWER line.'
51
+
52
+ /** Sandbox research (live web/market sources) refine directive — hand-written. */
53
+ export const DEFAULT_SANDBOX_REFINE_DIRECTIVE =
54
+ 'Double-check it: re-verify the figure against live sources and the requested units/precision/tolerance. If it is correct, restate the SAME final answer unchanged. Change it ONLY if you find a concrete error in the value or the source. End with the explicit final answer.'
55
+
56
+ /** GEPA-learned sandbox refine directive (bycd31l10, +7.1pp held-out vs the
57
+ * hand-written one on the GEPA run, n=8/noisy). Separates the verification note
58
+ * from the verbatim-preserved final answer, fixing the blank-reply failure mode. */
59
+ export const GEPA_LEARNED_DIRECTIVE =
60
+ 'Double-check it: re-verify the fact/value against a reliable, citable source. Provide a brief Verification note naming the source you used (link or title); this note is not part of the final answer. Confirm the requested units/precision/tolerance exactly. If the prior answer is correct, copy the SAME final answer text verbatim with identical formatting—do not add or remove words. Change it ONLY if you find a concrete error in the value or in the cited source; in that case, briefly describe the specific error in the Verification note and provide the corrected value with the requested units/precision/tolerance. If you cannot verify, state that in the Verification note, but do not alter or omit the final answer. Always place the final answer as the last line of your reply, containing only the answer text.'
61
+
62
+ /** OpenSCAD authoring directive (CAD design) — the GEPA-optimizable system prompt.
63
+ * Minimal on purpose: states the contract (compile, source-only) but not HOW to
64
+ * satisfy the geometric spec, leaving headroom for the optimizer. */
65
+ export const DEFAULT_CAD_DIRECTIVE =
66
+ 'You are an expert OpenSCAD engineer. Output ONLY valid OpenSCAD source — no prose, no markdown fences. The model MUST compile with `openscad -o out.stl model.scad`. Match the brief as closely as you can.'
67
+
68
+ /** OpenSCAD authoring directive for the SANDBOX path (solveCadRefine) — a terser
69
+ * variant of DEFAULT_CAD_DIRECTIVE (no "match the brief" tail). Kept distinct
70
+ * rather than unified to preserve that path's exact prompt. */
71
+ export const DEFAULT_CAD_SANDBOX_DIRECTIVE =
72
+ 'You are an expert OpenSCAD engineer. Output ONLY valid OpenSCAD source — no prose, no markdown fences. The model must compile with `openscad -o out.stl model.scad`.'
73
+
74
+ /** Blender bpy authoring directive (BlenderLLM/CADBench) — the GEPA-optimizable
75
+ * system prompt. States the contract (runs headless, builds mesh(es) at origin,
76
+ * no cameras/lights) but not how to satisfy the criteria. */
77
+ export const DEFAULT_BLENDER_DIRECTIVE =
78
+ 'You are an expert Blender Python (bpy) modeller. Output ONLY a complete bpy script — no prose, no markdown fences. The script must run under `blender --background --python` and build the described object as one or more mesh objects at the world origin. Do NOT add cameras, lights, or render calls; the harness adds those. Match the description.'
79
+
80
+ /** build123d authoring directive (CADGenBench) — the GEPA-optimizable system
81
+ * prompt. States the contract (build123d → output.step, valid watertight solid,
82
+ * exact dimensions) but not how to satisfy it. */
83
+ export const DEFAULT_BUILD123D_DIRECTIVE =
84
+ 'You are an expert CAD engineer. Write a build123d (Python, OpenCascade BREP) script that builds the described part and saves it with `export_step(part, "output.step")`. Output ONLY the Python script — no prose, no markdown fences. The script must run, produce a single VALID watertight solid, and match the described geometry as precisely as possible (exact stated dimensions).'
@@ -0,0 +1,112 @@
1
+ // The beat-blind gate, as ONE command — the experiment that decides whether the
2
+ // diversification/selection surface (the one PR #145 defers to as "tracked separately")
3
+ // actually beats compute-matched best-of-N. Composes only LANDED pieces:
4
+ // 1. random@k corpus — k identical-directive shots/instance (the compute control)
5
+ // 2. diverse@k corpus — k DIFFERENT strategy lenses/instance (DIVERSE=1; the bet)
6
+ // 3. selector replay — selfConsistencySelect@k over each (corpus-replay --selector)
7
+ // 4. paired report — bootstrap CI + Benjamini-Hochberg over both (corpus-report)
8
+ //
9
+ // The decomposition it yields:
10
+ // random@k = more-compute, no picking (control)
11
+ // selector@k (homog) = picking over IDENTICAL attempts (#143: −8.2pp on the committed corpus)
12
+ // diverse-selector@k = picking over DIVERSE attempts (THE bet: does approach-diversity
13
+ // give self-consistency the signal identical attempts don't?)
14
+ // Beat-blind iff diverse-selector@k > random@k at significant n.
15
+ //
16
+ // node diverse-gate.mjs run it (generates corpora — a real worker run)
17
+ // node diverse-gate.mjs --dry print the plan only (no run; safe while another
18
+ // sandbox run is live — zero router/sandbox contention)
19
+ //
20
+ // Knobs (env): BENCH (default hotpotqa) · N (default 30) · K (default 4) ·
21
+ // RESEARCH=1 (local opencode, default) | SANDBOX=1 (prod sandbox web worker) · MODELS ·
22
+ // DIVERSE_BASE (compose #145's GEPA-learned directive as the lens base — follow-on).
23
+
24
+ import { spawn } from 'node:child_process'
25
+
26
+ const DRY = process.argv.includes('--dry')
27
+ const BENCH = process.env.BENCH ?? 'hotpotqa'
28
+ const N = process.env.N ?? '30'
29
+ const K = process.env.K ?? '4'
30
+ const RANDOM_CORPUS = process.env.RANDOM_CORPUS ?? '/tmp/dg-random.jsonl'
31
+ const DIVERSE_CORPUS = process.env.DIVERSE_CORPUS ?? '/tmp/dg-diverse.jsonl'
32
+ // Worker-mode env passes through to batch-oracle unchanged (RESEARCH=1 / SANDBOX=1 / MODELS / TANGLE_API_KEY).
33
+ const passEnv = { ...process.env, BENCH, N, K }
34
+
35
+ const steps = [
36
+ {
37
+ label: 'random@k corpus (control — identical directive)',
38
+ cmd: 'npx',
39
+ args: ['tsx', 'src/run.ts', 'batch-oracle', N],
40
+ env: { ...passEnv, CORPUS: RANDOM_CORPUS },
41
+ },
42
+ {
43
+ label: 'diverse@k corpus (the bet — k distinct strategy lenses)',
44
+ cmd: 'npx',
45
+ args: ['tsx', 'src/run.ts', 'batch-oracle', N],
46
+ env: { ...passEnv, CORPUS: DIVERSE_CORPUS, DIVERSE: '1' },
47
+ },
48
+ {
49
+ label: 'selector@k over the CONTROL corpus (homogeneous)',
50
+ cmd: 'npx',
51
+ args: ['tsx', 'src/corpus-replay.mts', RANDOM_CORPUS, '--selector'],
52
+ env: passEnv,
53
+ },
54
+ {
55
+ label: 'selector@k over the DIVERSE corpus (the beat-blind number)',
56
+ cmd: 'npx',
57
+ // The diverse corpus records carry condition="diverse@4"; corpus-replay's
58
+ // selector filter defaults to "random", so match the diverse condition here.
59
+ args: ['tsx', 'src/corpus-replay.mts', DIVERSE_CORPUS, '--selector', '--condition=diverse'],
60
+ env: passEnv,
61
+ },
62
+ {
63
+ label: 'paired bootstrap CI + Benjamini-Hochberg over both corpora',
64
+ cmd: 'npx',
65
+ args: ['tsx', 'src/corpus-report.mts', RANDOM_CORPUS, DIVERSE_CORPUS],
66
+ env: passEnv,
67
+ },
68
+ ]
69
+
70
+ const shellPreview = (s) => {
71
+ const envStr = Object.entries(s.env)
72
+ .filter(([k]) => ['BENCH', 'N', 'K', 'CORPUS', 'DIVERSE', 'RESEARCH', 'SANDBOX', 'MODELS'].includes(k))
73
+ .map(([k, v]) => `${k}=${v}`)
74
+ .join(' ')
75
+ return ` ${envStr} ${s.cmd} ${s.args.join(' ')}`.replace(/\s+/g, ' ')
76
+ }
77
+
78
+ function runStep(s) {
79
+ return new Promise((resolve, reject) => {
80
+ console.log(`\n▶ ${s.label}`)
81
+ const child = spawn(s.cmd, s.args, { cwd: process.cwd(), env: s.env, stdio: 'inherit' })
82
+ child.on('error', reject)
83
+ // Fail loud: a non-zero step aborts the gate (no silent partial result).
84
+ child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`${s.label} exited ${code}`))))
85
+ })
86
+ }
87
+
88
+ async function main() {
89
+ console.log(`=== beat-blind gate · BENCH=${BENCH} N=${N} K=${K} ${DRY ? '(DRY — plan only)' : ''} ===`)
90
+ if (DRY) {
91
+ console.log('plan (no run — zero sandbox/router contention):')
92
+ for (const s of steps) console.log(shellPreview(s))
93
+ console.log(
94
+ '\nbeat-blind iff diverse-selector@k > random@k at significant n.' +
95
+ '\nDIVERSE_BASE=<file> composes #145\'s GEPA-learned directive as the lens base (follow-on).',
96
+ )
97
+ return
98
+ }
99
+ for (const s of steps) await runStep(s)
100
+ console.log(
101
+ '\n=== read the gate ===\n' +
102
+ ` random@k (control) — from ${RANDOM_CORPUS} replay\n` +
103
+ ` selector@k (homogeneous) — same corpus, the pick (#143: −8.2pp on committed finsearch)\n` +
104
+ ` diverse-selector@k — ${DIVERSE_CORPUS} replay: THE bet\n` +
105
+ ' beat-blind iff diverse-selector@k > random@k, significant per the BH report above.',
106
+ )
107
+ }
108
+
109
+ main().catch((err) => {
110
+ console.error(`diverse-gate: ${err instanceof Error ? err.message : String(err)}`)
111
+ process.exit(1)
112
+ })
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Sandbox->router egress probe: spins a box and makes one authed chat call to
3
+ * https://router.tangle.tools/v1 using the BOX-PROVISIONED key (OPENCODE_MODEL_API_KEY
4
+ * inside the box). That is the sanctioned flow: the egress proxy validates/injects
5
+ * credentials at the boundary and 403s foreign keys passed in from outside — a raw
6
+ * external key in this probe would report a false "blocked".
7
+ * dotenvx run -f ~/company/devops/secrets/.env.keys -f ~/company/devops/secrets/agent-state.env -- npx tsx src/egress-probe.mts
8
+ */
9
+ import { Sandbox } from '@tangle-network/sandbox'
10
+
11
+ const key = process.env.SANDBOX_KEY ?? process.env.TANGLE_API_KEY
12
+ if (!key) throw new Error('no SANDBOX_KEY/TANGLE_API_KEY')
13
+ const client = new Sandbox({ baseUrl: process.env.SANDBOX_BASE_URL ?? 'https://sandbox.tangle.tools', apiKey: key, timeoutMs: 300_000 } as never)
14
+ const box = await client.create({ name: 'egress-probe' } as never)
15
+ console.log('sandbox up:', box.id)
16
+ const out = (r: unknown) => ((r as { stdout?: string }).stdout ?? '').trim()
17
+ try {
18
+ for (const h of ['router.tangle.tools', 'id.tangle.tools']) {
19
+ const r = await box.exec(`curl -s -o /dev/null -w '%{http_code}' --max-time 15 https://${h}/ || echo FAIL`)
20
+ console.log(h, '→', out(r))
21
+ }
22
+ const chat = await box.exec(`curl -s --max-time 30 -X POST https://router.tangle.tools/v1/chat/completions -H "Authorization: Bearer $OPENCODE_MODEL_API_KEY" -H 'Content-Type: application/json' -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"say ok"}],"max_tokens":5}' -o /tmp/b -w '%{http_code}'; echo ' |'; head -c 250 /tmp/b`)
23
+ console.log('chat (box-provisioned key):', out(chat) || 'EMPTY — egress broken')
24
+ } finally {
25
+ await box.delete()
26
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: address-every-subtask
3
+ description: Decompose the request into every distinct sub-task and plan a tool call for each — partial completion is the dominant failure.
4
+ ---
5
+ Before planning calls, list every distinct change the request implies (each user, each ticket, each field). Plan tool calls that complete ALL of them. Many requests bundle several independent actions; finishing only the first is the most common way to fail the final-state check.
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: exact-tools-and-args
3
+ description: Use only the listed tools, with their exact names and argument shapes.
4
+ ---
5
+ Call ONLY the tools listed as available, by their exact names. For each call, provide every required argument with the correct key names and value types the tool expects. A misspelled tool name or a missing/extra argument makes the call a no-op in the judge and the final state will be wrong.
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: full-sequence-to-goal
3
+ description: Plan the COMPLETE ordered sequence that brings the database to the required final state.
4
+ ---
5
+ Think in terms of the required FINAL state, then plan the full ordered sequence of calls that gets there from the seeded start — including any reads needed to ground values, and in an order that respects dependencies (create before reference, set status before close). Do not stop at the first action.
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: ground-every-value
3
+ description: Never invent a field value, id, or relationship — derive each from what the task gives you.
4
+ ---
5
+ Do not guess ids, names, statuses, or relationships. Use the exact values the task supplies; when a value must be looked up, plan the read call first and use its result. A hallucinated field value is a leading cause of a silently-wrong final state.
@@ -0,0 +1,5 @@
1
+ ---
2
+ name: honor-the-policies
3
+ description: Re-read the role's policies and constraints before planning, and make every call comply.
4
+ ---
5
+ The role description contains policies (who may do what, required ordering, forbidden actions). Re-read them, then ensure every planned tool call complies — correct assignee, correct status transitions, no skipped approval step. A plan that reaches the data goal but violates a stated policy still fails.
@@ -0,0 +1,58 @@
1
+ # Examples — the optimization suite in three layers
2
+
3
+ `strategy-demo.mts` is the smallest end-to-end demonstration of the optimization suite.
4
+ It runs on a toy "counter" `Environment` so it needs only a router key — no benchmark
5
+ dataset, no sandbox, no gym.
6
+
7
+ ```bash
8
+ TANGLE_API_KEY=... WORKER_MODEL=gpt-4o-mini pnpm tsx src/examples/strategy-demo.mts
9
+ ```
10
+
11
+ ## The model
12
+
13
+ You have a **task**, a deployable **check**, and a compute **budget**. A *strategy* is
14
+ **how you spend the budget to beat the check**. You implement an `Environment` (5 hooks)
15
+ and get the strategies compared, scored by your own check, for free.
16
+
17
+ ## The three layers (each is a few lines in the demo)
18
+
19
+ 1. **Just run it** — `runBenchmark({ environment, tasks, worker })` compares the default
20
+ strategies and reports the paired lift. Black box; no vocabulary needed.
21
+
22
+ 2. **Pick strategies** — pass `strategies: [sample, refine, adaptiveRefine]`. Named by
23
+ what they *do*:
24
+ - **`sample`** — N independent attempts, keep the best-verifying (best-of-N / resample).
25
+ - **`refine`** — attempt → a critic reads the trace → steer the next → repeat (iterate).
26
+ - **`adaptiveRefine`** — refine, but abandon-and-restart a line that stops improving
27
+ (branch-when-stuck).
28
+
29
+ 3. **Author your own** — `defineStrategy(name, body)`. A strategy body composes two steps
30
+ — `shot()` (one worker attempt over an artifact) and `critique()` (the firewalled
31
+ analyst reads the trace → a steer) — with **zero** Supervisor/Scope ceremony. The demo
32
+ authors `doubleCheck` inline in ~10 lines. This is the unit a skill (or an agent) emits.
33
+
34
+ ## The answer-shaped template — `math-demo.mts`
35
+
36
+ `math-demo.mts` is the same suite on the **answer-shaped** domain template — the shape
37
+ tax/legal/gtm products use. `createVerifierEnvironment({ name, check, extraTools,
38
+ callExtra })` builds the whole `Environment` from one deterministic `check` (here: 3
39
+ GSM8K-style problems, graded by exact numeric match), and `sampleThenRefine` joins the
40
+ built-ins compared at equal budget.
41
+
42
+ ```bash
43
+ TANGLE_API_KEY=... WORKER_MODEL=gpt-4o-mini pnpm tsx src/examples/math-demo.mts
44
+ ```
45
+
46
+ ## The hooks you customize (world-class-DX surface)
47
+
48
+ - **the check / verifier** → `Environment.score` (your deployable success criterion)
49
+ - **the critic / steerer** → `worker.analystInstruction` (the analyst prompt; GEPA tunes this)
50
+ - **the worker** → the model (`worker.model`)
51
+ - **the strategy** → `defineStrategy` (or drop to `runAgentic` / the Supervisor for novel topologies)
52
+
53
+ ## Where the real results live
54
+
55
+ On a trivial task all strategies tie. The differences (e.g. refine/adaptiveRefine beating
56
+ sample on stateful agentic work, +16.4pp on EnterpriseOps-Gym) show on real domains — see
57
+ `bench/HARNESS.md` and `bench/src/agentic-run.mts` (the EOPS Environment), and
58
+ `bench/src/eops-gepa.mts` (GEPA evolving the analyst/critic prompt against the check).
@@ -0,0 +1,110 @@
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.
6
+ *
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`.
9
+ *
10
+ * WORKER_MODEL=gpt-4o-mini BUDGET=3 tsx src/examples/math-demo.mts
11
+ */
12
+ import {
13
+ type AgenticTask,
14
+ createVerifierEnvironment,
15
+ printBenchmarkReport,
16
+ refine,
17
+ runBenchmark,
18
+ sample,
19
+ sampleThenRefine,
20
+ } from '@tangle-network/agent-runtime/loops'
21
+
22
+ // GSM8K-style problems; meta.answer is the ground truth the deployable check compares to.
23
+ const problems: Array<{ q: string; answer: number }> = [
24
+ {
25
+ q: 'A bakery sells loaves for $4 each. On Monday it sold 23 loaves, on Tuesday twice as many as Monday, and on Wednesday 11 fewer than Tuesday. How many dollars did the bakery earn across the three days?',
26
+ answer: (23 + 46 + 35) * 4, // 416
27
+ },
28
+ {
29
+ q: 'Tickets cost $12 for adults and $7 for children. A group of 9 people paid $83. How many children were in the group?',
30
+ answer: 5,
31
+ },
32
+ {
33
+ q: 'A tank holds 2400 liters. Pump A fills 40 L/min and pump B drains 25 L/min. Both run together starting from an empty tank. After how many minutes is the tank exactly half full?',
34
+ answer: 80,
35
+ },
36
+ ]
37
+
38
+ const tasks: AgenticTask[] = problems.map((p, i) => ({
39
+ id: `math-${i + 1}`,
40
+ systemPrompt:
41
+ 'You are a careful mathematician. Work step by step, use the calculator tool for arithmetic, then submit ONLY the final number with submit_answer.',
42
+ userPrompt: p.q,
43
+ meta: { answer: p.answer },
44
+ }))
45
+
46
+ /** Last number in the submission — tolerant of "The answer is 416." */
47
+ function extractNumber(answer: string): number | null {
48
+ const m = answer.replace(/,/g, '').match(/-?\d+(?:\.\d+)?(?!.*-?\d)/s)
49
+ return m ? Number(m[0]) : null
50
+ }
51
+
52
+ const mathEnv = createVerifierEnvironment({
53
+ name: 'math',
54
+ check: (task, answer) => {
55
+ const got = extractNumber(answer)
56
+ const want = (task.meta as { answer: number }).answer
57
+ return { passes: got !== null && Math.abs(got - want) < 1e-6 ? 1 : 0, total: 1, errored: 0 }
58
+ },
59
+ extraTools: [
60
+ {
61
+ type: 'function',
62
+ function: {
63
+ name: 'calculator',
64
+ description: 'Evaluate an arithmetic expression (numbers and + - * / ( ) only).',
65
+ parameters: {
66
+ type: 'object',
67
+ properties: { expression: { type: 'string' } },
68
+ required: ['expression'],
69
+ },
70
+ },
71
+ },
72
+ ],
73
+ callExtra: (_task, name, args) => {
74
+ if (name !== 'calculator') return `ERROR: unknown tool ${name}`
75
+ const expr = String(args.expression ?? '')
76
+ if (!/^[\d\s+\-*/().]+$/.test(expr)) return 'ERROR: arithmetic only'
77
+ try {
78
+ // The whitelist above admits only arithmetic — no identifiers can reach Function.
79
+ const value = new Function(`return (${expr})`)() as number
80
+ return Number.isFinite(value) ? String(value) : 'ERROR: not finite'
81
+ } catch (e) {
82
+ return `ERROR: ${e instanceof Error ? e.message : String(e)}`
83
+ }
84
+ },
85
+ })
86
+
87
+ async function main(): Promise<void> {
88
+ const routerKey = process.env.TANGLE_API_KEY
89
+ if (!routerKey) throw new Error('set TANGLE_API_KEY')
90
+ const report = await runBenchmark({
91
+ environment: mathEnv,
92
+ tasks,
93
+ worker: {
94
+ routerBaseUrl: process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1',
95
+ routerKey,
96
+ model: process.env.WORKER_MODEL ?? 'deepseek-v4-flash',
97
+ innerTurns: 6,
98
+ temperature: 0.6,
99
+ },
100
+ strategies: [sample, refine, sampleThenRefine],
101
+ budget: Number(process.env.BUDGET ?? 3),
102
+ concurrency: 3,
103
+ })
104
+ printBenchmarkReport(report)
105
+ }
106
+
107
+ main().catch((e) => {
108
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
109
+ process.exit(1)
110
+ })