@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
@@ -0,0 +1,64 @@
1
+ import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { afterEach, describe, expect, it } from 'vitest'
5
+ import {
6
+ fileTreeImplementationRef,
7
+ pythonDistributionImplementationRef,
8
+ } from './implementation-ref.ts'
9
+
10
+ describe('implementation refs', () => {
11
+ const roots: string[] = []
12
+
13
+ afterEach(async () => {
14
+ await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
15
+ })
16
+
17
+ it('is stable across creation order and changes with file content or symlink targets', async () => {
18
+ const first = await mkdtemp(join(tmpdir(), 'implementation-ref-a-'))
19
+ const second = await mkdtemp(join(tmpdir(), 'implementation-ref-b-'))
20
+ roots.push(first, second)
21
+ await mkdir(join(first, 'nested'))
22
+ await writeFile(join(first, 'nested', 'b.ts'), 'export const b = 2\n')
23
+ await writeFile(join(first, 'a.ts'), 'export const a = 1\n')
24
+ await symlink('a.ts', join(first, 'entry.ts'))
25
+ await writeFile(join(second, 'a.ts'), 'export const a = 1\n')
26
+ await symlink('a.ts', join(second, 'entry.ts'))
27
+ await mkdir(join(second, 'nested'))
28
+ await writeFile(join(second, 'nested', 'b.ts'), 'export const b = 2\n')
29
+
30
+ const initial = await fileTreeImplementationRef(first)
31
+ expect(await fileTreeImplementationRef(second)).toBe(initial)
32
+
33
+ await writeFile(join(second, 'nested', 'b.ts'), 'export const b = 3\n')
34
+ expect(await fileTreeImplementationRef(second)).not.toBe(initial)
35
+ await writeFile(join(second, 'nested', 'b.ts'), 'export const b = 2\n')
36
+ await rm(join(second, 'entry.ts'))
37
+ await symlink('nested/b.ts', join(second, 'entry.ts'))
38
+ expect(await fileTreeImplementationRef(second)).not.toBe(initial)
39
+ })
40
+
41
+ it('binds a Python distribution to its complete manifest', async () => {
42
+ const runPython = async (_script: string, args: string[]): Promise<string> =>
43
+ `import warning\n${JSON.stringify({
44
+ distribution: args[0],
45
+ version: '1.0.0',
46
+ python: '3.12.4',
47
+ files: [{ path: '__init__.py', sha256: 'a'.repeat(64) }],
48
+ })}`
49
+ const original = await pythonDistributionImplementationRef('swebench', runPython)
50
+ const changed = await pythonDistributionImplementationRef(
51
+ 'swebench',
52
+ async (_script, args) =>
53
+ JSON.stringify({
54
+ distribution: args[0],
55
+ version: '1.0.1',
56
+ python: '3.12.4',
57
+ files: [{ path: '__init__.py', sha256: 'b'.repeat(64) }],
58
+ }),
59
+ )
60
+
61
+ expect(original).toMatch(/^sha256:[a-f0-9]{64}$/)
62
+ expect(changed).not.toBe(original)
63
+ })
64
+ })
@@ -0,0 +1,62 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { lstat, readFile, readdir, readlink, realpath } from 'node:fs/promises'
3
+ import { join, relative } from 'node:path'
4
+ import { canonicalCandidateDigest } from '@tangle-network/agent-interface'
5
+
6
+ const sha256Content = (value: Uint8Array): string =>
7
+ `sha256:${createHash('sha256').update(value).digest('hex')}`
8
+
9
+ export async function fileTreeImplementationRef(root: string): Promise<string> {
10
+ const canonicalRoot = await realpath(root)
11
+ const files: Array<{ path: string; sha256?: string; symlink?: string }> = []
12
+
13
+ const walk = async (directory: string): Promise<void> => {
14
+ const entries = await readdir(directory, { withFileTypes: true })
15
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
16
+ for (const entry of entries) {
17
+ const path = join(directory, entry.name)
18
+ const relativePath = relative(canonicalRoot, path)
19
+ if (entry.isDirectory()) {
20
+ await walk(path)
21
+ } else if (entry.isSymbolicLink()) {
22
+ files.push({ path: relativePath, symlink: await readlink(path) })
23
+ } else if (entry.isFile() || (await lstat(path)).isFile()) {
24
+ files.push({ path: relativePath, sha256: sha256Content(await readFile(path)) })
25
+ }
26
+ }
27
+ }
28
+
29
+ await walk(canonicalRoot)
30
+ return canonicalCandidateDigest(files)
31
+ }
32
+
33
+ export async function pythonDistributionImplementationRef(
34
+ distribution: string,
35
+ runPython: (script: string, args: string[]) => Promise<string>,
36
+ ): Promise<string> {
37
+ const output = await runPython(
38
+ [
39
+ 'import hashlib, importlib, importlib.metadata, json, pathlib, sys',
40
+ 'name = sys.argv[1]',
41
+ 'module = importlib.import_module(name)',
42
+ 'root = pathlib.Path(module.__file__).resolve().parent',
43
+ 'files = []',
44
+ 'for path in sorted(p for p in root.rglob("*") if p.is_file()):',
45
+ ' if "__pycache__" in path.parts or path.suffix == ".pyc":',
46
+ ' continue',
47
+ ' files.append({"path": str(path.relative_to(root)), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()})',
48
+ 'try:',
49
+ ' version = importlib.metadata.version(name)',
50
+ 'except importlib.metadata.PackageNotFoundError:',
51
+ ' version = None',
52
+ 'print(json.dumps({"distribution": name, "version": version, "python": sys.version, "files": files}, sort_keys=True))',
53
+ ].join('\n'),
54
+ [distribution],
55
+ )
56
+ const line = output.trim().split('\n').at(-1)
57
+ if (line === undefined) {
58
+ throw new Error(`python distribution ${distribution} returned no implementation manifest`)
59
+ }
60
+ const manifest = JSON.parse(line) as unknown
61
+ return canonicalCandidateDigest(manifest)
62
+ }
@@ -17,7 +17,7 @@
17
17
  * `rawTraceDistiller` path-context so the coding agent also greps the raw
18
18
  * traces itself (`rawTraceContext: true` names the mechanism; an explicit
19
19
  * `analyzeGeneration` wins, so the distiller is composed in directly).
20
- * (b) PROPOSE — `improvementDriver` + a change-space-constrained
20
+ * (b) PROPOSE — Runtime's code candidate driver + a change-space-constrained
21
21
  * `agenticGenerator` edit an isolated git worktree of loops. The DECLARED
22
22
  * CHANGE-SPACE is enforced twice: in the generator's verifier (feedback →
23
23
  * next shot) and fail-closed in the dispatch below (an out-of-space
@@ -72,6 +72,7 @@ import {
72
72
  type Verifier,
73
73
  } from '@tangle-network/agent-runtime'
74
74
  import { runLocalHarness } from '@tangle-network/agent-runtime/mcp'
75
+ import { canonicalCandidateDigest } from '@tangle-network/agent-interface'
75
76
  import { makeFinding } from '@tangle-network/agent-eval'
76
77
  import {
77
78
  FsLabeledScenarioStore,
@@ -85,7 +86,12 @@ import {
85
86
  type Scenario,
86
87
  } from '@tangle-network/agent-eval/campaign'
87
88
  import type { CostLedgerHandle } from '@tangle-network/agent-eval'
89
+ import { runVenvPython } from '../benchmarks/_harness.ts'
88
90
  import { createSweBenchAdapter } from '../benchmarks/swe-bench.ts'
91
+ import {
92
+ fileTreeImplementationRef,
93
+ pythonDistributionImplementationRef,
94
+ } from './implementation-ref.ts'
89
95
  import {
90
96
  baselineDriftWarnings,
91
97
  cellsFromCampaign,
@@ -152,7 +158,6 @@ import {
152
158
  type ScoreSplit,
153
159
  type ScoreSplitConfig,
154
160
  } from './score-split.mts'
155
- import { recordLineageGeneration, type LineageCandidateInput } from './lineage-record.mts'
156
161
  import {
157
162
  reportSupervisorRound,
158
163
  writeSupervisorRunReportSafe,
@@ -644,7 +649,7 @@ export interface OuterLoopConfig {
644
649
  * When set, `populationSize` MUST equal `proposers.length` (one candidate
645
650
  * slot per proposer — enforced at launch). Unset = the legacy
646
651
  * single-author generator (`proposerHarness` + bare invocation).
647
- * GEN-6: a spec with `engine` set is a GEPA seat (gepa-seat.mts) the
652
+ * A spec with `engine` set is a GEPA seat (gepa-seat.mts); the
648
653
  * agent-eval external-GEPA adapter optimizes ONE change-space file as a
649
654
  * string against the pre-filter smoke cell; requires `prefilter.enabled`. */
650
655
  proposers?: ProposerSpec[]
@@ -685,10 +690,6 @@ export interface OuterLoopConfig {
685
690
  * emit tangle.rollout.v1 lines live after each cell judges, with label-v2
686
691
  * rewards. Default path: <outDir>/rollout-ledger.jsonl. */
687
692
  rolloutLedger?: { enabled: boolean; path?: string; opencodeDb?: string }
688
- /** GEN-5 lineage DAG (lineage-record.mts): record every candidate as a
689
- * LineageNode at <outDir>/.evolve/lineage.jsonl and put the governor's
690
- * continuation decision in the round summary. */
691
- lineage?: boolean
692
693
  /** GEN-5 evidence map: prior run outDirs whose arm-runs/judge/candidate
693
694
  * evidence the authors may mine (rendered into the evidence index). */
694
695
  priorEvidenceDirs?: string[]
@@ -970,9 +971,6 @@ export function defaultGen4Config(
970
971
  // never-fired mechanisms are quarantined even on an improved score.
971
972
  // 4. SETTLE-TIME ROLLOUT LEDGER — tangle.rollout.v1 lines live per cell,
972
973
  // label v2 (contribution-aware workers, baseline-relative proposers).
973
- // 5. LINEAGE DAG — agent-eval Lineage at <outDir>/.evolve/lineage.jsonl +
974
- // governor continuation decision in the round summary; staircase rows
975
- // unchanged (observatory contract).
976
974
  // ---------------------------------------------------------------------------
977
975
 
978
976
  export function defaultGen5Config(
@@ -989,7 +987,6 @@ export function defaultGen5Config(
989
987
  briefing: AUTHOR_BRIEFING_VERSION,
990
988
  activationGate: true,
991
989
  rolloutLedger: { enabled: true },
992
- lineage: true,
993
990
  priorEvidenceDirs: [join(hh, 'gen4'), join(hh, 'gen3')],
994
991
  }
995
992
  }
@@ -1537,11 +1534,24 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
1537
1534
  const smokeRunner: SmokeRunner | undefined =
1538
1535
  smokeIid === null
1539
1536
  ? undefined
1540
- : async ({ scratchPath, generation, proposer, costLedger }): Promise<SmokeVerdict> => {
1537
+ : async ({
1538
+ scratchPath,
1539
+ generation,
1540
+ proposer,
1541
+ evaluationKey,
1542
+ costLedger,
1543
+ }): Promise<SmokeVerdict> => {
1541
1544
  const iid = smokeIid
1545
+ if (!/^[a-zA-Z0-9_-]+$/.test(evaluationKey)) {
1546
+ throw new Error(`outer-loop: invalid smoke evaluation key ${JSON.stringify(evaluationKey)}`)
1547
+ }
1542
1548
  const requireResolved = config.prefilter?.requireResolved === true
1543
1549
  const entry = images[iid]!
1544
- const armOutDir = join(config.outDir, 'prefilter-smoke', `gen${generation}-${proposer.name}`)
1550
+ const armOutDir = join(
1551
+ config.outDir,
1552
+ 'prefilter-smoke',
1553
+ `gen${generation}-${proposer.name}-${evaluationKey}`,
1554
+ )
1545
1555
  const nm = join(scratchPath, 'node_modules')
1546
1556
  let linked = false
1547
1557
  const t0 = Date.now()
@@ -1600,7 +1610,7 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
1600
1610
  const paid = await costLedger.runPaidCall({
1601
1611
  channel: 'agent',
1602
1612
  phase: 'search.prefilter',
1603
- actor: `prefilter-smoke:${iid}:g${generation}:${proposer.name}`,
1613
+ actor: `prefilter-smoke:${iid}:g${generation}:${proposer.name}:${evaluationKey}`,
1604
1614
  model: config.arm.workerModel,
1605
1615
  execute: runWork,
1606
1616
  receipt: ({ armRes }) => {
@@ -1636,7 +1646,7 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
1636
1646
  resolved: outcome.resolved,
1637
1647
  patchLines: outcome.armRes.patch_lines,
1638
1648
  wallS,
1639
- // GEN-6: the GEPA seat's inner-score tiebreak.
1649
+ // The GEPA seat's inner-score tiebreak.
1640
1650
  verifyPass: outcome.armRes.verify_pass,
1641
1651
  }
1642
1652
  await mkdir(armOutDir, { recursive: true })
@@ -1661,6 +1671,79 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
1661
1671
  }
1662
1672
  }
1663
1673
 
1674
+ let runnerImplementationRef: string | undefined
1675
+ let judgeImplementationRef: string | undefined
1676
+ const hasGepaSeat = config.proposers?.some((proposer) => proposer.engine !== undefined) === true
1677
+ if (hasGepaSeat && smokeIid !== null && smokeRunner !== undefined) {
1678
+ const runtimeRoot = fileURLToPath(new URL('../../..', import.meta.url))
1679
+ const benchRoot = fileURLToPath(new URL('../..', import.meta.url))
1680
+ const sourceCommit = (
1681
+ await runOk('git', ['-C', config.loopsRepo, 'rev-parse', 'HEAD'])
1682
+ ).stdout.trim()
1683
+ const entry = images[smokeIid]!
1684
+ const verifyScript = await readFile(join(config.verifyDir, `${smokeIid}.sh`), 'utf8')
1685
+ const implementationArtifacts = {
1686
+ benchSource: await fileTreeImplementationRef(join(benchRoot, 'src')),
1687
+ runtimeSource: await fileTreeImplementationRef(join(runtimeRoot, 'src')),
1688
+ runtimeDist: await fileTreeImplementationRef(join(runtimeRoot, 'dist')),
1689
+ packageFiles: canonicalCandidateDigest({
1690
+ runtimePackage: await readFile(join(runtimeRoot, 'package.json'), 'utf8'),
1691
+ benchPackage: await readFile(join(benchRoot, 'package.json'), 'utf8'),
1692
+ lockfile: await readFile(join(runtimeRoot, 'pnpm-lock.yaml'), 'utf8'),
1693
+ }),
1694
+ swebench: await pythonDistributionImplementationRef(
1695
+ 'swebench',
1696
+ (script, args) => runVenvPython(script, args),
1697
+ ),
1698
+ }
1699
+ const runnerSourceRef = canonicalCandidateDigest({
1700
+ benchSource: implementationArtifacts.benchSource,
1701
+ runtimeSource: implementationArtifacts.runtimeSource,
1702
+ runtimeDist: implementationArtifacts.runtimeDist,
1703
+ packageFiles: implementationArtifacts.packageFiles,
1704
+ })
1705
+ const judgeSourceRef = canonicalCandidateDigest({
1706
+ benchSource: implementationArtifacts.benchSource,
1707
+ packageFiles: implementationArtifacts.packageFiles,
1708
+ swebench: implementationArtifacts.swebench,
1709
+ })
1710
+ runnerImplementationRef = canonicalCandidateDigest({
1711
+ implementation: 'swe-arena-smoke-runner',
1712
+ sourceCommit,
1713
+ sourceRef: runnerSourceRef,
1714
+ smoke: {
1715
+ iid: smokeIid,
1716
+ image: entry.image,
1717
+ baseCommit: entry.base_commit,
1718
+ problemStatement: problemById.get(smokeIid)!,
1719
+ verifyScript,
1720
+ requireResolved: config.prefilter?.requireResolved === true,
1721
+ },
1722
+ arm: {
1723
+ name: config.armName,
1724
+ workerModel: config.arm.workerModel,
1725
+ driverModel: config.arm.driverModel,
1726
+ budget: config.arm.budget,
1727
+ maxSandboxes: config.arm.maxSandboxes,
1728
+ maxUsd: config.arm.maxUsd,
1729
+ maxDepth: config.arm.maxDepth,
1730
+ timeoutMs: config.arm.timeoutMs,
1731
+ envKnobs: config.arm.envKnobs ?? null,
1732
+ },
1733
+ dispatchTimeoutMs: config.dispatchTimeoutMs,
1734
+ capacityModel: config.capacityModel ?? null,
1735
+ gateWaitCeilingMs: config.gateWaitCeilingMs ?? null,
1736
+ excludes,
1737
+ })
1738
+ judgeImplementationRef = canonicalCandidateDigest({
1739
+ implementation: 'serialized-swebench-judge',
1740
+ sourceCommit,
1741
+ sourceRef: judgeSourceRef,
1742
+ timeoutMs: config.judgeTimeoutMs ?? JUDGE_TIMEOUT_FLOOR_MS,
1743
+ cacheLevel: 'instance',
1744
+ })
1745
+ }
1746
+
1664
1747
  // ── dispatch: one (surface × scenario) cell ──────────────────────────
1665
1748
  const agent = async (surface: MutableSurface, scenario: Scenario, ctx: DispatchContext): Promise<R4Artifact> => {
1666
1749
  const cs = asCodeSurface(surface)
@@ -2041,9 +2124,11 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2041
2124
  config.proposers !== undefined
2042
2125
  ? fanOutLoopsGenerator(config, {
2043
2126
  ...(smokeRunner ? { smokeRunner } : {}),
2127
+ ...(runnerImplementationRef ? { runnerImplementationRef } : {}),
2128
+ ...(judgeImplementationRef ? { judgeImplementationRef } : {}),
2044
2129
  ...(paretoParents.length > 0 ? { parents: paretoParents } : {}),
2045
2130
  ...(briefingCtx !== undefined ? { briefing: briefingCtx } : {}),
2046
- // GEN-6: the GEPA seat's inner evaluator rides the SAME public-only
2131
+ // The GEPA seat's inner evaluator uses the same public-only
2047
2132
  // smoke instance; the split guards the never-surfaced invariant at
2048
2133
  // the bridge boundary too.
2049
2134
  ...(smokeIid !== null ? { smokeInstanceId: smokeIid } : {}),
@@ -2056,10 +2141,9 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2056
2141
  // ── the improve() call: the optimizer seat ───────────────────────────
2057
2142
  // Typed from improve()'s own parameter: the monorepo hoists two
2058
2143
  // agent-interface majors, so a nominal import can resolve to the wrong one.
2059
- const profile = { name: 'loops-pi-supervisor' } as Parameters<typeof improve>[0]
2060
2144
  signal?.throwIfAborted()
2061
2145
  log(`round ${config.round} runId=${runId}: improve(surface:'code') over ${config.loopsRepo}@${config.loopsBaseRef}`)
2062
- const result = await improve<Scenario, R4Artifact>(profile, [], {
2146
+ const result = await improve<Scenario, R4Artifact>({
2063
2147
  surface: 'code',
2064
2148
  // analyzeGeneration wins over this flag; the composite above embeds
2065
2149
  // rawTraceDistiller directly so the raw-trace mechanism stays active.
@@ -2143,10 +2227,8 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2143
2227
  }
2144
2228
  }
2145
2229
 
2146
- // Collected per-candidate facts for the gen-5 machinery (activation by
2147
- // surface hash for the winner brief, lineage nodes, proposer v2 rewards).
2230
+ // Collected per-candidate facts for activation and proposer rewards.
2148
2231
  const activationBySurface = new Map<string, ActivationRecord>()
2149
- const lineageCandidates: LineageCandidateInput[] = []
2150
2232
  interface ProposerOutcomeFact {
2151
2233
  generation: number
2152
2234
  candidateIndex: number
@@ -2290,14 +2372,6 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2290
2372
  })
2291
2373
 
2292
2374
  const label = cand.label ?? cs?.candidateCommit?.slice(0, 10) ?? cand.surfaceHash.slice(0, 10)
2293
- lineageCandidates.push({
2294
- label,
2295
- commit: cs?.candidateCommit ?? null,
2296
- resolvedCount: candResolved,
2297
- verdicts,
2298
- merge: config.proposers?.find((p) => p.name === cand.label)?.merge === true,
2299
- verdict,
2300
- })
2301
2375
  proposerFacts.push({
2302
2376
  generation: g,
2303
2377
  candidateIndex: candIndex,
@@ -2386,42 +2460,6 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2386
2460
  }
2387
2461
  }
2388
2462
 
2389
- // GEN-5 lineage DAG: record baseline root + pareto parents + every
2390
- // evaluated candidate (multi-parent for the merge seat) at the
2391
- // .evolve-compatible store, and ask the governor for the continuation
2392
- // decision (recorded below — never acted on inside this run).
2393
- let lineageResult: Awaited<ReturnType<typeof recordLineageGeneration>> | null = null
2394
- if (config.lineage === true) {
2395
- try {
2396
- const baselineCommit =
2397
- baselineCells.find((c) => c.artifact !== null && c.artifact.kind === 'swe-arm')?.artifact?.commit ??
2398
- config.loopsBaseRef
2399
- lineageResult = await recordLineageGeneration({
2400
- outDir: config.outDir,
2401
- runId,
2402
- instances: config.instances,
2403
- baseline: {
2404
- commit: baselineCommit,
2405
- resolvedCount: measuredBaselineCount,
2406
- verdicts: instanceVerdictsFromCells(baselineCells, config.instances, reps),
2407
- },
2408
- paretoParents: (config.paretoParents ?? []).map((p) => ({
2409
- label: p.label,
2410
- commit: p.commit,
2411
- resolvedInstances: p.resolvedInstances,
2412
- })),
2413
- candidates: lineageCandidates,
2414
- })
2415
- log(
2416
- `lineage: ${lineageResult.appended.length} node(s) appended (total ${lineageResult.nodesTotal}) → ` +
2417
- `${lineageResult.path}; governor decision: ${JSON.stringify(lineageResult.governor)}` +
2418
- `${lineageResult.skipped.length > 0 ? `; skipped (no commit): ${lineageResult.skipped.join(', ')}` : ''}`,
2419
- )
2420
- } catch (cause) {
2421
- log(`lineage: recording FAILED: ${(cause as Error).message}`)
2422
- }
2423
- }
2424
-
2425
2463
  const winnerSurface = result.raw.winner.surface
2426
2464
  const winnerCs =
2427
2465
  typeof winnerSurface === 'object' && winnerSurface !== null && winnerSurface.kind === 'code'
@@ -2547,17 +2585,6 @@ export async function runRound(config: OuterLoopConfig, signal?: AbortSignal): P
2547
2585
  : { version: AUTHOR_BRIEFING_VERSION, indexPath: briefingCtx.indexPath, textSource: briefingCtx.briefingSource },
2548
2586
  // GEN-5: settle-time rollout ledger location (tangle.rollout.v1, label v2).
2549
2587
  rolloutLedger: settleCapture === null ? null : { path: settleCapture.path, capture: 'settle-time', labels: 'v2' },
2550
- // GEN-5: lineage DAG + the governor's recorded continuation decision.
2551
- lineage:
2552
- lineageResult === null
2553
- ? null
2554
- : {
2555
- path: lineageResult.path,
2556
- nodesTotal: lineageResult.nodesTotal,
2557
- appended: lineageResult.appended.length,
2558
- skipped: lineageResult.skipped,
2559
- governor: lineageResult.governor,
2560
- },
2561
2588
  // Honest run-wide spend from the lib's CostLedger: per-channel rollups
2562
2589
  // (agent = arm cells, judge = official-judge calls, driver = proposer
2563
2590
  // shots), token totals, and accounting-completeness flags.
@@ -64,9 +64,20 @@ import {
64
64
  parseActivationPredicate,
65
65
  } from './activation.mts'
66
66
  import { briefingPromptSection, type BriefingContext } from './briefing.mts'
67
- import { gepaSeatAuthor, isGepaSeat, validateGepaSeat, type GepaMethodFactory } from './gepa-seat.mts'
67
+ import {
68
+ gepaSeatAuthor,
69
+ isGepaSeat,
70
+ validateGepaSeat,
71
+ type GepaMethodFactory,
72
+ type GepaSeatDeps,
73
+ } from './gepa-seat.mts'
68
74
  import type { ScoreSplit } from './score-split.mts'
69
75
  import { run, runOk } from './proc.ts'
76
+ import {
77
+ createDetachedWorktree,
78
+ pruneDetachedWorktrees,
79
+ removeDetachedWorktree,
80
+ } from './scratch-worktree.ts'
70
81
 
71
82
  // ---------------------------------------------------------------------------
72
83
  // Config types.
@@ -78,7 +89,7 @@ export interface ProposerSpec {
78
89
  /** Path to an `AgentProfile` JSON. Absolute, or relative to this module's
79
90
  * `profiles/` directory. Omitted = bare profile (legacy invocation). */
80
91
  profile?: string
81
- /** Required for harness-authored seats. ABSENT on a GEN-6 engine seat
92
+ /** Required for harness-authored seats. Absent on an engine seat
82
93
  * (`engine` set) — enforced both ways at generator construction. */
83
94
  harness?: 'claude' | 'codex' | 'opencode'
84
95
  /** GEN-4 pinned model id, threaded to the harness CLI as `-m <model>` via
@@ -98,21 +109,20 @@ export interface ProposerSpec {
98
109
  /** Which diagnosis findings this proposer sees. Protocol/steering and
99
110
  * raw-trace-context findings always pass through. Default 'all'. */
100
111
  diagnosisSlice?: 'all' | 'mechanics' | 'prompts'
101
- /** GEN-6 GEPA seat: this seat is an ENGINE invocation (agent-eval's
112
+ /** GEPA seat: this seat is an engine invocation (agent-eval's
102
113
  * external-GEPA adapter), not a harness CLI. `gepa` = one bounded engine
103
- * run; `omni` = GEPA's published best-of-then-continue shape. Validation +
114
+ * run; `omni` = GEPA's official Omni recipe. Validation +
104
115
  * authoring live in gepa-seat.mts. */
105
116
  engine?: 'gepa' | 'omni'
106
- /** GEN-6 GEPA seat: the ONE repo-relative change-space file GEPA optimizes
117
+ /** GEPA seat: the one repo-relative change-space file GEPA optimizes
107
118
  * as a string; the rest of the loops repo stays at the incumbent commit. */
108
119
  surface?: string
109
- /** GEN-6 GEPA seat: total inner-evaluation budget (each inner call is one
120
+ /** GEPA seat: total inner-evaluation budget (each inner call is one
110
121
  * real smoke arm cell). Default 10. */
111
122
  maxMetricCalls?: number
112
- /** GEN-6 GEPA seat: requested GEPA proposer spend cap in USD (the adapter
113
- * reports GEPA's own model/CLI spend without an agent-eval receipt). */
123
+ /** GEPA seat: hard cap for metered optimizer-model spend. */
114
124
  maxProposerCostUsd?: number
115
- /** GEN-6 GEPA seat: python executable for the bridge. Default 'python3'. */
125
+ /** GEPA seat: Python executable for the bridge. Default 'python3'. */
116
126
  python?: string
117
127
  }
118
128
 
@@ -137,8 +147,8 @@ export interface SmokeVerdict {
137
147
  resolved: boolean | null
138
148
  patchLines: number
139
149
  wallS: number
140
- /** Committed verify fixture passed (GEN-6: the GEPA seat's inner-score
141
- * tiebreak). Absent on errored smokes and pre-gen-6 records. */
150
+ /** Committed verify fixture passed, used as the GEPA seat's inner-score
151
+ * tiebreak. Absent on errored smokes and older records. */
142
152
  verifyPass?: boolean
143
153
  }
144
154
 
@@ -146,6 +156,7 @@ export type SmokeRunner = (args: {
146
156
  scratchPath: string
147
157
  generation: number
148
158
  proposer: ProposerSpec
159
+ evaluationKey: string
149
160
  costLedger?: CostLedgerHandle
150
161
  }) => Promise<SmokeVerdict>
151
162
 
@@ -437,21 +448,6 @@ export function proposerShotHooks(opts: {
437
448
  // smoke runner link one in temporarily when they need it).
438
449
  // ---------------------------------------------------------------------------
439
450
 
440
- async function addAuthoringWorktree(loopsRepo: string, commit: string, dest: string): Promise<void> {
441
- await run('git', ['-C', loopsRepo, 'worktree', 'remove', '--force', '--', dest])
442
- await rm(dest, { recursive: true, force: true })
443
- await run('git', ['-C', loopsRepo, 'worktree', 'prune'])
444
- await runOk('git', ['-C', loopsRepo, 'worktree', 'add', '--detach', dest, commit])
445
- }
446
-
447
- async function removeAuthoringWorktree(loopsRepo: string, dest: string): Promise<void> {
448
- const res = await run('git', ['-C', loopsRepo, 'worktree', 'remove', '--force', '--', dest])
449
- if (res.code !== 0) {
450
- await rm(dest, { recursive: true, force: true })
451
- await run('git', ['-C', loopsRepo, 'worktree', 'prune'])
452
- }
453
- }
454
-
455
451
  const sanitize = (s: string): string => s.replace(/[^a-zA-Z0-9_-]/g, '_')
456
452
 
457
453
  // ---------------------------------------------------------------------------
@@ -480,21 +476,26 @@ export type AuthorFn = (
480
476
  export interface FanOutDeps {
481
477
  author?: AuthorFn
482
478
  smokeRunner?: SmokeRunner
479
+ /** Immutable digest of the smoke runner plus every captured execution input. */
480
+ runnerImplementationRef?: string
481
+ /** Immutable digest of the official judge implementation and configuration. */
482
+ judgeImplementationRef?: string
483
483
  /** GEN-4: materialized Pareto parents seeded into every author's prompt (and
484
484
  * the merge seat's explicit merge input). Empty/omitted = gen-3 behavior. */
485
485
  parents?: ParetoParentContext[]
486
486
  /** GEN-5: MAP+TOOLBOX briefing context threaded into every author prompt. */
487
487
  briefing?: BriefingContext
488
- /** GEN-6: the resolved PUBLIC smoke instance the GEPA seat's inner
488
+ /** The resolved public smoke instance used by the GEPA seat's inner
489
489
  * evaluator target. Required (with `smokeRunner`) when a gepa seat is
490
490
  * configured and no custom `author` is injected. */
491
491
  smokeInstanceId?: string
492
- /** GEN-6: the gen-5 score split; private instance ids never reach the GEPA
492
+ /** The score split; private instance ids never reach the GEPA
493
493
  * bridge (gepa-seat.mts asserts, fail-closed). Null/omitted = no split. */
494
494
  scoreSplit?: Pick<ScoreSplit, 'privateInstances'> | null
495
- /** GEN-6 test seam: the adapter factory (default: checked dynamic import of
496
- * agent-eval's gepaOptimizationMethod, loud when the install predates it). */
495
+ /** GEPA method override for tests. Default: agent-eval's official GEPA method. */
497
496
  gepaMethodFactory?: GepaMethodFactory
497
+ /** Explicit GEPA optimizer model override, primarily for isolated tests. */
498
+ gepaOptimizer?: GepaSeatDeps['optimizer']
498
499
  log?: (msg: string) => void
499
500
  }
500
501
 
@@ -509,7 +510,7 @@ function defaultAuthor(config: OuterLoopConfig, deps: FanOutDeps): AuthorFn {
509
510
  // its own profile, lens prompt, and shot-receipt home.
510
511
  const inners = new Map<string, CandidateGenerator>()
511
512
  const ledgers = new Map<string, { ledger?: CostLedgerHandle; phase?: string }>()
512
- // GEN-6: the GEPA seat authors through the agent-eval adapter, not a
513
+ // The GEPA seat authors through the agent-eval adapter, not a
513
514
  // harness CLI. Its inner evaluator is the SAME injected smoke runner the
514
515
  // pre-filter uses (presence enforced at generator construction).
515
516
  let gepaAuthor: AuthorFn | undefined
@@ -517,9 +518,12 @@ function defaultAuthor(config: OuterLoopConfig, deps: FanOutDeps): AuthorFn {
517
518
  if (isGepaSeat(proposer)) {
518
519
  gepaAuthor ??= gepaSeatAuthor(config, {
519
520
  smokeRunner: deps.smokeRunner!,
521
+ runnerImplementationRef: deps.runnerImplementationRef!,
522
+ judgeImplementationRef: deps.judgeImplementationRef!,
520
523
  smokeInstanceId: deps.smokeInstanceId!,
521
524
  scoreSplit: deps.scoreSplit ?? null,
522
525
  ...(deps.gepaMethodFactory ? { methodFactory: deps.gepaMethodFactory } : {}),
526
+ ...(deps.gepaOptimizer ? { optimizer: deps.gepaOptimizer } : {}),
523
527
  ...(deps.log ? { log: deps.log } : {}),
524
528
  })
525
529
  return gepaAuthor(proposer, args)
@@ -586,7 +590,7 @@ export function fanOutLoopsGenerator(config: OuterLoopConfig, deps: FanOutDeps =
586
590
  `only ${(deps.parents ?? []).length} pareto parent(s) materialized — a merge seat needs >=2`,
587
591
  )
588
592
  }
589
- // GEN-6: engine seats are validated fail-closed at construction, and the
593
+ // Engine seats are validated at construction, and the
590
594
  // default author path requires the pre-filter smoke runner — it IS the GEPA
591
595
  // seat's inner evaluator (spec: score = smoke resolve + verify-pass
592
596
  // tiebreak). A custom injected `author` owns its own evaluator.
@@ -597,10 +601,19 @@ export function fanOutLoopsGenerator(config: OuterLoopConfig, deps: FanOutDeps =
597
601
  throw new Error(`fanOutLoopsGenerator: proposer ${seat.name} has neither a harness nor an engine`)
598
602
  }
599
603
  }
600
- if (gepaSeats.length > 0 && deps.author === undefined && (deps.smokeRunner === undefined || deps.smokeInstanceId === undefined)) {
604
+ if (
605
+ gepaSeats.length > 0 &&
606
+ deps.author === undefined &&
607
+ (
608
+ deps.smokeRunner === undefined ||
609
+ deps.smokeInstanceId === undefined ||
610
+ deps.runnerImplementationRef === undefined ||
611
+ deps.judgeImplementationRef === undefined
612
+ )
613
+ ) {
601
614
  throw new Error(
602
615
  `fanOutLoopsGenerator: gepa seat(s) ${gepaSeats.map((p) => p.name).join(', ')} need the pre-filter smoke ` +
603
- 'cell as their inner evaluator enable config.prefilter and provide smokeRunner + smokeInstanceId',
616
+ 'cell plus immutable runner and judge references as their inner evaluator',
604
617
  )
605
618
  }
606
619
  const author = deps.author ?? defaultAuthor(config, deps)
@@ -616,11 +629,12 @@ export function fanOutLoopsGenerator(config: OuterLoopConfig, deps: FanOutDeps =
616
629
  const baseCommit = head.stdout.trim()
617
630
  const patchesDir = join(config.outDir, 'proposer-patches')
618
631
  await mkdir(patchesDir, { recursive: true })
632
+ await pruneDetachedWorktrees(config.loopsRepo)
619
633
  log(`fan-out gen ${generation}: ${proposers.length} proposer(s) authoring in parallel from ${baseCommit.slice(0, 10)}`)
620
634
  return Promise.all(
621
635
  proposers.map(async (proposer, index): Promise<AuthorOutcome> => {
622
636
  const scratch = join(config.outDir, 'proposer-wt', `gen${generation}-cand${index}-${sanitize(proposer.name)}`)
623
- await addAuthoringWorktree(config.loopsRepo, baseCommit, scratch)
637
+ await createDetachedWorktree(config.loopsRepo, baseCommit, scratch)
624
638
  try {
625
639
  const authored = await author(proposer, {
626
640
  ...args,
@@ -697,6 +711,7 @@ export function fanOutLoopsGenerator(config: OuterLoopConfig, deps: FanOutDeps =
697
711
  scratchPath: scratch,
698
712
  generation,
699
713
  proposer,
714
+ evaluationKey: `candidate-${index}`,
700
715
  ...(args.costLedger ? { costLedger: args.costLedger } : {}),
701
716
  })
702
717
  if (!smoke.pass) {
@@ -719,7 +734,7 @@ export function fanOutLoopsGenerator(config: OuterLoopConfig, deps: FanOutDeps =
719
734
  }
720
735
  return { proposer, applied: true, summary: authored.summary, patch: diff, kill: null }
721
736
  } finally {
722
- await removeAuthoringWorktree(config.loopsRepo, scratch)
737
+ await removeDetachedWorktree(config.loopsRepo, scratch)
723
738
  }
724
739
  }),
725
740
  )
@@ -567,7 +567,6 @@ describe('defaultGen5Config', () => {
567
567
  expect(config.briefing).toBe('map-toolbox-v1')
568
568
  expect(config.activationGate).toBe(true)
569
569
  expect(config.rolloutLedger).toEqual({ enabled: true })
570
- expect(config.lineage).toBe(true)
571
570
  expect(config.priorEvidenceDirs!.some((d) => d.includes('gen4'))).toBe(true)
572
571
  })
573
572