@tangle-network/agent-bench 0.3.5 → 0.3.7

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 (262) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +13 -1
  3. package/dist/adapters.d.ts +15 -0
  4. package/dist/adapters.js +43 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/benchmarks/_harness.d.ts +125 -0
  7. package/dist/benchmarks/_harness.js +33 -0
  8. package/dist/benchmarks/_harness.js.map +1 -0
  9. package/dist/benchmarks/aec-bench.d.ts +27 -0
  10. package/dist/benchmarks/aec-bench.js +8 -0
  11. package/dist/benchmarks/aec-bench.js.map +1 -0
  12. package/dist/benchmarks/agentbench.d.ts +16 -0
  13. package/dist/benchmarks/agentbench.js +10 -0
  14. package/dist/benchmarks/agentbench.js.map +1 -0
  15. package/dist/benchmarks/appworld.d.ts +37 -0
  16. package/dist/benchmarks/appworld.js +14 -0
  17. package/dist/benchmarks/appworld.js.map +1 -0
  18. package/dist/benchmarks/bfcl.d.ts +18 -0
  19. package/dist/benchmarks/bfcl.js +10 -0
  20. package/dist/benchmarks/bfcl.js.map +1 -0
  21. package/dist/benchmarks/cad-design.d.ts +45 -0
  22. package/dist/benchmarks/cad-design.js +7 -0
  23. package/dist/benchmarks/cad-design.js.map +1 -0
  24. package/dist/benchmarks/cadbench.d.ts +19 -0
  25. package/dist/benchmarks/cadbench.js +8 -0
  26. package/dist/benchmarks/cadbench.js.map +1 -0
  27. package/dist/benchmarks/cadgenbench.d.ts +22 -0
  28. package/dist/benchmarks/cadgenbench.js +8 -0
  29. package/dist/benchmarks/cadgenbench.js.map +1 -0
  30. package/dist/benchmarks/commit0.d.ts +31 -0
  31. package/dist/benchmarks/commit0.js +10 -0
  32. package/dist/benchmarks/commit0.js.map +1 -0
  33. package/dist/benchmarks/crag.d.ts +14 -0
  34. package/dist/benchmarks/crag.js +9 -0
  35. package/dist/benchmarks/crag.js.map +1 -0
  36. package/dist/benchmarks/dabstep.d.ts +18 -0
  37. package/dist/benchmarks/dabstep.js +10 -0
  38. package/dist/benchmarks/dabstep.js.map +1 -0
  39. package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
  40. package/dist/benchmarks/enterpriseops-gym.js +10 -0
  41. package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
  42. package/dist/benchmarks/finresearchbench.d.ts +15 -0
  43. package/dist/benchmarks/finresearchbench.js +8 -0
  44. package/dist/benchmarks/finresearchbench.js.map +1 -0
  45. package/dist/benchmarks/finsearchcomp.d.ts +49 -0
  46. package/dist/benchmarks/finsearchcomp.js +7 -0
  47. package/dist/benchmarks/finsearchcomp.js.map +1 -0
  48. package/dist/benchmarks/frames.d.ts +59 -0
  49. package/dist/benchmarks/frames.js +13 -0
  50. package/dist/benchmarks/frames.js.map +1 -0
  51. package/dist/benchmarks/hotpotqa.d.ts +48 -0
  52. package/dist/benchmarks/hotpotqa.js +15 -0
  53. package/dist/benchmarks/hotpotqa.js.map +1 -0
  54. package/dist/benchmarks/humaneval.d.ts +62 -0
  55. package/dist/benchmarks/humaneval.js +17 -0
  56. package/dist/benchmarks/humaneval.js.map +1 -0
  57. package/dist/benchmarks/mind2web.d.ts +41 -0
  58. package/dist/benchmarks/mind2web.js +9 -0
  59. package/dist/benchmarks/mind2web.js.map +1 -0
  60. package/dist/benchmarks/nomiracl.d.ts +15 -0
  61. package/dist/benchmarks/nomiracl.js +9 -0
  62. package/dist/benchmarks/nomiracl.js.map +1 -0
  63. package/dist/benchmarks/open-rag-bench.d.ts +14 -0
  64. package/dist/benchmarks/open-rag-bench.js +9 -0
  65. package/dist/benchmarks/open-rag-bench.js.map +1 -0
  66. package/dist/benchmarks/programbench.d.ts +38 -0
  67. package/dist/benchmarks/programbench.js +10 -0
  68. package/dist/benchmarks/programbench.js.map +1 -0
  69. package/dist/benchmarks/rag-shared.d.ts +42 -0
  70. package/dist/benchmarks/rag-shared.js +39 -0
  71. package/dist/benchmarks/rag-shared.js.map +1 -0
  72. package/dist/benchmarks/ragbench.d.ts +16 -0
  73. package/dist/benchmarks/ragbench.js +9 -0
  74. package/dist/benchmarks/ragbench.js.map +1 -0
  75. package/dist/benchmarks/simpleqa.d.ts +64 -0
  76. package/dist/benchmarks/simpleqa.js +11 -0
  77. package/dist/benchmarks/simpleqa.js.map +1 -0
  78. package/dist/benchmarks/swe-bench.d.ts +56 -0
  79. package/dist/benchmarks/swe-bench.js +14 -0
  80. package/dist/benchmarks/swe-bench.js.map +1 -0
  81. package/dist/benchmarks/t2-ragbench.d.ts +14 -0
  82. package/dist/benchmarks/t2-ragbench.js +9 -0
  83. package/dist/benchmarks/t2-ragbench.js.map +1 -0
  84. package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
  85. package/dist/benchmarks/tau-bench-shared.js +10 -0
  86. package/dist/benchmarks/tau-bench-shared.js.map +1 -0
  87. package/dist/benchmarks/tau2-bench.d.ts +7 -0
  88. package/dist/benchmarks/tau2-bench.js +11 -0
  89. package/dist/benchmarks/tau2-bench.js.map +1 -0
  90. package/dist/benchmarks/tau3-banking.d.ts +15 -0
  91. package/dist/benchmarks/tau3-banking.js +9 -0
  92. package/dist/benchmarks/tau3-banking.js.map +1 -0
  93. package/dist/benchmarks/terminal-bench.d.ts +24 -0
  94. package/dist/benchmarks/terminal-bench.js +8 -0
  95. package/dist/benchmarks/terminal-bench.js.map +1 -0
  96. package/dist/benchmarks/toollm.d.ts +16 -0
  97. package/dist/benchmarks/toollm.js +10 -0
  98. package/dist/benchmarks/toollm.js.map +1 -0
  99. package/dist/benchmarks/trata-hedge.d.ts +32 -0
  100. package/dist/benchmarks/trata-hedge.js +7 -0
  101. package/dist/benchmarks/trata-hedge.js.map +1 -0
  102. package/dist/benchmarks/types.d.ts +107 -0
  103. package/dist/benchmarks/types.js +1 -0
  104. package/dist/benchmarks/types.js.map +1 -0
  105. package/dist/benchmarks/webarena-verified.d.ts +16 -0
  106. package/dist/benchmarks/webarena-verified.js +10 -0
  107. package/dist/benchmarks/webarena-verified.js.map +1 -0
  108. package/dist/chunk-2PVVP7GN.js +197 -0
  109. package/dist/chunk-2PVVP7GN.js.map +1 -0
  110. package/dist/chunk-2XU6OGEN.js +170 -0
  111. package/dist/chunk-2XU6OGEN.js.map +1 -0
  112. package/dist/chunk-53UPUNBZ.js +325 -0
  113. package/dist/chunk-53UPUNBZ.js.map +1 -0
  114. package/dist/chunk-5H5XV76F.js +240 -0
  115. package/dist/chunk-5H5XV76F.js.map +1 -0
  116. package/dist/chunk-7WSD27QQ.js +118 -0
  117. package/dist/chunk-7WSD27QQ.js.map +1 -0
  118. package/dist/chunk-C7T7WEK2.js +103 -0
  119. package/dist/chunk-C7T7WEK2.js.map +1 -0
  120. package/dist/chunk-CKUVRZ2T.js +251 -0
  121. package/dist/chunk-CKUVRZ2T.js.map +1 -0
  122. package/dist/chunk-HBSWHQNJ.js +30 -0
  123. package/dist/chunk-HBSWHQNJ.js.map +1 -0
  124. package/dist/chunk-HHXFIHXC.js +116 -0
  125. package/dist/chunk-HHXFIHXC.js.map +1 -0
  126. package/dist/chunk-IFAV6KEM.js +276 -0
  127. package/dist/chunk-IFAV6KEM.js.map +1 -0
  128. package/dist/chunk-INNOYXCP.js +387 -0
  129. package/dist/chunk-INNOYXCP.js.map +1 -0
  130. package/dist/chunk-J3KDJNX2.js +182 -0
  131. package/dist/chunk-J3KDJNX2.js.map +1 -0
  132. package/dist/chunk-JRWWGMK7.js +148 -0
  133. package/dist/chunk-JRWWGMK7.js.map +1 -0
  134. package/dist/chunk-JTHWEDEW.js +32 -0
  135. package/dist/chunk-JTHWEDEW.js.map +1 -0
  136. package/dist/chunk-KDIKRJGB.js +120 -0
  137. package/dist/chunk-KDIKRJGB.js.map +1 -0
  138. package/dist/chunk-LRRD7NAG.js +301 -0
  139. package/dist/chunk-LRRD7NAG.js.map +1 -0
  140. package/dist/chunk-ODT47UAY.js +221 -0
  141. package/dist/chunk-ODT47UAY.js.map +1 -0
  142. package/dist/chunk-PA2ZKHJC.js +230 -0
  143. package/dist/chunk-PA2ZKHJC.js.map +1 -0
  144. package/dist/chunk-PUIRNYI7.js +189 -0
  145. package/dist/chunk-PUIRNYI7.js.map +1 -0
  146. package/dist/chunk-PWQVGAJB.js +144 -0
  147. package/dist/chunk-PWQVGAJB.js.map +1 -0
  148. package/dist/chunk-R36V2VP7.js +169 -0
  149. package/dist/chunk-R36V2VP7.js.map +1 -0
  150. package/dist/chunk-R67DFVLO.js +142 -0
  151. package/dist/chunk-R67DFVLO.js.map +1 -0
  152. package/dist/chunk-SEVJPLZC.js +260 -0
  153. package/dist/chunk-SEVJPLZC.js.map +1 -0
  154. package/dist/chunk-SYDW647C.js +318 -0
  155. package/dist/chunk-SYDW647C.js.map +1 -0
  156. package/dist/chunk-TBKU5XQI.js +228 -0
  157. package/dist/chunk-TBKU5XQI.js.map +1 -0
  158. package/dist/chunk-TSWPNOYM.js +147 -0
  159. package/dist/chunk-TSWPNOYM.js.map +1 -0
  160. package/dist/chunk-UAIOHCUK.js +27 -0
  161. package/dist/chunk-UAIOHCUK.js.map +1 -0
  162. package/dist/chunk-UPAMRDX4.js +233 -0
  163. package/dist/chunk-UPAMRDX4.js.map +1 -0
  164. package/dist/chunk-VQRS7VUC.js +342 -0
  165. package/dist/chunk-VQRS7VUC.js.map +1 -0
  166. package/dist/chunk-X3BTXCJ4.js +262 -0
  167. package/dist/chunk-X3BTXCJ4.js.map +1 -0
  168. package/dist/chunk-X5YKXC6V.js +211 -0
  169. package/dist/chunk-X5YKXC6V.js.map +1 -0
  170. package/dist/chunk-Y6O2OCUO.js +130 -0
  171. package/dist/chunk-Y6O2OCUO.js.map +1 -0
  172. package/dist/chunk-YCGY7UIZ.js +208 -0
  173. package/dist/chunk-YCGY7UIZ.js.map +1 -0
  174. package/dist/chunk-Z7ML6L77.js +162 -0
  175. package/dist/chunk-Z7ML6L77.js.map +1 -0
  176. package/dist/chunk-ZEWMTR5M.js +136 -0
  177. package/dist/chunk-ZEWMTR5M.js.map +1 -0
  178. package/dist/index.d.ts +355 -0
  179. package/dist/index.js +1908 -0
  180. package/dist/index.js.map +1 -0
  181. package/package.json +26 -9
  182. package/scripts/run-package-tests.mjs +30 -8
  183. package/scripts/verify-packed-consumer.mjs +12 -1
  184. package/scripts/verify-pier-agent.mts +1 -0
  185. package/src/benchmarks/humaneval.test.mts +122 -0
  186. package/src/benchmarks/humaneval.ts +100 -27
  187. package/src/david-attribution.mts +78 -0
  188. package/src/david-goliath.mts +149 -0
  189. package/src/hev-improve.mts +25 -6
  190. package/src/humaneval-object-ablation.mts +201 -0
  191. package/src/live-improve-campaign-mbpp.mts +641 -0
  192. package/src/live-improve-campaign.mts +500 -0
  193. package/src/mbpp-structural.mts +12 -7
  194. package/src/stream-observe.py +45 -0
  195. package/src/stream-observe.tpl.html +247 -0
  196. package/src/supervisor-arena.mts +816 -0
  197. package/src/swe-arena/analyze.ts +211 -0
  198. package/src/swe-arena/arms.ts +788 -0
  199. package/src/swe-arena/bootstrap-meta.mts +188 -0
  200. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  201. package/src/swe-arena/calibrate.ts +116 -0
  202. package/src/swe-arena/capabilities.mts +76 -0
  203. package/src/swe-arena/capabilities.test.mts +57 -0
  204. package/src/swe-arena/capacity.ts +194 -0
  205. package/src/swe-arena/cell-evidence.mts +405 -0
  206. package/src/swe-arena/cell-evidence.test.mts +248 -0
  207. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  208. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  209. package/src/swe-arena/execution.test.mts +1170 -0
  210. package/src/swe-arena/fixtures/analyze.py +80 -0
  211. package/src/swe-arena/fixtures/excludes.txt +8 -0
  212. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  213. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  214. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  215. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  216. package/src/swe-arena/fixtures/holdout.json +44 -0
  217. package/src/swe-arena/fixtures/instances.json +146 -0
  218. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  219. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  220. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  221. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  222. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  223. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  224. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  225. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  226. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  227. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  228. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  229. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  230. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  231. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  232. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  233. package/src/swe-arena/fixtures.ts +104 -0
  234. package/src/swe-arena/holdout-certify.mts +408 -0
  235. package/src/swe-arena/holdout-certify.test.mts +160 -0
  236. package/src/swe-arena/judge-child.mts +37 -0
  237. package/src/swe-arena/manifest.mts +293 -0
  238. package/src/swe-arena/manifest.test.mts +169 -0
  239. package/src/swe-arena/materialize.ts +142 -0
  240. package/src/swe-arena/outer-loop.mts +2145 -0
  241. package/src/swe-arena/outer-loop.test.mts +696 -0
  242. package/src/swe-arena/parity.test.mts +87 -0
  243. package/src/swe-arena/proc.test.mts +174 -0
  244. package/src/swe-arena/proc.ts +260 -0
  245. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  246. package/src/swe-arena/proposer-fanout.mts +489 -0
  247. package/src/swe-arena/proposer-fanout.test.mts +372 -0
  248. package/src/swe-arena/reconcile.ts +0 -0
  249. package/src/swe-arena/replay.mts +183 -0
  250. package/src/swe-arena/replay.test.mts +300 -0
  251. package/src/swe-arena/run-experiment.mts +361 -0
  252. package/src/swe-arena/run-supervisor.mjs +297 -0
  253. package/src/swe-arena/run-supervisor.test.mts +498 -0
  254. package/src/swe-arena/serialized-judge.ts +414 -0
  255. package/src/swe-arena/types.ts +166 -0
  256. package/src/swe-code-improve.mts +328 -0
  257. package/src/swe-emit-patch.mts +104 -0
  258. package/src/swe-improve.mts +232 -0
  259. package/src/swe-jail.ts +2 -2
  260. package/src/swe-local-proof.mts +169 -0
  261. package/src/swe-repro-calibrate.mts +446 -0
  262. package/src/swe-stream.mts +1497 -0
@@ -0,0 +1,500 @@
1
+ /**
2
+ * live-improve-campaign — the FIRST LIVE self-improvement campaign on the merged
3
+ * machinery: `improve()` (surface 'rollout-policy') tunes the structuralRollout dials
4
+ * { k, repairRounds, testgen } against REAL HumanEval with a REAL worker model, and the
5
+ * library's own held-out gate (defaultProductionGate: paired bootstrap over held-out
6
+ * scenarios, ship iff CI.low > deltaThreshold 0.05) makes the ship/hold call. No human
7
+ * picks winners; this script only wires the real evaluator into the loop and reports.
8
+ *
9
+ * Wiring template: rollout-policy.test.ts's end-to-end improve() run, with the fake
10
+ * judge gradient replaced by the real evaluator (the smoke's harness):
11
+ * agent(surface, scenario) = runAgentic(structuralRollout(parsed policy)) over an
12
+ * INERT verifier surface (no hidden signal reaches selection/repair), visible checks
13
+ * via the shipped sandboxCheckRunner over a docker --network=none exec channel, then
14
+ * SCRIPT-SIDE hidden grading of the locked winner candidate by the nonce-sentinel
15
+ * judge (hev-structural's runHiddenJudge pattern: pass requires exit 0 AND the
16
+ * per-call nonce in stdout — a candidate printing a forged verdict cannot pass).
17
+ *
18
+ * Honesty split:
19
+ * - DEV = HumanEval index [0, DEV_N) and HELD-OUT = [DEV_N, DEV_N+HOLD_N) — fixed,
20
+ * disjoint slices, passed as explicit `budget.holdoutScenarios` so the library's
21
+ * own train/holdout split machinery enforces disjointness (it throws on overlap).
22
+ * - The proposer is deterministic enumeration; `analyzeGeneration: null` keeps the
23
+ * findings channel empty, so the improver's context is ONLY the DEV composites the
24
+ * loop itself accumulates. Held-out cells run after all generations, gate-side only.
25
+ * - The gate decision is `result.gateDecision` from the library — never recomputed here.
26
+ *
27
+ * Run (key via dotenvx; never in the shell history):
28
+ * cd ~/company/devops/secrets && dotenvx run -f agent-state.env -- bash -c ' \
29
+ * cd /home/drew/code/agent-runtime-swe && \
30
+ * HUMANEVAL_GZ=/abs/HumanEval.jsonl.gz npx tsx bench/src/live-improve-campaign.mts'
31
+ * Smoke first (cost gate): SMOKE=1 shrinks to 6 dev + 6 held-out tasks, 1 generation,
32
+ * population 2 — proves the full path completes before the real burn.
33
+ */
34
+
35
+ import { execFile, execFileSync } from 'node:child_process'
36
+ import { randomBytes } from 'node:crypto'
37
+ import { appendFileSync, mkdirSync } from 'node:fs'
38
+ import { tmpdir } from 'node:os'
39
+ import { join } from 'node:path'
40
+ import type {
41
+ DispatchContext,
42
+ JudgeConfig,
43
+ MutableSurface,
44
+ Scenario,
45
+ } from '@tangle-network/agent-eval/contract'
46
+ import type { AgentProfile } from '@tangle-network/agent-interface'
47
+ import { improve } from '../../src/improvement/improve'
48
+ import {
49
+ parseRolloutPolicy,
50
+ ROLLOUT_POLICY_EXTENSION,
51
+ serializeRolloutPolicy,
52
+ structuralRolloutPolicyFromProfile,
53
+ } from '../../src/improvement/rollout-policy'
54
+ import {
55
+ type AgenticRunResult,
56
+ type CheckExecChannel,
57
+ type CheckOutcome,
58
+ type CheckRunner,
59
+ createVerifierEnvironment,
60
+ runAgentic,
61
+ sandboxCheckRunner,
62
+ structuralRollout,
63
+ type StructuralRolloutResult,
64
+ } from '../../src/runtime/index'
65
+ import { basePrompt, type HumanEvalTask, loadHumanEval } from './benchmarks/humaneval'
66
+
67
+ function must(name: string): string {
68
+ const v = process.env[name]
69
+ if (!v) throw new Error(`env ${name} is required`)
70
+ return v
71
+ }
72
+
73
+ const SMOKE = process.env.SMOKE === '1'
74
+ const DEV_N = Number(process.env.DEV_N ?? (SMOKE ? 6 : 60))
75
+ const HOLD_N = Number(process.env.HOLD_N ?? (SMOKE ? 6 : 60))
76
+ const GENERATIONS = Number(process.env.GENERATIONS ?? (SMOKE ? 1 : 2))
77
+ const POPULATION = Number(process.env.POPULATION ?? (SMOKE ? 2 : 4))
78
+ const CONCURRENCY = Number(process.env.CONCURRENCY ?? 8)
79
+ const DOCKER_CONCURRENCY = Number(process.env.DOCKER_CONCURRENCY ?? 6)
80
+ // Default worker: Qwen2.5-7B — the second model of the strategy's measured basis
81
+ // (Llama-3-8B/Qwen2.5-7B). The original smoke worker (Meta-Llama-3-8B-Instruct-Lite)
82
+ // and every other 8B Llama variant were retired from Together serverless
83
+ // (`model_not_available`, verified 2026-07); this is the closest live weak worker.
84
+ const MODEL = process.env.MODEL ?? 'Qwen/Qwen2.5-7B-Instruct-Turbo'
85
+ const BASE = process.env.ROUTER_BASE ?? 'https://api.together.xyz/v1'
86
+ const TEMP = Number(process.env.TEMPERATURE ?? 0.8)
87
+ const MAX_TOKENS = Number(process.env.MAX_TOKENS ?? 2500)
88
+ const DOLLARS = Number(process.env.DOLLARS ?? 15)
89
+ const RUN_DIR = process.env.RUN_DIR ?? join(tmpdir(), `live-improve-campaign-${Date.now()}`)
90
+
91
+ const systemPrompt = 'You are an expert Python programmer.'
92
+ const dockerImage = 'python:3.12-slim'
93
+ const dockerTimeoutMs = Number(process.env.DOCKER_TIMEOUT_MS ?? 20000)
94
+
95
+ // ── Docker: ONE semaphored --network=none exec channel for BOTH judges ───────────────
96
+ // Visible checks (sandboxCheckRunner) and the hidden nonce judge each pipe a python
97
+ // program as `printf '%s' '<b64>' | base64 -d | python3 -`; every container passes
98
+ // through one global semaphore so task-level concurrency cannot stampede the daemon.
99
+
100
+ let dockerInFlight = 0
101
+ const dockerWaiters: Array<() => void> = []
102
+ async function withDockerSlot<T>(fn: () => Promise<T>): Promise<T> {
103
+ if (dockerInFlight >= DOCKER_CONCURRENCY) await new Promise<void>((r) => dockerWaiters.push(r))
104
+ dockerInFlight += 1
105
+ try {
106
+ return await fn()
107
+ } finally {
108
+ dockerInFlight -= 1
109
+ dockerWaiters.shift()?.()
110
+ }
111
+ }
112
+
113
+ const containerPrefix = `lic-${process.pid}`
114
+ let containerSeq = 0
115
+
116
+ function reapContainers(): void {
117
+ try {
118
+ const ids = execFileSync('docker', ['ps', '-aq', '--filter', `name=${containerPrefix}`], {
119
+ timeout: 10000,
120
+ })
121
+ .toString()
122
+ .trim()
123
+ if (ids) execFileSync('docker', ['rm', '-f', ...ids.split('\n')], { timeout: 15000 })
124
+ } catch {
125
+ /* reaper is best-effort by design */
126
+ }
127
+ }
128
+ process.on('SIGINT', () => {
129
+ reapContainers()
130
+ process.exit(130)
131
+ })
132
+ process.on('SIGTERM', () => {
133
+ reapContainers()
134
+ process.exit(143)
135
+ })
136
+
137
+ const dockerBox: CheckExecChannel = {
138
+ exec(command, options) {
139
+ const timeoutMs = options?.timeoutMs ?? dockerTimeoutMs
140
+ return withDockerSlot(
141
+ () =>
142
+ new Promise((resolve, reject) => {
143
+ const name = `${containerPrefix}-${containerSeq++}`
144
+ let settled = false
145
+ const reap = () => execFile('docker', ['rm', '-f', name], () => {})
146
+ const finish = (r: { exitCode: number; stdout: string; stderr: string }) => {
147
+ if (settled) return
148
+ settled = true
149
+ clearTimeout(backstop)
150
+ reap()
151
+ resolve(r)
152
+ }
153
+ const fail = (e: Error) => {
154
+ if (settled) return
155
+ settled = true
156
+ clearTimeout(backstop)
157
+ reap()
158
+ reject(e)
159
+ }
160
+ // execFile's timeout kills the docker CLIENT; a hung container could leave
161
+ // the callback unfired. The backstop guarantees resolution and the named
162
+ // reap kills the stray container.
163
+ const backstop = setTimeout(
164
+ () => finish({ exitCode: 124, stdout: '', stderr: 'timed out (backstop)' }),
165
+ timeoutMs + 3000,
166
+ )
167
+ execFile(
168
+ 'docker',
169
+ ['run', '--rm', '--name', name, '--network=none', '--cpus=1', '--memory=512m', dockerImage, 'sh', '-c', command],
170
+ { timeout: timeoutMs, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },
171
+ (err, stdout, stderr) => {
172
+ if (err) {
173
+ const e = err as NodeJS.ErrnoException & { code?: number | string }
174
+ if (e.code === 'ENOENT') {
175
+ fail(new Error('docker binary not found on PATH'))
176
+ return
177
+ }
178
+ if (/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(stderr ?? '')) {
179
+ fail(new Error(`docker daemon unreachable: ${(stderr ?? '').slice(0, 200)}`))
180
+ return
181
+ }
182
+ finish({ exitCode: typeof e.code === 'number' ? e.code : 1, stdout: stdout ?? '', stderr: stderr ?? '' })
183
+ return
184
+ }
185
+ finish({ exitCode: 0, stdout: stdout ?? '', stderr: stderr ?? '' })
186
+ },
187
+ )
188
+ }),
189
+ )
190
+ },
191
+ }
192
+
193
+ // ── The hidden nonce judge (script-side, AFTER the strategy locks its artifact) ──────
194
+ // Pass requires the per-call nonce sentinel that check() prints AFTER succeeding —
195
+ // exit-0-before-check (a candidate calling sys.exit(0)) is a fail here, where trusting
196
+ // the exit code alone would score it a pass. Nothing from this run reaches the strategy.
197
+
198
+ function buildHiddenProgram(task: HumanEvalTask, candidate: string, nonce: string): string {
199
+ return `${task.prompt}\n${candidate}\n\n${task.test}\n\ncheck(${task.entryPoint})\nprint("HIDDEN-${nonce} PASS")\n`
200
+ }
201
+
202
+ async function runHiddenJudge(
203
+ task: HumanEvalTask,
204
+ candidate: string,
205
+ ): Promise<{ pass: number; detail?: string }> {
206
+ const nonce = randomBytes(8).toString('hex')
207
+ const b64 = Buffer.from(buildHiddenProgram(task, candidate, nonce), 'utf8').toString('base64')
208
+ const r = await dockerBox.exec(`printf '%s' '${b64}' | base64 -d | python3 -`, {
209
+ timeoutMs: dockerTimeoutMs,
210
+ })
211
+ if (r.exitCode === 0 && r.stdout.includes(`HIDDEN-${nonce} PASS`)) return { pass: 1 }
212
+ return { pass: 0, detail: (r.stderr || r.stdout).slice(-300) || 'timed out (no output)' }
213
+ }
214
+
215
+ // ── Scenarios: fixed disjoint slices of HumanEval ─────────────────────────────────────
216
+
217
+ interface HevScenario extends Scenario {
218
+ kind: 'humaneval'
219
+ }
220
+
221
+ const taskById = new Map<string, HumanEvalTask>()
222
+
223
+ // ── The real evaluator: one cell = one structuralRollout run + hidden grade ──────────
224
+
225
+ interface CellArtifact {
226
+ taskId: string
227
+ policy: string
228
+ /** Hidden nonce-judge grade of the FINAL selected candidate: {0,1}. */
229
+ pass: number
230
+ detail?: string
231
+ repairStop: string
232
+ shots: number
233
+ completions: number
234
+ authoredChecks: number
235
+ tokens: { input: number; output: number }
236
+ usd: number
237
+ ms: number
238
+ }
239
+
240
+ interface ScoredCandidate {
241
+ candidate: string
242
+ outcome: CheckOutcome
243
+ }
244
+ function recordingRunner(inner: CheckRunner, log: ScoredCandidate[]): CheckRunner {
245
+ return {
246
+ async run(candidate, checks, ctx) {
247
+ const outcome = await inner.run(candidate, checks, ctx)
248
+ log.push({ candidate, outcome })
249
+ return outcome
250
+ },
251
+ }
252
+ }
253
+
254
+ // Global spend meter (every cell of every phase — baseline, generations, holdout).
255
+ const spend = { cells: 0, llmCalls: 0, tokensIn: 0, tokensOut: 0, usd: 0, hiddenPass: 0 }
256
+
257
+ async function evaluateCell(
258
+ surface: MutableSurface,
259
+ scenario: HevScenario,
260
+ ctx: DispatchContext,
261
+ ): Promise<CellArtifact> {
262
+ const policy = parseRolloutPolicy(surface)
263
+ if (!policy) {
264
+ throw new Error(`agent: surface carries no valid rollout policy: ${String(surface).slice(0, 120)}`)
265
+ }
266
+ const task = taskById.get(scenario.id)
267
+ if (!task) throw new Error(`agent: unknown scenario id ${scenario.id}`)
268
+
269
+ const scored: ScoredCandidate[] = []
270
+ const strategy = structuralRollout({
271
+ policy: { ...policy, temperature: TEMP },
272
+ checkRunner: recordingRunner(sandboxCheckRunner({ box: dockerBox }), scored),
273
+ })
274
+ // INERT check: the strategy's harness-verified score channel carries no hidden
275
+ // signal — hidden grading happens below, after the rollout locks its artifact.
276
+ const inertSurface = createVerifierEnvironment({
277
+ name: 'humaneval-inert',
278
+ check: () => ({ passes: 0, total: 1, errored: 0 }),
279
+ })
280
+ const result = (await runAgentic({
281
+ surface: inertSurface,
282
+ task: {
283
+ id: task.taskId,
284
+ systemPrompt,
285
+ userPrompt: basePrompt(task),
286
+ meta: { entryPoint: task.entryPoint },
287
+ },
288
+ routerBaseUrl: BASE,
289
+ routerKey: must('TOGETHER_API_KEY'),
290
+ model: MODEL,
291
+ temperature: TEMP,
292
+ maxTokens: MAX_TOKENS,
293
+ innerTurns: 2,
294
+ strategy,
295
+ // The strategy's documented sizing: k samples + repair rounds + the check-author consult.
296
+ budget: policy.k + policy.repairRounds + 1,
297
+ })) as AgenticRunResult & StructuralRolloutResult
298
+
299
+ // Backend integrity: report REAL usage on every cell (expectUsage 'assert' upstream).
300
+ ctx.cost.observe(result.usd, 'together')
301
+ ctx.cost.observeTokens(result.tokens)
302
+
303
+ const winner = result.selection.find((r) => r.selected)
304
+ if (!winner) {
305
+ throw new Error(`${task.taskId}: no receipt marked selected (repairStop=${result.repairStop})`)
306
+ }
307
+ const rec = scored[winner.candidateIndex]
308
+ if (!rec) {
309
+ throw new Error(
310
+ `${task.taskId}: selected receipt #${winner.candidateIndex} has no recorded candidate (${scored.length} scored)`,
311
+ )
312
+ }
313
+
314
+ const hidden = await runHiddenJudge(task, rec.candidate)
315
+
316
+ spend.cells += 1
317
+ spend.llmCalls += result.completions
318
+ spend.tokensIn += result.tokens.input
319
+ spend.tokensOut += result.tokens.output
320
+ spend.usd += result.usd
321
+ spend.hiddenPass += hidden.pass
322
+
323
+ const artifact: CellArtifact = {
324
+ taskId: task.taskId,
325
+ policy: serializeRolloutPolicy(policy),
326
+ pass: hidden.pass,
327
+ ...(hidden.detail ? { detail: hidden.detail } : {}),
328
+ repairStop: result.repairStop,
329
+ shots: result.shots,
330
+ completions: result.completions,
331
+ authoredChecks: result.authoredChecks,
332
+ tokens: result.tokens,
333
+ usd: result.usd,
334
+ ms: result.ms,
335
+ }
336
+ appendFileSync(
337
+ join(RUN_DIR, 'cells.jsonl'),
338
+ `${JSON.stringify({ cellId: ctx.cellId, generation: ctx.generation ?? null, ...artifact, detail: undefined })}\n`,
339
+ )
340
+ console.log(
341
+ ` [cell ${String(spend.cells).padStart(3)}] ${task.taskId.padEnd(14)} ${artifact.policy.padEnd(38)} hidden=${hidden.pass ? 'PASS' : 'fail'} ${result.repairStop} calls=${result.completions}`,
342
+ )
343
+ return artifact
344
+ }
345
+
346
+ // The in-loop judge is a deterministic transcriber of the script-side hidden grade —
347
+ // the grading itself never runs inside the strategy or the proposer's view.
348
+ const hiddenJudge: JudgeConfig<CellArtifact, HevScenario> = {
349
+ name: 'hidden-nonce-judge',
350
+ dimensions: [
351
+ { key: 'hidden', description: 'HumanEval hidden check() suite (docker --network=none, nonce sentinel)' },
352
+ ],
353
+ score: ({ artifact }) => ({
354
+ dimensions: { hidden: artifact.pass },
355
+ composite: artifact.pass,
356
+ notes: artifact.pass ? 'hidden PASS' : `hidden fail: ${(artifact.detail ?? '').slice(0, 160)}`,
357
+ }),
358
+ }
359
+
360
+ // ── Reporting helpers (read the library's own result objects; never re-decide) ───────
361
+
362
+ interface CampaignLike {
363
+ cells: Array<{ error?: string | null; judgeScores: Record<string, { composite: number }> }>
364
+ }
365
+ function passStats(campaign: CampaignLike): { passed: number; scored: number; errored: number; rate: number } {
366
+ let passed = 0
367
+ let scored = 0
368
+ let errored = 0
369
+ for (const cell of campaign.cells) {
370
+ if (cell.error) {
371
+ errored += 1
372
+ continue
373
+ }
374
+ scored += 1
375
+ const scores = Object.values(cell.judgeScores)
376
+ const composite = scores.length === 0 ? 0 : scores.reduce((s, j) => s + j.composite, 0) / scores.length
377
+ if (composite >= 0.999) passed += 1
378
+ }
379
+ return { passed, scored, errored, rate: scored > 0 ? passed / scored : 0 }
380
+ }
381
+ const pct = (x: number) => `${(100 * x).toFixed(1)}%`
382
+
383
+ async function main(): Promise<void> {
384
+ must('TOGETHER_API_KEY')
385
+ mkdirSync(RUN_DIR, { recursive: true })
386
+ const started = Date.now()
387
+
388
+ const all = await loadHumanEval(DEV_N + HOLD_N, 0)
389
+ if (all.length !== DEV_N + HOLD_N) {
390
+ throw new Error(`expected ${DEV_N + HOLD_N} tasks, loaded ${all.length}`)
391
+ }
392
+ const devTasks = all.slice(0, DEV_N)
393
+ const holdTasks = all.slice(DEV_N)
394
+ for (const t of all) taskById.set(t.taskId, t)
395
+
396
+ const toScenario = (t: HumanEvalTask): HevScenario => ({ id: t.taskId, kind: 'humaneval' })
397
+ const scenarios = all.map(toScenario)
398
+ const holdoutScenarios = holdTasks.map(toScenario)
399
+
400
+ const baselinePolicy = { k: 5, repairRounds: 2, testgen: 6 }
401
+ const profile: AgentProfile = {
402
+ name: 'humaneval-structural-worker',
403
+ extensions: { [ROLLOUT_POLICY_EXTENSION]: baselinePolicy },
404
+ }
405
+
406
+ console.log('=== LIVE self-improvement campaign · improve() surface rollout-policy ===')
407
+ console.log(` worker: ${MODEL} @ ${BASE} (temp=${TEMP}, maxTokens=${MAX_TOKENS}, innerTurns=2)`)
408
+ console.log(
409
+ ` DEV slice : HumanEval index [0, ${DEV_N}) — ${devTasks[0]?.taskId} .. ${devTasks[devTasks.length - 1]?.taskId} (n=${devTasks.length})`,
410
+ )
411
+ console.log(
412
+ ` HELD-OUT slice : HumanEval index [${DEV_N}, ${DEV_N + HOLD_N}) — ${holdTasks[0]?.taskId} .. ${holdTasks[holdTasks.length - 1]?.taskId} (n=${holdTasks.length})`,
413
+ )
414
+ console.log(` baseline policy: ${JSON.stringify(baselinePolicy)}`)
415
+ console.log(
416
+ ` budget: generations=${GENERATIONS} population<=${POPULATION} reps=1 concurrency=${CONCURRENCY} docker<=${DOCKER_CONCURRENCY} ceiling=$${DOLLARS}`,
417
+ )
418
+ console.log(` gate: library defaultProductionGate (paired bootstrap on held-out, ship iff CI.low > 0.05)`)
419
+ console.log(` runDir: ${RUN_DIR}`)
420
+ console.log(`\n profile BEFORE: ${JSON.stringify(profile)}\n`)
421
+
422
+ const result = await improve<HevScenario, CellArtifact>(profile, [], {
423
+ surface: 'rollout-policy',
424
+ scenarios,
425
+ judge: hiddenJudge,
426
+ agent: evaluateCell,
427
+ budget: {
428
+ generations: GENERATIONS,
429
+ populationSize: POPULATION,
430
+ maxConcurrency: CONCURRENCY,
431
+ holdoutScenarios,
432
+ reps: 1,
433
+ dollars: DOLLARS,
434
+ },
435
+ runDir: RUN_DIR,
436
+ // Deterministic proposer, empty findings channel: the improver's context is ONLY
437
+ // the DEV composites the loop accumulates — no distilled failure text, no trace
438
+ // paths, and (by the loop's own structure) never a held-out cell.
439
+ analyzeGeneration: null,
440
+ })
441
+
442
+ const loop = result.raw.raw
443
+ const wallMin = (Date.now() - started) / 60000
444
+
445
+ console.log('\n── DEV (train) results — what the improver saw ──')
446
+ const baseDev = passStats(loop.baselineCampaign)
447
+ console.log(
448
+ ` gen -1 baseline ${serializeRolloutPolicy(structuralRolloutPolicyFromProfile(profile)!).padEnd(38)} DEV ${baseDev.passed}/${baseDev.scored} = ${pct(baseDev.rate)} (errored ${baseDev.errored})`,
449
+ )
450
+ for (const gen of loop.generations) {
451
+ const surfaceByHash = new Map(gen.surfaces.map((s) => [s.surfaceHash, s]))
452
+ const promotedHashes = new Set(gen.record.promoted)
453
+ for (const cand of gen.record.candidates) {
454
+ const s = surfaceByHash.get(cand.surfaceHash)
455
+ const stats = s ? passStats(s.campaign) : undefined
456
+ console.log(
457
+ ` gen ${String(gen.record.generationIndex).padStart(2)} ${String(cand.label ?? '').padEnd(16)} ${String(s?.surface ?? '?').padEnd(38)} DEV ${stats ? `${stats.passed}/${stats.scored} = ${pct(stats.rate)} (errored ${stats.errored})` : '?'}${promotedHashes.has(cand.surfaceHash) ? ' [promoted]' : ''}`,
458
+ )
459
+ }
460
+ }
461
+ console.log(` training winner: ${String(loop.winnerSurface)}${loop.winnerLabel ? ` (${loop.winnerLabel})` : ''}`)
462
+
463
+ console.log('\n── HELD-OUT gate — the library decides ──')
464
+ const baseHold = passStats(loop.baselineOnHoldout)
465
+ const winHold = passStats(loop.winnerOnHoldout)
466
+ console.log(
467
+ ` baseline on held-out : ${baseHold.passed}/${baseHold.scored} = ${pct(baseHold.rate)} (errored ${baseHold.errored})`,
468
+ )
469
+ console.log(
470
+ ` winner on held-out : ${winHold.passed}/${winHold.scored} = ${pct(winHold.rate)} (errored ${winHold.errored})`,
471
+ )
472
+ console.log(` gate decision: ${result.gateDecision.toUpperCase()} (lift ${result.lift >= 0 ? '+' : ''}${result.lift.toFixed(3)})`)
473
+ for (const reason of loop.gateResult.reasons) console.log(` reason: ${reason}`)
474
+ for (const g of loop.gateResult.contributingGates) {
475
+ console.log(` gate[${g.name}] passed=${g.passed} detail=${JSON.stringify(g.detail).slice(0, 300)}`)
476
+ }
477
+
478
+ console.log('\n── ship/hold outcome ──')
479
+ console.log(` shipped: ${result.shipped}`)
480
+ console.log(` profile AFTER : ${JSON.stringify(result.profile)}`)
481
+ if (result.shipped) {
482
+ console.log(` policy change : ${serializeRolloutPolicy(structuralRolloutPolicyFromProfile(profile)!)} → ${serializeRolloutPolicy(structuralRolloutPolicyFromProfile(result.profile)!)}`)
483
+ } else {
484
+ console.log(' policy change : none (gate held — baseline policy stays)')
485
+ }
486
+
487
+ console.log('\n── spend / provenance ──')
488
+ console.log(
489
+ ` cells ${spend.cells} · llm calls ${spend.llmCalls} · tokens ${spend.tokensIn} in / ${spend.tokensOut} out · router-priced $${spend.usd.toFixed(4)} · loop-reported $${result.raw.totalCostUsd.toFixed(4)}`,
490
+ )
491
+ console.log(` wall ${wallMin.toFixed(1)} min · runDir ${RUN_DIR} (cells.jsonl + campaign cells + loop provenance)`)
492
+ reapContainers()
493
+ process.exit(0)
494
+ }
495
+
496
+ main().catch((e) => {
497
+ console.error(e)
498
+ reapContainers()
499
+ process.exit(1)
500
+ })
@@ -29,7 +29,8 @@ import { execFile, execFileSync } from 'node:child_process'
29
29
  import { randomBytes } from 'node:crypto'
30
30
  import { appendFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
31
31
  import { tmpdir } from 'node:os'
32
- import { join } from 'node:path'
32
+ import { join, resolve } from 'node:path'
33
+ import { fileURLToPath } from 'node:url'
33
34
  import { extractCode } from './benchmarks/humaneval'
34
35
  import { type PairedLift, pairedLift, pool } from './stats.mts'
35
36
 
@@ -107,7 +108,7 @@ export function loadMbpp(limit: number, offset = 0): { tasks: MbppTask[]; droppe
107
108
  const solveInstruction =
108
109
  'Write a Python function for the following task. Output the COMPLETE function definition (plus any imports it needs) inside a single ```python code block. Do not write tests or example calls.'
109
110
 
110
- function basePrompt(task: MbppTask): string {
111
+ export function basePrompt(task: MbppTask): string {
111
112
  return `${solveInstruction}\n\nTask: ${task.description}\nYour function must satisfy this example test:\n\`\`\`python\n${task.shownAssert}\n\`\`\``
112
113
  }
113
114
 
@@ -655,8 +656,12 @@ async function main(): Promise<void> {
655
656
  console.log(`\n VERDICT: ${verdict('full harness', rep)}; ${verdict('selection alone', sel)}`)
656
657
  }
657
658
 
658
- main().catch((e) => {
659
- reapContainers()
660
- console.error(`mbpp-structural: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`)
661
- process.exit(1)
662
- })
659
+ // Entrypoint guard (corpus-replay.mts precedent): campaign scripts import this module
660
+ // for loadMbpp/basePrompt; the benchmark itself only runs when executed directly.
661
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
662
+ main().catch((e) => {
663
+ reapContainers()
664
+ console.error(`mbpp-structural: ${e instanceof Error ? (e.stack ?? e.message) : String(e)}`)
665
+ process.exit(1)
666
+ })
667
+ }
@@ -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)))