@tangle-network/agent-bench 0.3.7 → 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 +11 -0
- package/HARNESS.md +43 -0
- package/README.md +7 -0
- package/dist/adapters.js +23 -23
- package/dist/benchmarks/_harness.d.ts +1 -1
- package/dist/benchmarks/_harness.js +1 -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-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
- package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
- package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
- package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
- package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
- package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
- package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
- package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
- package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
- package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
- package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
- package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
- package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
- package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
- package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
- package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
- package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
- package/dist/{chunk-PWQVGAJB.js → chunk-V7AEBY6U.js} +21 -21
- package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
- package/dist/chunk-WSKWVEQB.js.map +1 -0
- package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
- package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
- package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
- package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
- package/dist/index.js +23 -23
- package/package.json +5 -5
- package/scripts/trata-hedge/README.md +6 -5
- package/scripts/verify-packed-consumer.mjs +1 -1
- package/src/benchmarks/_harness.ts +20 -2
- package/src/benchmarks/humaneval.test.mts +2 -2
- 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/quant-arena/README.md +144 -0
- package/src/quant-arena/backtest.test.mts +135 -0
- package/src/quant-arena/backtest.ts +218 -0
- package/src/quant-arena/data.test.mts +44 -0
- package/src/quant-arena/data.ts +141 -0
- package/src/quant-arena/driver.test.mts +253 -0
- package/src/quant-arena/driver.ts +219 -0
- package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
- package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
- package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
- package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
- package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
- package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
- package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
- package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
- package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
- package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
- package/src/quant-arena/holdout-certify.mts +206 -0
- package/src/quant-arena/holdout-certify.test.mts +82 -0
- package/src/quant-arena/leak-audit.test.mts +79 -0
- package/src/quant-arena/leak-audit.ts +95 -0
- package/src/quant-arena/make-fixtures.mts +161 -0
- package/src/quant-arena/multiplicity.test.mts +68 -0
- package/src/quant-arena/multiplicity.ts +87 -0
- package/src/quant-arena/nautilus-certify.ts +31 -0
- package/src/quant-arena/oms.ts +90 -0
- package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
- package/src/quant-arena/python/pyproject.toml +8 -0
- package/src/quant-arena/python/uv.lock +1297 -0
- package/src/quant-arena/python/vbt-worker.py +192 -0
- package/src/quant-arena/quant-loop.mts +813 -0
- package/src/quant-arena/quant-loop.test.mts +75 -0
- package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
- package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
- package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
- package/src/quant-arena/types.ts +133 -0
- package/src/quant-arena/vbt-client.ts +321 -0
- package/src/quant-arena/vbt-parity.test.mts +183 -0
- package/src/quant-arena/windows.test.mts +45 -0
- package/src/quant-arena/windows.ts +54 -0
- package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
- package/src/rollout-ledger/backfill-swe-arena.test.mts +342 -0
- package/src/rollout-ledger/settle-capture.mts +442 -0
- package/src/rollout-ledger/settle-capture.test.mts +270 -0
- package/src/smoke-structural-rollout.mts +15 -9
- package/src/swe-arena/activation.mts +225 -0
- package/src/swe-arena/activation.test.mts +300 -0
- package/src/swe-arena/arms.ts +30 -14
- package/src/swe-arena/briefing.mts +217 -0
- package/src/swe-arena/briefing.test.mts +178 -0
- package/src/swe-arena/calibrate.ts +102 -1
- package/src/swe-arena/cell-evidence.mts +41 -9
- package/src/swe-arena/factory-command-container.ts +284 -0
- package/src/swe-arena/factory-judge-child.mts +228 -0
- package/src/swe-arena/factory.test.mts +643 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
- package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
- package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
- package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
- package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
- package/src/swe-arena/fixtures/run-report/README.md +43 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
- package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
- package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
- package/src/swe-arena/fixtures.ts +135 -2
- package/src/swe-arena/gepa-seat.mts +877 -0
- package/src/swe-arena/gepa-seat.test.mts +1059 -0
- package/src/swe-arena/implementation-ref.test.mts +64 -0
- package/src/swe-arena/implementation-ref.ts +62 -0
- package/src/swe-arena/ledger-orphans.mts +77 -0
- package/src/swe-arena/ledger-orphans.test.mts +147 -0
- package/src/swe-arena/outer-loop.mts +710 -33
- package/src/swe-arena/premeasured-from-cells.mts +281 -0
- package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
- package/src/swe-arena/proposer-fanout.mts +327 -31
- package/src/swe-arena/proposer-fanout.test.mts +246 -0
- package/src/swe-arena/proposer-provenance.mts +172 -0
- package/src/swe-arena/proposer-provenance.test.mts +106 -0
- package/src/swe-arena/run-experiment.mts +367 -1
- package/src/swe-arena/run-report.mts +75 -0
- package/src/swe-arena/run-supervisor.test.mts +5 -3
- package/src/swe-arena/score-split.mts +140 -0
- package/src/swe-arena/score-split.test.mts +123 -0
- package/src/swe-arena/scratch-worktree.test.mts +55 -0
- package/src/swe-arena/scratch-worktree.ts +34 -0
- package/src/swe-arena/types.ts +52 -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/swe-structural.mts +245 -837
- 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-LRRD7NAG.js.map +0 -1
- package/src/live-improve-campaign-mbpp.mts +0 -641
- package/src/live-improve-campaign.mts +0 -500
- /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
- /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
- /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
- /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
- /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
- /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
- /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
- /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
- /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
- /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
- /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
- /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
- /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
- /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
- /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
- /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
- /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
- /package/dist/{chunk-PWQVGAJB.js.map → chunk-V7AEBY6U.js.map} +0 -0
- /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
- /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
- /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
- /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gen-5 public/private score split: determinism (seeded by runId), persisted
|
|
3
|
+
* resume stability, and the never-surfaced invariant's checkable pieces.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
7
|
+
import { tmpdir } from 'node:os'
|
|
8
|
+
import { join } from 'node:path'
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
10
|
+
import {
|
|
11
|
+
leaksPrivateInstance,
|
|
12
|
+
loadOrCreateScoreSplit,
|
|
13
|
+
publicOnly,
|
|
14
|
+
SCORE_SPLIT_FILENAME,
|
|
15
|
+
splitInstances,
|
|
16
|
+
subScores,
|
|
17
|
+
} from './score-split.mts'
|
|
18
|
+
|
|
19
|
+
const SIX = [
|
|
20
|
+
'astropy__astropy-13033',
|
|
21
|
+
'django__django-11532',
|
|
22
|
+
'matplotlib__matplotlib-20826',
|
|
23
|
+
'pydata__xarray-4687',
|
|
24
|
+
'pytest-dev__pytest-6197',
|
|
25
|
+
'sphinx-doc__sphinx-9658',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
describe('splitInstances', () => {
|
|
29
|
+
it('is deterministic: same (runId, instances, publicCount) → identical split', () => {
|
|
30
|
+
const a = splitInstances('r4-abc123', SIX, 4)
|
|
31
|
+
const b = splitInstances('r4-abc123', SIX, 4)
|
|
32
|
+
expect(a).toEqual(b)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('is instance-ORDER-insensitive (ranking is content-derived)', () => {
|
|
36
|
+
const a = splitInstances('r4-abc123', SIX, 4)
|
|
37
|
+
const b = splitInstances('r4-abc123', [...SIX].reverse(), 4)
|
|
38
|
+
expect(a).toEqual(b)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('respects publicCount and partitions exactly (4 public / 2 private of 6)', () => {
|
|
42
|
+
const split = splitInstances('r4-abc123', SIX, 4)
|
|
43
|
+
expect(split.publicInstances).toHaveLength(4)
|
|
44
|
+
expect(split.privateInstances).toHaveLength(2)
|
|
45
|
+
expect([...split.publicInstances, ...split.privateInstances].sort()).toEqual([...SIX].sort())
|
|
46
|
+
expect(split.publicInstances.filter((i) => split.privateInstances.includes(i))).toEqual([])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('is SEEDED by runId: different runIds produce different splits', () => {
|
|
50
|
+
const splits = new Set(
|
|
51
|
+
Array.from({ length: 10 }, (_, i) => splitInstances(`r4-run${i}`, SIX, 4).privateInstances.join('|')),
|
|
52
|
+
)
|
|
53
|
+
expect(splits.size).toBeGreaterThan(1)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('rejects publicCount outside (0, n) and duplicate instance ids', () => {
|
|
57
|
+
expect(() => splitInstances('r', SIX, 0)).toThrow(/publicCount/)
|
|
58
|
+
expect(() => splitInstances('r', SIX, 6)).toThrow(/publicCount/)
|
|
59
|
+
expect(() => splitInstances('r', SIX, 2.5)).toThrow(/publicCount/)
|
|
60
|
+
expect(() => splitInstances('r', ['a', 'a', 'b'], 1)).toThrow(/duplicate/)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('loadOrCreateScoreSplit (resume stability)', () => {
|
|
65
|
+
let outDir: string
|
|
66
|
+
beforeEach(async () => {
|
|
67
|
+
outDir = await mkdtemp(join(tmpdir(), 'split-'))
|
|
68
|
+
})
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
await rm(outDir, { recursive: true, force: true })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('persists on first computation and RELOADS verbatim under a NEW runId (a resume cannot rotate private into view)', async () => {
|
|
74
|
+
const first = await loadOrCreateScoreSplit({ outDir, runId: 'r4-first', instances: SIX, publicCount: 4 })
|
|
75
|
+
expect(JSON.parse(await readFile(join(outDir, SCORE_SPLIT_FILENAME), 'utf8'))).toEqual(first)
|
|
76
|
+
const resumed = await loadOrCreateScoreSplit({ outDir, runId: 'r4-DIFFERENT', instances: SIX, publicCount: 4 })
|
|
77
|
+
expect(resumed).toEqual(first)
|
|
78
|
+
expect(resumed.seededBy).toBe('r4-first')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('fails loud when the persisted split covers different instances or a different publicCount', async () => {
|
|
82
|
+
await loadOrCreateScoreSplit({ outDir, runId: 'r4-first', instances: SIX, publicCount: 4 })
|
|
83
|
+
await expect(
|
|
84
|
+
loadOrCreateScoreSplit({ outDir, runId: 'r4-x', instances: [...SIX.slice(0, 5), 'other__iid-1'], publicCount: 4 }),
|
|
85
|
+
).rejects.toThrow(/refusing to silently re-split/)
|
|
86
|
+
await expect(
|
|
87
|
+
loadOrCreateScoreSplit({ outDir, runId: 'r4-x', instances: SIX, publicCount: 3 }),
|
|
88
|
+
).rejects.toThrow(/refusing to silently re-split/)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('fails loud on an unknown schema in the persisted file', async () => {
|
|
92
|
+
await writeFile(join(outDir, SCORE_SPLIT_FILENAME), JSON.stringify({ schema: 'nope' }))
|
|
93
|
+
await expect(
|
|
94
|
+
loadOrCreateScoreSplit({ outDir, runId: 'r', instances: SIX, publicCount: 4 }),
|
|
95
|
+
).rejects.toThrow(/unknown schema/)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('never-surfaced invariant helpers', () => {
|
|
100
|
+
const split = splitInstances('r4-abc123', SIX, 4)
|
|
101
|
+
|
|
102
|
+
it('leaksPrivateInstance names every private id present in a text', () => {
|
|
103
|
+
expect(leaksPrivateInstance('nothing to see', split)).toEqual([])
|
|
104
|
+
const leakText = `evidence for ${split.privateInstances[0]} here`
|
|
105
|
+
expect(leaksPrivateInstance(leakText, split)).toEqual([split.privateInstances[0]])
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('publicOnly drops private-instance records and is identity without a split', () => {
|
|
109
|
+
const records = SIX.map((iid) => ({ iid, x: 1 }))
|
|
110
|
+
const filtered = publicOnly(records, (r) => r.iid, split)
|
|
111
|
+
expect(filtered.map((r) => r.iid).sort()).toEqual(split.publicInstances)
|
|
112
|
+
expect(publicOnly(records, (r) => r.iid, null)).toEqual(records)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('subScores counts fail-closed AND-verdicts per half (missing verdict = 0)', () => {
|
|
116
|
+
const verdicts: Record<string, boolean> = {}
|
|
117
|
+
verdicts[split.publicInstances[0]!] = true
|
|
118
|
+
verdicts[split.publicInstances[1]!] = false
|
|
119
|
+
verdicts[split.privateInstances[0]!] = true
|
|
120
|
+
// privateInstances[1] has NO verdict — counts 0.
|
|
121
|
+
expect(subScores(verdicts, split)).toEqual({ publicResolvedCount: 1, privateResolvedCount: 1 })
|
|
122
|
+
})
|
|
123
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm, 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 { runOk } from './proc.ts'
|
|
6
|
+
import {
|
|
7
|
+
createDetachedWorktree,
|
|
8
|
+
pruneDetachedWorktrees,
|
|
9
|
+
removeDetachedWorktree,
|
|
10
|
+
} from './scratch-worktree.ts'
|
|
11
|
+
|
|
12
|
+
describe('scratch worktrees', () => {
|
|
13
|
+
const roots: string[] = []
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('creates and removes a parallel batch without pruning live metadata', async () => {
|
|
20
|
+
const repository = await mkdtemp(join(tmpdir(), 'scratch-worktree-repo-'))
|
|
21
|
+
const output = await mkdtemp(join(tmpdir(), 'scratch-worktree-out-'))
|
|
22
|
+
roots.push(repository, output)
|
|
23
|
+
await runOk('git', ['init', '-q', '-b', 'main', repository])
|
|
24
|
+
await runOk('git', ['-C', repository, 'config', 'user.email', 'test@example.com'])
|
|
25
|
+
await runOk('git', ['-C', repository, 'config', 'user.name', 'Test'])
|
|
26
|
+
await writeFile(join(repository, 'seed.txt'), 'seed\n')
|
|
27
|
+
await runOk('git', ['-C', repository, 'add', 'seed.txt'])
|
|
28
|
+
await runOk('git', ['-C', repository, 'commit', '-q', '-m', 'seed'])
|
|
29
|
+
const commit = (
|
|
30
|
+
await runOk('git', ['-C', repository, 'rev-parse', 'HEAD'])
|
|
31
|
+
).stdout.trim()
|
|
32
|
+
for (let batch = 0; batch < 5; batch += 1) {
|
|
33
|
+
const worktrees = Array.from(
|
|
34
|
+
{ length: 8 },
|
|
35
|
+
(_, index) => join(output, `batch-${batch}-candidate-${index}`),
|
|
36
|
+
)
|
|
37
|
+
await pruneDetachedWorktrees(repository)
|
|
38
|
+
await Promise.all(
|
|
39
|
+
worktrees.map((worktree) => createDetachedWorktree(repository, commit, worktree)),
|
|
40
|
+
)
|
|
41
|
+
expect(
|
|
42
|
+
await Promise.all(
|
|
43
|
+
worktrees.map((worktree) => readFile(join(worktree, 'seed.txt'), 'utf8')),
|
|
44
|
+
),
|
|
45
|
+
).toEqual(Array.from({ length: 8 }, () => 'seed\n'))
|
|
46
|
+
await Promise.all(
|
|
47
|
+
worktrees.map((worktree) => removeDetachedWorktree(repository, worktree)),
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
const listed = (
|
|
51
|
+
await runOk('git', ['-C', repository, 'worktree', 'list', '--porcelain'])
|
|
52
|
+
).stdout
|
|
53
|
+
expect(listed.match(/^worktree /gmu)).toHaveLength(1)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises'
|
|
2
|
+
import { run, runOk } from './proc'
|
|
3
|
+
|
|
4
|
+
export async function createDetachedWorktree(
|
|
5
|
+
repository: string,
|
|
6
|
+
commit: string,
|
|
7
|
+
destination: string,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
await run('git', ['-C', repository, 'worktree', 'remove', '--force', '--', destination])
|
|
10
|
+
await rm(destination, { recursive: true, force: true })
|
|
11
|
+
await runOk('git', ['-C', repository, 'worktree', 'add', '--detach', destination, commit])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function pruneDetachedWorktrees(repository: string): Promise<void> {
|
|
15
|
+
await runOk('git', ['-C', repository, 'worktree', 'prune'])
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function removeDetachedWorktree(
|
|
19
|
+
repository: string,
|
|
20
|
+
destination: string,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const result = await run('git', [
|
|
23
|
+
'-C',
|
|
24
|
+
repository,
|
|
25
|
+
'worktree',
|
|
26
|
+
'remove',
|
|
27
|
+
'--force',
|
|
28
|
+
'--',
|
|
29
|
+
destination,
|
|
30
|
+
])
|
|
31
|
+
if (result.code !== 0) {
|
|
32
|
+
await rm(destination, { recursive: true, force: true })
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/swe-arena/types.ts
CHANGED
|
@@ -30,6 +30,58 @@ export interface SweInstance {
|
|
|
30
30
|
environment_setup_commit: string | null
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* One factory-bench instance — a merged feature PR from our own repo history
|
|
35
|
+
* turned into a gradable end-to-end feature-building task (see
|
|
36
|
+
* supervisor-lab/factory-bench/docs/design.md). The worker sees only the tree
|
|
37
|
+
* at `base_commit` (archive export, synthetic git history) plus the rewritten
|
|
38
|
+
* spec; the PR's own added test files are the hidden judge, overlaid from
|
|
39
|
+
* `judge_ref` at judge time. Field names mirror the instance dirs'
|
|
40
|
+
* `manifest.json` byte-for-byte, same policy as SweInstance vs task-meta.json.
|
|
41
|
+
*/
|
|
42
|
+
export interface FactoryInstance {
|
|
43
|
+
/** `factory.<repo>.<pr>` */
|
|
44
|
+
id: string
|
|
45
|
+
/** `owner/name` */
|
|
46
|
+
repo: string
|
|
47
|
+
/** Judge-side local mirror; NEVER exposed to the worker workspace. */
|
|
48
|
+
repo_local_mirror: string
|
|
49
|
+
/** The worker's world — the PR's base commit. */
|
|
50
|
+
base_commit: string
|
|
51
|
+
/** Merge commit the judge tests are read from (`git show <judge_ref>:<path>`). */
|
|
52
|
+
judge_ref: string
|
|
53
|
+
/** Instance-dir-relative spec file (PM-ticket grade rewrite of the PR body). */
|
|
54
|
+
spec_md: string
|
|
55
|
+
/** Hidden judge test files, overlaid at judge time only. */
|
|
56
|
+
judge_tests: string[]
|
|
57
|
+
/** Flaky/env-dependent tests excluded at calibration, reasons in calibration.md. */
|
|
58
|
+
excluded_tests: string[]
|
|
59
|
+
/** Immutable Node container image used for setup and judge commands. */
|
|
60
|
+
command_image: string
|
|
61
|
+
setup_cmds: string[]
|
|
62
|
+
judge_cmds: string[]
|
|
63
|
+
/** e.g. "all 30 judge tests pass; partial score = passed/30" — the /NN is parsed. */
|
|
64
|
+
resolved_criterion: string
|
|
65
|
+
timeout_s: number
|
|
66
|
+
worker_visible_paths_note?: string
|
|
67
|
+
runtime?: string
|
|
68
|
+
calibration?: { gold: string; base: string; receipts: string }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Discriminated instance union for the seams that used to assume SweInstance.
|
|
73
|
+
* A tagged wrapper (not a structural union) because both shapes mirror their
|
|
74
|
+
* on-disk artifacts byte-for-byte and neither may grow a discriminant field.
|
|
75
|
+
*/
|
|
76
|
+
export type ArenaInstance =
|
|
77
|
+
| { kind: 'swe'; instance: SweInstance }
|
|
78
|
+
| { kind: 'factory'; instance: FactoryInstance }
|
|
79
|
+
|
|
80
|
+
/** The ledger/judge identity: SWE `instance_id` or factory `id`. */
|
|
81
|
+
export function arenaInstanceId(a: ArenaInstance): string {
|
|
82
|
+
return a.kind === 'swe' ? a.instance.instance_id : a.instance.id
|
|
83
|
+
}
|
|
84
|
+
|
|
33
85
|
/** Per-run opencode usage breakdown captured on the SOLO arm. */
|
|
34
86
|
export interface SoloUsage {
|
|
35
87
|
steps: number
|
package/src/swe-code-improve.mts
CHANGED
|
@@ -33,7 +33,6 @@ import { spawn, spawnSync } from 'node:child_process'
|
|
|
33
33
|
import { existsSync, mkdirSync, symlinkSync } from 'node:fs'
|
|
34
34
|
import { join } from 'node:path'
|
|
35
35
|
import { improve, agenticGenerator } from '@tangle-network/agent-runtime'
|
|
36
|
-
import type { AgentProfile } from '@tangle-network/agent-interface'
|
|
37
36
|
import type { DispatchContext, JudgeConfig, Scenario } from '@tangle-network/agent-eval/contract'
|
|
38
37
|
import { createSweBenchAdapter } from './benchmarks/swe-bench'
|
|
39
38
|
import type { BenchTask } from './benchmarks/types'
|
|
@@ -108,7 +107,6 @@ async function main(): Promise<void> {
|
|
|
108
107
|
if (!routerKey) throw new Error('TANGLE_API_KEY required (worker calls the router)')
|
|
109
108
|
const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
|
|
110
109
|
const workerModel = process.env.WORKER_MODEL ?? 'glm-4.6'
|
|
111
|
-
const reflectModel = process.env.REFLECT_MODEL ?? 'glm-4.6'
|
|
112
110
|
const trainIds = (process.env.TRAIN_IDS ?? 'psf__requests-2931,pallets__flask-5014').split(',').map((s) => s.trim()).filter(Boolean)
|
|
113
111
|
const holdoutIds = (process.env.HOLDOUT_IDS ?? 'psf__requests-1142,psf__requests-1921').split(',').map((s) => s.trim()).filter(Boolean)
|
|
114
112
|
const generations = Number(process.env.GENERATIONS ?? 1)
|
|
@@ -127,7 +125,7 @@ async function main(): Promise<void> {
|
|
|
127
125
|
const allIds = [...new Set([...trainIds, ...holdoutIds])]
|
|
128
126
|
|
|
129
127
|
console.log('=== META-HARNESS on the SWE scaffold — improve(surface:code) ===')
|
|
130
|
-
console.log(`worker=${workerModel}
|
|
128
|
+
console.log(`worker=${workerModel} router=${routerBaseUrl} runTool=${enableRun}`)
|
|
131
129
|
console.log(`train=[${trainIds.join(', ')}] holdout=[${holdoutIds.join(', ')}]`)
|
|
132
130
|
console.log(`generations=${generations} population=${population} innerTurns=${innerTurns} maxTokens=${maxTokens}`)
|
|
133
131
|
console.log(`repoRoot=${REPO_ROOT} baseRef=${baseRef}`)
|
|
@@ -156,15 +154,25 @@ async function main(): Promise<void> {
|
|
|
156
154
|
const worktreeRef = isCode ? String((surface as { worktreeRef?: string }).worktreeRef ?? '') : ''
|
|
157
155
|
const rootDir = isCode && worktreeRef && existsSync(worktreeRef) ? worktreeRef : SWE_MAIN_ROOT
|
|
158
156
|
const t0 = Date.now()
|
|
159
|
-
const
|
|
157
|
+
const paid = await ctx.cost.runPaidCall({
|
|
158
|
+
channel: 'agent',
|
|
159
|
+
actor: 'swe-scaffold-worker',
|
|
160
|
+
model: workerModel,
|
|
161
|
+
execute: () => runEmit(rootDir, scenario.id, workerEnv, emitTimeoutMs),
|
|
162
|
+
receipt: (result) => {
|
|
163
|
+
const usageUnknown = result.tokIn === 0 && result.tokOut === 0
|
|
164
|
+
return {
|
|
165
|
+
model: workerModel,
|
|
166
|
+
inputTokens: result.tokIn,
|
|
167
|
+
outputTokens: result.tokOut,
|
|
168
|
+
...(result.usd > 0 ? { actualCostUsd: result.usd } : {}),
|
|
169
|
+
...(usageUnknown ? { usageUnknown: true, costUnknown: result.usd <= 0 } : {}),
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
if (!paid.succeeded) throw paid.error
|
|
174
|
+
const r = paid.value
|
|
160
175
|
const hasPatch = r.patch.trim().length > 0
|
|
161
|
-
// Report real usage; floor a patch-bearing zero-usage cell so the stub-guard cannot abort on a
|
|
162
|
-
// router telemetry gap (lift is judge-derived, so this only affects cost accounting).
|
|
163
|
-
const zeroUsage = r.tokIn === 0 && r.tokOut === 0
|
|
164
|
-
ctx.cost.observe(zeroUsage && hasPatch ? Math.max(r.usd, 0.0001) : r.usd, workerModel)
|
|
165
|
-
ctx.cost.observeTokens(
|
|
166
|
-
zeroUsage && hasPatch ? { input: Math.max(r.tokIn, 1), output: Math.max(r.tokOut, 1) } : { input: r.tokIn, output: r.tokOut },
|
|
167
|
-
)
|
|
168
176
|
const files = hasPatch ? [...r.patch.matchAll(/^diff --git a\/(\S+)/gm)].map((m) => m[1]) : []
|
|
169
177
|
console.log(
|
|
170
178
|
` [measure] ${isCode ? 'cand' : 'base'} ${scenario.id} patch=${r.patch.length}b files=[${files.join(', ') || 'none'}] ` +
|
|
@@ -173,7 +181,7 @@ async function main(): Promise<void> {
|
|
|
173
181
|
return hasPatch ? r.patch : null
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
const judge: JudgeConfig<string, Scenario> = {
|
|
184
|
+
const judge: JudgeConfig<string | null, Scenario> = {
|
|
177
185
|
name: 'swebench-docker',
|
|
178
186
|
dimensions: [{ key: 'resolved', description: 'FAIL_TO_PASS + PASS_TO_PASS resolved by the official swebench Docker harness' }],
|
|
179
187
|
async score({ artifact, scenario }) {
|
|
@@ -286,11 +294,10 @@ async function main(): Promise<void> {
|
|
|
286
294
|
runHarness: runHarness as any,
|
|
287
295
|
})
|
|
288
296
|
|
|
289
|
-
const profile: AgentProfile = { name: 'swe-scaffold', prompt: { systemPrompt: '' } }
|
|
290
297
|
const scenarios: Scenario[] = allIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
|
|
291
298
|
const holdoutScenarios: Scenario[] = holdoutIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
|
|
292
299
|
|
|
293
|
-
const out = await improve(
|
|
300
|
+
const out = await improve({
|
|
294
301
|
surface: 'code',
|
|
295
302
|
gate: 'holdout',
|
|
296
303
|
code: { repoRoot: REPO_ROOT, baseRef, worktreeDir, generator },
|
|
@@ -301,25 +308,17 @@ async function main(): Promise<void> {
|
|
|
301
308
|
agent,
|
|
302
309
|
expectUsage: 'warn',
|
|
303
310
|
budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency: 1, reps: 1 },
|
|
304
|
-
llm: { baseUrl: routerBaseUrl, apiKey: routerKey, model: reflectModel },
|
|
305
311
|
})
|
|
306
312
|
|
|
307
313
|
console.log('\n=== RESULT ===')
|
|
308
|
-
console.log(`
|
|
314
|
+
console.log(`decision=${out.decision} lift=${out.lift}`)
|
|
309
315
|
console.log(`baseline holdout composite = ${out.raw.baseline.compositeMean}`)
|
|
310
316
|
console.log(`winner holdout composite = ${out.raw.winner.compositeMean}`)
|
|
311
317
|
console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
|
|
312
318
|
console.log(`winner per-scenario: ${JSON.stringify(out.raw.winner.perScenario)}`)
|
|
313
319
|
if (out.raw.winner.label) console.log(`winner label: ${out.raw.winner.label}`)
|
|
314
|
-
if (out.raw.winner.
|
|
315
|
-
|
|
316
|
-
console.log(`\n-- generation ${gen.record.generationIndex} candidates --`)
|
|
317
|
-
for (const c of gen.record.candidates) {
|
|
318
|
-
const perScenario = (c as { scenarios?: Array<{ scenarioId: string; composite: number }> }).scenarios ?? []
|
|
319
|
-
const detail = perScenario.map((s) => `${s.scenarioId}=${s.composite}`).join(' ')
|
|
320
|
-
console.log(` candidate ${c.surfaceHash.slice(0, 8)} composite=${c.composite}${c.label ? ` "${c.label}"` : ''} [${detail}]`)
|
|
321
|
-
}
|
|
322
|
-
}
|
|
320
|
+
if (out.raw.winner.rationale) console.log(`winner rationale: ${out.raw.winner.rationale}`)
|
|
321
|
+
console.log(`generations explored: ${out.generationsExplored ?? 0}`)
|
|
323
322
|
}
|
|
324
323
|
|
|
325
324
|
main().catch((e) => {
|