@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.
Files changed (211) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/README.md +7 -0
  4. package/dist/adapters.js +23 -23
  5. package/dist/benchmarks/_harness.d.ts +1 -1
  6. package/dist/benchmarks/_harness.js +1 -1
  7. package/dist/benchmarks/aec-bench.js +2 -2
  8. package/dist/benchmarks/agentbench.js +2 -2
  9. package/dist/benchmarks/appworld.js +2 -2
  10. package/dist/benchmarks/bfcl.js +2 -2
  11. package/dist/benchmarks/commit0.js +2 -2
  12. package/dist/benchmarks/crag.js +2 -2
  13. package/dist/benchmarks/dabstep.js +2 -2
  14. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  15. package/dist/benchmarks/finresearchbench.js +2 -2
  16. package/dist/benchmarks/nomiracl.js +2 -2
  17. package/dist/benchmarks/open-rag-bench.js +2 -2
  18. package/dist/benchmarks/programbench.js +2 -2
  19. package/dist/benchmarks/ragbench.js +2 -2
  20. package/dist/benchmarks/swe-bench.js +2 -2
  21. package/dist/benchmarks/t2-ragbench.js +2 -2
  22. package/dist/benchmarks/tau-bench-shared.js +2 -2
  23. package/dist/benchmarks/tau2-bench.js +3 -3
  24. package/dist/benchmarks/tau3-banking.js +3 -3
  25. package/dist/benchmarks/terminal-bench.js +2 -2
  26. package/dist/benchmarks/toollm.js +2 -2
  27. package/dist/benchmarks/webarena-verified.js +2 -2
  28. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  29. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  30. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  31. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  32. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  33. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  34. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  35. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  36. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  37. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  38. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  39. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  40. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  41. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  42. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  43. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  44. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  45. package/dist/{chunk-PWQVGAJB.js → chunk-V7AEBY6U.js} +21 -21
  46. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  47. package/dist/chunk-WSKWVEQB.js.map +1 -0
  48. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  49. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  50. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  51. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  52. package/dist/index.js +23 -23
  53. package/package.json +5 -5
  54. package/scripts/trata-hedge/README.md +6 -5
  55. package/scripts/verify-packed-consumer.mjs +1 -1
  56. package/src/benchmarks/_harness.ts +20 -2
  57. package/src/benchmarks/humaneval.test.mts +2 -2
  58. package/src/gate.ts +1 -1
  59. package/src/hev-eval.mts +5 -2
  60. package/src/hev-improve.mts +118 -73
  61. package/src/official-optimizer-config.mts +89 -0
  62. package/src/official-optimizer-config.test.mts +88 -0
  63. package/src/profiles.ts +2 -2
  64. package/src/quant-arena/README.md +144 -0
  65. package/src/quant-arena/backtest.test.mts +135 -0
  66. package/src/quant-arena/backtest.ts +218 -0
  67. package/src/quant-arena/data.test.mts +44 -0
  68. package/src/quant-arena/data.ts +141 -0
  69. package/src/quant-arena/driver.test.mts +253 -0
  70. package/src/quant-arena/driver.ts +219 -0
  71. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  72. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  73. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  74. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  75. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  76. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  77. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  78. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  83. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  84. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  85. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  86. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  87. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  88. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  89. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  94. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  95. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  96. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  97. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  98. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  99. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  100. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  101. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  102. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  103. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  104. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  105. package/src/quant-arena/holdout-certify.mts +206 -0
  106. package/src/quant-arena/holdout-certify.test.mts +82 -0
  107. package/src/quant-arena/leak-audit.test.mts +79 -0
  108. package/src/quant-arena/leak-audit.ts +95 -0
  109. package/src/quant-arena/make-fixtures.mts +161 -0
  110. package/src/quant-arena/multiplicity.test.mts +68 -0
  111. package/src/quant-arena/multiplicity.ts +87 -0
  112. package/src/quant-arena/nautilus-certify.ts +31 -0
  113. package/src/quant-arena/oms.ts +90 -0
  114. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  115. package/src/quant-arena/python/pyproject.toml +8 -0
  116. package/src/quant-arena/python/uv.lock +1297 -0
  117. package/src/quant-arena/python/vbt-worker.py +192 -0
  118. package/src/quant-arena/quant-loop.mts +813 -0
  119. package/src/quant-arena/quant-loop.test.mts +75 -0
  120. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  121. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  122. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  123. package/src/quant-arena/types.ts +133 -0
  124. package/src/quant-arena/vbt-client.ts +321 -0
  125. package/src/quant-arena/vbt-parity.test.mts +183 -0
  126. package/src/quant-arena/windows.test.mts +45 -0
  127. package/src/quant-arena/windows.ts +54 -0
  128. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  129. package/src/rollout-ledger/backfill-swe-arena.test.mts +342 -0
  130. package/src/rollout-ledger/settle-capture.mts +442 -0
  131. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  132. package/src/smoke-structural-rollout.mts +15 -9
  133. package/src/swe-arena/activation.mts +225 -0
  134. package/src/swe-arena/activation.test.mts +300 -0
  135. package/src/swe-arena/arms.ts +30 -14
  136. package/src/swe-arena/briefing.mts +217 -0
  137. package/src/swe-arena/briefing.test.mts +178 -0
  138. package/src/swe-arena/calibrate.ts +102 -1
  139. package/src/swe-arena/cell-evidence.mts +41 -9
  140. package/src/swe-arena/factory-command-container.ts +284 -0
  141. package/src/swe-arena/factory-judge-child.mts +228 -0
  142. package/src/swe-arena/factory.test.mts +643 -0
  143. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  144. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  145. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  146. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  147. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  148. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  149. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  150. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  151. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  152. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  153. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  154. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  155. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  156. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  157. package/src/swe-arena/fixtures.ts +135 -2
  158. package/src/swe-arena/gepa-seat.mts +877 -0
  159. package/src/swe-arena/gepa-seat.test.mts +1059 -0
  160. package/src/swe-arena/implementation-ref.test.mts +64 -0
  161. package/src/swe-arena/implementation-ref.ts +62 -0
  162. package/src/swe-arena/ledger-orphans.mts +77 -0
  163. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  164. package/src/swe-arena/outer-loop.mts +710 -33
  165. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  166. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  167. package/src/swe-arena/proposer-fanout.mts +327 -31
  168. package/src/swe-arena/proposer-fanout.test.mts +246 -0
  169. package/src/swe-arena/proposer-provenance.mts +172 -0
  170. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  171. package/src/swe-arena/run-experiment.mts +367 -1
  172. package/src/swe-arena/run-report.mts +75 -0
  173. package/src/swe-arena/run-supervisor.test.mts +5 -3
  174. package/src/swe-arena/score-split.mts +140 -0
  175. package/src/swe-arena/score-split.test.mts +123 -0
  176. package/src/swe-arena/scratch-worktree.test.mts +55 -0
  177. package/src/swe-arena/scratch-worktree.ts +34 -0
  178. package/src/swe-arena/types.ts +52 -0
  179. package/src/swe-code-improve.mts +24 -25
  180. package/src/swe-improve.mts +129 -96
  181. package/src/swe-local-proof.mts +6 -1
  182. package/src/swe-stream.mts +4 -2
  183. package/src/swe-structural.mts +245 -837
  184. package/src/tb-container-executor.test.mts +30 -6
  185. package/src/tb-supervisor-sidecar.mts +2 -1
  186. package/src/trata-gepa.mts +182 -245
  187. package/dist/chunk-LRRD7NAG.js.map +0 -1
  188. package/src/live-improve-campaign-mbpp.mts +0 -641
  189. package/src/live-improve-campaign.mts +0 -500
  190. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  191. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  192. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  193. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  194. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  195. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  196. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  197. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  198. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  199. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  200. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  201. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  202. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  203. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  204. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  205. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  206. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  207. /package/dist/{chunk-PWQVGAJB.js.map → chunk-V7AEBY6U.js.map} +0 -0
  208. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  209. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  210. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  211. /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
+ }
@@ -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
@@ -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} reflect=${reflectModel} router=${routerBaseUrl} runTool=${enableRun}`)
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 r = await runEmit(rootDir, scenario.id, workerEnv, emitTimeoutMs)
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(profile, [], {
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(`gateDecision=${out.gateDecision} shipped=${out.shipped} lift=${out.lift}`)
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.summary) console.log(`winner summary: ${out.raw.winner.summary}`)
315
- for (const gen of out.raw.generations ?? []) {
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) => {