world-model-optimizer 0.2.0__py3-none-any.whl

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 (308) hide show
  1. llm_waterfall/LICENSE +21 -0
  2. llm_waterfall/__init__.py +53 -0
  3. llm_waterfall/adapters/__init__.py +36 -0
  4. llm_waterfall/adapters/anthropic.py +105 -0
  5. llm_waterfall/adapters/aws_mantle.py +47 -0
  6. llm_waterfall/adapters/azure_openai.py +71 -0
  7. llm_waterfall/adapters/base.py +51 -0
  8. llm_waterfall/adapters/bedrock.py +309 -0
  9. llm_waterfall/adapters/openai.py +130 -0
  10. llm_waterfall/classify.py +184 -0
  11. llm_waterfall/pricing.py +110 -0
  12. llm_waterfall/py.typed +0 -0
  13. llm_waterfall/types.py +295 -0
  14. llm_waterfall/waterfall.py +255 -0
  15. wmo/__init__.py +38 -0
  16. wmo/agents/__init__.py +7 -0
  17. wmo/agents/default.py +29 -0
  18. wmo/agents/meta.py +55 -0
  19. wmo/agents/optimizer.py +55 -0
  20. wmo/agents/project.py +928 -0
  21. wmo/cli/__init__.py +5 -0
  22. wmo/cli/agent_session.py +1123 -0
  23. wmo/cli/app.py +2489 -0
  24. wmo/cli/e2b_cmds.py +212 -0
  25. wmo/cli/eval_closed_loop.py +207 -0
  26. wmo/cli/harness_app.py +1147 -0
  27. wmo/cli/harness_distill.py +659 -0
  28. wmo/cli/hosted_session.py +880 -0
  29. wmo/cli/ingest_cmd.py +165 -0
  30. wmo/cli/model_roles.py +82 -0
  31. wmo/cli/platform_cmds.py +372 -0
  32. wmo/cli/route_app.py +274 -0
  33. wmo/cli/session_state.py +243 -0
  34. wmo/cli/ui.py +1107 -0
  35. wmo/cli/workspace_sync.py +504 -0
  36. wmo/config/__init__.py +60 -0
  37. wmo/config/card.py +129 -0
  38. wmo/config/config.py +367 -0
  39. wmo/config/dotenv.py +67 -0
  40. wmo/config/settings.py +128 -0
  41. wmo/config/store.py +177 -0
  42. wmo/conftest.py +19 -0
  43. wmo/connect/__init__.py +88 -0
  44. wmo/connect/apps.py +78 -0
  45. wmo/connect/brave.py +284 -0
  46. wmo/connect/connector.py +79 -0
  47. wmo/connect/credentials.py +164 -0
  48. wmo/connect/github.py +321 -0
  49. wmo/connect/google.py +627 -0
  50. wmo/connect/notion.py +790 -0
  51. wmo/connect/oauth.py +461 -0
  52. wmo/connect/slack.py +555 -0
  53. wmo/connect/store.py +199 -0
  54. wmo/connect/types.py +156 -0
  55. wmo/core/__init__.py +21 -0
  56. wmo/core/parsing.py +281 -0
  57. wmo/core/render.py +271 -0
  58. wmo/core/text.py +40 -0
  59. wmo/core/types.py +116 -0
  60. wmo/distill/__init__.py +14 -0
  61. wmo/distill/agents.py +140 -0
  62. wmo/distill/config.py +1006 -0
  63. wmo/distill/cost.py +437 -0
  64. wmo/distill/data.py +921 -0
  65. wmo/distill/deadlines.py +254 -0
  66. wmo/distill/fake_tinker.py +734 -0
  67. wmo/distill/gate.py +122 -0
  68. wmo/distill/loop.py +3499 -0
  69. wmo/distill/renderers.py +399 -0
  70. wmo/distill/rendering.py +620 -0
  71. wmo/distill/rollouts.py +726 -0
  72. wmo/distill/samples.py +195 -0
  73. wmo/distill/store.py +829 -0
  74. wmo/distill/teacher.py +714 -0
  75. wmo/distill/tokens.py +535 -0
  76. wmo/distill/tracking.py +552 -0
  77. wmo/distill/tripwire.py +411 -0
  78. wmo/distill/xtoken/byte_offsets.py +152 -0
  79. wmo/distill/xtoken/chunks.py +457 -0
  80. wmo/distill/xtoken/prompt_logprobs.py +475 -0
  81. wmo/distill/xtoken/teacher_render.py +346 -0
  82. wmo/engine/__init__.py +28 -0
  83. wmo/engine/autoconfig.py +367 -0
  84. wmo/engine/build.py +346 -0
  85. wmo/engine/demo.py +77 -0
  86. wmo/engine/eval_suites.py +245 -0
  87. wmo/engine/grounding.py +491 -0
  88. wmo/engine/knowledge.py +291 -0
  89. wmo/engine/loader.py +36 -0
  90. wmo/engine/play.py +92 -0
  91. wmo/engine/prompts.py +99 -0
  92. wmo/engine/replay.py +443 -0
  93. wmo/engine/reporting.py +58 -0
  94. wmo/engine/workspace.py +468 -0
  95. wmo/engine/world_model.py +568 -0
  96. wmo/env/__init__.py +22 -0
  97. wmo/env/base.py +121 -0
  98. wmo/env/closed_loop.py +229 -0
  99. wmo/env/episode.py +107 -0
  100. wmo/env/llm_agent.py +93 -0
  101. wmo/env/scenarios.py +73 -0
  102. wmo/evals/__init__.py +52 -0
  103. wmo/evals/agreement.py +110 -0
  104. wmo/evals/base.py +45 -0
  105. wmo/evals/closed_loop.py +480 -0
  106. wmo/evals/failover.py +96 -0
  107. wmo/evals/gold.py +127 -0
  108. wmo/evals/grid.py +394 -0
  109. wmo/evals/grid_plot.py +205 -0
  110. wmo/evals/harbor/__init__.py +27 -0
  111. wmo/evals/harbor/agent.py +573 -0
  112. wmo/evals/harbor/ctrf.py +171 -0
  113. wmo/evals/harbor/e2b_environment.py +587 -0
  114. wmo/evals/harbor/e2b_template_policy.py +144 -0
  115. wmo/evals/harbor/scorer.py +875 -0
  116. wmo/evals/harbor/tasks.py +140 -0
  117. wmo/evals/open_loop.py +194 -0
  118. wmo/evals/tasks.py +53 -0
  119. wmo/harness/__init__.py +51 -0
  120. wmo/harness/code_runtime.py +288 -0
  121. wmo/harness/create.py +1191 -0
  122. wmo/harness/delta.py +220 -0
  123. wmo/harness/doc.py +556 -0
  124. wmo/harness/e2b_ledger.py +342 -0
  125. wmo/harness/e2b_reap.py +476 -0
  126. wmo/harness/e2b_sandbox.py +350 -0
  127. wmo/harness/environment.py +35 -0
  128. wmo/harness/live_session.py +543 -0
  129. wmo/harness/mutate.py +343 -0
  130. wmo/harness/pi_e2b.py +1710 -0
  131. wmo/harness/pi_entry/entry.ts +268 -0
  132. wmo/harness/pi_entry/runner_frames.ts +92 -0
  133. wmo/harness/pi_entry/runner_live.ts +587 -0
  134. wmo/harness/pi_entry/runner_service.ts +270 -0
  135. wmo/harness/pi_entry/runner_stdio.ts +374 -0
  136. wmo/harness/pi_entry/runner_termination.ts +142 -0
  137. wmo/harness/pi_local.py +262 -0
  138. wmo/harness/pi_runtime.py +495 -0
  139. wmo/harness/pi_vendor.py +65 -0
  140. wmo/harness/population.py +509 -0
  141. wmo/harness/project_proposer.py +569 -0
  142. wmo/harness/proposer.py +977 -0
  143. wmo/harness/runner_link.py +619 -0
  144. wmo/harness/runtime.py +389 -0
  145. wmo/harness/scoring.py +247 -0
  146. wmo/harness/skills.py +116 -0
  147. wmo/harness/source_tree.py +319 -0
  148. wmo/harness/store.py +176 -0
  149. wmo/harness/tools.py +105 -0
  150. wmo/harness/vendor/manifest.sha256 +58 -0
  151. wmo/harness/vendor/pi-agent/CHANGELOG.md +556 -0
  152. wmo/harness/vendor/pi-agent/LICENSE +21 -0
  153. wmo/harness/vendor/pi-agent/README.md +488 -0
  154. wmo/harness/vendor/pi-agent/VENDOR.md +39 -0
  155. wmo/harness/vendor/pi-agent/docs/agent-harness.md +486 -0
  156. wmo/harness/vendor/pi-agent/docs/durable-harness.md +212 -0
  157. wmo/harness/vendor/pi-agent/docs/hooks.md +445 -0
  158. wmo/harness/vendor/pi-agent/docs/models.md +966 -0
  159. wmo/harness/vendor/pi-agent/docs/observability.md +376 -0
  160. wmo/harness/vendor/pi-agent/package.json +60 -0
  161. wmo/harness/vendor/pi-agent/src/agent-loop.ts +748 -0
  162. wmo/harness/vendor/pi-agent/src/agent.ts +575 -0
  163. wmo/harness/vendor/pi-agent/src/harness/agent-harness.ts +1029 -0
  164. wmo/harness/vendor/pi-agent/src/harness/compaction/branch-summarization.ts +261 -0
  165. wmo/harness/vendor/pi-agent/src/harness/compaction/compaction.ts +747 -0
  166. wmo/harness/vendor/pi-agent/src/harness/compaction/utils.ts +144 -0
  167. wmo/harness/vendor/pi-agent/src/harness/env/nodejs.ts +550 -0
  168. wmo/harness/vendor/pi-agent/src/harness/messages.ts +164 -0
  169. wmo/harness/vendor/pi-agent/src/harness/prompt-templates.ts +267 -0
  170. wmo/harness/vendor/pi-agent/src/harness/session/jsonl-repo.ts +177 -0
  171. wmo/harness/vendor/pi-agent/src/harness/session/jsonl-storage.ts +293 -0
  172. wmo/harness/vendor/pi-agent/src/harness/session/memory-repo.ts +50 -0
  173. wmo/harness/vendor/pi-agent/src/harness/session/memory-storage.ts +131 -0
  174. wmo/harness/vendor/pi-agent/src/harness/session/repo-utils.ts +51 -0
  175. wmo/harness/vendor/pi-agent/src/harness/session/session.ts +267 -0
  176. wmo/harness/vendor/pi-agent/src/harness/session/uuid.ts +54 -0
  177. wmo/harness/vendor/pi-agent/src/harness/skills.ts +375 -0
  178. wmo/harness/vendor/pi-agent/src/harness/system-prompt.ts +34 -0
  179. wmo/harness/vendor/pi-agent/src/harness/types.ts +836 -0
  180. wmo/harness/vendor/pi-agent/src/harness/utils/shell-output.ts +135 -0
  181. wmo/harness/vendor/pi-agent/src/harness/utils/truncate.ts +344 -0
  182. wmo/harness/vendor/pi-agent/src/index.ts +44 -0
  183. wmo/harness/vendor/pi-agent/src/node.ts +2 -0
  184. wmo/harness/vendor/pi-agent/src/proxy.ts +367 -0
  185. wmo/harness/vendor/pi-agent/src/types.ts +428 -0
  186. wmo/harness/vendor/pi-agent/test/agent-loop.test.ts +1351 -0
  187. wmo/harness/vendor/pi-agent/test/agent.test.ts +699 -0
  188. wmo/harness/vendor/pi-agent/test/e2e.test.ts +404 -0
  189. wmo/harness/vendor/pi-agent/test/harness/agent-harness-stream.test.ts +213 -0
  190. wmo/harness/vendor/pi-agent/test/harness/agent-harness.test.ts +608 -0
  191. wmo/harness/vendor/pi-agent/test/harness/compaction.test.ts +655 -0
  192. wmo/harness/vendor/pi-agent/test/harness/nodejs-env.test.ts +321 -0
  193. wmo/harness/vendor/pi-agent/test/harness/prompt-templates.test.ts +90 -0
  194. wmo/harness/vendor/pi-agent/test/harness/repo.test.ts +68 -0
  195. wmo/harness/vendor/pi-agent/test/harness/resource-formatting.test.ts +24 -0
  196. wmo/harness/vendor/pi-agent/test/harness/session-test-utils.ts +55 -0
  197. wmo/harness/vendor/pi-agent/test/harness/session-uuid.test.ts +50 -0
  198. wmo/harness/vendor/pi-agent/test/harness/session.test.ts +156 -0
  199. wmo/harness/vendor/pi-agent/test/harness/skills.test.ts +116 -0
  200. wmo/harness/vendor/pi-agent/test/harness/storage.test.ts +299 -0
  201. wmo/harness/vendor/pi-agent/test/harness/system-prompt.test.ts +66 -0
  202. wmo/harness/vendor/pi-agent/test/harness/truncate.test.ts +169 -0
  203. wmo/harness/vendor/pi-agent/test/scratch/simple.ts +72 -0
  204. wmo/harness/vendor/pi-agent/test/utils/calculate.ts +32 -0
  205. wmo/harness/vendor/pi-agent/test/utils/get-current-time.ts +46 -0
  206. wmo/harness/vendor/pi-agent/tsconfig.build.json +13 -0
  207. wmo/harness/vendor/pi-agent/vitest.config.ts +19 -0
  208. wmo/harness/vendor/pi-agent/vitest.harness.config.ts +28 -0
  209. wmo/harness/vendor/vendor_pi.sh +59 -0
  210. wmo/harness/workspace_patch.py +270 -0
  211. wmo/ingest/__init__.py +47 -0
  212. wmo/ingest/adapter.py +72 -0
  213. wmo/ingest/base.py +114 -0
  214. wmo/ingest/braintrust.py +339 -0
  215. wmo/ingest/detect.py +126 -0
  216. wmo/ingest/langfuse.py +291 -0
  217. wmo/ingest/langsmith.py +444 -0
  218. wmo/ingest/mastra.py +330 -0
  219. wmo/ingest/messages.py +170 -0
  220. wmo/ingest/normalize.py +679 -0
  221. wmo/ingest/otel_genai.py +69 -0
  222. wmo/ingest/otel_writer.py +100 -0
  223. wmo/ingest/phoenix.py +150 -0
  224. wmo/ingest/postgres.py +246 -0
  225. wmo/ingest/posthog.py +320 -0
  226. wmo/ingest/quality.py +28 -0
  227. wmo/ingest/stream.py +209 -0
  228. wmo/ingest/testdata/sample_otlp.json +60 -0
  229. wmo/ingest/testdata/sample_spans.jsonl +3 -0
  230. wmo/optimize/__init__.py +25 -0
  231. wmo/optimize/base.py +143 -0
  232. wmo/optimize/gepa.py +806 -0
  233. wmo/optimize/judge.py +262 -0
  234. wmo/optimize/judge_quality.py +359 -0
  235. wmo/optimize/knn.py +468 -0
  236. wmo/optimize/numeric.py +152 -0
  237. wmo/optimize/outcomes.py +103 -0
  238. wmo/optimize/policy.py +669 -0
  239. wmo/optimize/report.py +231 -0
  240. wmo/optimize/reward.py +129 -0
  241. wmo/optimize/routing.py +373 -0
  242. wmo/platform/__init__.py +6 -0
  243. wmo/platform/auth.py +115 -0
  244. wmo/platform/client.py +551 -0
  245. wmo/platform/credentials.py +126 -0
  246. wmo/platform/transfer.py +158 -0
  247. wmo/providers/__init__.py +40 -0
  248. wmo/providers/_bedrock_chat.py +155 -0
  249. wmo/providers/_openai_common.py +182 -0
  250. wmo/providers/_responses_common.py +472 -0
  251. wmo/providers/anthropic.py +134 -0
  252. wmo/providers/azure_openai.py +296 -0
  253. wmo/providers/base.py +300 -0
  254. wmo/providers/bedrock.py +312 -0
  255. wmo/providers/models.py +205 -0
  256. wmo/providers/openai.py +143 -0
  257. wmo/providers/openai_responses.py +240 -0
  258. wmo/providers/pool.py +170 -0
  259. wmo/providers/registry.py +73 -0
  260. wmo/providers/retry.py +151 -0
  261. wmo/providers/tinker.py +936 -0
  262. wmo/providers/waterfall.py +336 -0
  263. wmo/research/__init__.py +81 -0
  264. wmo/research/ablation.py +133 -0
  265. wmo/research/concurrency_plot.py +523 -0
  266. wmo/research/concurrency_run.py +240 -0
  267. wmo/research/concurrency_scaling.py +270 -0
  268. wmo/research/gepa_scaling.py +274 -0
  269. wmo/research/pipeline.py +198 -0
  270. wmo/research/scaling_split.py +82 -0
  271. wmo/research/scenario_fidelity.py +198 -0
  272. wmo/research/scenario_recovery.py +92 -0
  273. wmo/research/seed_stability.py +90 -0
  274. wmo/research/trace_scaling.py +348 -0
  275. wmo/retrieval/__init__.py +6 -0
  276. wmo/retrieval/embedders.py +105 -0
  277. wmo/retrieval/leakfree.py +52 -0
  278. wmo/retrieval/retriever.py +173 -0
  279. wmo/scenarios/__init__.py +58 -0
  280. wmo/scenarios/builder.py +152 -0
  281. wmo/scenarios/mining/__init__.py +27 -0
  282. wmo/scenarios/mining/clustering.py +171 -0
  283. wmo/scenarios/mining/facets.py +226 -0
  284. wmo/scenarios/mining/selection.py +220 -0
  285. wmo/scenarios/synthesis/__init__.py +6 -0
  286. wmo/scenarios/synthesis/scenario_set.py +63 -0
  287. wmo/scenarios/synthesis/synthesizer.py +85 -0
  288. wmo/scenarios/verification/__init__.py +17 -0
  289. wmo/scenarios/verification/judge.py +97 -0
  290. wmo/scenarios/verification/verify.py +135 -0
  291. wmo/serving/__init__.py +5 -0
  292. wmo/serving/builds.py +451 -0
  293. wmo/serving/chat.py +878 -0
  294. wmo/serving/endpoint_config.py +64 -0
  295. wmo/serving/savings.py +250 -0
  296. wmo/serving/server.py +553 -0
  297. wmo/serving/traces_source.py +206 -0
  298. wmo/telemetry.py +213 -0
  299. wmo/tracking/__init__.py +36 -0
  300. wmo/tracking/clock.py +24 -0
  301. wmo/tracking/metered.py +125 -0
  302. wmo/tracking/pricing.py +99 -0
  303. wmo/tracking/store.py +31 -0
  304. wmo/tracking/tracker.py +149 -0
  305. world_model_optimizer-0.2.0.dist-info/METADATA +203 -0
  306. world_model_optimizer-0.2.0.dist-info/RECORD +308 -0
  307. world_model_optimizer-0.2.0.dist-info/WHEEL +4 -0
  308. world_model_optimizer-0.2.0.dist-info/entry_points.txt +2 -0
wmo/evals/grid_plot.py ADDED
@@ -0,0 +1,205 @@
1
+ """Render a `GridResult` as the World-Model Optimizer fidelity bar chart.
2
+
3
+ One vertical bar per (model x condition) cell, sorted ASCENDING by fidelity left->right, each
4
+ labeled with its fidelity and (when priced) its target-side cost. Matplotlib/seaborn live behind
5
+ the `viz` extra, so this module imports them lazily inside the function - the only sanctioned lazy
6
+ import here (the engine must import without the plotting deps installed).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ from wmo.evals.grid import CONDITIONS, GridResult
14
+
15
+ _TITLE = "World-Model Optimizer Fidelity"
16
+
17
+ # Brand palette (AGENTS.md rule 15) - no ad-hoc colors. Ink for text/lines, a light gridline, and
18
+ # one brand hue per condition so a bar's color reads its condition regardless of sorted position.
19
+ _INK = "#0a0a0a"
20
+ _GRIDLINE = "#ececec"
21
+ _BRAND_BY_CONDITION = {
22
+ "base": _INK, # baseline is neutral; the wmo conditions carry the brand hues
23
+ "base_rag": "#0070f3", # blue
24
+ "gepa": "#7928ca", # purple
25
+ "gepa_rag": "#f5a623", # amber
26
+ }
27
+
28
+
29
+ def plot_grid(
30
+ result: GridResult,
31
+ out_path: str | Path,
32
+ *,
33
+ dataset_label: str,
34
+ n_test_traces: int,
35
+ ) -> Path:
36
+ """Write the fidelity barplot for `result` to `out_path` (PNG). Returns the path.
37
+
38
+ `dataset_label`/`n_test_traces` populate the subtitle, e.g.
39
+ "armand0e/qwen3.7-max-pi-traces | 8 held-out test traces | 225 judged steps".
40
+ """
41
+ import matplotlib
42
+
43
+ matplotlib.use("Agg") # headless: write a file, never open a window
44
+ import matplotlib.pyplot as plt
45
+ import seaborn as sns
46
+
47
+ cells = sorted(result.cells, key=lambda c: c.fidelity) # ascending performance, left -> right
48
+ if not cells:
49
+ raise ValueError("grid result has no cells to plot")
50
+ labels = [c.bar_label for c in cells]
51
+ heights = [c.fidelity for c in cells]
52
+ colors = [_BRAND_BY_CONDITION.get(c.condition, _INK) for c in cells]
53
+
54
+ sns.set_theme(style="whitegrid", context="talk")
55
+ fig, ax = plt.subplots(figsize=(max(8, 1.6 * len(cells)), 6.5))
56
+ ax.grid(axis="y", color=_GRIDLINE)
57
+ ax.set_axisbelow(True)
58
+ bars = ax.bar(range(len(cells)), heights, color=colors, edgecolor="white", linewidth=0.8)
59
+
60
+ # Per-bar text: fidelity on top, target cost above it (omit the $ line when cost is None).
61
+ for bar, cell in zip(bars, cells, strict=True):
62
+ x = bar.get_x() + bar.get_width() / 2
63
+ top = bar.get_height()
64
+ ax.text(x, top + 0.012, f"{cell.fidelity:.3f}", ha="center", va="bottom", fontsize=11)
65
+ if cell.cost_usd is not None and cell.cost_usd > 0:
66
+ ax.text(
67
+ x,
68
+ top + 0.052,
69
+ f"${cell.cost_usd:.2f}",
70
+ ha="center",
71
+ va="bottom",
72
+ fontsize=10,
73
+ color=_INK,
74
+ )
75
+
76
+ # One legend entry per condition present, in canonical order, using the same brand hues.
77
+ seen = {c.condition: c.condition_label for c in cells}
78
+ handles = [
79
+ plt.Rectangle((0, 0), 1, 1, color=_BRAND_BY_CONDITION.get(cond, _INK))
80
+ for cond in CONDITIONS
81
+ if cond in seen
82
+ ]
83
+ ax.legend(
84
+ handles,
85
+ [seen[cond] for cond in CONDITIONS if cond in seen],
86
+ title="condition",
87
+ frameon=False,
88
+ fontsize=10,
89
+ title_fontsize=10,
90
+ loc="upper left",
91
+ )
92
+
93
+ ax.set_xticks(range(len(cells)))
94
+ ax.set_xticklabels(labels, fontsize=10)
95
+ ax.set_ylabel("Mean fidelity")
96
+ # Headroom above the tallest bar so the fidelity + cost labels never collide with the subtitle
97
+ # (near-ceiling bars at ~0.97 would otherwise push the $ label past y=1.0).
98
+ ax.set_ylim(0, max(1.0, max(heights) + 0.14))
99
+ ax.set_title(_TITLE, fontsize=17, fontweight="bold", pad=28)
100
+ subtitle = (
101
+ f"{dataset_label} | {n_test_traces} held-out test traces | "
102
+ f"{result.total_test_steps} judged steps | judge {result.judge_version}"
103
+ )
104
+ ax.text(
105
+ 0.5,
106
+ 1.02,
107
+ subtitle,
108
+ transform=ax.transAxes,
109
+ ha="center",
110
+ va="bottom",
111
+ fontsize=12,
112
+ color=_INK,
113
+ alpha=0.7,
114
+ )
115
+ sns.despine(ax=ax)
116
+ fig.tight_layout()
117
+ out = Path(out_path)
118
+ out.parent.mkdir(parents=True, exist_ok=True)
119
+ fig.savefig(out, dpi=150, bbox_inches="tight")
120
+ plt.close(fig)
121
+ return out
122
+
123
+
124
+ def plot_grid_heatmap(
125
+ results: dict[str, GridResult],
126
+ out_path: str | Path,
127
+ *,
128
+ benchmark_order: list[str] | None = None,
129
+ ) -> Path:
130
+ """Render the whole grid as one heatmap: rows = model x condition, columns = benchmark.
131
+
132
+ `results` maps a benchmark label to its merged `GridResult` (all 5 models x 4 conditions). Each
133
+ cell is the mean fidelity, annotated and colored on a shared 0..1 scale so every benchmark reads
134
+ on the same footing. A model x condition with no cell for a benchmark is left blank (NaN).
135
+ Columns follow `benchmark_order` when given, else the dict's insertion order.
136
+ """
137
+ import matplotlib
138
+
139
+ matplotlib.use("Agg") # headless
140
+ import matplotlib.pyplot as plt
141
+ import numpy as np
142
+ import seaborn as sns
143
+ from matplotlib.colors import LinearSegmentedColormap
144
+
145
+ if not results:
146
+ raise ValueError("no grid results to plot")
147
+ # Sequential brand ramp (light gridline -> brand blue -> brand purple) so higher fidelity reads
148
+ # darker/brand-er; seaborn auto-picks light/dark annotation text per cell luminance.
149
+ brand_cmap = LinearSegmentedColormap.from_list("wmo-brand", [_GRIDLINE, "#0070f3", "#7928ca"])
150
+ benchmarks = benchmark_order or list(results)
151
+
152
+ # Row order: models in first-seen order, each followed by its four conditions.
153
+ models: list[str] = []
154
+ labels: dict[str, str] = {}
155
+ for res in results.values():
156
+ for cell in res.cells:
157
+ if cell.model_label not in models:
158
+ models.append(cell.model_label)
159
+ labels.setdefault(cell.condition, cell.condition_label)
160
+ rows = [(m, c) for m in models for c in CONDITIONS]
161
+
162
+ # (model, condition, benchmark) -> fidelity lookup.
163
+ fid: dict[tuple[str, str, str], float] = {}
164
+ for bench, res in results.items():
165
+ for cell in res.cells:
166
+ fid[(cell.model_label, cell.condition, bench)] = cell.fidelity
167
+
168
+ matrix = np.full((len(rows), len(benchmarks)), np.nan)
169
+ for r, (model, cond) in enumerate(rows):
170
+ for c, bench in enumerate(benchmarks):
171
+ if (model, cond, bench) in fid:
172
+ matrix[r, c] = fid[(model, cond, bench)]
173
+ row_labels = [f"{m} · {labels.get(c, c)}" for m, c in rows]
174
+
175
+ sns.set_theme(style="white", context="talk")
176
+ fig, ax = plt.subplots(figsize=(1.7 * len(benchmarks) + 4, 0.42 * len(rows) + 2.2))
177
+ sns.heatmap(
178
+ matrix,
179
+ ax=ax,
180
+ cmap=brand_cmap,
181
+ vmin=0.0,
182
+ vmax=1.0,
183
+ annot=True,
184
+ fmt=".2f",
185
+ annot_kws={"fontsize": 9},
186
+ linewidths=0.6,
187
+ linecolor="white",
188
+ cbar_kws={"label": "Mean fidelity", "shrink": 0.6},
189
+ xticklabels=benchmarks,
190
+ yticklabels=row_labels,
191
+ )
192
+ # Separator lines between models (every 4 conditions) so the model blocks read as groups.
193
+ for i in range(len(CONDITIONS), len(rows), len(CONDITIONS)):
194
+ ax.axhline(i, color=_INK, linewidth=1.4)
195
+ judge_versions = sorted({r.judge_version for r in results.values()})
196
+ judge_tag = judge_versions[0] if len(judge_versions) == 1 else "/".join(judge_versions)
197
+ ax.set_title(f"{_TITLE}: full grid (judge {judge_tag})", fontsize=17, fontweight="bold", pad=16)
198
+ ax.tick_params(axis="y", labelsize=9, rotation=0)
199
+ ax.tick_params(axis="x", labelsize=11)
200
+ fig.tight_layout()
201
+ out = Path(out_path)
202
+ out.parent.mkdir(parents=True, exist_ok=True)
203
+ fig.savefig(out, dpi=150, bbox_inches="tight")
204
+ plt.close(fig)
205
+ return out
@@ -0,0 +1,27 @@
1
+ """Ground-truth harness evaluation through Harbor (optional `harbor` extra).
2
+
3
+ This subpackage imports the `harbor` PyPI package at module scope and is therefore imported
4
+ lazily by its consumers, exactly like the e2b extra: `import wmo` (and `wmo.evals`) must succeed
5
+ without it. The E2B task-environment path additionally needs the e2b extra and is itself only
6
+ imported through harbor's environment factory (`wmo.evals.harbor.e2b_environment`).
7
+ """
8
+
9
+ from wmo.evals.harbor.agent import WmoHarborAgent
10
+ from wmo.evals.harbor.scorer import (
11
+ HarborJobRunner,
12
+ HarborRewardMissingError,
13
+ HarborRun,
14
+ HarborRunner,
15
+ HarborScorer,
16
+ )
17
+ from wmo.evals.harbor.tasks import resolve_harbor_tasks
18
+
19
+ __all__ = [
20
+ "HarborJobRunner",
21
+ "HarborRewardMissingError",
22
+ "HarborRun",
23
+ "HarborRunner",
24
+ "HarborScorer",
25
+ "WmoHarborAgent",
26
+ "resolve_harbor_tasks",
27
+ ]