@tangle-network/agent-bench 0.3.6 → 0.3.8

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 (265) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/dist/adapters.js +24 -24
  4. package/dist/benchmarks/_harness.d.ts +1 -1
  5. package/dist/benchmarks/_harness.js +1 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/humaneval.d.ts +10 -1
  16. package/dist/benchmarks/humaneval.js +5 -3
  17. package/dist/benchmarks/nomiracl.js +2 -2
  18. package/dist/benchmarks/open-rag-bench.js +2 -2
  19. package/dist/benchmarks/programbench.js +2 -2
  20. package/dist/benchmarks/ragbench.js +2 -2
  21. package/dist/benchmarks/swe-bench.js +2 -2
  22. package/dist/benchmarks/t2-ragbench.js +2 -2
  23. package/dist/benchmarks/tau-bench-shared.js +2 -2
  24. package/dist/benchmarks/tau2-bench.js +3 -3
  25. package/dist/benchmarks/tau3-banking.js +3 -3
  26. package/dist/benchmarks/terminal-bench.js +2 -2
  27. package/dist/benchmarks/toollm.js +2 -2
  28. package/dist/benchmarks/webarena-verified.js +2 -2
  29. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  30. package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
  31. package/dist/chunk-5H5XV76F.js.map +1 -0
  32. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  33. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  34. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  35. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  36. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  37. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  38. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  39. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  40. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  41. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  42. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  43. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  44. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  45. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  46. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  47. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  48. package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
  49. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  50. package/dist/chunk-WSKWVEQB.js.map +1 -0
  51. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  52. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  53. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  54. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  55. package/dist/index.js +24 -24
  56. package/package.json +6 -5
  57. package/scripts/run-package-tests.mjs +30 -8
  58. package/scripts/verify-packed-consumer.mjs +1 -1
  59. package/scripts/verify-pier-agent.mts +1 -0
  60. package/src/benchmarks/_harness.ts +20 -2
  61. package/src/benchmarks/humaneval.test.mts +122 -0
  62. package/src/benchmarks/humaneval.ts +100 -27
  63. package/src/david-attribution.mts +78 -0
  64. package/src/david-goliath.mts +149 -0
  65. package/src/hev-improve.mts +25 -6
  66. package/src/humaneval-object-ablation.mts +201 -0
  67. package/src/live-improve-campaign-mbpp.mts +641 -0
  68. package/src/live-improve-campaign.mts +500 -0
  69. package/src/mbpp-structural.mts +12 -7
  70. package/src/quant-arena/README.md +144 -0
  71. package/src/quant-arena/backtest.test.mts +135 -0
  72. package/src/quant-arena/backtest.ts +218 -0
  73. package/src/quant-arena/data.test.mts +44 -0
  74. package/src/quant-arena/data.ts +141 -0
  75. package/src/quant-arena/driver.test.mts +253 -0
  76. package/src/quant-arena/driver.ts +219 -0
  77. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  78. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  83. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  84. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  85. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  86. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  87. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  88. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  89. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  94. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  95. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  96. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  97. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  98. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  99. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  100. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  101. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  102. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  103. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  104. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  105. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  106. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  107. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  108. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  109. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  110. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  111. package/src/quant-arena/holdout-certify.mts +206 -0
  112. package/src/quant-arena/holdout-certify.test.mts +82 -0
  113. package/src/quant-arena/leak-audit.test.mts +79 -0
  114. package/src/quant-arena/leak-audit.ts +95 -0
  115. package/src/quant-arena/make-fixtures.mts +161 -0
  116. package/src/quant-arena/multiplicity.test.mts +68 -0
  117. package/src/quant-arena/multiplicity.ts +87 -0
  118. package/src/quant-arena/nautilus-certify.ts +31 -0
  119. package/src/quant-arena/oms.ts +90 -0
  120. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  121. package/src/quant-arena/python/pyproject.toml +8 -0
  122. package/src/quant-arena/python/uv.lock +1297 -0
  123. package/src/quant-arena/python/vbt-worker.py +192 -0
  124. package/src/quant-arena/quant-loop.mts +813 -0
  125. package/src/quant-arena/quant-loop.test.mts +75 -0
  126. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  127. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  128. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  129. package/src/quant-arena/types.ts +133 -0
  130. package/src/quant-arena/vbt-client.ts +321 -0
  131. package/src/quant-arena/vbt-parity.test.mts +183 -0
  132. package/src/quant-arena/windows.test.mts +45 -0
  133. package/src/quant-arena/windows.ts +54 -0
  134. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  135. package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
  136. package/src/rollout-ledger/settle-capture.mts +442 -0
  137. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  138. package/src/stream-observe.py +45 -0
  139. package/src/stream-observe.tpl.html +247 -0
  140. package/src/supervisor-arena.mts +816 -0
  141. package/src/swe-arena/activation.mts +228 -0
  142. package/src/swe-arena/activation.test.mts +303 -0
  143. package/src/swe-arena/analyze.ts +211 -0
  144. package/src/swe-arena/arms.ts +804 -0
  145. package/src/swe-arena/bootstrap-meta.mts +188 -0
  146. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  147. package/src/swe-arena/briefing.mts +217 -0
  148. package/src/swe-arena/briefing.test.mts +178 -0
  149. package/src/swe-arena/calibrate.ts +217 -0
  150. package/src/swe-arena/capabilities.mts +76 -0
  151. package/src/swe-arena/capabilities.test.mts +57 -0
  152. package/src/swe-arena/capacity.ts +194 -0
  153. package/src/swe-arena/cell-evidence.mts +437 -0
  154. package/src/swe-arena/cell-evidence.test.mts +248 -0
  155. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  156. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  157. package/src/swe-arena/execution.test.mts +1170 -0
  158. package/src/swe-arena/factory-command-container.ts +284 -0
  159. package/src/swe-arena/factory-judge-child.mts +228 -0
  160. package/src/swe-arena/factory.test.mts +643 -0
  161. package/src/swe-arena/fixtures/analyze.py +80 -0
  162. package/src/swe-arena/fixtures/excludes.txt +8 -0
  163. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  164. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  165. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  166. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  167. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  168. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  169. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  170. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  171. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  172. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  173. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  174. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  175. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  176. package/src/swe-arena/fixtures/holdout.json +44 -0
  177. package/src/swe-arena/fixtures/instances.json +146 -0
  178. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  179. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  180. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  181. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  182. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  183. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  184. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  185. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  186. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  187. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  188. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  189. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  190. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  191. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  192. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  193. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  194. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  195. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  196. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  197. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  198. package/src/swe-arena/fixtures.ts +237 -0
  199. package/src/swe-arena/gepa-seat.mts +583 -0
  200. package/src/swe-arena/gepa-seat.test.mts +635 -0
  201. package/src/swe-arena/holdout-certify.mts +408 -0
  202. package/src/swe-arena/holdout-certify.test.mts +160 -0
  203. package/src/swe-arena/judge-child.mts +37 -0
  204. package/src/swe-arena/ledger-orphans.mts +77 -0
  205. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  206. package/src/swe-arena/lineage-record.mts +164 -0
  207. package/src/swe-arena/lineage-record.test.mts +115 -0
  208. package/src/swe-arena/manifest.mts +293 -0
  209. package/src/swe-arena/manifest.test.mts +169 -0
  210. package/src/swe-arena/materialize.ts +142 -0
  211. package/src/swe-arena/outer-loop.mts +2795 -0
  212. package/src/swe-arena/outer-loop.test.mts +696 -0
  213. package/src/swe-arena/parity.test.mts +87 -0
  214. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  215. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  216. package/src/swe-arena/proc.test.mts +174 -0
  217. package/src/swe-arena/proc.ts +260 -0
  218. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  219. package/src/swe-arena/proposer-fanout.mts +770 -0
  220. package/src/swe-arena/proposer-fanout.test.mts +619 -0
  221. package/src/swe-arena/proposer-provenance.mts +177 -0
  222. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  223. package/src/swe-arena/reconcile.ts +0 -0
  224. package/src/swe-arena/replay.mts +183 -0
  225. package/src/swe-arena/replay.test.mts +300 -0
  226. package/src/swe-arena/run-experiment.mts +727 -0
  227. package/src/swe-arena/run-report.mts +75 -0
  228. package/src/swe-arena/run-supervisor.mjs +297 -0
  229. package/src/swe-arena/run-supervisor.test.mts +500 -0
  230. package/src/swe-arena/score-split.mts +140 -0
  231. package/src/swe-arena/score-split.test.mts +123 -0
  232. package/src/swe-arena/serialized-judge.ts +414 -0
  233. package/src/swe-arena/types.ts +218 -0
  234. package/src/swe-code-improve.mts +328 -0
  235. package/src/swe-emit-patch.mts +104 -0
  236. package/src/swe-improve.mts +232 -0
  237. package/src/swe-jail.ts +2 -2
  238. package/src/swe-local-proof.mts +169 -0
  239. package/src/swe-repro-calibrate.mts +446 -0
  240. package/src/swe-stream.mts +1497 -0
  241. package/src/swe-structural.mts +245 -837
  242. package/dist/chunk-LRRD7NAG.js.map +0 -1
  243. package/dist/chunk-PPYSEKFM.js.map +0 -1
  244. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  245. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  246. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  247. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  248. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  249. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  250. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  251. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  252. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  253. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  254. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  255. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  256. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  257. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  258. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  259. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  260. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  261. /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
  262. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  263. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  264. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  265. /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
+ })
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+ """Autogenerate a self-contained observability dashboard (topology + telemetry) from a stream run.
3
+ Usage: python stream-observe.py <STREAM_DIR> [REF_DIR] > out.html
4
+ Reads <dir>/{ledger.jsonl, events.jsonl, plan.json}. Reproducible: re-run anytime for live state."""
5
+ import json, sys, os
6
+ from collections import Counter
7
+ from html import escape
8
+
9
+ def load(d):
10
+ def jl(p):
11
+ try: return [json.loads(l) for l in open(os.path.join(d,p))]
12
+ except FileNotFoundError: return []
13
+ def jf(p):
14
+ try: return json.load(open(os.path.join(d,p)))
15
+ except FileNotFoundError: return []
16
+ return jl('ledger.jsonl'), jl('events.jsonl'), jf('plan.json')
17
+
18
+ def curve(rows):
19
+ by={}
20
+ for r in rows: by.setdefault(r['streamIndex'],{})[r['arm']]=r
21
+ out=[]; cf=cl=n=0; costF=costL=fires=goodfires=0
22
+ for i in sorted(by):
23
+ f,l=by[i].get('F'),by[i].get('L')
24
+ if not f or not l: continue
25
+ sp=l.get('supervisorPlan') or {}
26
+ if sp.get('fired'): fires+=1
27
+ if sp.get('fired') and len(sp.get('plan') or '')>50: goodfires+=1
28
+ if f.get('error') or l.get('error'):
29
+ out.append({'i':i,'id':f['instanceId'].split('__')[-1],'err':(f.get('error') or l.get('error'))[:16]}); continue
30
+ n+=1; cf+=1 if f.get('hiddenResolved') else 0; cl+=1 if l.get('hiddenResolved') else 0
31
+ costF+=f.get('usd',0); costL+=l.get('usd',0)
32
+ out.append({'i':i,'id':f['instanceId'].split('__')[-1],'F':int(bool(f.get('hiddenResolved'))),'L':int(bool(l.get('hiddenResolved'))),
33
+ 'cumF':round(cf/n,3),'cumL':round(cl/n,3),'recall':(l.get('recall') or {}).get('injectedChars',0),
34
+ 'supFired':bool(sp.get('fired')),'supReason':sp.get('reason','')})
35
+ return out,{'scored':n,'F':cf,'L':cl,'costF':round(costF,2),'costL':round(costL,2),'fires':fires,'goodfires':goodfires}
36
+
37
+ sd=sys.argv[1]; ref=sys.argv[2] if len(sys.argv)>2 else sd
38
+ lrows,levents,lplan=load(sd)
39
+ rrows,_,_=load(ref)
40
+ lc,lstat=curve(lrows)
41
+ rc,rstat=curve(rrows)
42
+ data={'live':{'events':dict(Counter(e.get('type') for e in levents)) or {'(none)':0},'plan':lplan,'curve':lc,'stat':lstat,'dir':os.path.basename(sd)},
43
+ 'ref':{'curve':rc,'stat':rstat,'dir':os.path.basename(ref)}}
44
+ tpl=open(os.path.join(os.path.dirname(__file__),'stream-observe.tpl.html')).read()
45
+ sys.stdout.write(tpl.replace('__DATA__', json.dumps(data)))
@@ -0,0 +1,247 @@
1
+ <title>Stream loop — run topology & telemetry</title>
2
+ <style>
3
+ :root{
4
+ --bg:#0f1216; --panel:#171b21; --panel2:#1c2129; --line:#2a3039; --ink:#e6e9ee; --ink2:#98a2b3;
5
+ --zai:#5b8cff; --zai-bg:#141d33; --docker:#28c7a8; --sup:#f0a63b; --worker:#8b7cf0;
6
+ --ok:#3fb96b; --no:#e0574d; --mono:ui-monospace,'SF Mono',Menlo,Consolas,monospace;
7
+ --sans:ui-sans-serif,system-ui,'Segoe UI',Roboto,Arial,sans-serif;
8
+ }
9
+ :root{--docker-bg:#12241f; --zai-bg:#141d33; --sup-bg:#2a2113; --worker-bg:#1c1930; --ok-bg:#12241a; --no-bg:#2a1614;}
10
+ @media (prefers-color-scheme: light){:root{
11
+ --bg:#f5f6f8; --panel:#ffffff; --panel2:#f0f2f5; --line:#dfe3e9; --ink:#1a1f28; --ink2:#5a6373;
12
+ --zai:#2f5fe0; --docker:#0e9e84; --sup:#b5741a; --worker:#6a56d6;
13
+ --zai-bg:#e9eefc; --docker-bg:#e0f4ef; --sup-bg:#f7ecd8; --worker-bg:#ece8fa; --ok-bg:#e3f2e9; --no-bg:#fbe6e3; --ok:#1f9c53; --no:#c53727;
14
+ }}
15
+ :root[data-theme="light"]{
16
+ --bg:#f5f6f8; --panel:#ffffff; --panel2:#f0f2f5; --line:#dfe3e9; --ink:#1a1f28; --ink2:#5a6373;
17
+ --zai:#2f5fe0; --docker:#0e9e84; --sup:#b5741a; --worker:#6a56d6;
18
+ --zai-bg:#e9eefc; --docker-bg:#e0f4ef; --sup-bg:#f7ecd8; --worker-bg:#ece8fa; --ok-bg:#e3f2e9; --no-bg:#fbe6e3; --ok:#1f9c53; --no:#c53727;
19
+ }
20
+ :root[data-theme="dark"]{
21
+ --bg:#0f1216; --panel:#171b21; --panel2:#1c2129; --line:#2a3039; --ink:#e6e9ee; --ink2:#98a2b3;
22
+ --zai:#5b8cff; --docker:#28c7a8; --sup:#f0a63b; --worker:#8b7cf0;
23
+ --zai-bg:#141d33; --docker-bg:#12241f; --sup-bg:#2a2113; --worker-bg:#1c1930; --ok-bg:#12241a; --no-bg:#2a1614;
24
+ }
25
+ *{box-sizing:border-box}
26
+ body{background:var(--bg);color:var(--ink);font-family:var(--sans);margin:0;padding:32px 20px 80px;line-height:1.5}
27
+ .wrap{max-width:1080px;margin:0 auto}
28
+ h1{font-size:23px;letter-spacing:-.01em;margin:0 0 4px}
29
+ .sub{color:var(--ink2);font-size:14px;margin:0 0 26px;max-width:70ch}
30
+ h2{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink2);margin:34px 0 12px;font-weight:600}
31
+ .panel{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:18px 20px}
32
+ .mono{font-family:var(--mono)}
33
+ .legend{display:flex;gap:16px;flex-wrap:wrap;font-size:12px;margin:12px 0 0}
34
+ .lg{display:inline-flex;align-items:center;gap:6px}
35
+ .sw{width:11px;height:11px;border-radius:3px;display:inline-block}
36
+ .flow{overflow-x:auto}
37
+ svg{display:block;max-width:100%}
38
+ .kv{display:grid;grid-template-columns:auto 1fr;gap:4px 16px;font-size:13px}
39
+ .kv dt{color:var(--ink2)} .kv dd{margin:0;font-family:var(--mono);font-size:12px}
40
+ .grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
41
+ @media(max-width:720px){.grid2{grid-template-columns:1fr}}
42
+ .stat{display:flex;flex-direction:column;gap:2px}
43
+ .stat .n{font-size:26px;font-weight:650;font-variant-numeric:tabular-nums}
44
+ .stat .l{font-size:11px;color:var(--ink2);text-transform:uppercase;letter-spacing:.05em}
45
+ .stats{display:flex;gap:28px;flex-wrap:wrap}
46
+ table{border-collapse:collapse;width:100%;font-size:12.5px}
47
+ th{text-align:left;color:var(--ink2);font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;padding:7px 10px;border-bottom:1px solid var(--line);font-weight:600}
48
+ td{padding:6px 10px;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums}
49
+ tr:last-child td{border-bottom:none}
50
+ .pill{display:inline-block;padding:1px 7px;border-radius:20px;font-size:11px;font-family:var(--mono)}
51
+ .p-ok{background:var(--ok-bg);color:var(--ok)} .p-no{background:var(--no-bg);color:var(--no)} .p-dl{background:var(--panel2);color:var(--ink2)}
52
+ .note{font-size:12px;color:var(--ink2);margin-top:8px;max-width:78ch}
53
+ .badge{font-family:var(--mono);font-size:11px;padding:2px 8px;border-radius:4px;background:var(--panel2);color:var(--ink2);border:1px solid var(--line)}
54
+ </style>
55
+ <div class="wrap">
56
+ <h1>Stream loop — run topology &amp; telemetry</h1>
57
+ <p class="sub">The two-arm SWE-bench stream: frozen (F) vs supervised-learning (L), same instance in the same slot. This snapshots the request graph, the model routing, and the measured curve. Live run = <span class="badge" id="livecfg">glm-5.2 self-supervision · CONC=1</span></p>
58
+
59
+ <div class="panel" style="border-color:var(--sup);background:var(--sup-bg);margin-bottom:16px">
60
+ <p style="margin:0;font-size:13px;color:var(--ink)"><b>NOT a delegation tree.</b> This experiment does <b>not</b> have a supervisor spawn workers a/b/c. The driver samples <b>k=4 workers independently and in parallel</b> (no supervisor involved), scores them, keeps the best — and <b>only if it failed</b> does the supervisor write <b>one</b> advice plan for a retry. The supervisor is a <b>reviewer at the end</b>, not a <b>dispatcher at the start</b>. (The delegation-tree topology — supervisor → workers — exists in the codebase via <span class="mono">supervise()</span>/coordination-MCP, but this run doesn't use it.)</p>
61
+ </div>
62
+
63
+ <h2>Execution sequence — one instance, left→right in time</h2>
64
+ <div class="panel flow">
65
+ <svg viewBox="0 0 1040 470" width="1040" height="470" font-family="var(--sans)" font-size="12">
66
+ <defs><marker id="ar" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="currentColor"/></marker></defs>
67
+ <!-- phase headers -->
68
+ <g fill="var(--ink2)" font-size="10.5" font-family="var(--mono)">
69
+ <text x="95" y="20">t0 · setup</text><text x="300" y="20">t1 · sample k=4</text><text x="530" y="20">t2 · score</text>
70
+ <text x="690" y="20">t3 · select</text><text x="800" y="20">t4 · repair</text><text x="945" y="20">t5 · grade</text>
71
+ </g>
72
+ <line x1="30" y1="28" x2="1020" y2="28" stroke="var(--line)"/>
73
+ <!-- t0: driver + repro author -->
74
+ <g>
75
+ <rect x="30" y="205" width="130" height="58" rx="8" fill="var(--panel2)" stroke="var(--line)"/>
76
+ <text x="95" y="228" text-anchor="middle" fill="var(--ink)" font-weight="600" font-size="11.5">stream driver</text>
77
+ <text x="95" y="243" text-anchor="middle" fill="var(--ink2)" font-size="9.5">local · swe-stream.mts</text>
78
+ <text x="95" y="255" text-anchor="middle" fill="var(--ink2)" font-size="9.5">no model</text>
79
+ </g>
80
+ <g color="var(--zai)">
81
+ <line x1="160" y1="215" x2="230" y2="80" stroke="currentColor" stroke-width="1.4" marker-end="url(#ar)"/>
82
+ <rect x="230" y="44" width="165" height="60" rx="8" fill="var(--zai-bg)" stroke="var(--zai)"/>
83
+ <text x="312" y="64" text-anchor="middle" fill="var(--ink)" font-weight="600" font-size="11.5">repro author</text>
84
+ <text x="312" y="79" text-anchor="middle" fill="var(--zai)" font-size="9.5" font-family="var(--mono)">glm-5.2 · HTTPS zai</text>
85
+ <text x="312" y="92" text-anchor="middle" fill="var(--ink2)" font-size="9">no tools · writes 1 test</text>
86
+ </g>
87
+ <!-- t1: k=4 workers, parallel siblings -->
88
+ <g color="var(--worker)">
89
+ <line x1="160" y1="230" x2="228" y2="230" stroke="currentColor" stroke-width="1.4" marker-end="url(#ar)"/>
90
+ <text x="180" y="222" fill="var(--ink2)" font-size="9">sample</text>
91
+ </g>
92
+ <g>
93
+ <!-- 4 sibling boxes a-d -->
94
+ <g fill="var(--worker-bg)" stroke="var(--worker)">
95
+ <rect x="230" y="135" width="165" height="42" rx="7"/><rect x="230" y="185" width="165" height="42" rx="7"/>
96
+ <rect x="230" y="235" width="165" height="42" rx="7"/><rect x="230" y="285" width="165" height="42" rx="7"/>
97
+ </g>
98
+ <g fill="var(--ink)" font-weight="600" font-size="11" text-anchor="middle">
99
+ <text x="312" y="152">worker a</text><text x="312" y="202">worker b</text><text x="312" y="252">worker c</text><text x="312" y="302">worker d</text>
100
+ </g>
101
+ <g fill="var(--worker)" font-size="9" font-family="var(--mono)" text-anchor="middle">
102
+ <text x="312" y="166">glm-5.2 · ≤12 tool turns</text><text x="312" y="216">glm-5.2 · ≤12 tool turns</text>
103
+ <text x="312" y="266">glm-5.2 · ≤12 tool turns</text><text x="312" y="316">glm-5.2 · ≤12 tool turns</text>
104
+ </g>
105
+ </g>
106
+ <text x="312" y="345" text-anchor="middle" fill="var(--ink2)" font-size="9.5">independent · parallel · emit-patch harness (read/edit/run tools)</text>
107
+ <!-- t2: score each in jail -->
108
+ <g color="var(--docker)">
109
+ <g stroke="currentColor" stroke-width="1.2">
110
+ <line x1="395" y1="156" x2="455" y2="200" marker-end="url(#ar)"/><line x1="395" y1="206" x2="455" y2="212" marker-end="url(#ar)"/>
111
+ <line x1="395" y1="256" x2="455" y2="224" marker-end="url(#ar)"/><line x1="395" y1="306" x2="455" y2="236" marker-end="url(#ar)"/>
112
+ </g>
113
+ <rect x="455" y="175" width="150" height="90" rx="8" fill="var(--docker-bg)" stroke="var(--docker)"/>
114
+ <text x="530" y="200" text-anchor="middle" fill="var(--ink)" font-weight="600" font-size="11.5">score in jail</text>
115
+ <text x="530" y="216" text-anchor="middle" fill="var(--docker)" font-size="9.5" font-family="var(--mono)">docker · net=none</text>
116
+ <text x="530" y="232" text-anchor="middle" fill="var(--ink2)" font-size="9">apply patch → run</text>
117
+ <text x="530" y="245" text-anchor="middle" fill="var(--ink2)" font-size="9">the repro → severity</text>
118
+ <text x="530" y="258" text-anchor="middle" fill="var(--ink2)" font-size="9">(local, no model)</text>
119
+ </g>
120
+ <!-- t3: select argmax -->
121
+ <g>
122
+ <line x1="605" y1="220" x2="655" y2="220" stroke="var(--ink2)" stroke-width="1.4" marker-end="url(#ar)"/>
123
+ <rect x="655" y="196" width="80" height="48" rx="8" fill="var(--panel2)" stroke="var(--line)"/>
124
+ <text x="695" y="216" text-anchor="middle" fill="var(--ink)" font-weight="600" font-size="11">argmax</text>
125
+ <text x="695" y="231" text-anchor="middle" fill="var(--ink2)" font-size="9">pick best</text>
126
+ </g>
127
+ <!-- t4: repair branch -->
128
+ <line x1="735" y1="215" x2="785" y2="150" stroke="var(--ink2)" stroke-width="1.2" marker-end="url(#ar)" color="var(--ink2)"/>
129
+ <line x1="735" y1="225" x2="785" y2="290" stroke="var(--sup)" stroke-width="1.2" marker-end="url(#ar)" color="var(--sup)"/>
130
+ <g color="var(--ink2)">
131
+ <rect x="785" y="118" width="175" height="62" rx="8" fill="var(--panel2)" stroke="var(--line)"/>
132
+ <text x="872" y="138" text-anchor="middle" fill="var(--ink)" font-weight="600" font-size="11">ARM F · repair</text>
133
+ <text x="872" y="153" text-anchor="middle" fill="var(--ink2)" font-size="9.5">worker retries on raw</text>
134
+ <text x="872" y="165" text-anchor="middle" fill="var(--ink2)" font-size="9.5">error text (the control)</text>
135
+ </g>
136
+ <g color="var(--sup)">
137
+ <rect x="785" y="258" width="175" height="76" rx="8" fill="var(--sup-bg)" stroke="var(--sup)"/>
138
+ <text x="872" y="278" text-anchor="middle" fill="var(--ink)" font-weight="600" font-size="11">ARM L · supervisor</text>
139
+ <text x="872" y="293" text-anchor="middle" fill="var(--sup)" font-size="9.5" font-family="var(--mono)">glm-5.2 · HTTPS zai</text>
140
+ <text x="872" y="306" text-anchor="middle" fill="var(--ink2)" font-size="9">no-tools · writes 1 plan</text>
141
+ <text x="872" y="318" text-anchor="middle" fill="var(--ink2)" font-size="9">→ worker retries w/ plan</text>
142
+ </g>
143
+ <!-- t5: hidden judge -->
144
+ <line x1="960" y1="150" x2="985" y2="400" stroke="var(--ink2)" stroke-width="1" stroke-dasharray="3 3" marker-end="url(#ar)" color="var(--ink2)"/>
145
+ <line x1="960" y1="300" x2="985" y2="410" stroke="var(--ink2)" stroke-width="1" stroke-dasharray="3 3" marker-end="url(#ar)" color="var(--ink2)"/>
146
+ <g color="var(--docker)">
147
+ <rect x="620" y="395" width="400" height="56" rx="8" fill="var(--docker-bg)" stroke="var(--docker)"/>
148
+ <text x="820" y="418" text-anchor="middle" fill="var(--ink)" font-weight="600">HIDDEN JUDGE · official SWE-bench tests</text>
149
+ <text x="820" y="435" text-anchor="middle" fill="var(--docker)" font-size="9.5" font-family="var(--mono)">docker · runs AFTER both arms lock · never in any prompt</text>
150
+ </g>
151
+ </svg>
152
+ <div class="legend">
153
+ <span class="lg"><span class="sw" style="background:var(--zai)"></span>repro author (glm-5.2, no tools)</span>
154
+ <span class="lg"><span class="sw" style="background:var(--worker)"></span>worker (glm-5.2, emit-patch harness)</span>
155
+ <span class="lg"><span class="sw" style="background:var(--sup)"></span>supervisor (glm-5.2, no tools, arm L only)</span>
156
+ <span class="lg"><span class="sw" style="background:var(--docker)"></span>docker jail (local, no network)</span>
157
+ </div>
158
+ <p class="note">Every model box calls one endpoint — <span class="mono">api.z.ai/api/coding/paas/v4</span> over HTTPS; every docker box runs locally, network-isolated. In THIS run all three model roles are the same model (glm-5.2 = self-supervision); in the cross-strength config the worker is a cheaper model and only the supervisor is glm-5.2. Arms F and L share t0–t3 (same repro, same 4 candidates); they diverge only at t4 (raw-failure retry vs supervisor-plan retry). Timings per phase are in the ledger (imagePullMs, wallMs, judgeMs); per-model-call timestamps are not yet emitted — see the note below.</p>
159
+ </div>
160
+
161
+ <h2>Supervisor gate — when arm L fires</h2>
162
+ <div class="panel">
163
+ <table>
164
+ <tr><th>candidate outcome</th><th>severity</th><th>arm L action</th><th>why</th></tr>
165
+ <tr><td>bug fixed (repro passes)</td><td class="mono">0</td><td><span class="pill p-ok">HOLD · already-passing</span></td><td>nothing to repair</td></tr>
166
+ <tr><td>applied but bug persists</td><td class="mono">1</td><td><span class="pill" style="background:var(--sup-bg);color:var(--sup)">FIRE · wrong-fix</span></td><td>verified wrong fix</td></tr>
167
+ <tr><td>repro timed out</td><td class="mono">2</td><td><span class="pill p-dl">HOLD · ambiguous</span></td><td>not a clean signal</td></tr>
168
+ <tr><td>patch failed to apply</td><td class="mono">3</td><td><span class="pill" style="background:var(--sup-bg);color:var(--sup)">FIRE · apply-failed</span></td><td>objective; re-anchor guidance</td></tr>
169
+ <tr><td>empty diff (no attempt)</td><td class="mono">4</td><td><span class="pill" style="background:var(--sup-bg);color:var(--sup)">FIRE · empty-diff</span></td><td>plan helps most here</td></tr>
170
+ </table>
171
+ <p class="note">The supervisor's plan is grounded ONLY in execution-verified evidence (the failing diff + the jailed failure output) — never the worker's self-report — so the credulity hazard measured in the arena cannot occur. Every plan is stripped of code and audited for hidden-test leaks.</p>
172
+ </div>
173
+
174
+ <div class="grid2" style="margin-top:16px">
175
+ <div>
176
+ <h2>Live run</h2>
177
+ <div class="panel">
178
+ <dl class="kv" id="livekv"></dl>
179
+ </div>
180
+ </div>
181
+ <div>
182
+ <h2>Prior full run (day-1, n=20) — reference</h2>
183
+ <div class="panel">
184
+ <div class="stats" id="d1stats"></div>
185
+ <p class="note" style="margin-top:14px">Day-1 arm L = memory recall only (no supervisor). L−F = −1 (noise). The live run adds the supervisor, testing whether execution-grounded advice moves the curve.</p>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <h2>L − F cumulative resolved rate — day-1 run (memory-only L)</h2>
191
+ <div class="panel flow">
192
+ <svg id="curve" viewBox="0 0 960 260" width="960" height="260"></svg>
193
+ <div class="legend">
194
+ <span class="lg"><span class="sw" style="background:var(--ink2)"></span>Frozen (F)</span>
195
+ <span class="lg"><span class="sw" style="background:var(--sup)"></span>Learning (L)</span>
196
+ <span class="lg"><span class="sw" style="background:var(--no)"></span>deadline drop (both arms)</span>
197
+ </div>
198
+ </div>
199
+
200
+ <h2>Per-instance — day-1 run</h2>
201
+ <div class="panel flow">
202
+ <table id="pertbl"><thead><tr><th>#</th><th>instance</th><th>F</th><th>L</th><th>recall injected</th></tr></thead><tbody></tbody></table>
203
+ </div>
204
+ <p class="note" id="foot"></p>
205
+ </div>
206
+ <script>
207
+ const DATA = __DATA__;
208
+ const $=s=>document.querySelector(s);
209
+ const L=DATA.live, R=DATA.ref;
210
+ // live panel — settled count from live curve
211
+ const settled = L.curve.filter(c=>c.cumF!=null).length, drops = L.curve.filter(c=>c.err).length, tot=L.plan.length||23;
212
+ const lk=$('#livekv');
213
+ const cfg=[['run dir',L.dir],['plan',`${settled} settled · ${drops} dropped · ${tot} total`],
214
+ ['supervisor fires',`${L.stat.goodfires} good / ${L.stat.fires} fired`],
215
+ ['live resolved F/L',`${L.stat.F} / ${L.stat.L}`],
216
+ ['cost F/L','$'+L.stat.costF+' / $'+L.stat.costL],
217
+ ['events',Object.entries(L.events).map(([k,v])=>k+'×'+v).join(', ')]];
218
+ lk.innerHTML=cfg.map(([k,v])=>`<dt>${k}</dt><dd>${v}</dd>`).join('');
219
+ // reference stats
220
+ const d=R.stat;
221
+ $('#d1stats').innerHTML=[['F resolved',`${d.F}/${d.scored}`],['L resolved',`${d.L}/${d.scored}`],['ΔL−F',(d.L-d.F>=0?'+':'')+(d.L-d.F)],['cost F/L','$'+d.costF+' / $'+d.costL]].map(([l,n])=>`<div class="stat"><span class="n">${n}</span><span class="l">${l}</span></div>`).join('');
222
+ // pick which curve to chart: live if it has >=2 scored points, else reference
223
+ const chart = L.curve.filter(c=>c.cumF!=null).length>=2 ? {c:L.curve,name:L.dir+' (LIVE)'} : {c:R.curve,name:R.dir+' (reference)'};
224
+ // curve
225
+ const scored=chart.c.filter(c=>c.cumF!=null);
226
+ const W=960,H=260,pl=44,pr=20,pt=20,pb=34, iw=W-pl-pr, ih=H-pt-pb;
227
+ const xs=i=>pl+(iw*i/(scored.length-1)); const ys=v=>pt+ih*(1-v);
228
+ let svg='';
229
+ // grid
230
+ for(let g=0;g<=100;g+=25){const y=ys(g/100);svg+=`<line x1="${pl}" y1="${y}" x2="${W-pr}" y2="${y}" stroke="var(--line)" stroke-width="1"/><text x="${pl-8}" y="${y+3}" text-anchor="end" fill="var(--ink2)" font-size="10">${g}%</text>`;}
231
+ const line=(key,col)=>{let p='';scored.forEach((c,i)=>{p+=(i?'L':'M')+xs(i)+','+ys(c[key]);});return `<path d="${p}" fill="none" stroke="${col}" stroke-width="2.5"/>`;};
232
+ svg+=line('cumF','var(--ink2)')+line('cumL','var(--sup)');
233
+ // endpoints + deadline marks
234
+ scored.forEach((c,i)=>{svg+=`<circle cx="${xs(i)}" cy="${ys(c.cumL)}" r="2.5" fill="var(--sup)"/><circle cx="${xs(i)}" cy="${ys(c.cumF)}" r="2.5" fill="var(--ink2)"/>`;});
235
+ const lastF=scored[scored.length-1].cumF,lastL=scored[scored.length-1].cumL;
236
+ svg+=`<text x="${W-pr+2}" y="${ys(lastF)+3}" fill="var(--ink2)" font-size="11" font-weight="600">${Math.round(lastF*100)}%</text>`;
237
+ svg+=`<text x="${W-pr+2}" y="${ys(lastL)+3}" fill="var(--sup)" font-size="11" font-weight="600">${Math.round(lastL*100)}%</text>`;
238
+ $('#curve').innerHTML=svg;
239
+ // table — always the reference (fully settled) run; live table would be sparse
240
+ $('#pertbl tbody').innerHTML=R.curve.map(c=>{
241
+ if(c.err) return `<tr><td>${c.i}</td><td class="mono">${c.id}</td><td colspan="3"><span class="pill p-no">deadline drop</span></td></tr>`;
242
+ const pf=c.F?'<span class="pill p-ok">✓</span>':'<span class="pill p-no">✗</span>';
243
+ const pll=c.L?'<span class="pill p-ok">✓</span>':'<span class="pill p-no">✗</span>';
244
+ return `<tr><td>${c.i}</td><td class="mono">${c.id}</td><td>${pf}</td><td>${pll}</td><td class="mono">${c.recall}ch</td></tr>`;
245
+ }).join('');
246
+ $('#foot').textContent=`Autogenerated by bench/src/stream-observe.py from ${L.dir}/ledger.jsonl — re-run the generator anytime for live state. Chart source: ${chart.name}. Per-instance table: ${R.dir} (${d.scored} scored, ${R.curve.filter(c=>c.err).length} deadline drops).`;
247
+ </script>