@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,270 @@
1
+ /**
2
+ * Settle-time capture with LABEL v2: the contribution rule (delivered worker
3
+ * vs sibling bystander vs identity gap), baseline-relative proposer rewards,
4
+ * campaign-path attribution, and end-to-end line validity on a synthetic
5
+ * supervisor run dir (opencode store absent → labeled gap lines).
6
+ */
7
+
8
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
9
+ import { tmpdir } from 'node:os'
10
+ import { join } from 'node:path'
11
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
12
+ import { readRolloutLedger } from '@tangle-network/agent-eval/rollout'
13
+ import {
14
+ campaignCoordsFromCellPath,
15
+ createSettleCapture,
16
+ deliveredWorkerLabels,
17
+ PROPOSER_REWARD_SOURCE_V2,
18
+ proposerRewardV2,
19
+ readWorkerEvidence,
20
+ WORKER_REWARD_SOURCE_V2,
21
+ workerRewardV2,
22
+ type CellCaptureArgs,
23
+ } from './settle-capture.mts'
24
+
25
+ describe('workerRewardV2 (contribution rule)', () => {
26
+ it('rewards ONLY the delivering worker in a resolved cell', () => {
27
+ expect(workerRewardV2({ resolved: true, isDelivered: true, identityKnown: true })).toEqual({
28
+ reward: 1,
29
+ bystander: false,
30
+ deliveredMatch: 'delivered',
31
+ })
32
+ })
33
+
34
+ it('marks resolved-cell siblings as bystanders with reward 0', () => {
35
+ expect(workerRewardV2({ resolved: true, isDelivered: false, identityKnown: true })).toEqual({
36
+ reward: 0,
37
+ bystander: true,
38
+ deliveredMatch: 'bystander',
39
+ })
40
+ })
41
+
42
+ it('gives every worker 0 in an unresolved cell (no bystander flag)', () => {
43
+ expect(workerRewardV2({ resolved: false, isDelivered: false, identityKnown: true })).toEqual({
44
+ reward: 0,
45
+ bystander: false,
46
+ deliveredMatch: 'unresolved',
47
+ })
48
+ })
49
+
50
+ it('labels an identity gap as reward null (never fabricated credit) and an inconclusive cell as null', () => {
51
+ expect(workerRewardV2({ resolved: true, isDelivered: false, identityKnown: false })).toEqual({
52
+ reward: null,
53
+ bystander: false,
54
+ deliveredMatch: 'unknown',
55
+ })
56
+ expect(workerRewardV2({ resolved: null, isDelivered: false, identityKnown: false }).reward).toBeNull()
57
+ })
58
+ })
59
+
60
+ describe('deliveredWorkerLabels', () => {
61
+ const workers = [
62
+ { label: 'w1', cwd: '/tmp/w1', patch: 'diff --git a/x b/x\n+fix\n' },
63
+ { label: 'w2', cwd: '/tmp/w2', patch: 'diff --git a/y b/y\n+other\n' },
64
+ { label: 'w3', cwd: null, patch: null },
65
+ ]
66
+
67
+ it('matches by trimmed patch equality', () => {
68
+ expect(deliveredWorkerLabels('diff --git a/x b/x\n+fix\n\n', workers)).toEqual(['w1'])
69
+ })
70
+
71
+ it('returns [] for an empty delivery or no match', () => {
72
+ expect(deliveredWorkerLabels('', workers)).toEqual([])
73
+ expect(deliveredWorkerLabels('diff --git a/z b/z\n+mystery\n', workers)).toEqual([])
74
+ })
75
+ })
76
+
77
+ describe('proposerRewardV2 (baseline-relative)', () => {
78
+ it('is positive for an improvement, negative for a regression, zero for a tie', () => {
79
+ expect(proposerRewardV2(3, 1, 6)).toBeCloseTo(2 / 6)
80
+ expect(proposerRewardV2(0, 1, 6)).toBeCloseTo(-1 / 6)
81
+ expect(proposerRewardV2(1, 1, 6)).toBe(0)
82
+ })
83
+
84
+ it('rejects a zero instance count', () => {
85
+ expect(() => proposerRewardV2(1, 0, 0)).toThrow(/instanceCount/)
86
+ })
87
+ })
88
+
89
+ describe('campaignCoordsFromCellPath (directory attribution, never dispatch order)', () => {
90
+ it('parses candidate and baseline cells; unknown shapes return null', () => {
91
+ expect(campaignCoordsFromCellPath('/x/improve-run/gen-0/candidate-2/cell-1/arm-summary.json')).toEqual({
92
+ generation: 0,
93
+ candidateIndex: 2,
94
+ })
95
+ expect(campaignCoordsFromCellPath('/x/improve-run/baseline/cell-1/arm-summary.json')).toEqual({
96
+ generation: -1,
97
+ candidateIndex: -1,
98
+ })
99
+ expect(campaignCoordsFromCellPath('/x/somewhere/else.json')).toBeNull()
100
+ })
101
+ })
102
+
103
+ describe('readWorkerEvidence', () => {
104
+ let supRunDir: string
105
+
106
+ beforeEach(async () => {
107
+ supRunDir = await mkdtemp(join(tmpdir(), 'sup-'))
108
+ const workers = join(supRunDir, 'workers')
109
+ await mkdir(workers, { recursive: true })
110
+ await writeFile(
111
+ join(workers, 'w1.ndjson'),
112
+ `${JSON.stringify({ kind: 'started', cwd: '/tmp/clone-w1' })}\n${JSON.stringify({ kind: 'settled' })}\n`,
113
+ )
114
+ await writeFile(join(workers, 'w1.patch'), 'diff --git a/x b/x\n+fix\n')
115
+ await writeFile(join(workers, 'w2.ndjson'), `${JSON.stringify({ kind: 'started', cwd: '/tmp/clone-w2' })}\n`)
116
+ // Inbox files must not create phantom workers.
117
+ await writeFile(join(workers, 'w1.inbox.ndjson'), '{}\n')
118
+ })
119
+
120
+ afterEach(async () => {
121
+ await rm(supRunDir, { recursive: true, force: true })
122
+ })
123
+
124
+ it('joins per-worker cwd + patch; a worker without a patch reads patch:null', async () => {
125
+ const records = await readWorkerEvidence(supRunDir)
126
+ expect(records).toEqual([
127
+ { label: 'w1', cwd: '/tmp/clone-w1', patch: 'diff --git a/x b/x\n+fix\n' },
128
+ { label: 'w2', cwd: '/tmp/clone-w2', patch: null },
129
+ ])
130
+ })
131
+
132
+ it('returns [] for a run dir without worker evidence', async () => {
133
+ expect(await readWorkerEvidence(join(supRunDir, 'nope'))).toEqual([])
134
+ })
135
+ })
136
+
137
+ describe('createSettleCapture end-to-end (opencode store absent)', () => {
138
+ let root: string
139
+ let supRunDir: string
140
+ let ledgerPath: string
141
+
142
+ const cellArgs = (over: Partial<CellCaptureArgs> = {}): CellCaptureArgs => ({
143
+ generation: 0,
144
+ candidateIndex: 1,
145
+ iid: 'pydata__xarray-4687',
146
+ rep: 0,
147
+ seed: 42,
148
+ splitVisibility: 'public',
149
+ commit: 'c'.repeat(40),
150
+ resolved: true,
151
+ judgeVerdict: { resolved: true, attempts: 1 },
152
+ runDir: join(root, 'runs', 'pydata__xarray-4687', 'R4'),
153
+ patchPath: join(root, 'delivered.patch'),
154
+ supRunDir,
155
+ deliveredPatch: 'diff --git a/x b/x\n+fix\n',
156
+ workerModel: 'zai-coding-plan/glm-5.2',
157
+ metrics: { resolved: true, verify_pass: true },
158
+ cost: { usd: 1.25, wallS: 900, spentTokens: 1000 },
159
+ ...over,
160
+ })
161
+
162
+ beforeEach(async () => {
163
+ root = await mkdtemp(join(tmpdir(), 'settle-'))
164
+ ledgerPath = join(root, 'rollout-ledger.jsonl')
165
+ supRunDir = join(root, 'ws', '.loops', 'supervisor', 's1')
166
+ const workers = join(supRunDir, 'workers')
167
+ await mkdir(workers, { recursive: true })
168
+ await writeFile(join(workers, 'w1.ndjson'), `${JSON.stringify({ kind: 'started', cwd: '/tmp/clone-w1' })}\n`)
169
+ await writeFile(join(workers, 'w1.patch'), 'diff --git a/x b/x\n+fix\n')
170
+ await writeFile(join(workers, 'w2.ndjson'), `${JSON.stringify({ kind: 'started', cwd: '/tmp/clone-w2' })}\n`)
171
+ await writeFile(join(workers, 'w2.patch'), 'diff --git a/y b/y\n+other\n')
172
+ })
173
+
174
+ afterEach(async () => {
175
+ await rm(root, { recursive: true, force: true })
176
+ })
177
+
178
+ const capture = () =>
179
+ createSettleCapture({
180
+ ledgerPath,
181
+ runId: 'r4-test',
182
+ instanceCount: 6,
183
+ opencodeDb: join(root, 'no-such.db'),
184
+ now: () => new Date('2026-07-23T00:00:00Z'),
185
+ })
186
+
187
+ it('emits schema-valid supervisor + worker lines with v2 labels: delivered worker 1, bystander 0', async () => {
188
+ const { lines } = await capture().captureCell(cellArgs())
189
+ expect(lines).toBe(3)
190
+ const ledger = await readRolloutLedger(ledgerPath) // validates every line
191
+ expect(ledger).toHaveLength(3)
192
+
193
+ const sup = ledger.find((l) => l.role === 'supervisor')!
194
+ expect(sup.outcome.reward).toBe(1)
195
+ expect(sup.outcome.reward_source).toBe('swe-arena-official-judge')
196
+ expect(sup.generation).toBe(0)
197
+ expect(sup.candidate_index).toBe(1)
198
+ expect(sup.candidate_id).toBe('gen0-cand1')
199
+ // The canonical trainable split, not the legacy `train` alias.
200
+ expect(sup.task.split).toBe('search')
201
+ expect(sup.outcome.metrics.split_visibility).toBe('public')
202
+ expect(sup.provenance.capture).toBe('settle-time')
203
+
204
+ const workers = ledger.filter((l) => l.role === 'worker')
205
+ expect(workers).toHaveLength(2)
206
+ for (const w of workers) {
207
+ expect(w.parent_rollout_id).toBe(sup.rollout_id)
208
+ expect(w.candidate_id).toBe('gen0-cand1')
209
+ expect(w.task.split).toBe('search')
210
+ expect(w.outcome.reward_source).toBe(WORKER_REWARD_SOURCE_V2)
211
+ // Store absent → labeled gap, never a silent drop.
212
+ expect(w.messages).toEqual([])
213
+ expect(w.provenance.gap).toBeTruthy()
214
+ // No session at all, so no completed invocation to claim.
215
+ expect(w.outcome.metrics.has_session).toBe(false)
216
+ expect(w.outcome.is_completed).toBe(false)
217
+ }
218
+ const delivered = workers.find((w) => w.outcome.metrics.worker_label === 'w1')!
219
+ const bystander = workers.find((w) => w.outcome.metrics.worker_label === 'w2')!
220
+ expect(delivered.outcome.reward).toBe(1)
221
+ expect(delivered.outcome.metrics.bystander).toBe(false)
222
+ expect(delivered.outcome.metrics.delivered_match).toBe('delivered')
223
+ expect(bystander.outcome.reward).toBe(0)
224
+ expect(bystander.outcome.metrics.bystander).toBe(true)
225
+ expect(bystander.outcome.metrics.delivered_match).toBe('bystander')
226
+ })
227
+
228
+ it('gives all workers 0 in an unresolved cell', async () => {
229
+ await capture().captureCell(cellArgs({ resolved: false }))
230
+ const workers = (await readRolloutLedger(ledgerPath)).filter((l) => l.role === 'worker')
231
+ expect(workers.map((w) => w.outcome.reward)).toEqual([0, 0])
232
+ expect(workers.every((w) => w.outcome.metrics.bystander === false)).toBe(true)
233
+ })
234
+
235
+ it('labels an unmatched delivery as an identity gap: worker rewards null', async () => {
236
+ await capture().captureCell(cellArgs({ deliveredPatch: 'diff --git a/z b/z\n+mystery\n' }))
237
+ const workers = (await readRolloutLedger(ledgerPath)).filter((l) => l.role === 'worker')
238
+ expect(workers.map((w) => w.outcome.reward)).toEqual([null, null])
239
+ expect(workers.every((w) => w.outcome.metrics.delivered_match === 'unknown')).toBe(true)
240
+ })
241
+
242
+ it('emits a baseline-relative proposer line (improvement positive, v2 source)', async () => {
243
+ await capture().captureProposer({
244
+ generation: 0,
245
+ candidateIndex: 1,
246
+ proposer: 'glm-author',
247
+ harness: 'opencode',
248
+ commit: 'c'.repeat(40),
249
+ candResolved: 3,
250
+ baselineResolved: 1,
251
+ shotReceiptPaths: [],
252
+ diffPath: null,
253
+ })
254
+ const [line] = await readRolloutLedger(ledgerPath)
255
+ expect(line!.role).toBe('proposer')
256
+ expect(line!.outcome.reward).toBeCloseTo(2 / 6)
257
+ expect(line!.outcome.reward_source).toBe(PROPOSER_REWARD_SOURCE_V2)
258
+ expect(line!.outcome.metrics.baseline_resolved_count).toBe(1)
259
+ expect(line!.task.instance_id).toBe('gen0-cand1-glm-author')
260
+ })
261
+
262
+ it('appends across cells (one ledger, many flushes) and keeps every line valid', async () => {
263
+ const c = capture()
264
+ await c.captureCell(cellArgs())
265
+ await c.captureCell(cellArgs({ rep: 1, resolved: false, splitVisibility: 'private' }))
266
+ const ledger = await readRolloutLedger(ledgerPath)
267
+ expect(ledger).toHaveLength(6)
268
+ expect(ledger.filter((l) => l.outcome.metrics.split_visibility === 'private')).toHaveLength(3)
269
+ })
270
+ })
@@ -199,14 +199,25 @@ async function runTask(t: HumanEvalTask): Promise<TaskRow> {
199
199
  `${t.taskId}: ${result.selection.length} receipts vs ${scored.length} scored candidates`,
200
200
  )
201
201
  }
202
- for (const r of result.selection) {
202
+ const receipts = result.selection.map((receipt) => {
203
+ if (receipt.score === undefined || receipt.reason === undefined) {
204
+ throw new Error(`${t.taskId}: receipt #${receipt.candidateIndex} is missing score or reason`)
205
+ }
206
+ return {
207
+ candidateIndex: receipt.candidateIndex,
208
+ selected: receipt.selected,
209
+ score: receipt.score,
210
+ reason: receipt.reason,
211
+ }
212
+ })
213
+ for (const r of receipts) {
203
214
  const rec = scored[r.candidateIndex]
204
215
  if (!rec || Math.abs(r.score - visibleCheckScore(rec.outcome)) > 1e-9) {
205
216
  throw new Error(`${t.taskId}: receipt #${r.candidateIndex} score ${r.score} does not match the recorded outcome`)
206
217
  }
207
218
  }
208
219
 
209
- const sampleCount = result.selection.filter((r) => r.reason.startsWith('sample')).length
220
+ const sampleCount = receipts.filter((r) => r.reason.startsWith('sample')).length
210
221
  const samples = scored.slice(0, sampleCount)
211
222
  if (samples.length === 0) throw new Error(`${t.taskId}: no sample candidates settled`)
212
223
 
@@ -223,7 +234,7 @@ async function runTask(t: HumanEvalTask): Promise<TaskRow> {
223
234
  const sampleHidden = await Promise.all(samples.map((s) => grade(s.candidate)))
224
235
  const selectedIdx = selectBestIndex(samples.map((s) => s.outcome))
225
236
  const selectedHidden = sampleHidden[selectedIdx] as number
226
- const winner = result.selection.find((r) => r.selected)
237
+ const winner = receipts.find((r) => r.selected)
227
238
  if (!winner) throw new Error(`${t.taskId}: no receipt marked selected`)
228
239
  const finalIdx = winner.candidateIndex
229
240
  const finalHidden = await grade((scored[finalIdx] as ScoredCandidate).candidate)
@@ -241,12 +252,7 @@ async function runTask(t: HumanEvalTask): Promise<TaskRow> {
241
252
  finalIdx,
242
253
  finalHidden,
243
254
  selectedVisible: visibleCheckScore((samples[selectedIdx] as ScoredCandidate).outcome),
244
- receipts: result.selection.map((r) => ({
245
- candidateIndex: r.candidateIndex,
246
- selected: r.selected,
247
- score: r.score,
248
- reason: r.reason,
249
- })),
255
+ receipts,
250
256
  tokens: result.tokens,
251
257
  usd: result.usd,
252
258
  ms: result.ms,
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Gen-5 activation gate (SOTA adoption #2, GSME-style "verify the mechanism
3
+ * fired before the score counts").
4
+ *
5
+ * Every proposer's deliverable must include a MACHINE-CHECKABLE ACTIVATION
6
+ * PREDICATE at `.improve/activation.json` (inside the change-space's metadata
7
+ * prefix, so the improvement driver's finalize commits it with the candidate):
8
+ * a grep pattern or script over the candidate's OWN campaign run artifacts
9
+ * that proves its mechanism actually fired (e.g. "the new prompt section
10
+ * rendered in worker prompts", "patchRiskWarnings emitted in >=1 settle").
11
+ *
12
+ * Enforcement is two-stage, both fail-closed:
13
+ * 1. PREFILTER — a candidate without a parseable predicate is killed before
14
+ * any evaluation spend (proposer-fanout.mts, stage 'activation-predicate').
15
+ * 2. POST-EVAL — the evaluator runs the predicate over the candidate's own
16
+ * cell run dirs; a candidate whose mechanism NEVER fired is QUARANTINED
17
+ * (staircase verdict 'quarantined-inactive': recorded, never promoted)
18
+ * even when its score improved — a score with an inactive mechanism is
19
+ * indistinguishable from luck or from gaming the visible instances.
20
+ */
21
+
22
+ import { readFile, readdir, stat } from 'node:fs/promises'
23
+ import { join, relative } from 'node:path'
24
+ import { run } from './proc.ts'
25
+
26
+ export const ACTIVATION_PREDICATE_RELPATH = '.improve/activation.json'
27
+
28
+ export interface ActivationPredicate {
29
+ /** One sentence: which mechanism this proves fired. */
30
+ description: string
31
+ kind: 'grep' | 'script'
32
+ /** kind 'grep': JS RegExp source tested line-by-line over run artifacts. */
33
+ pattern?: string
34
+ /** kind 'grep': optional relative-path substring filters (a file is searched
35
+ * when its run-dir-relative path contains ANY entry). Empty/absent = all. */
36
+ files?: string[]
37
+ /** kind 'script': bash script; run once per run dir with cwd=<runDir> and
38
+ * $RUN_DIR set; exit 0 in ANY run dir = mechanism fired. */
39
+ script?: string
40
+ }
41
+
42
+ export type ParsedPredicate = { ok: true; predicate: ActivationPredicate } | { ok: false; error: string }
43
+
44
+ export function parseActivationPredicate(raw: string): ParsedPredicate {
45
+ let value: unknown
46
+ try {
47
+ value = JSON.parse(raw)
48
+ } catch (cause) {
49
+ return { ok: false, error: `not valid JSON: ${(cause as Error).message}` }
50
+ }
51
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
52
+ return { ok: false, error: 'must be a JSON object' }
53
+ }
54
+ const p = value as Record<string, unknown>
55
+ if (typeof p.description !== 'string' || p.description.trim().length === 0) {
56
+ return { ok: false, error: 'description must be a non-empty string' }
57
+ }
58
+ if (p.kind === 'grep') {
59
+ if (typeof p.pattern !== 'string' || p.pattern.length === 0) {
60
+ return { ok: false, error: 'kind "grep" requires a non-empty pattern' }
61
+ }
62
+ try {
63
+ new RegExp(p.pattern)
64
+ } catch (cause) {
65
+ return { ok: false, error: `pattern is not a valid RegExp: ${(cause as Error).message}` }
66
+ }
67
+ if (p.files !== undefined && (!Array.isArray(p.files) || p.files.some((f) => typeof f !== 'string'))) {
68
+ return { ok: false, error: 'files must be an array of strings when present' }
69
+ }
70
+ } else if (p.kind === 'script') {
71
+ if (typeof p.script !== 'string' || p.script.trim().length === 0) {
72
+ return { ok: false, error: 'kind "script" requires a non-empty script' }
73
+ }
74
+ } else {
75
+ return { ok: false, error: `kind must be "grep" or "script", got ${JSON.stringify(p.kind)}` }
76
+ }
77
+ return { ok: true, predicate: p as unknown as ActivationPredicate }
78
+ }
79
+
80
+ /** The prompt-visible contract: template + worked example. */
81
+ export function activationPredicateInstruction(): string {
82
+ return [
83
+ 'ACTIVATION PREDICATE (required deliverable — a candidate without one is rejected before evaluation):',
84
+ `Write ${ACTIVATION_PREDICATE_RELPATH} in this worktree: a machine-checkable proof that YOUR mechanism`,
85
+ "actually fired during evaluation. The evaluator runs it over your candidate's own run artifacts",
86
+ '(each arm run dir: driver.log, brain.jsonl, result.json, ws/.loops/** journal + worker evidence);',
87
+ 'if it never fires, your candidate is QUARANTINED even when its score improved.',
88
+ '',
89
+ 'Template (kind "grep" — a RegExp tested over the run artifacts):',
90
+ ' {',
91
+ ' "description": "patchRiskWarnings emitted in at least one settle",',
92
+ ' "kind": "grep",',
93
+ ' "pattern": "patchRiskWarnings|patch-risk",',
94
+ ' "files": ["journal.jsonl", "workers/"]',
95
+ ' }',
96
+ 'Or kind "script": {"description":"...","kind":"script","script":"grep -rq NEW_SECTION ws/.loops"}',
97
+ '(exit 0 in any run dir = fired).',
98
+ 'Pick a pattern that can ONLY appear when your mechanism ran — not one that matches the diff itself.',
99
+ ].join('\n')
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Predicate execution over run dirs.
104
+ // ---------------------------------------------------------------------------
105
+
106
+ export interface ActivationResult {
107
+ fired: boolean
108
+ /** Bounded evidence: matching `path:line` refs (grep) or script stdout tails. */
109
+ evidence: string[]
110
+ checkedRunDirs: number
111
+ checkedFiles: number
112
+ /** Bounded notes on skipped inputs (oversized files, walk caps). */
113
+ warnings: string[]
114
+ }
115
+
116
+ const MAX_FILES_PER_RUN_DIR = 4000
117
+ const MAX_FILE_BYTES = 32 * 1024 * 1024
118
+ const MAX_EVIDENCE = 10
119
+ const SCRIPT_TIMEOUT_MS = 120_000
120
+
121
+ /** Walk one run dir. The `ws/` workspace subtree (a whole checked-out repo) is
122
+ * skipped EXCEPT `ws/.loops/**` — the supervisor's own artifacts live there
123
+ * and are exactly where mechanism traces land. */
124
+ async function walkRunDir(runDir: string, warnings: string[]): Promise<string[]> {
125
+ const files: string[] = []
126
+ const queue: string[] = [runDir]
127
+ while (queue.length > 0) {
128
+ const dir = queue.shift()!
129
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
130
+ for (const entry of entries) {
131
+ const abs = join(dir, entry.name)
132
+ const rel = relative(runDir, abs)
133
+ if (entry.isDirectory()) {
134
+ if (rel === 'ws') {
135
+ queue.push(join(abs, '.loops'))
136
+ continue
137
+ }
138
+ queue.push(abs)
139
+ } else if (entry.isFile()) {
140
+ files.push(abs)
141
+ if (files.length >= MAX_FILES_PER_RUN_DIR) {
142
+ warnings.push(`${runDir}: file walk capped at ${MAX_FILES_PER_RUN_DIR} files`)
143
+ return files
144
+ }
145
+ }
146
+ }
147
+ }
148
+ return files
149
+ }
150
+
151
+ /** Run the predicate over the candidate's run dirs. Fail-closed: an unreadable
152
+ * artifact contributes nothing (with a warning) — it can never count as
153
+ * "fired". */
154
+ export async function runActivationPredicate(
155
+ predicate: ActivationPredicate,
156
+ runDirs: readonly string[],
157
+ ): Promise<ActivationResult> {
158
+ const result: ActivationResult = { fired: false, evidence: [], checkedRunDirs: 0, checkedFiles: 0, warnings: [] }
159
+ for (const runDir of runDirs) {
160
+ result.checkedRunDirs += 1
161
+ if (predicate.kind === 'script') {
162
+ const res = await run('bash', ['-c', predicate.script!], {
163
+ cwd: runDir,
164
+ timeoutMs: SCRIPT_TIMEOUT_MS,
165
+ env: { ...process.env, RUN_DIR: runDir },
166
+ })
167
+ if (res.code === 0) {
168
+ result.fired = true
169
+ if (result.evidence.length < MAX_EVIDENCE) {
170
+ result.evidence.push(`${runDir}: script rc=0${res.stdout.trim() ? ` — ${res.stdout.trim().slice(0, 200)}` : ''}`)
171
+ }
172
+ } else if (res.timedOut) {
173
+ result.warnings.push(`${runDir}: script timed out after ${SCRIPT_TIMEOUT_MS}ms (counted as not-fired)`)
174
+ }
175
+ continue
176
+ }
177
+ const regex = new RegExp(predicate.pattern!)
178
+ const filters = (predicate.files ?? []).filter((f) => f.length > 0)
179
+ for (const file of await walkRunDir(runDir, result.warnings)) {
180
+ const rel = relative(runDir, file)
181
+ if (filters.length > 0 && !filters.some((f) => rel.includes(f))) continue
182
+ const info = await stat(file).catch(() => null)
183
+ if (info === null) continue
184
+ if (info.size > MAX_FILE_BYTES) {
185
+ result.warnings.push(`${rel}: skipped (${info.size} bytes > ${MAX_FILE_BYTES})`)
186
+ continue
187
+ }
188
+ result.checkedFiles += 1
189
+ const content = await readFile(file, 'utf8').catch(() => null)
190
+ if (content === null) continue
191
+ const lines = content.split('\n')
192
+ for (let i = 0; i < lines.length; i++) {
193
+ if (regex.test(lines[i]!)) {
194
+ result.fired = true
195
+ if (result.evidence.length < MAX_EVIDENCE) {
196
+ result.evidence.push(`${file}:${i + 1}: ${lines[i]!.trim().slice(0, 200)}`)
197
+ }
198
+ break // one match per file is enough evidence
199
+ }
200
+ }
201
+ }
202
+ }
203
+ return result
204
+ }
205
+
206
+ /** Read + parse the predicate committed at a candidate's loops commit. */
207
+ export async function readCommittedPredicate(
208
+ loopsRepo: string,
209
+ commit: string,
210
+ ): Promise<{ raw: string; parsed: ParsedPredicate } | null> {
211
+ const show = await run('git', ['-C', loopsRepo, 'show', `${commit}:${ACTIVATION_PREDICATE_RELPATH}`])
212
+ if (show.code !== 0) return null
213
+ return { raw: show.stdout, parsed: parseActivationPredicate(show.stdout) }
214
+ }
215
+
216
+ /** The staircase row's activation record. */
217
+ export interface ActivationRecord {
218
+ /** Whether a parseable predicate was present on the candidate commit. */
219
+ present: boolean
220
+ description: string | null
221
+ /** null = not evaluated (no predicate / gate disabled / baseline). */
222
+ fired: boolean | null
223
+ evidence: string[]
224
+ warnings: string[]
225
+ }