open-interpretability 0.0.20__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 (256) hide show
  1. open_interpretability-0.0.20.dist-info/METADATA +157 -0
  2. open_interpretability-0.0.20.dist-info/RECORD +256 -0
  3. open_interpretability-0.0.20.dist-info/WHEEL +4 -0
  4. open_interpretability-0.0.20.dist-info/entry_points.txt +2 -0
  5. open_interpretability-0.0.20.dist-info/licenses/LICENSE +21 -0
  6. openinterp/__init__.py +6 -0
  7. openinterp/agents/__init__.py +25 -0
  8. openinterp/agents/base.py +229 -0
  9. openinterp/agents/judge.py +149 -0
  10. openinterp/agents/proposer.py +471 -0
  11. openinterp/agents/proposer_profiles.py +79 -0
  12. openinterp/agents/review_measurements.py +173 -0
  13. openinterp/agents/reviewer.py +498 -0
  14. openinterp/agreement.py +63 -0
  15. openinterp/audit_options.py +79 -0
  16. openinterp/baseline.py +254 -0
  17. openinterp/benchmark.py +450 -0
  18. openinterp/blends.py +239 -0
  19. openinterp/builder/__init__.py +6 -0
  20. openinterp/builder/agents.py +260 -0
  21. openinterp/builder/aligned.py +41 -0
  22. openinterp/builder/charts.py +411 -0
  23. openinterp/builder/circuit_pages.py +37 -0
  24. openinterp/builder/circuits.py +264 -0
  25. openinterp/builder/compiler.py +495 -0
  26. openinterp/builder/corpora.py +98 -0
  27. openinterp/builder/coverage.py +46 -0
  28. openinterp/builder/dashboard.py +255 -0
  29. openinterp/builder/disk_memo.py +65 -0
  30. openinterp/builder/experiment.py +105 -0
  31. openinterp/builder/heat.py +121 -0
  32. openinterp/builder/hrefs.py +71 -0
  33. openinterp/builder/learned_baseline.py +204 -0
  34. openinterp/builder/model_facts.py +138 -0
  35. openinterp/builder/neuronpedia_export.py +107 -0
  36. openinterp/builder/parallel.py +100 -0
  37. openinterp/builder/parquet_export.py +458 -0
  38. openinterp/builder/pieces.py +127 -0
  39. openinterp/builder/pii.py +211 -0
  40. openinterp/builder/registry_stats.py +144 -0
  41. openinterp/builder/research.py +109 -0
  42. openinterp/builder/sections.py +28 -0
  43. openinterp/builder/significance.py +103 -0
  44. openinterp/builder/sister_graph.py +180 -0
  45. openinterp/builder/standing.py +185 -0
  46. openinterp/builder/statuses.py +34 -0
  47. openinterp/builder/stopped.py +91 -0
  48. openinterp/builder/stops.py +30 -0
  49. openinterp/builder/superpositions.py +102 -0
  50. openinterp/builder/templates/_agent_role_cells.html +9 -0
  51. openinterp/builder/templates/_causal_chart.html +67 -0
  52. openinterp/builder/templates/_circuit.html +8 -0
  53. openinterp/builder/templates/_dashboard.html +133 -0
  54. openinterp/builder/templates/_experiment.html +500 -0
  55. openinterp/builder/templates/_macros.html +82 -0
  56. openinterp/builder/templates/_patchscope.html +33 -0
  57. openinterp/builder/templates/_prior_art.html +24 -0
  58. openinterp/builder/templates/_specificity.html +21 -0
  59. openinterp/builder/templates/_steering.html +34 -0
  60. openinterp/builder/templates/_superposition.html +62 -0
  61. openinterp/builder/templates/_vocab_chart.html +88 -0
  62. openinterp/builder/templates/_wiring.html +44 -0
  63. openinterp/builder/templates/about.html +95 -0
  64. openinterp/builder/templates/agent.html +69 -0
  65. openinterp/builder/templates/agents.html +80 -0
  66. openinterp/builder/templates/base.html +103 -0
  67. openinterp/builder/templates/circuit.html +64 -0
  68. openinterp/builder/templates/circuits.html +46 -0
  69. openinterp/builder/templates/experiment.html +21 -0
  70. openinterp/builder/templates/hub.html +16 -0
  71. openinterp/builder/templates/index.html +387 -0
  72. openinterp/builder/templates/layer.html +69 -0
  73. openinterp/builder/templates/learned-baseline.html +44 -0
  74. openinterp/builder/templates/model.html +151 -0
  75. openinterp/builder/templates/models.html +47 -0
  76. openinterp/builder/templates/neuron.html +234 -0
  77. openinterp/builder/templates/paper.html +39 -0
  78. openinterp/builder/templates/redirect.html +1 -0
  79. openinterp/builder/templates/research.html +31 -0
  80. openinterp/builder/templates/user.html +54 -0
  81. openinterp/builder/templates/users.html +39 -0
  82. openinterp/builder/templates/wiki/experiment.md +271 -0
  83. openinterp/builder/templates/wiki/index.md +32 -0
  84. openinterp/builder/templates/wiki/neuron.md +30 -0
  85. openinterp/builder/templates/wiki/stop.md +36 -0
  86. openinterp/builder/tree.py +470 -0
  87. openinterp/builder/users.py +142 -0
  88. openinterp/builder/wiki.py +427 -0
  89. openinterp/checks.py +429 -0
  90. openinterp/cli.py +494 -0
  91. openinterp/cli_baseline.py +332 -0
  92. openinterp/cli_benchmark.py +300 -0
  93. openinterp/cli_build.py +128 -0
  94. openinterp/cli_check.py +51 -0
  95. openinterp/cli_concepts.py +76 -0
  96. openinterp/cli_corpus.py +252 -0
  97. openinterp/cli_costs.py +257 -0
  98. openinterp/cli_coverage.py +76 -0
  99. openinterp/cli_crossref.py +86 -0
  100. openinterp/cli_errata.py +142 -0
  101. openinterp/cli_evidence.py +495 -0
  102. openinterp/cli_evidence_store.py +93 -0
  103. openinterp/cli_explain.py +101 -0
  104. openinterp/cli_loop.py +125 -0
  105. openinterp/cli_memory.py +28 -0
  106. openinterp/cli_neuronpedia.py +175 -0
  107. openinterp/cli_pause.py +45 -0
  108. openinterp/cli_pole_effects.py +133 -0
  109. openinterp/cli_probing.py +119 -0
  110. openinterp/cli_registry.py +348 -0
  111. openinterp/cli_reproduce.py +446 -0
  112. openinterp/cli_review.py +302 -0
  113. openinterp/cli_scan.py +223 -0
  114. openinterp/cli_score.py +446 -0
  115. openinterp/cli_sisters.py +86 -0
  116. openinterp/cli_submission.py +217 -0
  117. openinterp/cli_superpositions.py +118 -0
  118. openinterp/cli_wiring.py +54 -0
  119. openinterp/codeid.py +73 -0
  120. openinterp/concepts.py +400 -0
  121. openinterp/core/__init__.py +59 -0
  122. openinterp/core/agent_names.py +76 -0
  123. openinterp/core/attribution.py +101 -0
  124. openinterp/core/cache_dirs.py +15 -0
  125. openinterp/core/causal.py +82 -0
  126. openinterp/core/confounds.py +103 -0
  127. openinterp/core/contributors.py +26 -0
  128. openinterp/core/corpora.py +126 -0
  129. openinterp/core/costs.py +430 -0
  130. openinterp/core/draws.py +109 -0
  131. openinterp/core/errata.py +244 -0
  132. openinterp/core/evidence.py +160 -0
  133. openinterp/core/examples.py +93 -0
  134. openinterp/core/exceptions.py +29 -0
  135. openinterp/core/explanation.py +130 -0
  136. openinterp/core/fence.py +42 -0
  137. openinterp/core/heavy_lock.py +114 -0
  138. openinterp/core/identifiers.py +198 -0
  139. openinterp/core/isolation.py +149 -0
  140. openinterp/core/ledger.py +130 -0
  141. openinterp/core/licenses.py +28 -0
  142. openinterp/core/manifold.py +117 -0
  143. openinterp/core/partners.py +32 -0
  144. openinterp/core/patchscope.py +59 -0
  145. openinterp/core/paths.py +40 -0
  146. openinterp/core/pause.py +96 -0
  147. openinterp/core/pricing.py +178 -0
  148. openinterp/core/prior_art.py +44 -0
  149. openinterp/core/probing.py +146 -0
  150. openinterp/core/profiles.py +221 -0
  151. openinterp/core/project.py +28 -0
  152. openinterp/core/run_environment.py +38 -0
  153. openinterp/core/schemas.py +484 -0
  154. openinterp/core/specificity.py +49 -0
  155. openinterp/core/stats.py +172 -0
  156. openinterp/core/steering.py +49 -0
  157. openinterp/core/strict.py +19 -0
  158. openinterp/core/takedowns.py +136 -0
  159. openinterp/core/transcripts.py +130 -0
  160. openinterp/core/wiring.py +80 -0
  161. openinterp/corpus.py +476 -0
  162. openinterp/corpus_cache.py +145 -0
  163. openinterp/corpus_layouts.py +108 -0
  164. openinterp/corpus_presets.py +168 -0
  165. openinterp/corpus_rebuild.py +93 -0
  166. openinterp/coverage.py +155 -0
  167. openinterp/crossref.py +442 -0
  168. openinterp/data/__init__.py +9 -0
  169. openinterp/data/offensive_words.txt +58 -0
  170. openinterp/data/public_suffix_list.dat +15771 -0
  171. openinterp/data/sample_corpus.txt +40 -0
  172. openinterp/draw_steps.py +359 -0
  173. openinterp/errata_checks.py +129 -0
  174. openinterp/evidence.py +462 -0
  175. openinterp/evidence_branch.py +119 -0
  176. openinterp/evidence_lenses.py +75 -0
  177. openinterp/experiment_loop.py +357 -0
  178. openinterp/explain.py +252 -0
  179. openinterp/half_precision.py +79 -0
  180. openinterp/harness/__init__.py +37 -0
  181. openinterp/harness/ane/__init__.py +9 -0
  182. openinterp/harness/ane/backend.py +324 -0
  183. openinterp/harness/ane/cache.py +276 -0
  184. openinterp/harness/ane/chunks.py +163 -0
  185. openinterp/harness/ane/convert.py +116 -0
  186. openinterp/harness/ane/forward.py +258 -0
  187. openinterp/harness/ane/qualified.py +40 -0
  188. openinterp/harness/annotations.py +329 -0
  189. openinterp/harness/attribution.py +96 -0
  190. openinterp/harness/causal.py +264 -0
  191. openinterp/harness/confounds.py +184 -0
  192. openinterp/harness/covariance.py +301 -0
  193. openinterp/harness/crossref.py +195 -0
  194. openinterp/harness/cuda_devices.py +88 -0
  195. openinterp/harness/dead.py +124 -0
  196. openinterp/harness/devices.py +401 -0
  197. openinterp/harness/dictionaries.py +496 -0
  198. openinterp/harness/dreaming.py +116 -0
  199. openinterp/harness/environment.py +64 -0
  200. openinterp/harness/explanation_scores.py +371 -0
  201. openinterp/harness/features.py +276 -0
  202. openinterp/harness/first_position.py +137 -0
  203. openinterp/harness/heads.py +226 -0
  204. openinterp/harness/hooks.py +499 -0
  205. openinterp/harness/input_attribution.py +367 -0
  206. openinterp/harness/jlens.py +183 -0
  207. openinterp/harness/layer_cache.py +301 -0
  208. openinterp/harness/layer_major.py +274 -0
  209. openinterp/harness/lookahead.py +93 -0
  210. openinterp/harness/manifold.py +200 -0
  211. openinterp/harness/memory.py +471 -0
  212. openinterp/harness/moe.py +134 -0
  213. openinterp/harness/patcher.py +322 -0
  214. openinterp/harness/patchscope.py +177 -0
  215. openinterp/harness/probing.py +383 -0
  216. openinterp/harness/scan.py +462 -0
  217. openinterp/harness/scan_backend.py +201 -0
  218. openinterp/harness/sister_passes.py +463 -0
  219. openinterp/harness/sisters.py +363 -0
  220. openinterp/harness/sites.py +101 -0
  221. openinterp/harness/specificity.py +107 -0
  222. openinterp/harness/steering.py +159 -0
  223. openinterp/harness/superpositions.py +116 -0
  224. openinterp/harness/units.py +203 -0
  225. openinterp/harness/wiring.py +432 -0
  226. openinterp/harness/wiring_activations.py +285 -0
  227. openinterp/langid.py +73 -0
  228. openinterp/names.py +129 -0
  229. openinterp/neuronpedia_baseline.py +88 -0
  230. openinterp/neuronpedia_review.py +148 -0
  231. openinterp/planner.py +466 -0
  232. openinterp/planner_effects.py +89 -0
  233. openinterp/planner_heads.py +92 -0
  234. openinterp/planner_models.py +174 -0
  235. openinterp/planner_sisters.py +122 -0
  236. openinterp/planner_states.py +57 -0
  237. openinterp/planner_superpositions.py +114 -0
  238. openinterp/pole_effects.py +102 -0
  239. openinterp/prior_art.py +143 -0
  240. openinterp/reproduce.py +450 -0
  241. openinterp/revision.py +45 -0
  242. openinterp/runner_cache.py +35 -0
  243. openinterp/score_controls.py +169 -0
  244. openinterp/sensitive.py +243 -0
  245. openinterp/show.py +236 -0
  246. openinterp/signing.py +209 -0
  247. openinterp/sisters.py +476 -0
  248. openinterp/superposition_finders.py +225 -0
  249. openinterp/sync/__init__.py +5 -0
  250. openinterp/sync/r2.py +280 -0
  251. openinterp/syntax.py +399 -0
  252. openinterp/takedown_checks.py +76 -0
  253. openinterp/toxicity.py +158 -0
  254. openinterp/verdict_regression.py +79 -0
  255. openinterp/wiring_backfill.py +170 -0
  256. openinterp/wordnet.py +362 -0
@@ -0,0 +1,348 @@
1
+ """Registry-maintenance commands: ``wiki``, ``stats`` and ``next``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+ from click.core import ParameterSource
9
+
10
+ from openinterp.cli import cli, wiki_dir_option
11
+ from openinterp.core.paths import experiments_dir
12
+ from openinterp.planner import DEFAULT_EPSILON
13
+
14
+
15
+ @cli.command()
16
+ @click.option(
17
+ "--import-dir",
18
+ type=click.Path(exists=True, file_okay=False, path_type=Path),
19
+ help="Import loose audit records (*.json) found here as experiments.",
20
+ )
21
+ @wiki_dir_option()
22
+ def wiki(import_dir, wiki_dir):
23
+ """Rebuild the wiki indexes, importing loose records as experiments if asked."""
24
+ from openinterp.builder.compiler import load_records
25
+ from openinterp.builder.wiki import backfill
26
+
27
+ written = backfill(load_records(import_dir) if import_dir else [], wiki_dir)
28
+ for page in written:
29
+ click.echo(f"Wiki: {page}")
30
+ click.echo(f"{len(written)} new experiments; indexes rebuilt in {wiki_dir}")
31
+
32
+
33
+ def registry_stats(records, set_aside: int = 0) -> str:
34
+ """Return the state of the registry.
35
+
36
+ That is the experiments, units and models, the VERIFIED and self-reviewed shares, and
37
+ the protocol versions that computed the verdicts. ``set_aside`` is how many records the
38
+ errata left out of ``records``.
39
+ """
40
+ from collections import Counter
41
+
42
+ def share(n: int, of: int) -> str:
43
+ return f"{n}/{of} ({100 * n / of:.0f}%)" if of else "0/0"
44
+
45
+ def summary(rs) -> str:
46
+ units = {r.submission.identifier.slug for r in rs}
47
+ reviewed = [r for r in rs if r.verdict is not None]
48
+ verified = sum(r.is_verified for r in rs)
49
+ self_reviewed = sum(r.self_reviewed for r in reviewed)
50
+ return (
51
+ f"{len(rs)} experiments on {len(units)} units; VERIFIED {share(verified, len(rs))}, "
52
+ f"self-reviewed {share(self_reviewed, len(reviewed))}"
53
+ )
54
+
55
+ by_model: dict[str, list] = {}
56
+ for r in records:
57
+ by_model.setdefault(r.submission.identifier.model_name, []).append(r)
58
+ versions = Counter(
59
+ (r.verdict.policy.protocol_version if r.verdict.policy else "< 0.2")
60
+ for r in records
61
+ if r.verdict is not None
62
+ )
63
+ lines = [f"Registry: {summary(records)}, {len(by_model)} models"]
64
+ lines += [f" {model}: {summary(rs)}" for model, rs in sorted(by_model.items())]
65
+ lines.append(
66
+ "Protocol versions: "
67
+ + (", ".join(f"{v} ×{n}" for v, n in sorted(versions.items(), key=_newest)) or "none")
68
+ )
69
+ if set_aside:
70
+ lines.append(f"Set aside by errata, and not counted: {set_aside} experiments")
71
+ return "\n".join(lines)
72
+
73
+
74
+ def _newest(item) -> tuple:
75
+ version = item[0]
76
+ return (1,) if version == "< 0.2" else (0, *(-int(p) for p in version.split(".")))
77
+
78
+
79
+ @cli.command()
80
+ @wiki_dir_option()
81
+ def stats(wiki_dir):
82
+ """Print the state of the registry.
83
+
84
+ It prints experiments, units, models, VERIFIED share, self-reviewed share and the
85
+ protocol versions in use. It counts the records as the site does, with the errata's
86
+ edits applied and the records that they set aside left out.
87
+ """
88
+ from openinterp.builder.compiler import load_records
89
+ from openinterp.core.errata import load_errata, standing_records
90
+
91
+ records = load_records(experiments_dir(wiki_dir))
92
+ counted = standing_records(records, load_errata(wiki_dir))
93
+ click.echo(registry_stats(counted, set_aside=len(records) - len(counted)))
94
+
95
+
96
+ @cli.command()
97
+ @click.argument("path", type=click.Path(exists=True, path_type=Path))
98
+ @click.option("--full", is_flag=True, help="Don't cut probes, examples or the Discussion.")
99
+ def show(path, full):
100
+ """Summarise a record, submission, scan evidence (ev.json) or a unit's directory.
101
+
102
+ Read this instead of opening the JSON. For a record, it shows the verdict, failed
103
+ gates and probes, scores and each page's Discussion. For a scan, it shows the corpus,
104
+ peak and top examples.
105
+ """
106
+ from openinterp.show import show as summarise
107
+
108
+ try:
109
+ click.echo(summarise(path, full))
110
+ except ValueError as exc:
111
+ raise click.BadParameter(str(exc), param_hint="PATH") from exc
112
+
113
+
114
+ def _refuse_while_paused() -> None:
115
+ """Exit 75 with the reason if `open-interpretability pause` is in force."""
116
+ from openinterp.core import pause
117
+
118
+ if paused := pause.read():
119
+ click.echo(paused.describe(), err=True)
120
+ raise SystemExit(pause.EXIT_PAUSED)
121
+
122
+
123
+ @cli.command(name="next")
124
+ @wiki_dir_option()
125
+ @click.option("--seed", type=int, help="Reproduce a previous choice. Default: fresh randomness.")
126
+ @click.option(
127
+ "--epsilon",
128
+ default=DEFAULT_EPSILON,
129
+ show_default=True,
130
+ type=click.FloatRange(0, 1),
131
+ help="Probability of exploring a uniformly random new neuron.",
132
+ )
133
+ @click.option(
134
+ "--temperature",
135
+ default=1.0,
136
+ show_default=True,
137
+ type=click.FloatRange(min=0.05),
138
+ help="Higher flattens the weights (more exploration among follow-ups).",
139
+ )
140
+ @click.option(
141
+ "--model",
142
+ "models",
143
+ multiple=True,
144
+ help="Model(s) to explore. Default: [tool.openinterp] models in pyproject.toml.",
145
+ )
146
+ @click.option(
147
+ "--claims/--no-claims",
148
+ default=True,
149
+ show_default=True,
150
+ help="Down-weight neurons claimed by open PRs (needs `gh`).",
151
+ )
152
+ @click.option("--top", default=0, show_default=True, help="Also list the N heaviest candidates.")
153
+ @click.option("--json", "as_json", is_flag=True, help="Print the full plan as JSON.")
154
+ @click.option(
155
+ "--steps",
156
+ is_flag=True,
157
+ help="Also print every step of the draw as exact commands: claim, history, run, post.",
158
+ )
159
+ @click.option(
160
+ "--corpus",
161
+ default="corpus.jsonl",
162
+ show_default=True,
163
+ help="For --steps: the corpus a new scan reads.",
164
+ )
165
+ @click.option(
166
+ "--fresh-corpus",
167
+ is_flag=True,
168
+ help="For --steps: a new scan first builds its own corpus (the default blend, with the "
169
+ "draw's seed). Not with --corpus.",
170
+ )
171
+ @click.option(
172
+ "--proposer",
173
+ help="For --steps: a LiteLLM model for `audit`. Default: the neuron-proposer subagent.",
174
+ )
175
+ @click.option(
176
+ "--reviewer",
177
+ help="For --steps: a LiteLLM model for `verify`. Default: the neuron-reviewer and "
178
+ "explanation-judge subagents.",
179
+ )
180
+ def next_experiment(
181
+ wiki_dir,
182
+ seed,
183
+ epsilon,
184
+ temperature,
185
+ models,
186
+ claims,
187
+ top,
188
+ as_json,
189
+ steps,
190
+ corpus,
191
+ fresh_corpus,
192
+ proposer,
193
+ reviewer,
194
+ ):
195
+ """Pick the next experiment (randomised; balances exploration and follow-ups).
196
+
197
+ While `open-interpretability pause` is in force it draws nothing and exits 75.
198
+ """
199
+ from openinterp.planner import open_pr_claims, plan_next
200
+
201
+ _refuse_while_paused()
202
+
203
+ source = click.get_current_context().get_parameter_source("corpus")
204
+ if fresh_corpus and source != ParameterSource.DEFAULT:
205
+ raise click.UsageError("--fresh-corpus and --corpus both set the scan's corpus.")
206
+ plan = plan_next(
207
+ wiki_dir,
208
+ seed=seed,
209
+ epsilon=epsilon,
210
+ temperature=temperature,
211
+ models=models,
212
+ claims=open_pr_claims() if claims else (),
213
+ )
214
+ if as_json:
215
+ click.echo(plan.to_json())
216
+ return
217
+ c = plan.choice
218
+ click.echo(f"Next experiment: {c.kind}" + (f" on {c.slug}" if c.slug else f" ({c.model})"))
219
+ click.echo(f"Why: {c.rationale}")
220
+ for command in c.commands:
221
+ click.echo(f" $ {command}")
222
+ if any(
223
+ cmd.startswith("open-interpretability audit") and "--neuron" in cmd for cmd in c.commands
224
+ ):
225
+ _scan_cache_note(c)
226
+ if c.skills:
227
+ click.echo(f"Skills: {', '.join(c.skills)}")
228
+ click.echo(
229
+ f"(seed {plan.seed}; {len(plan.candidates)} follow-up candidates; "
230
+ f"{'explored' if plan.explored else 'exploited'})"
231
+ )
232
+ _list_candidates(plan, top)
233
+ if steps:
234
+ _print_steps(plan, wiki_dir, corpus, proposer, reviewer, fresh_corpus)
235
+
236
+
237
+ def _print_steps(plan, wiki_dir, corpus, proposer, reviewer, fresh_corpus) -> None:
238
+ """Record the draw in the ledger, then print its steps, whose claim commits the entry."""
239
+ from openinterp.draw_steps import render
240
+ from openinterp.draw_steps import steps as draw_steps
241
+
242
+ ledger = record_draw_entry(plan, wiki_dir)
243
+ click.echo(f"\nDraw recorded in the ledger: {ledger}")
244
+ click.echo("\nSteps:")
245
+ click.echo(render(draw_steps(plan, corpus, proposer, reviewer, fresh_corpus, ledger)))
246
+
247
+
248
+ @cli.command(name="ledger")
249
+ @wiki_dir_option()
250
+ @click.option("--json", "as_json", is_flag=True, help="Print the summary as JSON.")
251
+ def ledger_report(wiki_dir, as_json):
252
+ """Report every recorded draw's outcome, and VERIFIED rates against all draws.
253
+
254
+ The ledger (wiki/ledger/) holds every draw that `open-interpretability next --steps` made,
255
+ before its run. A draw with no record and no stop was dropped, or is still running.
256
+ """
257
+ import json
258
+
259
+ from openinterp.core.ledger import load_ledger, resolve, summary
260
+
261
+ entries = load_ledger(Path(wiki_dir))
262
+ records, stopped = _ledger_rows(Path(wiki_dir))
263
+ resolved = [(e, *resolve(e, records, stopped)) for e in entries]
264
+ result = summary(resolved)
265
+ if as_json:
266
+ click.echo(json.dumps(result, indent=1))
267
+ return
268
+ for e, outcome, status in resolved:
269
+ what = e.slug or f"{e.model} (seed {e.audit_seed})"
270
+ click.echo(f"{e.timestamp:%Y-%m-%d %H:%M} {e.kind:<18} {what}: {status or outcome}")
271
+ n = result["explore_entries"]
272
+ click.echo(
273
+ f"{result['entries']} draws: {result['outcomes']}. Explore draws: {n}, "
274
+ f"{result['explore_verified']} VERIFIED."
275
+ )
276
+ if n:
277
+ share = result["verified_share_of_finished_explore"]
278
+ click.echo(
279
+ f"VERIFIED share of explore draws: {result['verified_share_of_all_explore']:.1%} "
280
+ f"of all, {share:.1%} of finished."
281
+ if share is not None
282
+ else ""
283
+ )
284
+
285
+
286
+ def _ledger_rows(wiki_dir: Path):
287
+ """(slug, random seed, time, status) of every record, and of every draw or stop."""
288
+ from openinterp.core.draws import load_draws, load_stops
289
+ from openinterp.core.schemas import AuditRecord
290
+
291
+ records = []
292
+ for path in sorted(experiments_dir(wiki_dir).rglob("*.json")):
293
+ r = AuditRecord.model_validate_json(path.read_text())
294
+ if r.verdict is None:
295
+ continue
296
+ p = r.submission.provenance
297
+ seed = p.seed if p and p.selection == "random" else None
298
+ records.append((r.submission.identifier.slug, seed, r.verdict.timestamp, r.verdict.status))
299
+ stopped = [
300
+ (d.identifier.slug, d.provenance.seed, d.timestamp, None)
301
+ for d in [*load_draws(wiki_dir), *load_stops(wiki_dir)]
302
+ ]
303
+ return records, stopped
304
+
305
+
306
+ def record_draw_entry(plan, wiki_dir) -> str:
307
+ """Write the draw's ledger entry before any run (issue #3205), and return its path."""
308
+ from datetime import UTC, datetime
309
+
310
+ from openinterp.core.ledger import DrawEntry, write_entry
311
+ from openinterp.draw_steps import draw_slug
312
+
313
+ c = plan.choice
314
+ options = (c.audit or "").split()
315
+ entry = DrawEntry(
316
+ timestamp=datetime.now(UTC).replace(microsecond=0),
317
+ seed=plan.seed,
318
+ kind=c.kind,
319
+ model=c.model,
320
+ slug=draw_slug(c),
321
+ record=c.record,
322
+ audit=c.audit,
323
+ pole_forced=("--pole" in options) if "--random" in options else None,
324
+ )
325
+ return str(write_entry(entry, Path(wiki_dir)))
326
+
327
+
328
+ def _scan_cache_note(c) -> None:
329
+ from openinterp.harness.layer_cache import LayerCache
330
+
331
+ cache = LayerCache.default()
332
+ if cache is not None and c.layer in cache.cached_layers(c.model):
333
+ click.echo(
334
+ f"Scan cache: layer {c.layer} is cached, so the audit replays its scan "
335
+ "(when the corpus options match) instead of running the model."
336
+ )
337
+
338
+
339
+ def _list_candidates(plan, top: int) -> None:
340
+ """The ``top`` heaviest follow-ups by share of the weight, and the explore draws."""
341
+ total = sum(x.weight for x in plan.candidates) or 1.0
342
+ for x in plan.candidates[:top]:
343
+ click.echo(f" {x.weight / total:6.1%} {x.kind:18s} {x.slug} — {x.rationale}")
344
+ if top and len(plan.explore_models) > 1:
345
+ click.echo("Explore draws by model (neurons covered):")
346
+ for model, p in sorted(plan.explore_models.items(), key=lambda kv: -kv[1]):
347
+ focus = " ← smallest model below the verified target" if model == plan.focus else ""
348
+ click.echo(f" {p:6.1%} {model} {plan.coverage[model]}{focus}")