@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
@@ -1,35 +1,58 @@
1
1
  /**
2
- * Two-session SWE-bench experiment with generation and official scoring split into separate modes.
2
+ * Stage 1 of the SWE-bench frontier push the STRUCTURAL-SYSTEM mechanics pilot
3
+ * (contract: supervisor-lab/docs/results/PREREG-swe-frontier.md, Stage 1; execution design per the
4
+ * AMENDMENT + CLOSURE: image substrate everywhere, per-instance execution canary asserted at open).
3
5
  *
4
- * MODE=generate requires EXPERIMENT_ARM=independent-2|persistent-refine-2 and writes only Phase A.
5
- * The independent arm runs two fresh attempts. The persistent arm runs one attempt and one fresh
6
- * continuation with attempt one's cumulative patch pre-applied. Both use the same run-capable tools,
7
- * prompt, temperature, two worker sessions, and later-on-visible-tie selection rule. Per-row hashes
8
- * bind source, config, reproduction, prompt, tools, task, parent patch, and final patch.
6
+ * ARM=system, per instance:
7
+ * 1. canary the Stage-0 execution canary on the image substrate (gold applied in-container,
8
+ * `import <pkg>` must resolve into /testbed). Gold is used strictly script-side, never near a
9
+ * model message (transport-level leak guard, below).
10
+ * 2. repro the instance's Stage-0 valid+sound repro script is REUSED (REPRO_MANIFEST; source
11
+ * recorded per row) and re-verified on THIS substrate with zero model calls: validity (nonzero
12
+ * exit on the unpatched /testbed) + soundness (exit 0 under gold, in-container). A script that
13
+ * fails re-verification degrades to repro=none — recorded, never silent. Instances with no
14
+ * Stage-0 repro run repro=none and measure the no-signal path (selection = blind-first).
15
+ * 3. k=4 independent patch attempts — each the swe-emit-patch protocol verbatim (SWE_SEED_PROMPT,
16
+ * list/read/edit tools on a fresh host clone, runAgentic refine budget=1, glm-5.2 temp 0.8;
17
+ * the candidate is the workspace `git diff`, captured from inside score()).
18
+ * 4. selection — each candidate is scored in-image: `git apply` the candidate to the container's
19
+ * /testbed (writable layer, --rm discards), run the repro. Argmax: repro-pass first, then
20
+ * crash-lowest (fail < timeout < apply-fail < empty), first index breaks ties. With repro=none
21
+ * the selection degrades to blind-first (first non-empty candidate).
22
+ * 5. ≤2 guarded repair rounds on the best — the repro failure output (+ the current diff, both
23
+ * model-visible by construction) steers a fresh emit-patch attempt on a workspace with the
24
+ * best diff pre-applied; the combined diff must STRICTLY improve the repro outcome to displace
25
+ * (a repro-pass can never be displaced by a repro-fail — repair only runs while failing).
26
+ * 6. final diff locked → Phase B.
27
+ * ARM=solo: one emit-patch attempt (temp 0.7 — runShot's July-protocol default), same environment,
28
+ * same 23 instances. The honest single-attempt reference.
9
29
  *
10
- * MODE=judge-only requires INDEPENDENT_PHASE_A, PERSISTENT_PHASE_A, and a distinct OUT. It validates
11
- * both complete Phase-A files and every paired fingerprint before the first serialized official score.
30
+ * Phase B (hidden judge): adapter.judge the OFFICIAL swebench harness runs serialized
31
+ * (max_workers 1) over the locked final diffs only, strictly AFTER every arm decision. Judge
32
+ * separation is asserted at the transport chokepoint: every outbound request's system/user messages
33
+ * (the strings WE author) are checked against gold-patch + test_patch content marks and refused on
34
+ * contact. Assistant/tool messages are exempt by construction (the model's own text and reads of
35
+ * the base tree — a model that independently authors the gold line is a success, not a leak).
12
36
  *
13
- * Generate env: ZAI_API_KEY, REPRO_MANIFEST, IDS, OUT, MODEL, ZAI_BASE, MAX_TOKENS, TEMPERATURE,
14
- * INNER_TURNS, CONC, REPRO_TIMEOUT, LLM_TIMEOUT_MS, SWE_RUN_TIMEOUT, SWE_RUN_OUTPUT_LIMIT,
15
- * PRICE_IN, PRICE_OUT. Judge env: INDEPENDENT_PHASE_A, PERSISTENT_PHASE_A, OUT.
37
+ * cd ~/company/devops/secrets && dotenvx run -f agent-state.env -- bash -c \
38
+ * 'cd ~/code/agent-runtime-swe && ARM=system REPRO_MANIFEST=/path/manifest.json \
39
+ * OUT=/path/swe-stage1-system.jsonl node_modules/.bin/tsx bench/src/swe-structural.mts'
40
+ *
41
+ * Env: ARM=system|solo (required), ZAI_API_KEY (required), ZAI_BASE, MODEL=glm-5.2,
42
+ * MAX_TOKENS=12000, K=4, REPAIRS=2, TEMP=0.8 (system attempts+repairs), SOLO_TEMP=0.7,
43
+ * INNER_TURNS=40, CONC=2, REPRO_TIMEOUT=120 (s), LLM_TIMEOUT_MS=480000, IDS=comma-list,
44
+ * OUT=jsonl path, REPRO_MANIFEST=json path (system), SKIP_JUDGE=1 (Phase A only),
45
+ * PRICE_IN/PRICE_OUT (USD per Mtok for the cost table; defaults are the assumed zai list rate).
46
+ * Rows are incremental (OUT.phaseA then OUT) and both phases resume by instance id.
16
47
  */
17
48
  import { execFile } from 'node:child_process'
18
- import { appendFileSync, existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
19
- import { join, relative } from 'node:path'
20
- import { fileURLToPath } from 'node:url'
49
+ import { appendFileSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
50
+ import { join } from 'node:path'
21
51
  import { promisify } from 'node:util'
22
52
  import type { AgenticSurface, AgenticTask, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/loops'
23
53
  import { refine, runAgentic } from '@tangle-network/agent-runtime/loops'
24
54
  import type { BenchTask } from './benchmarks/types'
25
- import {
26
- createSweBenchEnvironment,
27
- resolveImageForMetadata,
28
- resolveSweBenchScorerVersion,
29
- SWE_RUN_TOOL_CONFIG,
30
- SWE_SEED_PROMPT_WITH_RUN,
31
- type SweImageIdentity,
32
- } from './swe-bench-env'
55
+ import { createSweBenchEnvironment, resolveImageForMetadata, SWE_SEED_PROMPT } from './swe-bench-env'
33
56
  import {
34
57
  APPLY_SENTINEL,
35
58
  assertNoHiddenLeak,
@@ -40,98 +63,30 @@ import {
40
63
  tail,
41
64
  zaiChatRaw,
42
65
  } from './swe-jail'
43
- import {
44
- type ExperimentArm,
45
- type ExperimentArmPreset,
46
- assertExactCompletedWorkerSessions,
47
- continuationDisposition,
48
- continuationStateNotice,
49
- preferLaterCandidate,
50
- resolveExperimentArm,
51
- resolveExperimentTemperature,
52
- shouldAcceptContinuation,
53
- shouldRunContinuation,
54
- } from './swe-structural-policy'
55
- import {
56
- assertFingerprintsEqual,
57
- createExecutionReceipt,
58
- createFingerprints,
59
- diffChanged,
60
- diffFingerprint,
61
- fingerprint,
62
- runtimeImplementationFingerprint,
63
- type ExecutionReceipt,
64
- type Fingerprints,
65
- type SharedExecutionReceipt,
66
- } from './swe-structural-provenance'
67
- import {
68
- assertCompleteTaskSet,
69
- assertDistinctArtifactPaths,
70
- assertJudgeCompletionMatchesInput,
71
- assertJudgeResumeFingerprints,
72
- assertPairedExecutionFingerprint,
73
- assertPairedFingerprints,
74
- completeJudgeScore,
75
- } from './swe-structural-judge-policy'
76
66
 
77
67
  const exec = promisify(execFile)
78
- const TEMPERATURE = resolveExperimentTemperature(process.env)
79
-
80
- function sourceTreeReceipt(
81
- rootUrl: URL,
82
- label: string,
83
- include: (path: string) => boolean,
84
- ): Array<{ name: string; content: string }> {
85
- const root = fileURLToPath(rootUrl)
86
- const receipt: Array<{ name: string; content: string }> = []
87
- const visit = (dir: string): void => {
88
- for (const entry of readdirSync(dir, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
89
- const path = join(dir, entry.name)
90
- if (entry.isDirectory()) visit(path)
91
- else if (entry.isFile() && include(path)) {
92
- receipt.push({ name: `${label}/${relative(root, path)}`, content: readFileSync(path, 'utf8') })
93
- }
94
- }
95
- }
96
- visit(root)
97
- return receipt
98
- }
99
68
 
100
69
  // ---------- config ----------
101
70
 
102
- type Mode = 'generate' | 'judge-only'
103
- const MODE_INPUT = process.env.MODE ?? 'generate'
104
- if (MODE_INPUT !== 'generate' && MODE_INPUT !== 'judge-only') {
105
- throw new Error(`MODE must be generate|judge-only, got "${MODE_INPUT}"`)
106
- }
107
- const MODE: Mode = MODE_INPUT
108
- const OFFICIAL_SCORER_CACHE_LEVEL = 'instance' as const
109
- if (
110
- MODE === 'judge-only' &&
111
- process.env.SWEBENCH_CACHE_LEVEL !== undefined &&
112
- process.env.SWEBENCH_CACHE_LEVEL !== OFFICIAL_SCORER_CACHE_LEVEL
113
- ) {
114
- throw new Error('MODE=judge-only requires SWEBENCH_CACHE_LEVEL=instance')
115
- }
116
- if (MODE === 'judge-only') process.env.SWEBENCH_CACHE_LEVEL = OFFICIAL_SCORER_CACHE_LEVEL
117
- for (const legacy of ['ARM', 'ARM_NAME', 'K', 'REPAIRS', 'FORCE_TWO_SESSIONS', 'SOLO_TEMP', 'SKIP_JUDGE']) {
118
- if (process.env[legacy] !== undefined) {
119
- throw new Error(`${legacy} is not supported; use typed EXPERIMENT_ARM plus MODE=generate|judge-only`)
120
- }
121
- }
122
- const ARM_PRESET: ExperimentArmPreset | null = MODE === 'generate'
123
- ? resolveExperimentArm(process.env.EXPERIMENT_ARM ?? '')
124
- : null
71
+ const ARM = process.env.ARM ?? ''
72
+ if (ARM !== 'system' && ARM !== 'solo') throw new Error(`ARM must be system|solo, got "${ARM}"`)
125
73
  const ZAI_BASE = process.env.ZAI_BASE ?? 'https://api.z.ai/api/coding/paas/v4'
126
74
  const ZAI_KEY = process.env.ZAI_API_KEY ?? ''
127
- if (MODE === 'generate' && !ZAI_KEY) throw new Error('ZAI_API_KEY required for MODE=generate')
75
+ if (!ZAI_KEY) throw new Error('ZAI_API_KEY required (run under dotenvx: agent-state.env)')
128
76
  const MODEL = process.env.MODEL ?? 'glm-5.2'
129
77
  // glm-5.2 is a reasoning model: hidden reasoning consumes max_tokens, so <8000 starves content.
130
78
  const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 12_000)
79
+ const K = Number(process.env.K ?? 4)
80
+ const REPAIRS = Number(process.env.REPAIRS ?? 2)
81
+ const TEMP = Number(process.env.TEMP ?? 0.8)
82
+ const SOLO_TEMP = Number(process.env.SOLO_TEMP ?? 0.7)
131
83
  const INNER_TURNS = Number(process.env.INNER_TURNS ?? 40)
132
84
  const CONC = Math.max(1, Math.min(4, Number(process.env.CONC ?? 2)))
133
85
  const REPRO_TIMEOUT_S = Number(process.env.REPRO_TIMEOUT ?? 120)
134
86
  const LLM_TIMEOUT_MS = Number(process.env.LLM_TIMEOUT_MS ?? 480_000)
87
+ const OUT = process.env.OUT ?? `swe-stage1-${ARM}.jsonl`
88
+ const PHASE_A_OUT = `${OUT}.phaseA`
89
+ const SKIP_JUDGE = process.env.SKIP_JUDGE === '1'
135
90
  // Cost-table rates, USD per Mtok. ASSUMED (zai coding-plan tokens have no per-call list price);
136
91
  // override with PRICE_IN/PRICE_OUT. The summary labels them as assumed.
137
92
  const PRICE_IN = Number(process.env.PRICE_IN ?? 0.6)
@@ -144,137 +99,15 @@ interface ReproManifestEntry {
144
99
  }
145
100
 
146
101
  const MANIFEST: Record<string, ReproManifestEntry> = (() => {
147
- if (MODE !== 'generate') return {}
102
+ if (ARM !== 'system') return {}
148
103
  const p = process.env.REPRO_MANIFEST
149
- if (!p) throw new Error('REPRO_MANIFEST required for MODE=generate')
104
+ if (!p) throw new Error('REPRO_MANIFEST required for ARM=system (Stage-0 valid+sound repro scripts)')
150
105
  return JSON.parse(readFileSync(p, 'utf8')) as Record<string, ReproManifestEntry>
151
106
  })()
152
107
 
153
- interface CommonConfigReceipt {
154
- schema: 'swe-structural-v2'
155
- model: string
156
- zaiBase: string
157
- maxTokens: number
158
- temperature: number
159
- innerTurns: number
160
- concurrency: number
161
- reproTimeoutS: number
162
- llmTimeoutMs: number
163
- sweRunTimeoutS: number
164
- sweRunOutputLimit: number
165
- runTool: true
166
- seedPrompt: 'SWE_SEED_PROMPT_WITH_RUN'
167
- taskIds: string[]
168
- }
169
-
170
- interface ExperimentConfigReceipt extends CommonConfigReceipt {
171
- arm: ExperimentArm
172
- k: 1 | 2
173
- repairs: 0 | 1
174
- alwaysRunContinuation: boolean
175
- persistent: boolean
176
- workerSessions: 2
177
- }
178
-
179
- const SOURCE_RECEIPT = [
180
- ['swe-structural.mts', new URL('./swe-structural.mts', import.meta.url)],
181
- ['swe-structural-policy.ts', new URL('./swe-structural-policy.ts', import.meta.url)],
182
- ['swe-structural-provenance.ts', new URL('./swe-structural-provenance.ts', import.meta.url)],
183
- ['swe-structural-judge-policy.ts', new URL('./swe-structural-judge-policy.ts', import.meta.url)],
184
- ['swe-bench-env.ts', new URL('./swe-bench-env.ts', import.meta.url)],
185
- ['swe-jail.ts', new URL('./swe-jail.ts', import.meta.url)],
186
- ['swe-temp.ts', new URL('./swe-temp.ts', import.meta.url)],
187
- ['benchmarks/swe-bench.ts', new URL('./benchmarks/swe-bench.ts', import.meta.url)],
188
- ['benchmarks/_harness.ts', new URL('./benchmarks/_harness.ts', import.meta.url)],
189
- ['runtime/strategy.ts', new URL('../../src/runtime/strategy.ts', import.meta.url)],
190
- ].map(([name, url]) => ({ name: String(name), content: readFileSync(url as URL, 'utf8') }))
191
-
192
- const RUNTIME_IMPLEMENTATION_FINGERPRINT = runtimeImplementationFingerprint({ runAgentic, refine })
193
- const RUNTIME_TREE_FINGERPRINT = fingerprint([
194
- ...sourceTreeReceipt(new URL('../../src/', import.meta.url), 'agent-runtime/src', (path) => path.endsWith('.ts')),
195
- ...sourceTreeReceipt(
196
- new URL('../../node_modules/@tangle-network/agent-eval/dist/', import.meta.url),
197
- 'agent-eval/dist',
198
- (path) => path.endsWith('.js'),
199
- ),
200
- {
201
- name: 'agent-runtime/package.json',
202
- content: readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),
203
- },
204
- {
205
- name: 'agent-runtime/pnpm-lock.yaml',
206
- content: readFileSync(new URL('../../pnpm-lock.yaml', import.meta.url), 'utf8'),
207
- },
208
- {
209
- name: 'agent-eval/package.json',
210
- content: readFileSync(new URL('../../node_modules/@tangle-network/agent-eval/package.json', import.meta.url), 'utf8'),
211
- },
212
- ])
213
-
214
- function makeExperimentConfig(preset: ExperimentArmPreset, taskIds: string[]): ExperimentConfigReceipt {
215
- return {
216
- schema: 'swe-structural-v2',
217
- arm: preset.arm,
218
- k: preset.k,
219
- repairs: preset.repairs,
220
- alwaysRunContinuation: preset.alwaysRunContinuation,
221
- persistent: preset.persistent,
222
- workerSessions: preset.workerSessions,
223
- model: MODEL,
224
- zaiBase: ZAI_BASE,
225
- maxTokens: MAX_TOKENS,
226
- temperature: TEMPERATURE,
227
- innerTurns: INNER_TURNS,
228
- concurrency: CONC,
229
- reproTimeoutS: REPRO_TIMEOUT_S,
230
- llmTimeoutMs: LLM_TIMEOUT_MS,
231
- sweRunTimeoutS: SWE_RUN_TOOL_CONFIG.timeoutS,
232
- sweRunOutputLimit: SWE_RUN_TOOL_CONFIG.outputLimit,
233
- runTool: true,
234
- seedPrompt: 'SWE_SEED_PROMPT_WITH_RUN',
235
- taskIds: [...taskIds],
236
- }
237
- }
238
-
239
- function commonConfig(config: ExperimentConfigReceipt): CommonConfigReceipt {
240
- const {
241
- arm: _arm,
242
- k: _k,
243
- repairs: _repairs,
244
- alwaysRunContinuation: _alwaysRunContinuation,
245
- persistent: _persistent,
246
- workerSessions: _workerSessions,
247
- ...common
248
- } = config
249
- return common
250
- }
251
-
252
- function reproReceipt(entry: ReproManifestEntry | undefined): ReproManifestEntry | null {
253
- return entry ? { script: entry.script, source: entry.source, stage0Class: entry.stage0Class } : null
254
- }
255
-
256
- function expectedFingerprints(
257
- bt: BenchTask,
258
- config: ExperimentConfigReceipt,
259
- tools: unknown,
260
- repro: ReproManifestEntry | undefined,
261
- ): Fingerprints {
262
- return createFingerprints({
263
- source: SOURCE_RECEIPT,
264
- config,
265
- commonConfig: commonConfig(config),
266
- repro: reproReceipt(repro),
267
- prompt: SWE_SEED_PROMPT_WITH_RUN,
268
- tools,
269
- task: { id: bt.id, prompt: bt.prompt, metadata: bt.metadata ?? null },
270
- })
271
- }
272
-
273
108
  // ---------- transport: zai direct, patient ladder, leak guard at the chokepoint ----------
274
109
 
275
110
  interface Counter {
276
- workerSessionsStarted: number
277
- workerSessionsCompleted: number
278
111
  calls: number
279
112
  httpAttempts: number
280
113
  tokensIn: number
@@ -282,15 +115,7 @@ interface Counter {
282
115
  guardedMsgs: number
283
116
  }
284
117
 
285
- const newCounter = (): Counter => ({
286
- workerSessionsStarted: 0,
287
- workerSessionsCompleted: 0,
288
- calls: 0,
289
- httpAttempts: 0,
290
- tokensIn: 0,
291
- tokensOut: 0,
292
- guardedMsgs: 0,
293
- })
118
+ const newCounter = (): Counter => ({ calls: 0, httpAttempts: 0, tokensIn: 0, tokensOut: 0, guardedMsgs: 0 })
294
119
 
295
120
  /** Distinctive content marks for the leak guard: the first substantive ADDED line of the gold patch
296
121
  * and of the hidden test patch. Never shown to any model; used only to refuse outbound messages. */
@@ -379,13 +204,12 @@ async function emitAttempt(
379
204
  }
380
205
  const task: AgenticTask = {
381
206
  id: bt.id,
382
- systemPrompt: SWE_SEED_PROMPT_WITH_RUN,
207
+ systemPrompt: SWE_SEED_PROMPT,
383
208
  userPrompt: cfg.promptAppendix ? `${bt.prompt}\n\n${cfg.promptAppendix}` : bt.prompt,
384
209
  meta: { instanceId: bt.id },
385
210
  }
386
211
  let error: string | undefined
387
212
  try {
388
- cfg.instanceCounter.workerSessionsStarted += 1
389
213
  const r = await runAgentic({
390
214
  surface: proxy,
391
215
  task,
@@ -399,8 +223,6 @@ async function emitAttempt(
399
223
  budget: 1,
400
224
  complete: makeTransport(cfg.marks, counter),
401
225
  })
402
- if (counter.calls < 1) throw new Error('worker session completed without a successful model call')
403
- cfg.instanceCounter.workerSessionsCompleted += 1
404
226
  cfg.instanceCounter.calls += counter.calls
405
227
  cfg.instanceCounter.httpAttempts += counter.httpAttempts
406
228
  cfg.instanceCounter.tokensIn += counter.tokensIn
@@ -433,17 +255,11 @@ async function emitAttempt(
433
255
  }
434
256
  }
435
257
 
436
- // Arm composition deliberately stays outside the built-in sample/refine strategies while each
437
- // worker session still runs through runAgentic(refine, budget=1). The built-ins cannot reproduce
438
- // this controlled comparison: sample exposes only aggregate scores and has no stable later-on-tie
439
- // patch receipt; refine may stop after shot one, adds an analyst call, and carries conversation
440
- // history. This layer supplies only the missing experiment policy: exactly two fresh worker
441
- // sessions, optional parent-patch state, shared visible selection, and exact per-session receipts.
442
-
443
258
  // ---------- in-image candidate scoring ----------
444
259
 
445
- /** Severity ordering for selection (lower is better): 0 repro-pass, 1 repro-fail, 2 timeout,
446
- * 3 apply-fail, 4 empty. Both experiment arms deterministically prefer session two on a tie. */
260
+ /** Severity ordering for argmax (lower is better): 0 repro-pass, 1 repro-fail (clean nonzero exit),
261
+ * 2 repro-timeout, 3 candidate failed to apply, 4 empty candidate. "Crash-lowest" is realized as
262
+ * this exit-class ordering; ties break on first index (deterministic). */
447
263
  interface CandScore {
448
264
  applyOk: boolean | null
449
265
  exit: number | null
@@ -471,7 +287,6 @@ async function scoreCandidate(imageTag: string, repro: string | null, diff: stri
471
287
  interface CandidateRow {
472
288
  idx: number
473
289
  diff: string
474
- diffHash: string
475
290
  diffBytes: number
476
291
  completions: number
477
292
  calls: number
@@ -490,10 +305,7 @@ interface RepairRow {
490
305
  round: number
491
306
  baseFrom: string
492
307
  baseSeverity: number
493
- parentDiffHash: string
494
308
  diff: string
495
- finalDiffHash: string
496
- changedFromParent: boolean
497
309
  diffBytes: number
498
310
  completions: number
499
311
  calls: number
@@ -507,46 +319,37 @@ interface RepairRow {
507
319
  accepted: boolean
508
320
  }
509
321
 
510
- interface PhaseARow {
322
+ interface Row {
511
323
  instanceId: string
512
- arm: ExperimentArm
513
- config: ExperimentConfigReceipt
514
- fingerprints: Fingerprints
324
+ arm: string
515
325
  repo: string
516
326
  model: string
517
327
  image: string | null
518
- execution: ExecutionReceipt | null
519
- executionFingerprint: string
520
328
  execMode: 'image'
521
329
  temperature: number
522
330
  innerTurns: number
523
331
  k: number
524
- maxRepairs: number
525
332
  // canary + repro provenance (system arm)
526
333
  canaryExit: number | null
527
334
  canaryPass: boolean | null
528
335
  reproSource: string
529
- reproStage0Class: string | null
530
336
  reproStatus: string
531
337
  reproScript: string | null
532
338
  reproPreExit: number | null
533
339
  reproGoldExit: number | null
534
- reproOutcomeFingerprint: string
535
- // candidates + selection + continuation receipts
340
+ // candidates + selection + repair receipts (system arm)
536
341
  candidates: CandidateRow[]
537
342
  selection: { mode: string; selectedIdx: number; movedOffFirst: boolean } | null
538
343
  repairs: RepairRow[]
539
344
  repairStop: string | null
540
345
  finalFrom: string
541
- parentDiffHash: string | null
542
346
  finalDiff: string
543
- finalDiffHash: string
544
- changedFromParent: boolean | null
347
+ // hidden judge (Phase B; locked-after-decisions)
348
+ hiddenResolved: boolean | null
349
+ judgeDetail: string | null
350
+ judgeMs: number | null
351
+ judgeSkipped: string | null
545
352
  // cost + guard receipts
546
- /** Number of fresh runAgentic worker invocations, including invocations that returned an error. */
547
- workerSessionsStarted: number
548
- /** Number of runAgentic invocations that returned successfully after at least one model call. */
549
- workerSessions: number
550
353
  llmCalls: number
551
354
  httpAttempts: number
552
355
  tokensIn: number
@@ -560,32 +363,17 @@ interface PhaseARow {
560
363
 
561
364
  type Env = Awaited<ReturnType<typeof createSweBenchEnvironment>>
562
365
 
563
- interface PhaseAContext {
564
- preset: ExperimentArmPreset
565
- config: ExperimentConfigReceipt
566
- tools: unknown
567
- sharedExecution: SharedExecutionReceipt
568
- expectedImageIdentities: Map<string, SweImageIdentity>
569
- }
570
-
571
- async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<PhaseARow> {
366
+ async function phaseA(env: Env, bt: BenchTask): Promise<Row> {
572
367
  const t0 = Date.now()
573
368
  const md = bt.metadata as Record<string, string>
574
369
  const counter = newCounter()
575
- const row: PhaseARow = {
576
- instanceId: bt.id, arm: ctx.preset.arm, config: ctx.config,
577
- fingerprints: expectedFingerprints(bt, ctx.config, ctx.tools, MANIFEST[bt.id]),
578
- repo: md.repo, model: MODEL, image: null, execution: null, executionFingerprint: '', execMode: 'image',
579
- temperature: TEMPERATURE, innerTurns: INNER_TURNS,
580
- k: ctx.preset.k, maxRepairs: ctx.preset.repairs,
581
- canaryExit: null, canaryPass: null, reproSource: 'none', reproStage0Class: null,
582
- reproStatus: 'none', reproScript: null,
583
- reproPreExit: null, reproGoldExit: null, reproOutcomeFingerprint: '',
584
- candidates: [], selection: null, repairs: [],
585
- repairStop: null, finalFrom: 'none', parentDiffHash: null, finalDiff: '',
586
- finalDiffHash: diffFingerprint(''), changedFromParent: null,
587
- workerSessionsStarted: 0, workerSessions: 0, llmCalls: 0, httpAttempts: 0,
588
- tokensIn: 0, tokensOut: 0,
370
+ const row: Row = {
371
+ instanceId: bt.id, arm: ARM, repo: md.repo, model: MODEL, image: null, execMode: 'image',
372
+ temperature: ARM === 'system' ? TEMP : SOLO_TEMP, innerTurns: INNER_TURNS, k: ARM === 'system' ? K : 1,
373
+ canaryExit: null, canaryPass: null, reproSource: 'none', reproStatus: 'none', reproScript: null,
374
+ reproPreExit: null, reproGoldExit: null, candidates: [], selection: null, repairs: [],
375
+ repairStop: null, finalFrom: 'none', finalDiff: '', hiddenResolved: null, judgeDetail: null,
376
+ judgeMs: null, judgeSkipped: null, llmCalls: 0, httpAttempts: 0, tokensIn: 0, tokensOut: 0,
589
377
  guardedMsgs: 0, wallMs: 0,
590
378
  }
591
379
  const marks = leakMarks(md)
@@ -593,16 +381,25 @@ async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<Phas
593
381
  const img = await resolveImageForMetadata(bt.metadata ?? {})
594
382
  if (!img.ok) throw new Error(`image missing: ${img.reason}`)
595
383
  row.image = img.tag
596
- row.execution = createExecutionReceipt(ctx.sharedExecution, img)
597
- row.executionFingerprint = fingerprint(row.execution)
598
- ctx.expectedImageIdentities.set(bt.id, img.identity)
384
+
385
+ if (ARM === 'solo') {
386
+ const a = await emitAttempt(env.environment, bt, { temperature: SOLO_TEMP, marks, instanceCounter: counter })
387
+ row.candidates.push({
388
+ idx: 0, diff: a.diff, diffBytes: a.diff.length, completions: a.completions, calls: a.calls,
389
+ tokensIn: a.tokensIn, tokensOut: a.tokensOut, wallMs: a.wallMs, attemptError: a.error ?? null,
390
+ applyOk: null, reproExit: null, reproTimedOut: false, severity: a.diff.trim() ? 1 : 4, reproOutTail: '',
391
+ })
392
+ row.finalDiff = a.diff
393
+ row.finalFrom = 'candidate:0'
394
+ return row
395
+ }
599
396
 
600
397
  // 1. EXECUTION CANARY (image substrate, gold in-container — script-side only, zero model calls).
601
398
  const pkg = IMPORT_NAME[md.repo]
602
399
  if (!pkg) throw new Error(`no IMPORT_NAME for ${md.repo} — canary not expressible`)
603
400
  const gold = String(md.patch ?? '')
604
401
  if (!gold.trim()) throw new Error('gold patch missing from metadata')
605
- const c = await runPyInJail(img.identity.id, null, importCanaryScript(pkg), gold, { timeoutS: REPRO_TIMEOUT_S })
402
+ const c = await runPyInJail(img.tag, null, importCanaryScript(pkg), gold, { timeoutS: REPRO_TIMEOUT_S })
606
403
  if (c.infraError) throw new Error(c.infraError)
607
404
  row.canaryExit = c.code
608
405
  row.canaryPass = c.code === 0 && c.out.includes(APPLY_SENTINEL)
@@ -613,15 +410,14 @@ async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<Phas
613
410
  let repro: string | null = null
614
411
  if (manifest) {
615
412
  row.reproSource = manifest.source
616
- row.reproStage0Class = manifest.stage0Class
617
413
  row.reproScript = manifest.script
618
- const pre = await runPyInJail(img.identity.id, null, manifest.script, undefined, { timeoutS: REPRO_TIMEOUT_S })
414
+ const pre = await runPyInJail(img.tag, null, manifest.script, undefined, { timeoutS: REPRO_TIMEOUT_S })
619
415
  if (pre.infraError) throw new Error(pre.infraError)
620
416
  row.reproPreExit = pre.code
621
417
  if (pre.timedOut) row.reproStatus = 'degraded-timeout'
622
418
  else if (pre.code === 0) row.reproStatus = 'degraded-invalid'
623
419
  else {
624
- const post = await runPyInJail(img.identity.id, null, manifest.script, gold, { timeoutS: REPRO_TIMEOUT_S })
420
+ const post = await runPyInJail(img.tag, null, manifest.script, gold, { timeoutS: REPRO_TIMEOUT_S })
625
421
  if (post.infraError) throw new Error(post.infraError)
626
422
  row.reproGoldExit = post.code
627
423
  if (post.code === 0 && post.out.includes(APPLY_SENTINEL)) {
@@ -635,12 +431,11 @@ async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<Phas
635
431
 
636
432
  // 3. k independent candidates (serial within the instance — CONC instances bound zai concurrency).
637
433
  const diffs: string[] = []
638
- for (let i = 0; i < ctx.preset.k; i += 1) {
639
- const a = await emitAttempt(env.environment, bt, { temperature: TEMPERATURE, marks, instanceCounter: counter })
434
+ for (let i = 0; i < K; i += 1) {
435
+ const a = await emitAttempt(env.environment, bt, { temperature: TEMP, marks, instanceCounter: counter })
640
436
  diffs.push(a.diff)
641
437
  row.candidates.push({
642
- idx: i, diff: a.diff, diffHash: diffFingerprint(a.diff), diffBytes: a.diff.length,
643
- completions: a.completions, calls: a.calls,
438
+ idx: i, diff: a.diff, diffBytes: a.diff.length, completions: a.completions, calls: a.calls,
644
439
  tokensIn: a.tokensIn, tokensOut: a.tokensOut, wallMs: a.wallMs, attemptError: a.error ?? null,
645
440
  applyOk: null, reproExit: null, reproTimedOut: false, severity: -1, reproOutTail: '',
646
441
  })
@@ -648,8 +443,8 @@ async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<Phas
648
443
 
649
444
  // 4. In-image scoring + argmax.
650
445
  const scores: CandScore[] = []
651
- for (let i = 0; i < ctx.preset.k; i += 1) {
652
- const s = await scoreCandidate(img.identity.id, repro, diffs[i] as string)
446
+ for (let i = 0; i < K; i += 1) {
447
+ const s = await scoreCandidate(img.tag, repro, diffs[i] as string)
653
448
  scores.push(s)
654
449
  const cand = row.candidates[i] as CandidateRow
655
450
  cand.applyOk = s.applyOk
@@ -658,96 +453,61 @@ async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<Phas
658
453
  cand.severity = s.severity
659
454
  cand.reproOutTail = s.out
660
455
  }
661
- let selectedIdx = 0
662
- for (let i = 1; i < ctx.preset.k; i += 1) {
663
- if (preferLaterCandidate((scores[selectedIdx] as CandScore).severity, (scores[i] as CandScore).severity)) {
664
- selectedIdx = i
456
+ let selectedIdx: number
457
+ let mode: string
458
+ if (repro) {
459
+ mode = 'repro-argmax'
460
+ selectedIdx = 0
461
+ for (let i = 1; i < K; i += 1) {
462
+ if ((scores[i] as CandScore).severity < (scores[selectedIdx] as CandScore).severity) selectedIdx = i
665
463
  }
464
+ } else {
465
+ mode = 'blind-first'
466
+ const firstNonEmpty = diffs.findIndex((d) => d.trim().length > 0)
467
+ selectedIdx = firstNonEmpty === -1 ? 0 : firstNonEmpty
666
468
  }
667
- const mode = repro ? 'visible-severity-later-tie' : 'no-repro-later-tie'
668
- row.selection = { mode, selectedIdx, movedOffFirst: selectedIdx !== 0 }
669
-
670
- // 5. The persistent arm starts exactly one continuation from session one's cumulative patch.
671
- // Both arms use the same later-on-visible-tie policy.
672
- let best = {
673
- diff: diffs[selectedIdx] as string,
674
- score: scores[selectedIdx] as CandScore,
675
- from: ctx.preset.persistent ? 'session:1' : `attempt:${selectedIdx + 1}`,
676
- }
677
- if (!ctx.preset.persistent) {
678
- row.repairStop = 'independent-arm'
469
+ row.selection = { mode, selectedIdx, movedOffFirst: mode === 'repro-argmax' && selectedIdx !== 0 }
470
+
471
+ // 5. Guarded repair (repro-armed instances only — no signal, no repair).
472
+ let best = { diff: diffs[selectedIdx] as string, score: scores[selectedIdx] as CandScore, from: `candidate:${selectedIdx}` }
473
+ if (!repro) {
474
+ row.repairStop = 'no-signal'
475
+ } else if (best.score.severity === 0) {
476
+ row.repairStop = 'already-passing'
679
477
  } else {
680
- for (let round = 1; shouldRunContinuation({ round, preset: ctx.preset }); round += 1) {
681
- const parentDiff = best.diff
682
- const parentDiffHash = diffFingerprint(parentDiff)
683
- row.parentDiffHash = parentDiffHash
684
- const reproductionEvidence = repro
685
- ? `--- REPRODUCTION SCRIPT (written from the issue; exit 0 = fixed) ---\n${tail(repro, 6_000)}\n\n` +
686
- `--- REPRODUCTION OUTPUT on the current state (exit ${best.score.exit ?? 'n/a'}) ---\n${best.score.out}\n\n`
687
- : '--- EXTERNAL REPRODUCTION ---\nNo external reproduction is available. Use the run tool to construct local, issue-specific checks.\n\n'
688
- const repairInstruction = best.score.severity === 0
689
- ? 'The visible reproduction PASSES, but it is only a partial check. Re-read the full issue and audit ' +
690
- 'the current patch for missed cases or regressions. Keep the visible check passing while correcting ' +
691
- 'any incomplete source behavior you find with minimal edit_file changes. Do not modify tests.'
692
- : 'The visible reproduction still fails (or no external reproduction is available). Diagnose why the ' +
693
- 'current state does not resolve the full issue, then correct the SOURCE with minimal edit_file changes ' +
694
- '(you may revise or revert parts of the previous fix — it is already in the files). Do not modify tests.'
478
+ for (let round = 1; round <= REPAIRS && best.score.severity > 0; round += 1) {
695
479
  const appendix =
696
- continuationStateNotice(best.diff) +
697
- reproductionEvidence +
480
+ (best.diff.trim()
481
+ ? `--- PREVIOUS FIX (already applied to this checkout) ---\n${tail(best.diff, 8_000)}\n\n`
482
+ : '--- NO FIX APPLIED YET (every prior attempt produced no change) ---\n\n') +
483
+ `--- REPRODUCTION SCRIPT (written from the issue; exit 0 = fixed) ---\n${tail(repro, 6_000)}\n\n` +
484
+ `--- REPRODUCTION OUTPUT on the current state (exit ${best.score.exit ?? 'n/a'}) ---\n${best.score.out}\n\n` +
698
485
  '--- REPAIR INSTRUCTIONS ---\n' +
699
- repairInstruction
486
+ 'The reproduction above STILL FAILS. Diagnose why the current state does not resolve the issue, ' +
487
+ 'then correct the SOURCE with minimal edit_file changes (you may revise or revert parts of the ' +
488
+ 'previous fix — it is already in the files). Do not modify tests.'
700
489
  const a = await emitAttempt(env.environment, bt, {
701
- temperature: TEMPERATURE, marks, instanceCounter: counter, preApply: best.diff, promptAppendix: appendix,
490
+ temperature: TEMP, marks, instanceCounter: counter, preApply: best.diff, promptAppendix: appendix,
702
491
  })
703
- const ns = await scoreCandidate(img.identity.id, repro, a.diff)
704
- const finalDiffHash = diffFingerprint(a.diff)
705
- const changedFromParent = diffChanged(parentDiff, a.diff)
706
- const accepted = shouldAcceptContinuation(best.score.severity, ns.severity)
707
- const disposition = continuationDisposition(accepted, changedFromParent)
492
+ const ns = await scoreCandidate(img.tag, repro, a.diff)
493
+ const accepted = ns.severity < best.score.severity
708
494
  row.repairs.push({
709
- round, baseFrom: best.from, baseSeverity: best.score.severity, parentDiffHash,
710
- diff: a.diff, finalDiffHash, changedFromParent, diffBytes: a.diff.length,
495
+ round, baseFrom: best.from, baseSeverity: best.score.severity, diff: a.diff, diffBytes: a.diff.length,
711
496
  completions: a.completions, calls: a.calls, tokensIn: a.tokensIn, tokensOut: a.tokensOut,
712
497
  wallMs: a.wallMs, attemptError: a.error ?? null, applyOk: ns.applyOk, reproExit: ns.exit,
713
498
  severity: ns.severity, accepted,
714
499
  })
715
- if (accepted) {
716
- best = {
717
- diff: a.diff,
718
- score: ns,
719
- from: disposition.finalFrom,
720
- }
721
- row.repairStop = disposition.stop
722
- } else {
723
- best.from = disposition.finalFrom
724
- row.repairStop = disposition.stop
725
- }
500
+ if (accepted) best = { diff: a.diff, score: ns, from: `repair:${round}` }
726
501
  }
502
+ row.repairStop = best.score.severity === 0 ? 'repaired-pass' : 'rounds-exhausted'
727
503
  }
728
504
  row.finalDiff = best.diff
729
- row.finalDiffHash = diffFingerprint(best.diff)
730
505
  row.finalFrom = best.from
731
- row.changedFromParent = row.parentDiffHash === null
732
- ? null
733
- : row.finalDiffHash !== row.parentDiffHash
734
506
  return row
735
507
  } catch (e) {
736
508
  row.error = e instanceof Error ? e.message.slice(0, 400) : String(e).slice(0, 400)
737
509
  return row
738
510
  } finally {
739
- row.reproOutcomeFingerprint = fingerprint({
740
- canaryExit: row.canaryExit,
741
- canaryPass: row.canaryPass,
742
- image: row.execution?.image ?? null,
743
- reproGoldExit: row.reproGoldExit,
744
- reproPreExit: row.reproPreExit,
745
- reproSource: row.reproSource,
746
- reproStage0Class: row.reproStage0Class,
747
- reproStatus: row.reproStatus,
748
- })
749
- row.workerSessionsStarted = counter.workerSessionsStarted
750
- row.workerSessions = counter.workerSessionsCompleted
751
511
  row.llmCalls = counter.calls
752
512
  row.httpAttempts = counter.httpAttempts
753
513
  row.tokensIn = counter.tokensIn
@@ -759,501 +519,149 @@ async function phaseA(env: Env, bt: BenchTask, ctx: PhaseAContext): Promise<Phas
759
519
 
760
520
  // ---------- driver ----------
761
521
 
762
- function loadPhaseRows(path: string, required = false): Map<string, PhaseARow> {
763
- if (!existsSync(path)) {
764
- if (required) throw new Error(`required Phase-A file does not exist: ${path}`)
765
- return new Map()
766
- }
767
- const rows = new Map<string, PhaseARow>()
768
- for (const [index, line] of readFileSync(path, 'utf8').split('\n').entries()) {
522
+ function loadRows(path: string): Map<string, Row> {
523
+ const m = new Map<string, Row>()
524
+ if (!existsSync(path)) return m
525
+ for (const line of readFileSync(path, 'utf8').split('\n')) {
769
526
  if (!line.trim()) continue
770
- const row = JSON.parse(line) as PhaseARow
771
- if (!row.instanceId) throw new Error(`${path}:${index + 1}: missing instanceId`)
772
- if (rows.has(row.instanceId)) throw new Error(`${path}:${index + 1}: duplicate instanceId ${row.instanceId}`)
773
- rows.set(row.instanceId, row)
527
+ const r = JSON.parse(line) as Row
528
+ m.set(r.instanceId, r)
774
529
  }
775
- if (required && rows.size === 0) throw new Error(`required Phase-A file is empty: ${path}`)
776
- return rows
530
+ return m
777
531
  }
778
532
 
779
- function manifestFromRow(row: PhaseARow): ReproManifestEntry | undefined {
780
- if (row.reproScript === null) {
781
- if (row.reproSource !== 'none' || row.reproStage0Class !== null) {
782
- throw new Error(`${row.instanceId}: incomplete null-repro receipt`)
783
- }
784
- return undefined
785
- }
786
- if (row.reproSource === 'none' || row.reproStage0Class === null) {
787
- throw new Error(`${row.instanceId}: incomplete repro receipt`)
788
- }
789
- return { script: row.reproScript, source: row.reproSource, stage0Class: row.reproStage0Class }
790
- }
791
-
792
- function assertPresetConfig(config: ExperimentConfigReceipt, arm: ExperimentArm, context: string): void {
793
- const preset = resolveExperimentArm(arm)
794
- if (
795
- config.schema !== 'swe-structural-v2' ||
796
- !config.model ||
797
- !config.zaiBase ||
798
- !Number.isFinite(config.maxTokens) ||
799
- config.maxTokens <= 0 ||
800
- !Number.isFinite(config.temperature) ||
801
- !Number.isInteger(config.innerTurns) ||
802
- config.innerTurns <= 0 ||
803
- !Number.isInteger(config.concurrency) ||
804
- config.concurrency < 1 ||
805
- !Number.isFinite(config.reproTimeoutS) ||
806
- config.reproTimeoutS <= 0 ||
807
- !Number.isFinite(config.llmTimeoutMs) ||
808
- config.llmTimeoutMs <= 0 ||
809
- config.runTool !== true ||
810
- config.seedPrompt !== 'SWE_SEED_PROMPT_WITH_RUN' ||
811
- !Number.isFinite(config.sweRunTimeoutS) ||
812
- config.sweRunTimeoutS <= 0 ||
813
- !Number.isFinite(config.sweRunOutputLimit) ||
814
- config.sweRunOutputLimit <= 0 ||
815
- !Array.isArray(config.taskIds) ||
816
- config.taskIds.length === 0
817
- ) {
818
- throw new Error(`${context}: unsupported config schema or worker surface`)
819
- }
820
- for (const key of ['arm', 'k', 'repairs', 'alwaysRunContinuation', 'persistent', 'workerSessions'] as const) {
821
- if (config[key] !== preset[key]) {
822
- throw new Error(`${context}: config ${key}=${String(config[key])}, expected ${String(preset[key])}`)
823
- }
824
- }
825
- }
826
-
827
- async function assertPhaseRow(
828
- row: PhaseARow,
829
- bt: BenchTask,
830
- config: ExperimentConfigReceipt,
831
- tools: unknown,
832
- repro: ReproManifestEntry | undefined,
833
- sharedExecution: SharedExecutionReceipt,
834
- context: string,
835
- ): Promise<void> {
836
- if (row.instanceId !== bt.id) throw new Error(`${context}: instance mismatch ${row.instanceId} != ${bt.id}`)
837
- if (row.arm !== config.arm) throw new Error(`${context}: arm mismatch ${row.arm} != ${config.arm}`)
838
- if (!row.config) throw new Error(`${context}: missing config receipt`)
839
- assertPresetConfig(row.config, row.arm, context)
840
- if (fingerprint(row.config) !== fingerprint(config)) throw new Error(`${context}: config receipt mismatch`)
841
- assertFingerprintsEqual(row.fingerprints, expectedFingerprints(bt, config, tools, repro), context)
842
- if (
843
- row.model !== config.model ||
844
- row.temperature !== config.temperature ||
845
- row.innerTurns !== config.innerTurns ||
846
- row.k !== config.k ||
847
- row.maxRepairs !== config.repairs
848
- ) {
849
- throw new Error(`${context}: row execution fields do not match config receipt`)
850
- }
851
- if (row.repo !== String((bt.metadata as Record<string, unknown> | undefined)?.repo ?? '')) {
852
- throw new Error(`${context}: row repo does not match task metadata`)
853
- }
854
- if (row.error) throw new Error(`${context}: Phase A contains an error: ${row.error}`)
855
- assertExactCompletedWorkerSessions({
856
- started: row.workerSessionsStarted,
857
- completed: row.workerSessions,
858
- sessions: [...row.candidates, ...row.repairs],
859
- context,
860
- })
861
- const currentImage = await resolveImageForMetadata(bt.metadata ?? {})
862
- if (!currentImage.ok) throw new Error(`${context}: image unavailable during receipt validation: ${currentImage.reason}`)
863
- const expectedExecution = createExecutionReceipt(sharedExecution, currentImage)
864
- const actualExecution = row.execution
865
- if (!actualExecution || fingerprint(actualExecution) !== fingerprint(expectedExecution)) {
866
- throw new Error(`${context}: execution receipt mismatch`)
867
- }
868
- if (
869
- row.config.sweRunTimeoutS !== actualExecution.runTool.timeoutS ||
870
- row.config.sweRunOutputLimit !== actualExecution.runTool.outputLimit
871
- ) {
872
- throw new Error(`${context}: config and execution run-tool settings do not match`)
873
- }
874
- if (row.executionFingerprint !== fingerprint(expectedExecution)) {
875
- throw new Error(`${context}: execution fingerprint mismatch`)
876
- }
877
- if (row.image !== expectedExecution.image.tag) throw new Error(`${context}: image tag receipt mismatch`)
878
- if (row.finalDiffHash !== diffFingerprint(row.finalDiff)) throw new Error(`${context}: final diff hash mismatch`)
879
- const expectedReproOutcome = fingerprint({
880
- canaryExit: row.canaryExit,
881
- canaryPass: row.canaryPass,
882
- image: actualExecution.image,
883
- reproGoldExit: row.reproGoldExit,
884
- reproPreExit: row.reproPreExit,
885
- reproSource: row.reproSource,
886
- reproStage0Class: row.reproStage0Class,
887
- reproStatus: row.reproStatus,
888
- })
889
- if (row.reproOutcomeFingerprint !== expectedReproOutcome) {
890
- throw new Error(`${context}: reproduction outcome fingerprint mismatch`)
891
- }
892
- for (const candidate of row.candidates) {
893
- if (candidate.diffHash !== diffFingerprint(candidate.diff)) {
894
- throw new Error(`${context}: candidate ${candidate.idx} diff hash mismatch`)
895
- }
896
- }
897
-
898
- const preset = resolveExperimentArm(row.arm)
899
- if (row.candidates.length !== preset.k || row.repairs.length !== preset.repairs) {
900
- throw new Error(
901
- `${context}: arm shape mismatch (candidates=${row.candidates.length}, repairs=${row.repairs.length})`,
902
- )
903
- }
904
- let selectedIdx = 0
905
- for (let i = 1; i < row.candidates.length; i += 1) {
906
- if (preferLaterCandidate(row.candidates[selectedIdx]!.severity, row.candidates[i]!.severity)) selectedIdx = i
907
- }
908
- const expectedMode = row.reproStatus === 'ok' ? 'visible-severity-later-tie' : 'no-repro-later-tie'
909
- if (
910
- row.selection?.selectedIdx !== selectedIdx ||
911
- row.selection.mode !== expectedMode ||
912
- row.selection.movedOffFirst !== (selectedIdx !== 0)
913
- ) {
914
- throw new Error(`${context}: selectedIdx violates later-on-visible-tie policy`)
915
- }
916
-
917
- if (!preset.persistent) {
918
- if (row.parentDiffHash !== null || row.changedFromParent !== null) {
919
- throw new Error(`${context}: independent arm must not claim a parent/refinement`)
920
- }
921
- if (row.repairStop !== 'independent-arm') throw new Error(`${context}: independent arm stop receipt mismatch`)
922
- const selected = row.candidates[selectedIdx]!
923
- if (row.finalDiff !== selected.diff || row.finalFrom !== `attempt:${selectedIdx + 1}`) {
924
- throw new Error(`${context}: independent final patch does not match selected attempt`)
925
- }
926
- return
927
- }
928
-
929
- const continuation = row.repairs[0]!
930
- const parent = row.candidates[0]!.diff
931
- if (continuation.baseFrom !== 'session:1' || continuation.baseSeverity !== row.candidates[0]!.severity) {
932
- throw new Error(`${context}: continuation parent receipt mismatch`)
933
- }
934
- if (continuation.parentDiffHash !== diffFingerprint(parent)) throw new Error(`${context}: parent diff hash mismatch`)
935
- if (continuation.finalDiffHash !== diffFingerprint(continuation.diff)) throw new Error(`${context}: continuation diff hash mismatch`)
936
- if (continuation.changedFromParent !== diffChanged(parent, continuation.diff)) {
937
- throw new Error(`${context}: continuation changedFromParent mismatch`)
938
- }
939
- const shouldAccept = shouldAcceptContinuation(continuation.baseSeverity, continuation.severity)
940
- if (continuation.accepted !== shouldAccept) throw new Error(`${context}: continuation violates shared tie policy`)
941
- const expectedFinal = continuation.accepted ? continuation.diff : parent
942
- const disposition = continuationDisposition(continuation.accepted, continuation.changedFromParent)
943
- if (row.finalDiff !== expectedFinal || row.finalFrom !== disposition.finalFrom || row.repairStop !== disposition.stop) {
944
- throw new Error(`${context}: persistent final patch/provenance mismatch`)
945
- }
946
- if (row.parentDiffHash !== continuation.parentDiffHash) throw new Error(`${context}: row parent diff hash mismatch`)
947
- if (row.changedFromParent !== (row.finalDiffHash !== row.parentDiffHash)) {
948
- throw new Error(`${context}: row changedFromParent mismatch`)
949
- }
950
- }
951
-
952
- async function assertOfficialScoreImagePinned(row: PhaseARow, bt: BenchTask, context: string): Promise<void> {
953
- const expected = row.execution?.image
954
- if (!expected) throw new Error(`${context}: missing immutable image receipt`)
955
- const current = await resolveImageForMetadata(bt.metadata ?? {})
956
- if (!current.ok) throw new Error(`${context}: image unavailable: ${current.reason}`)
957
- if (
958
- current.tag !== expected.tag ||
959
- current.namespace !== expected.namespace ||
960
- current.identity.id !== expected.id
961
- ) {
962
- throw new Error(
963
- `${context}: official-score image changed ` +
964
- `(${expected.namespace}:${expected.tag}@${expected.id} -> ` +
965
- `${current.namespace}:${current.tag}@${current.identity.id})`,
966
- )
967
- }
968
- }
969
-
970
- async function loadTasksAndTools(ids: string[]): Promise<{
971
- env: Env
972
- taskById: Map<string, BenchTask>
973
- tools: unknown
974
- sharedExecution: SharedExecutionReceipt
975
- expectedImageIdentities: Map<string, SweImageIdentity>
976
- }> {
977
- const expectedImageIdentities = new Map<string, SweImageIdentity>()
978
- const env = await createSweBenchEnvironment(ids.length, {
979
- ids,
980
- cloneCache: true,
981
- enableRun: true,
982
- expectedImageIdentities,
983
- adapterOptions: { cacheLevel: OFFICIAL_SCORER_CACHE_LEVEL },
984
- })
985
- await env.adapter.preflight?.()
986
- const scorerVersion = await resolveSweBenchScorerVersion()
987
- const taskById = new Map((await env.adapter.loadTasks({ ids, split: 'test' })).map((task) => [task.id, task]))
988
- const missing = ids.filter((id) => !taskById.has(id))
989
- if (missing.length) throw new Error(`instances not found in SWE-bench_Verified: ${missing.join(', ')}`)
990
- const first = taskById.get(ids[0]!)!
991
- const fingerprintTask: AgenticTask = {
992
- id: first.id,
993
- systemPrompt: SWE_SEED_PROMPT_WITH_RUN,
994
- userPrompt: first.prompt,
995
- meta: { instanceId: first.id },
996
- }
997
- const fingerprintHandle: ArtifactHandle = { id: 'fingerprint-only', surface: 'swe-bench-verified' }
998
- return {
999
- env,
1000
- taskById,
1001
- tools: await env.environment.tools(fingerprintTask, fingerprintHandle),
1002
- sharedExecution: {
1003
- runTool: { ...SWE_RUN_TOOL_CONFIG },
1004
- runtimeImplementationFingerprint: RUNTIME_IMPLEMENTATION_FINGERPRINT,
1005
- runtimeTreeFingerprint: RUNTIME_TREE_FINGERPRINT,
1006
- officialScorer: {
1007
- package: 'swebench',
1008
- version: scorerVersion,
1009
- cacheLevel: OFFICIAL_SCORER_CACHE_LEVEL,
1010
- namespacePolicy: 'phase-a-image',
1011
- },
1012
- },
1013
- expectedImageIdentities,
1014
- }
1015
- }
1016
-
1017
- async function generateMain(): Promise<void> {
1018
- const preset = ARM_PRESET as ExperimentArmPreset
533
+ async function main(): Promise<void> {
1019
534
  const ids = process.env.IDS
1020
- ? process.env.IDS.split(',').map((value) => value.trim()).filter(Boolean)
535
+ ? process.env.IDS.split(',').map((s) => s.trim()).filter(Boolean)
1021
536
  : await cachedInstanceIds()
1022
537
  if (!ids.length) throw new Error('no cached sweb.eval images found and no IDS given')
1023
- if (new Set(ids).size !== ids.length) throw new Error('IDS contains duplicates')
1024
- const out = process.env.OUT ?? `swe-stage1-${preset.arm}.phaseA.jsonl`
1025
- const config = makeExperimentConfig(preset, ids)
1026
- assertPresetConfig(config, preset.arm, 'generate config')
1027
- const { env, taskById, tools, sharedExecution, expectedImageIdentities } = await loadTasksAndTools(ids)
1028
538
 
1029
- console.log(`═══ SWE-bench Phase A${preset.arm} ═══`)
539
+ console.log(`═══ SWE-bench Stage 1structural pilot, ARM=${ARM} ═══`)
1030
540
  console.log(
1031
- `sessions=2 k=${preset.k} repairs=${preset.repairs} persistent=${preset.persistent ? 1 : 0} ` +
1032
- `model=${MODEL} maxTokens=${MAX_TOKENS} innerTurns=${INNER_TURNS} temperature=${TEMPERATURE} runTool=1 judge=DISABLED`,
541
+ `model=${MODEL} base=${ZAI_BASE} maxTokens=${MAX_TOKENS} innerTurns=${INNER_TURNS} ` +
542
+ (ARM === 'system' ? `k=${K} temp=${TEMP} repairs<=${REPAIRS} ` : `temp=${SOLO_TEMP} `) +
543
+ `conc=${CONC} reproTimeout=${REPRO_TIMEOUT_S}s exec=image judge=${SKIP_JUDGE ? 'SKIPPED' : 'phase-B serialized'}`,
1033
544
  )
1034
- console.log(`instances=${ids.length} out=${out}`)
1035
-
1036
- const done = loadPhaseRows(out)
1037
- for (const [id, row] of done) {
1038
- if (!ids.includes(id)) throw new Error(`${out}: resume row ${id} is outside current IDS`)
1039
- await assertPhaseRow(row, taskById.get(id)!, config, tools, MANIFEST[id], sharedExecution, `${out}:${id}`)
545
+ console.log(`instances (${ids.length}): ${ids.join(', ')}`)
546
+ if (ARM === 'system') {
547
+ const withRepro = ids.filter((id) => MANIFEST[id]).length
548
+ console.log(`repro manifest: ${withRepro}/${ids.length} instances with a Stage-0 valid+sound repro (rest run repro=none)`)
1040
549
  }
1041
- const todo = ids.filter((id) => !done.has(id))
1042
- if (done.size) console.log(`resume accepted: ${done.size}/${ids.length} fingerprint-matched rows`)
550
+ console.log(`out=${OUT} (phase A receipts: ${PHASE_A_OUT})`)
1043
551
 
552
+ const env = await createSweBenchEnvironment(ids.length, { ids, cloneCache: true })
553
+ await env.adapter.preflight?.()
554
+ const taskById = new Map((await env.adapter.loadTasks({ ids, split: 'test' })).map((t) => [t.id, t]))
555
+ const missing = ids.filter((id) => !taskById.has(id))
556
+ if (missing.length) throw new Error(`instances not found in SWE-bench_Verified: ${missing.join(', ')}`)
557
+
558
+ // ── Phase A (resumable): every arm decision, no judge anywhere ──
559
+ const done = loadRows(PHASE_A_OUT)
560
+ const todo = ids.filter((id) => !done.has(id))
561
+ if (done.size) console.log(`phase A resume: ${done.size} rows already present, ${todo.length} to run`)
1044
562
  let next = 0
1045
563
  const worker = async (): Promise<void> => {
1046
564
  while (next < todo.length) {
1047
- const index = next++
1048
- const id = todo[index]!
1049
- const row = await phaseA(env, taskById.get(id)!, {
1050
- preset,
1051
- config,
1052
- tools,
1053
- sharedExecution,
1054
- expectedImageIdentities,
1055
- })
1056
- await assertPhaseRow(
1057
- row,
1058
- taskById.get(id)!,
1059
- config,
1060
- tools,
1061
- MANIFEST[id],
1062
- sharedExecution,
1063
- `${preset.arm}:${id}`,
1064
- )
565
+ const i = next++
566
+ const id = todo[i] as string
567
+ console.log(`[A ${i + 1}/${todo.length}] ${id} …`)
568
+ const row = await phaseA(env, taskById.get(id) as BenchTask)
1065
569
  done.set(id, row)
1066
- appendFileSync(out, `${JSON.stringify(row)}\n`)
570
+ appendFileSync(PHASE_A_OUT, `${JSON.stringify(row)}\n`)
571
+ const sel = row.selection ? `${row.selection.mode}@${row.selection.selectedIdx}${row.selection.movedOffFirst ? ' MOVED' : ''}` : '-'
1067
572
  console.log(
1068
- `[${index + 1}/${todo.length}] ${id} sessions=${row.workerSessions} ` +
1069
- `severity=[${row.candidates.map((candidate) => candidate.severity).join(',')}] ` +
1070
- `continuation=[${row.repairs.map((repair) => repair.severity).join(',')}] ` +
1071
- `final=${row.finalFrom} changed=${row.changedFromParent ?? 'n/a'} calls=${row.llmCalls}`,
573
+ `[A ${i + 1}/${todo.length}] ${id} → repro=${row.reproStatus} sel=${sel} ` +
574
+ `sev=[${row.candidates.map((c) => c.severity).join(',')}] repairs=${row.repairs.length}(${row.repairStop ?? '-'}) ` +
575
+ `final=${row.finalFrom} diff=${row.finalDiff.length}b calls=${row.llmCalls} tok=${row.tokensIn}/${row.tokensOut} ` +
576
+ `wall=${Math.round(row.wallMs / 1000)}s${row.error ? ` ERR=${row.error.slice(0, 120)}` : ''}`,
1072
577
  )
1073
578
  }
1074
579
  }
1075
580
  await Promise.all(Array.from({ length: CONC }, () => worker()))
1076
581
 
1077
- const rows = ids.map((id) => done.get(id)!)
1078
- const totalSessions = rows.reduce((sum, row) => sum + row.workerSessions, 0)
1079
- const totalIn = rows.reduce((sum, row) => sum + row.tokensIn, 0)
1080
- const totalOut = rows.reduce((sum, row) => sum + row.tokensOut, 0)
1081
- const usd = (totalIn / 1e6) * PRICE_IN + (totalOut / 1e6) * PRICE_OUT
1082
- console.log(
1083
- `Phase A complete: rows=${rows.length}/${ids.length} sessions=${totalSessions}/${2 * ids.length} ` +
1084
- `tokens=${totalIn}/${totalOut} assumedCost=$${usd.toFixed(2)}; no official scores were called`,
1085
- )
1086
- }
1087
-
1088
- interface JudgeRow {
1089
- schema: 'swe-structural-judge-v2'
1090
- instanceId: string
1091
- arm: ExperimentArm
1092
- pairFingerprint: string
1093
- phaseFileFingerprint: string
1094
- inputFingerprint: string
1095
- executionFingerprint: string
1096
- finalDiffHash: string
1097
- hiddenResolved: boolean
1098
- judgeDetail: string | null
1099
- judgeMs: number
1100
- judgeSkipped: 'empty-patch' | null
1101
- }
1102
-
1103
- function loadJudgeRows(path: string): Map<string, JudgeRow> {
1104
- if (!existsSync(path)) return new Map()
1105
- const rows = new Map<string, JudgeRow>()
1106
- for (const [index, line] of readFileSync(path, 'utf8').split('\n').entries()) {
1107
- if (!line.trim()) continue
1108
- const row = JSON.parse(line) as JudgeRow
1109
- if (row.schema !== 'swe-structural-judge-v2') {
1110
- throw new Error(`${path}:${index + 1}: unsupported judge row schema`)
1111
- }
1112
- if (typeof row.hiddenResolved !== 'boolean') {
1113
- throw new Error(`${path}:${index + 1}: incomplete judge row must be removed and retried`)
1114
- }
1115
- if (row.judgeSkipped !== null && row.judgeSkipped !== 'empty-patch') {
1116
- throw new Error(`${path}:${index + 1}: unsupported judge skip receipt ${row.judgeSkipped}`)
582
+ // ── Phase B: hidden judge, serialized, strictly after every arm decision locked ──
583
+ console.log(`\nphase A complete all arm decisions locked (${new Date().toISOString()}).`)
584
+ const judged = loadRows(OUT)
585
+ if (SKIP_JUDGE) {
586
+ console.log('SKIP_JUDGE=1 phase B not run; rows remain in the phase-A file only.')
587
+ } else {
588
+ console.log('phase B: official swebench judge, serialized (max_workers 1)…')
589
+ for (const id of ids) {
590
+ if (judged.has(id)) continue
591
+ const row = done.get(id) as Row
592
+ const t0 = Date.now()
593
+ if (!row.finalDiff.trim()) {
594
+ row.hiddenResolved = false
595
+ row.judgeSkipped = row.error ? 'error-no-patch' : 'empty-patch'
596
+ } else {
597
+ try {
598
+ const s = await env.adapter.judge(taskById.get(id) as BenchTask, row.finalDiff)
599
+ row.hiddenResolved = s.resolved ?? false
600
+ row.judgeDetail = String(s.detail ?? '').slice(0, 1_000)
601
+ } catch (e) {
602
+ row.hiddenResolved = false
603
+ row.judgeSkipped = `judge-error: ${e instanceof Error ? e.message.slice(0, 200) : String(e).slice(0, 200)}`
604
+ }
605
+ }
606
+ row.judgeMs = Date.now() - t0
607
+ judged.set(id, row)
608
+ appendFileSync(OUT, `${JSON.stringify(row)}\n`)
609
+ console.log(`[B] ${id} → resolved=${row.hiddenResolved} (${Math.round((row.judgeMs ?? 0) / 1000)}s)${row.judgeSkipped ? ` [${row.judgeSkipped}]` : ''}`)
1117
610
  }
1118
- resolveExperimentArm(row.arm)
1119
- const key = `${row.arm}:${row.instanceId}`
1120
- if (rows.has(key)) throw new Error(`${path}:${index + 1}: duplicate judge row ${key}`)
1121
- rows.set(key, row)
1122
611
  }
1123
- return rows
1124
- }
1125
612
 
1126
- function requirePath(name: string): string {
1127
- const value = process.env[name]?.trim()
1128
- if (!value) throw new Error(`${name} required for MODE=judge-only`)
1129
- return value
1130
- }
1131
-
1132
- async function judgeOnlyMain(): Promise<void> {
1133
- const independentPath = requirePath('INDEPENDENT_PHASE_A')
1134
- const persistentPath = requirePath('PERSISTENT_PHASE_A')
1135
- const out = requirePath('OUT')
1136
- assertDistinctArtifactPaths({ INDEPENDENT_PHASE_A: independentPath, PERSISTENT_PHASE_A: persistentPath, OUT: out })
1137
- const independent = loadPhaseRows(independentPath, true)
1138
- const persistent = loadPhaseRows(persistentPath, true)
1139
- const independentConfig = [...independent.values()][0]!.config
1140
- const persistentConfig = [...persistent.values()][0]!.config
1141
- if (!independentConfig || !persistentConfig) throw new Error('Phase-A file is missing its config receipt')
1142
- assertPresetConfig(independentConfig, 'independent-2', independentPath)
1143
- assertPresetConfig(persistentConfig, 'persistent-refine-2', persistentPath)
1144
- if (fingerprint(commonConfig(independentConfig)) !== fingerprint(commonConfig(persistentConfig))) {
1145
- throw new Error('Phase-A files have different common configuration fingerprints')
1146
- }
1147
- const ids = independentConfig.taskIds
1148
- if (fingerprint(ids) !== fingerprint(persistentConfig.taskIds)) {
1149
- throw new Error('Phase-A files have different task-id fingerprints')
1150
- }
1151
- for (const [path, rows] of [[independentPath, independent], [persistentPath, persistent]] as const) {
1152
- assertCompleteTaskSet(rows.keys(), ids, path)
1153
- }
1154
-
1155
- // No official judge call occurs before every row in both files passes these checks.
1156
- const { env, taskById, tools, sharedExecution } = await loadTasksAndTools(ids)
1157
- for (const id of ids) {
1158
- const left = independent.get(id)!
1159
- const right = persistent.get(id)!
1160
- await assertPhaseRow(
1161
- left,
1162
- taskById.get(id)!,
1163
- independentConfig,
1164
- tools,
1165
- manifestFromRow(left),
1166
- sharedExecution,
1167
- `${independentPath}:${id}`,
1168
- )
1169
- await assertPhaseRow(
1170
- right,
1171
- taskById.get(id)!,
1172
- persistentConfig,
1173
- tools,
1174
- manifestFromRow(right),
1175
- sharedExecution,
1176
- `${persistentPath}:${id}`,
613
+ // ── summary ──
614
+ const rows = [...(SKIP_JUDGE ? done : judged).values()].filter((r) => ids.includes(r.instanceId))
615
+ const n = rows.length
616
+ const resolved = rows.filter((r) => r.hiddenResolved === true).length
617
+ const errors = rows.filter((r) => r.error).length
618
+ const totIn = rows.reduce((s, r) => s + r.tokensIn, 0)
619
+ const totOut = rows.reduce((s, r) => s + r.tokensOut, 0)
620
+ const totCalls = rows.reduce((s, r) => s + r.llmCalls, 0)
621
+ const totHttp = rows.reduce((s, r) => s + r.httpAttempts, 0)
622
+ const totGuard = rows.reduce((s, r) => s + r.guardedMsgs, 0)
623
+ const walls = rows.map((r) => r.wallMs / 1000).sort((a, b) => a - b)
624
+ const q = (p: number): number => walls.length ? (walls[Math.min(walls.length - 1, Math.floor(p * (walls.length - 1)))] as number) : 0
625
+ const usd = (totIn / 1e6) * PRICE_IN + (totOut / 1e6) * PRICE_OUT
626
+
627
+ console.log(`\n══ per-instance (${ARM}) ══`)
628
+ console.log('instance | repro(src) | cand sev | sel | moved | repairs | stop | final | resolved | calls | tokIn/out | wall_s | err')
629
+ for (const r of [...rows].sort((a, b) => a.instanceId.localeCompare(b.instanceId))) {
630
+ const sel = r.selection ? `${r.selection.mode === 'repro-argmax' ? 'argmax' : 'blind'}@${r.selection.selectedIdx}` : '-'
631
+ console.log(
632
+ `${r.instanceId} | ${r.reproStatus}${r.reproSource !== 'none' ? `(${r.reproSource.replace('stage0-', '')})` : ''} | ` +
633
+ `[${r.candidates.map((c) => c.severity).join(',')}] | ${sel} | ${r.selection?.movedOffFirst ? 1 : 0} | ` +
634
+ `${r.repairs.length}${r.repairs.some((x) => x.accepted) ? '+acc' : ''} | ${r.repairStop ?? '-'} | ${r.finalFrom} | ` +
635
+ `${r.hiddenResolved === null ? '?' : r.hiddenResolved ? 1 : 0} | ${r.llmCalls} | ${r.tokensIn}/${r.tokensOut} | ` +
636
+ `${Math.round(r.wallMs / 1000)} | ${r.error ? r.error.slice(0, 60) : '-'}`,
1177
637
  )
1178
- assertPairedFingerprints(left.fingerprints, right.fingerprints, id)
1179
- assertPairedExecutionFingerprint(left.executionFingerprint, right.executionFingerprint, id)
1180
- if (left.reproOutcomeFingerprint !== right.reproOutcomeFingerprint) {
1181
- throw new Error(`${id}: paired reproduction outcomes do not match`)
1182
- }
1183
- }
1184
-
1185
- const phaseFileFingerprints: Record<ExperimentArm, string> = {
1186
- 'independent-2': fingerprint({ bytes: readFileSync(independentPath, 'utf8') }),
1187
- 'persistent-refine-2': fingerprint({ bytes: readFileSync(persistentPath, 'utf8') }),
1188
- }
1189
- const pairFingerprint = fingerprint({
1190
- commonConfig: independent.get(ids[0]!)!.fingerprints.commonConfig,
1191
- ids,
1192
- phaseFiles: phaseFileFingerprints,
1193
- prompt: independent.get(ids[0]!)!.fingerprints.prompt,
1194
- source: independent.get(ids[0]!)!.fingerprints.source,
1195
- tools: independent.get(ids[0]!)!.fingerprints.tools,
1196
- executions: ids.map((id) => independent.get(id)!.executionFingerprint),
1197
- })
1198
- const judged = loadJudgeRows(out)
1199
- const arms: Array<{ arm: ExperimentArm; path: string; rows: Map<string, PhaseARow> }> = [
1200
- { arm: 'independent-2', path: independentPath, rows: independent },
1201
- { arm: 'persistent-refine-2', path: persistentPath, rows: persistent },
1202
- ]
1203
- for (const existing of judged.values()) {
1204
- const input = arms.find(({ arm }) => arm === existing.arm)?.rows.get(existing.instanceId)
1205
- if (!input) throw new Error(`${out}: judge resume row has no paired Phase-A input`)
1206
- assertJudgeCompletionMatchesInput(existing, input.finalDiff, `${out}:${existing.arm}:${existing.instanceId}`)
1207
- assertJudgeResumeFingerprints(existing, {
1208
- pairFingerprint,
1209
- phaseFileFingerprint: phaseFileFingerprints[existing.arm],
1210
- inputFingerprint: input.fingerprints.composite,
1211
- executionFingerprint: input.executionFingerprint,
1212
- finalDiffHash: input.finalDiffHash,
1213
- }, `${out}:${existing.arm}:${existing.instanceId}`)
1214
638
  }
1215
639
 
1216
- console.log(`all ${2 * ids.length} Phase-A rows matched; official scoring starts now (serialized)`)
1217
- for (const id of ids) {
1218
- for (const { arm, rows } of arms) {
1219
- const key = `${arm}:${id}`
1220
- if (judged.has(key)) continue
1221
- const input = rows.get(id)!
1222
- const started = Date.now()
1223
- // Fail without appending: a transient official-scorer error must be retried on resume.
1224
- const { hiddenResolved, judgeDetail, judgeSkipped } = await completeJudgeScore(
1225
- input.finalDiff,
1226
- async () => {
1227
- const task = taskById.get(id)!
1228
- process.env.SWEBENCH_NAMESPACE = input.execution!.image.namespace
1229
- await assertOfficialScoreImagePinned(input, task, `${key}:before`)
1230
- const score = await env.adapter.judge(task, input.finalDiff)
1231
- await assertOfficialScoreImagePinned(input, task, `${key}:after`)
1232
- return score
1233
- },
1234
- )
1235
- const row: JudgeRow = {
1236
- schema: 'swe-structural-judge-v2',
1237
- instanceId: id,
1238
- arm,
1239
- pairFingerprint,
1240
- phaseFileFingerprint: phaseFileFingerprints[arm],
1241
- inputFingerprint: input.fingerprints.composite,
1242
- executionFingerprint: input.executionFingerprint,
1243
- finalDiffHash: input.finalDiffHash,
1244
- hiddenResolved,
1245
- judgeDetail,
1246
- judgeMs: Date.now() - started,
1247
- judgeSkipped,
1248
- }
1249
- judged.set(key, row)
1250
- appendFileSync(out, `${JSON.stringify(row)}\n`)
1251
- console.log(`[judge] ${key} resolved=${hiddenResolved} skipped=${judgeSkipped ?? '-'}`)
1252
- }
1253
- }
640
+ console.log(`\n══ summary (${ARM}) ══`)
641
+ console.log(`n=${n} resolved(hidden)=${resolved}/${n} errorRows=${errors}`)
642
+ if (ARM === 'system') {
643
+ const armed = rows.filter((r) => r.reproStatus === 'ok')
644
+ const blind = rows.filter((r) => r.selection?.mode === 'blind-first')
645
+ const moved = rows.filter((r) => r.selection?.movedOffFirst)
646
+ const scored = armed.flatMap((r) => r.candidates)
647
+ const passCands = scored.filter((c) => c.severity === 0)
648
+ const fired = rows.filter((r) => r.repairs.length > 0)
649
+ const accepted = rows.filter((r) => r.repairs.some((x) => x.accepted))
650
+ const failToPass = rows.filter((r) => r.repairStop === 'repaired-pass')
651
+ console.log(`repro: ok=${armed.length} degraded=${rows.filter((r) => r.reproStatus.startsWith('degraded')).length} none=${rows.filter((r) => r.reproStatus === 'none').length}`)
652
+ console.log(`selection: repro-argmax=${armed.length} blind-first=${blind.length} | argmax moved off idx0 on ${moved.length} instance(s)`)
653
+ console.log(`candidates (repro-armed): ${scored.length} scored, ${passCands.length} repro-pass (${scored.length ? ((100 * passCands.length) / scored.length).toFixed(1) : 0}%)`)
654
+ console.log(`repair: fired on ${fired.length} instance(s), accepted(improved) on ${accepted.length}, repro fail→pass on ${failToPass.length}`)
655
+ const stops = new Map<string, number>()
656
+ for (const r of rows) if (r.repairStop) stops.set(r.repairStop, (stops.get(r.repairStop) ?? 0) + 1)
657
+ console.log(`repair stops: ${[...stops.entries()].map(([k, v]) => `${k}=${v}`).join(' ')}`)
658
+ }
659
+ console.log(`leak guard: ${totGuard} system/user messages checked across ${totCalls} completions — 0 trips (a trip throws)`)
660
+ console.log(`cost: tokens in=${totIn} out=${totOut} | llmCalls=${totCalls} httpAttempts=${totHttp} | ` +
661
+ `$${usd.toFixed(2)} @ $${PRICE_IN}/M in + $${PRICE_OUT}/M out (ASSUMED rate — override PRICE_IN/PRICE_OUT)`)
662
+ console.log(`wall per instance: min/med/p90/max = ${Math.round(q(0))}/${Math.round(q(0.5))}/${Math.round(q(0.9))}/${Math.round(q(1))}s sum=${Math.round(walls.reduce((a, b) => a + b, 0))}s`)
1254
663
  }
1255
664
 
1256
- const main = MODE === 'generate' ? generateMain : judgeOnlyMain
1257
665
  main().catch((e) => {
1258
666
  console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
1259
667
  process.exit(1)