@tangle-network/agent-bench 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +7 -0
- package/package.json +4 -4
- package/scripts/trata-hedge/README.md +6 -5
- package/src/gate.ts +1 -1
- package/src/hev-eval.mts +5 -2
- package/src/hev-improve.mts +118 -73
- package/src/official-optimizer-config.mts +89 -0
- package/src/official-optimizer-config.test.mts +88 -0
- package/src/profiles.ts +2 -2
- package/src/rollout-ledger/backfill-swe-arena.test.mts +28 -24
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +1 -4
- package/src/swe-arena/activation.test.mts +10 -13
- package/src/swe-arena/gepa-seat.mts +425 -131
- package/src/swe-arena/gepa-seat.test.mts +524 -100
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/outer-loop.mts +103 -76
- package/src/swe-arena/proposer-fanout.mts +51 -36
- package/src/swe-arena/proposer-fanout.test.mts +0 -1
- package/src/swe-arena/proposer-provenance.mts +11 -16
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -0
- package/src/swe-code-improve.mts +24 -25
- package/src/swe-improve.mts +129 -96
- package/src/swe-local-proof.mts +6 -1
- package/src/swe-stream.mts +4 -2
- package/src/tb-container-executor.test.mts +30 -6
- package/src/tb-supervisor-sidecar.mts +2 -1
- package/src/trata-gepa.mts +182 -245
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- package/src/swe-arena/lineage-record.mts +0 -164
- package/src/swe-arena/lineage-record.test.mts +0 -115
|
@@ -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
|
-
}
|