@tangle-network/agent-bench 0.3.8 → 0.4.1

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 (115) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +7 -0
  3. package/dist/adapters.js +23 -23
  4. package/dist/benchmarks/_harness.d.ts +4 -1
  5. package/dist/benchmarks/_harness.js +3 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/nomiracl.js +2 -2
  16. package/dist/benchmarks/open-rag-bench.js +2 -2
  17. package/dist/benchmarks/programbench.js +2 -2
  18. package/dist/benchmarks/ragbench.js +2 -2
  19. package/dist/benchmarks/swe-bench.js +2 -2
  20. package/dist/benchmarks/t2-ragbench.js +2 -2
  21. package/dist/benchmarks/tau-bench-shared.js +2 -2
  22. package/dist/benchmarks/tau2-bench.js +3 -3
  23. package/dist/benchmarks/tau3-banking.js +3 -3
  24. package/dist/benchmarks/terminal-bench.js +2 -2
  25. package/dist/benchmarks/toollm.js +2 -2
  26. package/dist/benchmarks/webarena-verified.js +2 -2
  27. package/dist/{chunk-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
  28. package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
  29. package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
  30. package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
  31. package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
  32. package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
  33. package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
  34. package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
  35. package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
  36. package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
  37. package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
  38. package/dist/chunk-EIETHPD5.js.map +1 -0
  39. package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
  40. package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
  41. package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
  42. package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
  43. package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
  44. package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
  45. package/dist/chunk-JSQOUKXS.js.map +1 -0
  46. package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
  47. package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
  48. package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
  49. package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
  50. package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
  51. package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
  52. package/dist/index.js +23 -23
  53. package/package.json +4 -4
  54. package/pier_agents/candidate_contract.py +238 -24
  55. package/pier_agents/tangle_candidate.py +75 -5
  56. package/scripts/trata-hedge/README.md +6 -5
  57. package/scripts/verify-packed-consumer.mjs +65 -1
  58. package/scripts/verify-pier-agent.mts +6 -4
  59. package/src/benchmarks/_harness.test.mts +16 -1
  60. package/src/benchmarks/_harness.ts +9 -2
  61. package/src/benchmarks/terminal-bench.ts +2 -1
  62. package/src/gate.ts +1 -1
  63. package/src/hev-eval.mts +5 -2
  64. package/src/hev-improve.mts +118 -73
  65. package/src/official-optimizer-config.mts +89 -0
  66. package/src/official-optimizer-config.test.mts +88 -0
  67. package/src/profiles.ts +2 -2
  68. package/src/rollout-ledger/backfill-swe-arena.test.mts +28 -24
  69. package/src/smoke-structural-rollout.mts +15 -9
  70. package/src/swe-arena/activation.mts +1 -4
  71. package/src/swe-arena/activation.test.mts +10 -13
  72. package/src/swe-arena/gepa-seat.mts +425 -131
  73. package/src/swe-arena/gepa-seat.test.mts +524 -100
  74. package/src/swe-arena/implementation-ref.test.mts +64 -0
  75. package/src/swe-arena/implementation-ref.ts +62 -0
  76. package/src/swe-arena/outer-loop.mts +103 -76
  77. package/src/swe-arena/proposer-fanout.mts +51 -36
  78. package/src/swe-arena/proposer-fanout.test.mts +0 -1
  79. package/src/swe-arena/proposer-provenance.mts +11 -16
  80. package/src/swe-arena/scratch-worktree.test.mts +55 -0
  81. package/src/swe-arena/scratch-worktree.ts +34 -0
  82. package/src/swe-code-improve.mts +24 -25
  83. package/src/swe-improve.mts +129 -96
  84. package/src/swe-local-proof.mts +6 -1
  85. package/src/swe-stream.mts +4 -2
  86. package/src/tb-container-executor.test.mts +30 -6
  87. package/src/tb-supervisor-sidecar.mts +2 -1
  88. package/src/trata-gepa.mts +182 -245
  89. package/dist/chunk-WSKWVEQB.js.map +0 -1
  90. package/dist/chunk-XYA4XSNU.js.map +0 -1
  91. package/src/live-improve-campaign-mbpp.mts +0 -641
  92. package/src/live-improve-campaign.mts +0 -500
  93. package/src/swe-arena/lineage-record.mts +0 -164
  94. package/src/swe-arena/lineage-record.test.mts +0 -115
  95. /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
  96. /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
  97. /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
  98. /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
  99. /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
  100. /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
  101. /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
  102. /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
  103. /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
  104. /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
  105. /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
  106. /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
  107. /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
  108. /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
  109. /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
  110. /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
  111. /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
  112. /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
  113. /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
  114. /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
  115. /package/dist/{chunk-Z4TZ76N7.js.map → chunk-ZNCCYTFG.js.map} +0 -0
@@ -1,500 +0,0 @@
1
- /**
2
- * live-improve-campaign — the FIRST LIVE self-improvement campaign on the merged
3
- * machinery: `improve()` (surface 'rollout-policy') tunes the structuralRollout dials
4
- * { k, repairRounds, testgen } against REAL HumanEval with a REAL worker model, and the
5
- * library's own held-out gate (defaultProductionGate: paired bootstrap over held-out
6
- * scenarios, ship iff CI.low > deltaThreshold 0.05) makes the ship/hold call. No human
7
- * picks winners; this script only wires the real evaluator into the loop and reports.
8
- *
9
- * Wiring template: rollout-policy.test.ts's end-to-end improve() run, with the fake
10
- * judge gradient replaced by the real evaluator (the smoke's harness):
11
- * agent(surface, scenario) = runAgentic(structuralRollout(parsed policy)) over an
12
- * INERT verifier surface (no hidden signal reaches selection/repair), visible checks
13
- * via the shipped sandboxCheckRunner over a docker --network=none exec channel, then
14
- * SCRIPT-SIDE hidden grading of the locked winner candidate by the nonce-sentinel
15
- * judge (hev-structural's runHiddenJudge pattern: pass requires exit 0 AND the
16
- * per-call nonce in stdout — a candidate printing a forged verdict cannot pass).
17
- *
18
- * Honesty split:
19
- * - DEV = HumanEval index [0, DEV_N) and HELD-OUT = [DEV_N, DEV_N+HOLD_N) — fixed,
20
- * disjoint slices, passed as explicit `budget.holdoutScenarios` so the library's
21
- * own train/holdout split machinery enforces disjointness (it throws on overlap).
22
- * - The proposer is deterministic enumeration; `analyzeGeneration: null` keeps the
23
- * findings channel empty, so the improver's context is ONLY the DEV composites the
24
- * loop itself accumulates. Held-out cells run after all generations, gate-side only.
25
- * - The gate decision is `result.gateDecision` from the library — never recomputed here.
26
- *
27
- * Run (key via dotenvx; never in the shell history):
28
- * cd ~/company/devops/secrets && dotenvx run -f agent-state.env -- bash -c ' \
29
- * cd /home/drew/code/agent-runtime-swe && \
30
- * HUMANEVAL_GZ=/abs/HumanEval.jsonl.gz npx tsx bench/src/live-improve-campaign.mts'
31
- * Smoke first (cost gate): SMOKE=1 shrinks to 6 dev + 6 held-out tasks, 1 generation,
32
- * population 2 — proves the full path completes before the real burn.
33
- */
34
-
35
- import { execFile, execFileSync } from 'node:child_process'
36
- import { randomBytes } from 'node:crypto'
37
- import { appendFileSync, mkdirSync } from 'node:fs'
38
- import { tmpdir } from 'node:os'
39
- import { join } from 'node:path'
40
- import type {
41
- DispatchContext,
42
- JudgeConfig,
43
- MutableSurface,
44
- Scenario,
45
- } from '@tangle-network/agent-eval/contract'
46
- import type { AgentProfile } from '@tangle-network/agent-interface'
47
- import { improve } from '../../src/improvement/improve'
48
- import {
49
- parseRolloutPolicy,
50
- ROLLOUT_POLICY_EXTENSION,
51
- serializeRolloutPolicy,
52
- structuralRolloutPolicyFromProfile,
53
- } from '../../src/improvement/rollout-policy'
54
- import {
55
- type AgenticRunResult,
56
- type CheckExecChannel,
57
- type CheckOutcome,
58
- type CheckRunner,
59
- createVerifierEnvironment,
60
- runAgentic,
61
- sandboxCheckRunner,
62
- structuralRollout,
63
- type StructuralRolloutResult,
64
- } from '../../src/runtime/index'
65
- import { basePrompt, type HumanEvalTask, loadHumanEval } from './benchmarks/humaneval'
66
-
67
- function must(name: string): string {
68
- const v = process.env[name]
69
- if (!v) throw new Error(`env ${name} is required`)
70
- return v
71
- }
72
-
73
- const SMOKE = process.env.SMOKE === '1'
74
- const DEV_N = Number(process.env.DEV_N ?? (SMOKE ? 6 : 60))
75
- const HOLD_N = Number(process.env.HOLD_N ?? (SMOKE ? 6 : 60))
76
- const GENERATIONS = Number(process.env.GENERATIONS ?? (SMOKE ? 1 : 2))
77
- const POPULATION = Number(process.env.POPULATION ?? (SMOKE ? 2 : 4))
78
- const CONCURRENCY = Number(process.env.CONCURRENCY ?? 8)
79
- const DOCKER_CONCURRENCY = Number(process.env.DOCKER_CONCURRENCY ?? 6)
80
- // Default worker: Qwen2.5-7B — the second model of the strategy's measured basis
81
- // (Llama-3-8B/Qwen2.5-7B). The original smoke worker (Meta-Llama-3-8B-Instruct-Lite)
82
- // and every other 8B Llama variant were retired from Together serverless
83
- // (`model_not_available`, verified 2026-07); this is the closest live weak worker.
84
- const MODEL = process.env.MODEL ?? 'Qwen/Qwen2.5-7B-Instruct-Turbo'
85
- const BASE = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
86
- const TEMP = Number(process.env.TEMPERATURE ?? 0.8)
87
- const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 2500)
88
- const DOLLARS = Number(process.env.DOLLARS ?? 15)
89
- const RUN_DIR = process.env.RUN_DIR ?? join(tmpdir(), `live-improve-campaign-${Date.now()}`)
90
-
91
- const systemPrompt = 'You are an expert Python programmer.'
92
- const dockerImage = 'python:3.12-slim'
93
- const dockerTimeoutMs = Number(process.env.DOCKER_TIMEOUT_MS ?? 20000)
94
-
95
- // ── Docker: ONE semaphored --network=none exec channel for BOTH judges ───────────────
96
- // Visible checks (sandboxCheckRunner) and the hidden nonce judge each pipe a python
97
- // program as `printf '%s' '<b64>' | base64 -d | python3 -`; every container passes
98
- // through one global semaphore so task-level concurrency cannot stampede the daemon.
99
-
100
- let dockerInFlight = 0
101
- const dockerWaiters: Array<() => void> = []
102
- async function withDockerSlot<T>(fn: () => Promise<T>): Promise<T> {
103
- if (dockerInFlight >= DOCKER_CONCURRENCY) await new Promise<void>((r) => dockerWaiters.push(r))
104
- dockerInFlight += 1
105
- try {
106
- return await fn()
107
- } finally {
108
- dockerInFlight -= 1
109
- dockerWaiters.shift()?.()
110
- }
111
- }
112
-
113
- const containerPrefix = `lic-${process.pid}`
114
- let containerSeq = 0
115
-
116
- function reapContainers(): void {
117
- try {
118
- const ids = execFileSync('docker', ['ps', '-aq', '--filter', `name=${containerPrefix}`], {
119
- timeout: 10000,
120
- })
121
- .toString()
122
- .trim()
123
- if (ids) execFileSync('docker', ['rm', '-f', ...ids.split('\n')], { timeout: 15000 })
124
- } catch {
125
- /* reaper is best-effort by design */
126
- }
127
- }
128
- process.on('SIGINT', () => {
129
- reapContainers()
130
- process.exit(130)
131
- })
132
- process.on('SIGTERM', () => {
133
- reapContainers()
134
- process.exit(143)
135
- })
136
-
137
- const dockerBox: CheckExecChannel = {
138
- exec(command, options) {
139
- const timeoutMs = options?.timeoutMs ?? dockerTimeoutMs
140
- return withDockerSlot(
141
- () =>
142
- new Promise((resolve, reject) => {
143
- const name = `${containerPrefix}-${containerSeq++}`
144
- let settled = false
145
- const reap = () => execFile('docker', ['rm', '-f', name], () => {})
146
- const finish = (r: { exitCode: number; stdout: string; stderr: string }) => {
147
- if (settled) return
148
- settled = true
149
- clearTimeout(backstop)
150
- reap()
151
- resolve(r)
152
- }
153
- const fail = (e: Error) => {
154
- if (settled) return
155
- settled = true
156
- clearTimeout(backstop)
157
- reap()
158
- reject(e)
159
- }
160
- // execFile's timeout kills the docker CLIENT; a hung container could leave
161
- // the callback unfired. The backstop guarantees resolution and the named
162
- // reap kills the stray container.
163
- const backstop = setTimeout(
164
- () => finish({ exitCode: 124, stdout: '', stderr: 'timed out (backstop)' }),
165
- timeoutMs + 3000,
166
- )
167
- execFile(
168
- 'docker',
169
- ['run', '--rm', '--name', name, '--network=none', '--cpus=1', '--memory=512m', dockerImage, 'sh', '-c', command],
170
- { timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
171
- (err, stdout, stderr) => {
172
- if (err) {
173
- const e = err as NodeJS.ErrnoException & { code?: number | string }
174
- if (e.code === 'ENOENT') {
175
- fail(new Error('docker binary not found on PATH'))
176
- return
177
- }
178
- if (/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(stderr ?? '')) {
179
- fail(new Error(`docker daemon unreachable: ${(stderr ?? '').slice(0, 200)}`))
180
- return
181
- }
182
- finish({ exitCode: typeof e.code === 'number' ? e.code : 1, stdout: stdout ?? '', stderr: stderr ?? '' })
183
- return
184
- }
185
- finish({ exitCode: 0, stdout: stdout ?? '', stderr: stderr ?? '' })
186
- },
187
- )
188
- }),
189
- )
190
- },
191
- }
192
-
193
- // ── The hidden nonce judge (script-side, AFTER the strategy locks its artifact) ──────
194
- // Pass requires the per-call nonce sentinel that check() prints AFTER succeeding —
195
- // exit-0-before-check (a candidate calling sys.exit(0)) is a fail here, where trusting
196
- // the exit code alone would score it a pass. Nothing from this run reaches the strategy.
197
-
198
- function buildHiddenProgram(task: HumanEvalTask, candidate: string, nonce: string): string {
199
- return `${task.prompt}\n${candidate}\n\n${task.test}\n\ncheck(${task.entryPoint})\nprint("HIDDEN-${nonce} PASS")\n`
200
- }
201
-
202
- async function runHiddenJudge(
203
- task: HumanEvalTask,
204
- candidate: string,
205
- ): Promise<{ pass: number; detail?: string }> {
206
- const nonce = randomBytes(8).toString('hex')
207
- const b64 = Buffer.from(buildHiddenProgram(task, candidate, nonce), 'utf8').toString('base64')
208
- const r = await dockerBox.exec(`printf '%s' '${b64}' | base64 -d | python3 -`, {
209
- timeoutMs: dockerTimeoutMs,
210
- })
211
- if (r.exitCode === 0 && r.stdout.includes(`HIDDEN-${nonce} PASS`)) return { pass: 1 }
212
- return { pass: 0, detail: (r.stderr || r.stdout).slice(-300) || 'timed out (no output)' }
213
- }
214
-
215
- // ── Scenarios: fixed disjoint slices of HumanEval ─────────────────────────────────────
216
-
217
- interface HevScenario extends Scenario {
218
- kind: 'humaneval'
219
- }
220
-
221
- const taskById = new Map<string, HumanEvalTask>()
222
-
223
- // ── The real evaluator: one cell = one structuralRollout run + hidden grade ──────────
224
-
225
- interface CellArtifact {
226
- taskId: string
227
- policy: string
228
- /** Hidden nonce-judge grade of the FINAL selected candidate: {0,1}. */
229
- pass: number
230
- detail?: string
231
- repairStop: string
232
- shots: number
233
- completions: number
234
- authoredChecks: number
235
- tokens: { input: number; output: number }
236
- usd: number
237
- ms: number
238
- }
239
-
240
- interface ScoredCandidate {
241
- candidate: string
242
- outcome: CheckOutcome
243
- }
244
- function recordingRunner(inner: CheckRunner, log: ScoredCandidate[]): CheckRunner {
245
- return {
246
- async run(candidate, checks, ctx) {
247
- const outcome = await inner.run(candidate, checks, ctx)
248
- log.push({ candidate, outcome })
249
- return outcome
250
- },
251
- }
252
- }
253
-
254
- // Global spend meter (every cell of every phase — baseline, generations, holdout).
255
- const spend = { cells: 0, llmCalls: 0, tokensIn: 0, tokensOut: 0, usd: 0, hiddenPass: 0 }
256
-
257
- async function evaluateCell(
258
- surface: MutableSurface,
259
- scenario: HevScenario,
260
- ctx: DispatchContext,
261
- ): Promise<CellArtifact> {
262
- const policy = parseRolloutPolicy(surface)
263
- if (!policy) {
264
- throw new Error(`agent: surface carries no valid rollout policy: ${String(surface).slice(0, 120)}`)
265
- }
266
- const task = taskById.get(scenario.id)
267
- if (!task) throw new Error(`agent: unknown scenario id ${scenario.id}`)
268
-
269
- const scored: ScoredCandidate[] = []
270
- const strategy = structuralRollout({
271
- policy: { ...policy, temperature: TEMP },
272
- checkRunner: recordingRunner(sandboxCheckRunner({ box: dockerBox }), scored),
273
- })
274
- // INERT check: the strategy's harness-verified score channel carries no hidden
275
- // signal — hidden grading happens below, after the rollout locks its artifact.
276
- const inertSurface = createVerifierEnvironment({
277
- name: 'humaneval-inert',
278
- check: () => ({ passes: 0, total: 1, errored: 0 }),
279
- })
280
- const result = (await runAgentic({
281
- surface: inertSurface,
282
- task: {
283
- id: task.taskId,
284
- systemPrompt,
285
- userPrompt: basePrompt(task),
286
- meta: { entryPoint: task.entryPoint },
287
- },
288
- routerBaseUrl: BASE,
289
- routerKey: must('TOGETHER_API_KEY'),
290
- model: MODEL,
291
- temperature: TEMP,
292
- maxTokens: MAX_TOKENS,
293
- innerTurns: 2,
294
- strategy,
295
- // The strategy's documented sizing: k samples + repair rounds + the check-author consult.
296
- budget: policy.k + policy.repairRounds + 1,
297
- })) as AgenticRunResult & StructuralRolloutResult
298
-
299
- // Backend integrity: report REAL usage on every cell (expectUsage 'assert' upstream).
300
- ctx.cost.observe(result.usd, 'together')
301
- ctx.cost.observeTokens(result.tokens)
302
-
303
- const winner = result.selection.find((r) => r.selected)
304
- if (!winner) {
305
- throw new Error(`${task.taskId}: no receipt marked selected (repairStop=${result.repairStop})`)
306
- }
307
- const rec = scored[winner.candidateIndex]
308
- if (!rec) {
309
- throw new Error(
310
- `${task.taskId}: selected receipt #${winner.candidateIndex} has no recorded candidate (${scored.length} scored)`,
311
- )
312
- }
313
-
314
- const hidden = await runHiddenJudge(task, rec.candidate)
315
-
316
- spend.cells += 1
317
- spend.llmCalls += result.completions
318
- spend.tokensIn += result.tokens.input
319
- spend.tokensOut += result.tokens.output
320
- spend.usd += result.usd
321
- spend.hiddenPass += hidden.pass
322
-
323
- const artifact: CellArtifact = {
324
- taskId: task.taskId,
325
- policy: serializeRolloutPolicy(policy),
326
- pass: hidden.pass,
327
- ...(hidden.detail ? { detail: hidden.detail } : {}),
328
- repairStop: result.repairStop,
329
- shots: result.shots,
330
- completions: result.completions,
331
- authoredChecks: result.authoredChecks,
332
- tokens: result.tokens,
333
- usd: result.usd,
334
- ms: result.ms,
335
- }
336
- appendFileSync(
337
- join(RUN_DIR, 'cells.jsonl'),
338
- `${JSON.stringify({ cellId: ctx.cellId, generation: ctx.generation ?? null, ...artifact, detail: undefined })}\n`,
339
- )
340
- console.log(
341
- ` [cell ${String(spend.cells).padStart(3)}] ${task.taskId.padEnd(14)} ${artifact.policy.padEnd(38)} hidden=${hidden.pass ? 'PASS' : 'fail'} ${result.repairStop} calls=${result.completions}`,
342
- )
343
- return artifact
344
- }
345
-
346
- // The in-loop judge is a deterministic transcriber of the script-side hidden grade —
347
- // the grading itself never runs inside the strategy or the proposer's view.
348
- const hiddenJudge: JudgeConfig<CellArtifact, HevScenario> = {
349
- name: 'hidden-nonce-judge',
350
- dimensions: [
351
- { key: 'hidden', description: 'HumanEval hidden check() suite (docker --network=none, nonce sentinel)' },
352
- ],
353
- score: ({ artifact }) => ({
354
- dimensions: { hidden: artifact.pass },
355
- composite: artifact.pass,
356
- notes: artifact.pass ? 'hidden PASS' : `hidden fail: ${(artifact.detail ?? '').slice(0, 160)}`,
357
- }),
358
- }
359
-
360
- // ── Reporting helpers (read the library's own result objects; never re-decide) ───────
361
-
362
- interface CampaignLike {
363
- cells: Array<{ error?: string | null; judgeScores: Record<string, { composite: number }> }>
364
- }
365
- function passStats(campaign: CampaignLike): { passed: number; scored: number; errored: number; rate: number } {
366
- let passed = 0
367
- let scored = 0
368
- let errored = 0
369
- for (const cell of campaign.cells) {
370
- if (cell.error) {
371
- errored += 1
372
- continue
373
- }
374
- scored += 1
375
- const scores = Object.values(cell.judgeScores)
376
- const composite = scores.length === 0 ? 0 : scores.reduce((s, j) => s + j.composite, 0) / scores.length
377
- if (composite >= 0.999) passed += 1
378
- }
379
- return { passed, scored, errored, rate: scored > 0 ? passed / scored : 0 }
380
- }
381
- const pct = (x: number) => `${(100 * x).toFixed(1)}%`
382
-
383
- async function main(): Promise<void> {
384
- must('TOGETHER_API_KEY')
385
- mkdirSync(RUN_DIR, { recursive: true })
386
- const started = Date.now()
387
-
388
- const all = await loadHumanEval(DEV_N + HOLD_N, 0)
389
- if (all.length !== DEV_N + HOLD_N) {
390
- throw new Error(`expected ${DEV_N + HOLD_N} tasks, loaded ${all.length}`)
391
- }
392
- const devTasks = all.slice(0, DEV_N)
393
- const holdTasks = all.slice(DEV_N)
394
- for (const t of all) taskById.set(t.taskId, t)
395
-
396
- const toScenario = (t: HumanEvalTask): HevScenario => ({ id: t.taskId, kind: 'humaneval' })
397
- const scenarios = all.map(toScenario)
398
- const holdoutScenarios = holdTasks.map(toScenario)
399
-
400
- const baselinePolicy = { k: 5, repairRounds: 2, testgen: 6 }
401
- const profile: AgentProfile = {
402
- name: 'humaneval-structural-worker',
403
- extensions: { [ROLLOUT_POLICY_EXTENSION]: baselinePolicy },
404
- }
405
-
406
- console.log('=== LIVE self-improvement campaign · improve() surface rollout-policy ===')
407
- console.log(` worker: ${MODEL} @ ${BASE} (temp=${TEMP}, maxTokens=${MAX_TOKENS}, innerTurns=2)`)
408
- console.log(
409
- ` DEV slice : HumanEval index [0, ${DEV_N}) — ${devTasks[0]?.taskId} .. ${devTasks[devTasks.length - 1]?.taskId} (n=${devTasks.length})`,
410
- )
411
- console.log(
412
- ` HELD-OUT slice : HumanEval index [${DEV_N}, ${DEV_N + HOLD_N}) — ${holdTasks[0]?.taskId} .. ${holdTasks[holdTasks.length - 1]?.taskId} (n=${holdTasks.length})`,
413
- )
414
- console.log(` baseline policy: ${JSON.stringify(baselinePolicy)}`)
415
- console.log(
416
- ` budget: generations=${GENERATIONS} population<=${POPULATION} reps=1 concurrency=${CONCURRENCY} docker<=${DOCKER_CONCURRENCY} ceiling=$${DOLLARS}`,
417
- )
418
- console.log(` gate: library defaultProductionGate (paired bootstrap on held-out, ship iff CI.low > 0.05)`)
419
- console.log(` runDir: ${RUN_DIR}`)
420
- console.log(`\n profile BEFORE: ${JSON.stringify(profile)}\n`)
421
-
422
- const result = await improve<HevScenario, CellArtifact>(profile, [], {
423
- surface: 'rollout-policy',
424
- scenarios,
425
- judge: hiddenJudge,
426
- agent: evaluateCell,
427
- budget: {
428
- generations: GENERATIONS,
429
- populationSize: POPULATION,
430
- maxConcurrency: CONCURRENCY,
431
- holdoutScenarios,
432
- reps: 1,
433
- dollars: DOLLARS,
434
- },
435
- runDir: RUN_DIR,
436
- // Deterministic proposer, empty findings channel: the improver's context is ONLY
437
- // the DEV composites the loop accumulates — no distilled failure text, no trace
438
- // paths, and (by the loop's own structure) never a held-out cell.
439
- analyzeGeneration: null,
440
- })
441
-
442
- const loop = result.raw.raw
443
- const wallMin = (Date.now() - started) / 60000
444
-
445
- console.log('\n── DEV (train) results — what the improver saw ──')
446
- const baseDev = passStats(loop.baselineCampaign)
447
- console.log(
448
- ` gen -1 baseline ${serializeRolloutPolicy(structuralRolloutPolicyFromProfile(profile)!).padEnd(38)} DEV ${baseDev.passed}/${baseDev.scored} = ${pct(baseDev.rate)} (errored ${baseDev.errored})`,
449
- )
450
- for (const gen of loop.generations) {
451
- const surfaceByHash = new Map(gen.surfaces.map((s) => [s.surfaceHash, s]))
452
- const promotedHashes = new Set(gen.record.promoted)
453
- for (const cand of gen.record.candidates) {
454
- const s = surfaceByHash.get(cand.surfaceHash)
455
- const stats = s ? passStats(s.campaign) : undefined
456
- console.log(
457
- ` gen ${String(gen.record.generationIndex).padStart(2)} ${String(cand.label ?? '').padEnd(16)} ${String(s?.surface ?? '?').padEnd(38)} DEV ${stats ? `${stats.passed}/${stats.scored} = ${pct(stats.rate)} (errored ${stats.errored})` : '?'}${promotedHashes.has(cand.surfaceHash) ? ' [promoted]' : ''}`,
458
- )
459
- }
460
- }
461
- console.log(` training winner: ${String(loop.winnerSurface)}${loop.winnerLabel ? ` (${loop.winnerLabel})` : ''}`)
462
-
463
- console.log('\n── HELD-OUT gate — the library decides ──')
464
- const baseHold = passStats(loop.baselineOnHoldout)
465
- const winHold = passStats(loop.winnerOnHoldout)
466
- console.log(
467
- ` baseline on held-out : ${baseHold.passed}/${baseHold.scored} = ${pct(baseHold.rate)} (errored ${baseHold.errored})`,
468
- )
469
- console.log(
470
- ` winner on held-out : ${winHold.passed}/${winHold.scored} = ${pct(winHold.rate)} (errored ${winHold.errored})`,
471
- )
472
- console.log(` gate decision: ${result.gateDecision.toUpperCase()} (lift ${result.lift >= 0 ? '+' : ''}${result.lift.toFixed(3)})`)
473
- for (const reason of loop.gateResult.reasons) console.log(` reason: ${reason}`)
474
- for (const g of loop.gateResult.contributingGates) {
475
- console.log(` gate[${g.name}] passed=${g.passed} detail=${JSON.stringify(g.detail).slice(0, 300)}`)
476
- }
477
-
478
- console.log('\n── ship/hold outcome ──')
479
- console.log(` shipped: ${result.shipped}`)
480
- console.log(` profile AFTER : ${JSON.stringify(result.profile)}`)
481
- if (result.shipped) {
482
- console.log(` policy change : ${serializeRolloutPolicy(structuralRolloutPolicyFromProfile(profile)!)} → ${serializeRolloutPolicy(structuralRolloutPolicyFromProfile(result.profile)!)}`)
483
- } else {
484
- console.log(' policy change : none (gate held — baseline policy stays)')
485
- }
486
-
487
- console.log('\n── spend / provenance ──')
488
- console.log(
489
- ` cells ${spend.cells} · llm calls ${spend.llmCalls} · tokens ${spend.tokensIn} in / ${spend.tokensOut} out · router-priced $${spend.usd.toFixed(4)} · loop-reported $${result.raw.totalCostUsd.toFixed(4)}`,
490
- )
491
- console.log(` wall ${wallMin.toFixed(1)} min · runDir ${RUN_DIR} (cells.jsonl + campaign cells + loop provenance)`)
492
- reapContainers()
493
- process.exit(0)
494
- }
495
-
496
- main().catch((e) => {
497
- console.error(e)
498
- reapContainers()
499
- process.exit(1)
500
- })
@@ -1,164 +0,0 @@
1
- /**
2
- * Gen-5 lineage DAG wiring (SOTA adoption #3) — the hand-rolled parent fields
3
- * stop being the only ancestry record: every candidate becomes a
4
- * `LineageNode` in agent-eval's Lineage DAG (campaign/lineage.ts —
5
- * multi-parent merges, tracks, deterministic content-hash ids, append-only
6
- * fsLineageStore), every generation appends, and the library's
7
- * `heuristicGovernor` reads the graph to decide per-generation continuation
8
- * (extend the winner / branch on stall / merge frontier tips / prune). The
9
- * governor's decision is RECORDED in the round summary — the operator (or a
10
- * future automated outer loop) acts on it; this run never acts on it itself.
11
- *
12
- * Store location: `<outDir>/.evolve/lineage.jsonl` (an .evolve-compatible
13
- * path). The existing staircase rows are UNCHANGED and still written — the
14
- * observatory depends on them; the DAG is additive.
15
- *
16
- * Node semantics:
17
- * - the BASELINE incumbent is the root (track 'baseline', proposer
18
- * 'baseline'),
19
- * - configured Pareto parents (prior-run frontier commits) become nodes off
20
- * the root under their own tracks, so a merge can name them as parents,
21
- * - each evaluated candidate is a node under its proposer-named track:
22
- * single-parent (root) for regular authors, MULTI-PARENT (the Pareto
23
- * parent nodes) for the merge seat,
24
- * - score = combined resolved fraction; scoreVector = per-instance
25
- * fail-closed AND-verdicts over the lexicographically sorted instance ids.
26
- */
27
-
28
- import { join } from 'node:path'
29
- import {
30
- Lineage,
31
- fsLineageStore,
32
- heuristicGovernor,
33
- lineageNodeId,
34
- type GovernorOp,
35
- type LineageNode,
36
- } from '@tangle-network/agent-eval/campaign'
37
-
38
- export const LINEAGE_STORE_RELPATH = join('.evolve', 'lineage.jsonl')
39
-
40
- export interface LineageCandidateInput {
41
- /** Proposer name — the node's track and proposer. */
42
- label: string
43
- /** Loops commit; null (never-committed candidate) is skipped with a note. */
44
- commit: string | null
45
- resolvedCount: number
46
- /** Per-instance AND-verdicts (missing instance = fail-closed false). */
47
- verdicts: Record<string, boolean>
48
- merge: boolean
49
- /** Staircase verdict — recorded as the node's rationale. */
50
- verdict: string
51
- }
52
-
53
- export interface LineageRecordArgs {
54
- outDir: string
55
- runId: string
56
- instances: readonly string[]
57
- baseline: { commit: string; resolvedCount: number; verdicts: Record<string, boolean> }
58
- paretoParents: Array<{ label: string; commit: string; resolvedInstances: string[] }>
59
- candidates: LineageCandidateInput[]
60
- }
61
-
62
- export interface LineageRecordResult {
63
- path: string
64
- /** Node ids appended by THIS call (idempotent re-runs append zero). */
65
- appended: string[]
66
- /** Nodes skipped because the candidate never became a commit. */
67
- skipped: string[]
68
- governor: GovernorOp
69
- nodesTotal: number
70
- }
71
-
72
- const scoreVectorOf = (instances: readonly string[], verdicts: Record<string, boolean>): number[] =>
73
- [...instances].sort().map((iid) => (verdicts[iid] === true ? 1 : 0))
74
-
75
- const scoreOf = (resolvedCount: number, instanceCount: number): number =>
76
- instanceCount > 0 ? resolvedCount / instanceCount : 0
77
-
78
- /** Record one generation into the lineage DAG and ask the governor for the
79
- * continuation decision. Append-only + idempotent: node ids are content
80
- * hashes, so re-recording an identical generation appends nothing. */
81
- export async function recordLineageGeneration(args: LineageRecordArgs): Promise<LineageRecordResult> {
82
- const path = join(args.outDir, LINEAGE_STORE_RELPATH)
83
- const store = fsLineageStore(path)
84
- const lineage = await store.load()
85
- const appended: string[] = []
86
- const skipped: string[] = []
87
-
88
- const ensure = async (input: Parameters<Lineage['addNode']>[0]): Promise<LineageNode> => {
89
- const id = lineageNodeId({
90
- parentIds: input.parentIds,
91
- track: input.track,
92
- surface: input.surface,
93
- proposer: input.proposer,
94
- })
95
- const existed = lineage.all().some((n) => n.id === id)
96
- const node = lineage.addNode(input)
97
- if (!existed) {
98
- await store.append(node)
99
- appended.push(node.id)
100
- }
101
- return node
102
- }
103
-
104
- const n = args.instances.length
105
- const root = await ensure({
106
- parentIds: [],
107
- track: 'baseline',
108
- surface: `loops@${args.baseline.commit}`,
109
- score: scoreOf(args.baseline.resolvedCount, n),
110
- scoreVector: scoreVectorOf(args.instances, args.baseline.verdicts),
111
- proposer: 'baseline',
112
- })
113
-
114
- // Prior-run frontier commits become referenceable parent nodes.
115
- const parentNodeByLabel = new Map<string, LineageNode>()
116
- for (const parent of args.paretoParents) {
117
- const verdicts: Record<string, boolean> = {}
118
- for (const iid of parent.resolvedInstances) verdicts[iid] = true
119
- const node = await ensure({
120
- parentIds: [root.id],
121
- track: parent.label,
122
- surface: `loops@${parent.commit}`,
123
- score: scoreOf(parent.resolvedInstances.length, n),
124
- scoreVector: scoreVectorOf(args.instances, verdicts),
125
- proposer: parent.label,
126
- })
127
- parentNodeByLabel.set(parent.label, node)
128
- }
129
-
130
- for (const cand of args.candidates) {
131
- if (cand.commit === null) {
132
- skipped.push(cand.label)
133
- continue
134
- }
135
- const parentIds = cand.merge
136
- ? [...parentNodeByLabel.values()].map((p) => p.id)
137
- : [root.id]
138
- if (cand.merge && parentIds.length < 2) {
139
- throw new Error(
140
- `lineage: merge candidate ${cand.label} needs >=2 pareto parent nodes, got ${parentIds.length}`,
141
- )
142
- }
143
- await ensure({
144
- parentIds,
145
- track: cand.label,
146
- surface: `loops@${cand.commit}`,
147
- score: scoreOf(cand.resolvedCount, n),
148
- scoreVector: scoreVectorOf(args.instances, cand.verdicts),
149
- proposer: cand.merge ? 'merge' : cand.label,
150
- rationale: `run ${args.runId}: ${cand.verdict}`,
151
- })
152
- }
153
-
154
- // The continuation decision — recorded, not acted on here. budgetRemaining=1:
155
- // the operator approves one next generation at a time.
156
- const governor = await heuristicGovernor().decide({
157
- lineage,
158
- step: lineage.all().length,
159
- budgetRemaining: 1,
160
- prunedTracks: [],
161
- })
162
-
163
- return { path, appended, skipped, governor, nodesTotal: lineage.all().length }
164
- }