@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.
- package/CHANGELOG.md +13 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- package/dist/benchmarks/_harness.d.ts +4 -1
- package/dist/benchmarks/_harness.js +3 -1
- package/dist/benchmarks/aec-bench.js +2 -2
- package/dist/benchmarks/agentbench.js +2 -2
- package/dist/benchmarks/appworld.js +2 -2
- package/dist/benchmarks/bfcl.js +2 -2
- package/dist/benchmarks/commit0.js +2 -2
- package/dist/benchmarks/crag.js +2 -2
- package/dist/benchmarks/dabstep.js +2 -2
- package/dist/benchmarks/enterpriseops-gym.js +2 -2
- package/dist/benchmarks/finresearchbench.js +2 -2
- package/dist/benchmarks/nomiracl.js +2 -2
- package/dist/benchmarks/open-rag-bench.js +2 -2
- package/dist/benchmarks/programbench.js +2 -2
- package/dist/benchmarks/ragbench.js +2 -2
- package/dist/benchmarks/swe-bench.js +2 -2
- package/dist/benchmarks/t2-ragbench.js +2 -2
- package/dist/benchmarks/tau-bench-shared.js +2 -2
- package/dist/benchmarks/tau2-bench.js +3 -3
- package/dist/benchmarks/tau3-banking.js +3 -3
- package/dist/benchmarks/terminal-bench.js +2 -2
- package/dist/benchmarks/toollm.js +2 -2
- package/dist/benchmarks/webarena-verified.js +2 -2
- package/dist/{chunk-YSMEKBTD.js → chunk-5FEQDSCT.js} +2 -2
- package/dist/{chunk-PB64GYIG.js → chunk-67ACKDCX.js} +2 -2
- package/dist/{chunk-NQG5XDSB.js → chunk-7FKBWOQT.js} +2 -2
- package/dist/{chunk-K3BQGZCT.js → chunk-BEN6IF2X.js} +2 -2
- package/dist/{chunk-SHYIRB7I.js → chunk-BZY5QARD.js} +2 -2
- package/dist/{chunk-KP5KD6EN.js → chunk-CLIKAXKH.js} +2 -2
- package/dist/{chunk-XKEFIFIC.js → chunk-CWIOBFSP.js} +2 -2
- package/dist/{chunk-IA2FBTWC.js → chunk-CXDUTWQE.js} +2 -2
- package/dist/{chunk-RH5F53JT.js → chunk-DWALFME7.js} +2 -2
- package/dist/{chunk-V7AEBY6U.js → chunk-EEOC6QPJ.js} +21 -21
- package/dist/{chunk-WSKWVEQB.js → chunk-EIETHPD5.js} +7 -3
- package/dist/chunk-EIETHPD5.js.map +1 -0
- package/dist/{chunk-SHM6MRRF.js → chunk-GC2EPS6L.js} +2 -2
- package/dist/{chunk-IZ5M6OAC.js → chunk-GCHL6XPM.js} +2 -2
- package/dist/{chunk-IFVINJ4B.js → chunk-HQ5HCCKF.js} +2 -2
- package/dist/{chunk-7GRVHU22.js → chunk-HVW25KSX.js} +2 -2
- package/dist/{chunk-3U5TXJZS.js → chunk-J6BU3NTM.js} +2 -2
- package/dist/{chunk-XYA4XSNU.js → chunk-JSQOUKXS.js} +4 -3
- package/dist/chunk-JSQOUKXS.js.map +1 -0
- package/dist/{chunk-SVR2LKYI.js → chunk-NRMGT25X.js} +2 -2
- package/dist/{chunk-MQMRLGOG.js → chunk-QZZEAHWJ.js} +2 -2
- package/dist/{chunk-RCYQEFNX.js → chunk-WG7TM7UV.js} +3 -3
- package/dist/{chunk-HWST3SED.js → chunk-XXFF3RRD.js} +2 -2
- package/dist/{chunk-SFLA7OH3.js → chunk-ZFNOM7WR.js} +3 -3
- package/dist/{chunk-Z4TZ76N7.js → chunk-ZNCCYTFG.js} +2 -2
- package/dist/index.js +23 -23
- package/package.json +4 -4
- package/pier_agents/candidate_contract.py +238 -24
- package/pier_agents/tangle_candidate.py +75 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +65 -1
- package/scripts/verify-pier-agent.mts +6 -4
- package/src/benchmarks/_harness.test.mts +16 -1
- package/src/benchmarks/_harness.ts +9 -2
- package/src/benchmarks/terminal-bench.ts +2 -1
- 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/dist/chunk-WSKWVEQB.js.map +0 -1
- package/dist/chunk-XYA4XSNU.js.map +0 -1
- 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
- /package/dist/{chunk-YSMEKBTD.js.map → chunk-5FEQDSCT.js.map} +0 -0
- /package/dist/{chunk-PB64GYIG.js.map → chunk-67ACKDCX.js.map} +0 -0
- /package/dist/{chunk-NQG5XDSB.js.map → chunk-7FKBWOQT.js.map} +0 -0
- /package/dist/{chunk-K3BQGZCT.js.map → chunk-BEN6IF2X.js.map} +0 -0
- /package/dist/{chunk-SHYIRB7I.js.map → chunk-BZY5QARD.js.map} +0 -0
- /package/dist/{chunk-KP5KD6EN.js.map → chunk-CLIKAXKH.js.map} +0 -0
- /package/dist/{chunk-XKEFIFIC.js.map → chunk-CWIOBFSP.js.map} +0 -0
- /package/dist/{chunk-IA2FBTWC.js.map → chunk-CXDUTWQE.js.map} +0 -0
- /package/dist/{chunk-RH5F53JT.js.map → chunk-DWALFME7.js.map} +0 -0
- /package/dist/{chunk-V7AEBY6U.js.map → chunk-EEOC6QPJ.js.map} +0 -0
- /package/dist/{chunk-SHM6MRRF.js.map → chunk-GC2EPS6L.js.map} +0 -0
- /package/dist/{chunk-IZ5M6OAC.js.map → chunk-GCHL6XPM.js.map} +0 -0
- /package/dist/{chunk-IFVINJ4B.js.map → chunk-HQ5HCCKF.js.map} +0 -0
- /package/dist/{chunk-7GRVHU22.js.map → chunk-HVW25KSX.js.map} +0 -0
- /package/dist/{chunk-3U5TXJZS.js.map → chunk-J6BU3NTM.js.map} +0 -0
- /package/dist/{chunk-SVR2LKYI.js.map → chunk-NRMGT25X.js.map} +0 -0
- /package/dist/{chunk-MQMRLGOG.js.map → chunk-QZZEAHWJ.js.map} +0 -0
- /package/dist/{chunk-RCYQEFNX.js.map → chunk-WG7TM7UV.js.map} +0 -0
- /package/dist/{chunk-HWST3SED.js.map → chunk-XXFF3RRD.js.map} +0 -0
- /package/dist/{chunk-SFLA7OH3.js.map → chunk-ZFNOM7WR.js.map} +0 -0
- /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 —
|
|
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
|
-
*
|
|
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 ({
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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>(
|
|
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
|
|
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 {
|
|
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.
|
|
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
|
-
/**
|
|
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
|
|
114
|
+
* run; `omni` = GEPA's official Omni recipe. Validation +
|
|
104
115
|
* authoring live in gepa-seat.mts. */
|
|
105
116
|
engine?: 'gepa' | 'omni'
|
|
106
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
141
|
-
* tiebreak
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|