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,484 @@
1
+ """Strict Pydantic v2 schemas for audits, hypotheses and evidence.
2
+
3
+ Every model is strict (no implicit type coercion) and forbids unknown fields. So a
4
+ record that validates here is safe to compile into the public registry. Records on disk
5
+ are JSON. Load them with ``Model.model_validate_json``: strict mode then accepts
6
+ ISO-8601 strings for datetimes.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Annotated, Literal
12
+
13
+ from pydantic import AwareDatetime, Field, model_validator
14
+
15
+ from openinterp.core import evidence
16
+ from openinterp.core.agent_names import same_agent
17
+ from openinterp.core.attribution import InputAttribution
18
+ from openinterp.core.causal import ( # noqa: F401 (re-exported)
19
+ CausalMetrics,
20
+ CausalPairEffect,
21
+ CausalTarget,
22
+ PatchEffect,
23
+ )
24
+ from openinterp.core.confounds import ( # noqa: F401 (re-exported)
25
+ ConfoundProfile,
26
+ LabelConfound,
27
+ LabelStat,
28
+ TokenStat,
29
+ )
30
+ from openinterp.core.contributors import CONTRIBUTOR_PATTERN
31
+ from openinterp.core.corpora import CorpusSample, CorpusSource # noqa: F401 (re-exported)
32
+ from openinterp.core.costs import ExperimentCosts
33
+ from openinterp.core.examples import ( # noqa: F401 (re-exported)
34
+ ActivatingExample,
35
+ Histogram,
36
+ OppositePole,
37
+ check_opposite_pole,
38
+ )
39
+ from openinterp.core.explanation import ExplanationScores
40
+ from openinterp.core.identifiers import ( # noqa: F401 (re-exported)
41
+ ComponentIdentifier,
42
+ FeatureIdentifier,
43
+ HeadIdentifier,
44
+ NeuronIdentifier,
45
+ Pole,
46
+ SuperpositionIdentifier,
47
+ )
48
+ from openinterp.core.manifold import ManifoldCheck
49
+ from openinterp.core.partners import CorrelatedNeuron # noqa: F401 (re-exported)
50
+ from openinterp.core.patchscope import PatchscopeReadout
51
+ from openinterp.core.prior_art import PriorArtField
52
+ from openinterp.core.profiles import ( # noqa: F401 (re-exported)
53
+ DictionaryProfile,
54
+ DictionarySite,
55
+ HeadProfile,
56
+ SuperpositionProfile,
57
+ require_unit_profile,
58
+ )
59
+ from openinterp.core.run_environment import RunEnvironment # noqa: F401 (re-exported)
60
+ from openinterp.core.specificity import WordnetSpecificity, WordStrength # noqa: F401
61
+ from openinterp.core.steering import SteeringSamples
62
+ from openinterp.core.strict import StrictModel, UnitFloat
63
+ from openinterp.core.wiring import Wiring, WiringEdge # noqa: F401 (re-exported)
64
+
65
+ VerdictStatus = Literal["VERIFIED", "REJECTED", "NEEDS_REVISION"]
66
+ ProbeKind = Literal["hard_negative", "positive_holdout", "counterfactual", "lookahead_consistency"]
67
+ SelectionMethod = Literal["random", "attribution", "manual"]
68
+
69
+ PROTOCOL_VERSION = "0.9"
70
+ # Probes a review asks for (PROTOCOL.md §2, step 5): 20 hard negatives, 10 holdouts.
71
+ MAX_PROBES = 30
72
+ # Share of max activation counted as "active" (PROTOCOL.md §1); ReviewPolicy's default.
73
+ ACTIVATION_THRESHOLD = 0.25
74
+ # An experiment is named by its verdict timestamp: ``%Y%m%dT%H%M%SZ``.
75
+ EXPERIMENT_ID_PATTERN = r"^\d{8}T\d{6}Z$"
76
+ # An experiment on a neuron pole anywhere in the registry: its pole's slug, then its id.
77
+ SISTER_OF_PATTERN = r"^[A-Za-z0-9._-]+/L\d+/N\d+-(positive|negative)/\d{8}T\d{6}Z$"
78
+ # ``provenance.corpus`` of a scan without ``--corpus``: the 552-token built-in sample.
79
+ BUILTIN_CORPUS = "builtin:sample_corpus"
80
+ # Sources of corpora that records scanned without recording one. They match by SHA-256.
81
+ # The first is the default scan corpus's sidecar (README, "A scan corpus"). The second is
82
+ # a table of the others.
83
+ COMMITTED_SOURCES = ("corpus.source.json", "corpus-sources.json")
84
+ Contributor = Annotated[
85
+ str,
86
+ Field(
87
+ pattern=CONTRIBUTOR_PATTERN,
88
+ description="The human behind this step: a public handle (e.g. a GitHub username), "
89
+ "never an email. None: not recorded.",
90
+ ),
91
+ ]
92
+
93
+
94
+ class CounterfactualPair(StrictModel):
95
+ """A minimal pair predicted to change the neuron's pole activation.
96
+
97
+ A delta is ``strength(base) - strength(counterfactual)``, divided by the neuron's
98
+ maximum observed activation. So ``1.0`` means that the base prompt fully activates
99
+ the pole, and the counterfactual does not activate it at all.
100
+ """
101
+
102
+ base_prompt: str = Field(min_length=1)
103
+ counterfactual_prompt: str = Field(min_length=1)
104
+ expected_delta: float
105
+ observed_delta: float | None = None
106
+
107
+ @model_validator(mode="after")
108
+ def _prompts_differ(self) -> CounterfactualPair:
109
+ if self.base_prompt == self.counterfactual_prompt:
110
+ raise ValueError("base_prompt and counterfactual_prompt must differ")
111
+ return self
112
+
113
+
114
+ class LookaheadProfile(StrictModel):
115
+ """Whether the neuron prepares specific upcoming tokens rather than reacting to past ones."""
116
+
117
+ is_lookahead: bool
118
+ target_upcoming_tokens: list[str] = Field(default_factory=list)
119
+ kl_divergence: float = Field(ge=0.0)
120
+
121
+
122
+ class ExperimentProvenance(StrictModel):
123
+ """Everything needed to re-run the scan that produced a submission."""
124
+
125
+ model_revision: str | None = Field(default=None, description="Hugging Face commit hash.")
126
+ dtype: str
127
+ corpus: str = Field(min_length=1, description="Human-readable corpus description.")
128
+ corpus_sha256: str | None = None
129
+ n_snippets: int = Field(ge=0)
130
+ tokens_scanned: int = Field(ge=0)
131
+ snippet_tokens: int | None = Field(
132
+ default=None,
133
+ gt=0,
134
+ description="Window length when documents were scanned as overlapping windows; "
135
+ "None when each was scanned whole.",
136
+ )
137
+ snippet_stride: int | None = Field(
138
+ default=None, gt=0, description="Tokens between the starts of consecutive windows."
139
+ )
140
+ selection: SelectionMethod = Field(
141
+ description="How the neuron was chosen; only 'random' gives unbiased registry statistics."
142
+ )
143
+ seed: int | None = None
144
+ pole_forced: bool | None = Field(
145
+ default=None,
146
+ description="For a random draw: True when --pole fixed the pole, False when the seed "
147
+ "drew it. None for a chosen unit, and for records before issue #3205.",
148
+ )
149
+ harness_version: str
150
+ environment: RunEnvironment | None = None
151
+ corpus_source: CorpusSource | None = None
152
+ label_detector: str | None = Field(
153
+ default=None,
154
+ description="What fixes the scan's match labels (openinterp.sensitive.detector, "
155
+ "openinterp.names.DETECTOR, openinterp.toxicity.DETECTOR). None: the scan "
156
+ "predates those labels.",
157
+ )
158
+ corpus_sample: CorpusSample | None = Field(
159
+ default=None,
160
+ description="The sample of the corpus scanned; None when the whole corpus was "
161
+ "(every record before protocol 0.7).",
162
+ )
163
+ costs: ExperimentCosts | None = Field(
164
+ default=None,
165
+ description="Wall time per stage and LLM usage of the run that produced this record "
166
+ "(reported, never gated).",
167
+ )
168
+
169
+
170
+ class VocabularyLens(StrictModel):
171
+ """The pole's output direction read through the logit lens and the Jacobian lens.
172
+
173
+ The Jacobian lens (Gurnee et al., 2026) first moves the direction to the final layer
174
+ with the context-averaged Jacobian. So it stays reliable in middle layers, where the
175
+ logit lens (J = I) does not. ``overlap`` is the shared fraction of the two top-k
176
+ lists. ``direction_cosine`` is cos(w, J·w). A low value means that later layers
177
+ rotate what this neuron writes by a large angle. Kurtosis of the vocabulary
178
+ z-scores is high when the pole promotes a small, specific token set.
179
+
180
+ The ``*_z`` lists hold each listed token's z-score over the whole vocabulary,
181
+ in the same order. They are scale-invariant, so the two lenses share an axis.
182
+ Records before they were added have ``None``.
183
+ """
184
+
185
+ logit_promoted: list[str]
186
+ logit_suppressed: list[str]
187
+ jacobian_promoted: list[str]
188
+ jacobian_suppressed: list[str]
189
+ logit_promoted_z: list[float] | None = None
190
+ logit_suppressed_z: list[float] | None = None
191
+ jacobian_promoted_z: list[float] | None = None
192
+ jacobian_suppressed_z: list[float] | None = None
193
+ overlap: UnitFloat
194
+ direction_cosine: float = Field(ge=-1.0, le=1.0)
195
+ logit_kurtosis: float = Field(ge=0.0)
196
+ jacobian_kurtosis: float = Field(ge=0.0)
197
+ prompts: int = Field(ge=0)
198
+
199
+
200
+ class JointAblation(StrictModel):
201
+ """Next-token KL when ablating the pole, its correlated partners, or both.
202
+
203
+ ``redundancy_ratio`` = (kl_joint − kl_partners) / kl_single: how much more the pole
204
+ matters once its correlated partners are gone (> 1 suggests redundancy).
205
+ """
206
+
207
+ partners: list[int]
208
+ kl_single: float = Field(ge=0.0)
209
+ kl_partners: float = Field(ge=0.0)
210
+ kl_joint: float = Field(ge=0.0)
211
+ redundancy_ratio: float | None = None
212
+
213
+
214
+ class ScanEvidence(StrictModel):
215
+ """What ``open-interpretability audit --evidence-out`` measured, for a proposer working offline.
216
+
217
+ It holds everything a submission needs except the hypothesis, so ``open-interpretability audit
218
+ --evidence <file> --hypothesis <file>`` can build the submission without rescanning.
219
+ """
220
+
221
+ identifier: ComponentIdentifier
222
+ top_examples: list[ActivatingExample]
223
+ activation_histogram: Histogram | None = None
224
+ first_token_floor: bool | None = None # v0.8, see NeuronAuditSubmission
225
+ provenance: ExperimentProvenance | None = None
226
+ confounds: ConfoundProfile | None = None
227
+ correlated_neurons: list[CorrelatedNeuron] = Field(default_factory=list)
228
+ vocabulary_lens: VocabularyLens | None = None
229
+ input_attribution: InputAttribution | None = None
230
+ head_profile: HeadProfile | None = None
231
+ model_n_heads: int | None = Field(default=None, gt=0)
232
+ dictionary_profile: DictionaryProfile | None = None
233
+ superposition_profile: SuperpositionProfile | None = None
234
+ opposite_pole: OppositePole | None = None
235
+ manifold: ManifoldCheck | None = None # see NeuronAuditSubmission
236
+
237
+ @model_validator(mode="after")
238
+ def _head_profile(self) -> ScanEvidence:
239
+ check_opposite_pole(self)
240
+ return require_unit_profile(self)
241
+
242
+
243
+ class NeuronAuditSubmission(StrictModel):
244
+ """A hypothesis about one unit, frozen once submitted.
245
+
246
+ The unit is a neuron pole, an attention head (since protocol 0.6) or a dictionary
247
+ feature. The name predates heads and is kept for compatibility.
248
+ """
249
+
250
+ identifier: ComponentIdentifier
251
+ natural_language_explanation: str = Field(min_length=1)
252
+ activating_tokens: list[str]
253
+ counterfactuals: list[CounterfactualPair] = Field(min_length=1)
254
+ lookahead: LookaheadProfile
255
+ submitting_agent_model: str = Field(min_length=1)
256
+ contributor: Contributor | None = None
257
+ # Evidence gathered by the harness. Optional so hand-written submissions validate.
258
+ max_activation: float | None = Field(default=None, gt=0.0)
259
+ top_examples: list[ActivatingExample] = Field(default_factory=list)
260
+ activation_histogram: Histogram | None = None
261
+ first_token_floor: bool | None = Field(
262
+ default=None,
263
+ description="Protocol 0.8: the unit is active at the first position on at least half "
264
+ "the vocabulary, so it is not measured at a text's first token. None: not judged (a "
265
+ "record from before 0.8), and a review judges it.",
266
+ )
267
+ model_num_layers: int | None = Field(default=None, gt=0)
268
+ model_mlp_width: int | None = Field(default=None, gt=0, description="Neurons per MLP layer.")
269
+ provenance: ExperimentProvenance | None = None
270
+ correlated_neurons: list[CorrelatedNeuron] = Field(default_factory=list)
271
+ confounds: ConfoundProfile | None = None
272
+ vocabulary_lens: VocabularyLens | None = None
273
+ input_attribution: InputAttribution | None = None
274
+ head_profile: HeadProfile | None = None
275
+ model_n_heads: int | None = Field(default=None, gt=0, description="Heads per layer.")
276
+ dictionary_profile: DictionaryProfile | None = None
277
+ superposition_profile: SuperpositionProfile | None = None
278
+ patchscope: PatchscopeReadout | None = Field(
279
+ default=None,
280
+ description="The model's own description of the unit's output direction, read after "
281
+ "the proposer answered and shown to neither agent (reported, not gated).",
282
+ )
283
+ prior_art: PriorArtField = None
284
+ steering: SteeringSamples | None = Field(
285
+ default=None,
286
+ description="Neutral prompts continued with the unit clamped high, at baseline and "
287
+ "ablated, taken after the proposer answered and shown to neither agent (reported, "
288
+ "not gated).",
289
+ )
290
+ wordnet_specificity: WordnetSpecificity | None = Field(
291
+ default=None,
292
+ description="The unit on held-out words of a WordNet category against words of its "
293
+ "sibling categories, measured after the proposer answered and shown to neither "
294
+ "agent (reported, not gated).",
295
+ )
296
+ supersedes: str | None = Field(
297
+ default=None,
298
+ pattern=EXPERIMENT_ID_PATTERN,
299
+ description="Experiment id of the earlier experiment on the same unit that this "
300
+ "submission revises (reported, not gated).",
301
+ )
302
+ sister_of: str | None = Field(
303
+ default=None,
304
+ pattern=SISTER_OF_PATTERN,
305
+ description="A sister-neuron study: the experiment (``<pole slug>/<experiment id>``) "
306
+ "on a VERIFIED pole of another model whose explanation this submission tests on its "
307
+ "sister, unchanged (reported, not gated).",
308
+ )
309
+ opposite_pole: OppositePole | None = Field(
310
+ default=None,
311
+ description="A neuron's other pole, from the same scan: its top examples and "
312
+ "histogram, shown beside this pole on the neuron page (reported, not gated).",
313
+ )
314
+ wiring: Wiring | None = Field(
315
+ default=None,
316
+ description="A neuron pole's top connections by virtual weight, read after the "
317
+ "proposer answered and shown to neither agent (reported, not gated).",
318
+ )
319
+ manifold: ManifoldCheck | None = Field(
320
+ default=None,
321
+ description="A neuron pole and its partner poles on the activating set and on "
322
+ "ordered word families, and whether they trace a circle or a line (reported, not "
323
+ "gated).",
324
+ )
325
+
326
+ @model_validator(mode="after")
327
+ def _head_profile(self) -> NeuronAuditSubmission:
328
+ check_opposite_pole(self)
329
+ return require_unit_profile(self)
330
+
331
+
332
+ class AdversarialTestResult(StrictModel):
333
+ """The outcome of one reviewer probe, measured on the model; the reviewer never sees it."""
334
+
335
+ kind: ProbeKind
336
+ prompt: str
337
+ passed: bool
338
+ expected_active: bool | None = None
339
+ activation: float | None = None
340
+ expected_delta: float | None = None
341
+ observed_delta: float | None = None
342
+ detail: str = ""
343
+ # v0.8: False for a one-token prompt to a unit with a first-token floor, which is not
344
+ # measured at the first token. It is recorded but counts in no metric.
345
+ measured: bool = True
346
+
347
+
348
+ class ReviewPolicy(StrictModel):
349
+ """The decision rule a verdict was computed under; docs/PROTOCOL.md quotes its defaults."""
350
+
351
+ protocol_version: str = PROTOCOL_VERSION
352
+ activation_threshold: UnitFloat = ACTIVATION_THRESHOLD
353
+ delta_abs_tolerance: float = Field(default=0.10, ge=0.0)
354
+ delta_rel_tolerance: float = Field(default=0.50, ge=0.0)
355
+ min_hard_negatives: int = Field(default=6, ge=0)
356
+ min_holdouts: int = Field(default=3, ge=0)
357
+ min_specificity: UnitFloat = 0.80
358
+ min_sensitivity: UnitFloat = 0.60
359
+ min_counterfactual_reproduction: UnitFloat = 0.75
360
+ reject_below_balanced_accuracy: UnitFloat = 0.60
361
+ # v0.4: proposer/reviewer independence is recorded but no longer gated by default.
362
+ require_independent_review: bool = False
363
+ freeze_downstream: bool = True
364
+ # v0.5: causal effects are ranked against this many same-layer neurons whose strength
365
+ # changes as much between each minimal pair.
366
+ causal_null_neurons: int = Field(default=32, ge=1)
367
+ # v0.3: near-duplicate probes (word-set Jaccard >= this) count as one observation.
368
+ probe_cluster_threshold: UnitFloat = 0.5
369
+ # v0.3: the hypothesis must match or beat trivial token/position baselines.
370
+ require_confound_control: bool = True
371
+ joint_ablation_partners: int = Field(default=3, ge=0)
372
+ # Partners for joint ablation: lower-bound co-activation lift >= min_partner_lift on
373
+ # at least min_partner_coactive tokens, or |Spearman| >= min_partner_correlation.
374
+ min_partner_correlation: UnitFloat = 0.2
375
+ min_partner_lift: float = Field(default=2.0, ge=1.0)
376
+ min_partner_coactive: int = Field(default=5, ge=1)
377
+ # v0.8: a unit active on at least this share of its scan's tokens is default-on. It
378
+ # is not audited as a detector, because no hard negative of a few words can stay
379
+ # inactive. A unit active at the first position on this share of the vocabulary has
380
+ # a first-token floor.
381
+ max_active_share: UnitFloat = 0.5
382
+
383
+
384
+ def scan_active_share(submission: NeuronAuditSubmission, policy: ReviewPolicy) -> float | None:
385
+ """Return the share of the scan's tokens that the unit is active on.
386
+
387
+ It comes from the submission's histogram. ``None`` without one.
388
+ """
389
+ histogram, scale = submission.activation_histogram, submission.max_activation
390
+ if histogram is None or not scale:
391
+ return None
392
+ return histogram.share_at_least(policy.activation_threshold * scale)
393
+
394
+
395
+ class ReviewMetrics(StrictModel):
396
+ """The rates and gates the decision rule computes from the probes (docs/PROTOCOL.md §4)."""
397
+
398
+ specificity: UnitFloat | None # hard negatives correctly inactive
399
+ sensitivity: UnitFloat | None # positive holdouts correctly active
400
+ balanced_accuracy: UnitFloat
401
+ counterfactual_reproduction: UnitFloat | None
402
+ lookahead_consistent: bool
403
+ independent_review: bool
404
+ gates: dict[str, bool]
405
+ effective_hard_negatives: int | None = None # clusters of near-duplicate probes
406
+ effective_holdouts: int | None = None
407
+ confound_baseline_accuracy: UnitFloat | None = None # best trivial baseline, same probes
408
+ confound_baseline: str | None = None
409
+ # v0.8: the share of the scan's tokens at or above the "active" line (from its
410
+ # histogram). Also, whether the measurement left out each text's first token.
411
+ active_share: UnitFloat | None = None
412
+ first_token_floor: bool | None = None
413
+
414
+
415
+ class VerificationVerdict(StrictModel):
416
+ """The reviewer's verdict on one submission, with the evidence and the policy it used."""
417
+
418
+ status: VerdictStatus
419
+ confidence_score: UnitFloat
420
+ adversarial_test_results: list[AdversarialTestResult]
421
+ reviewer_agent_model: str = Field(min_length=1)
422
+ contributor: Contributor | None = None
423
+ timestamp: AwareDatetime
424
+ causal_metrics: CausalMetrics | None = None
425
+ metrics: ReviewMetrics | None = None
426
+ policy: ReviewPolicy | None = None
427
+ joint_ablation: JointAblation | None = None
428
+ environment: RunEnvironment | None = None
429
+ explanation_scores: ExplanationScores | None = None
430
+ wiring: Wiring | None = Field(
431
+ default=None,
432
+ description="A neuron pole's top connections by attribution on the review's prompts "
433
+ "where it fired (reported, not gated).",
434
+ )
435
+ notes: str = ""
436
+
437
+
438
+ class AuditRecord(StrictModel):
439
+ """The unit stored in the registry: a submission plus its review verdict.
440
+
441
+ Loading resolves a slim record's evidence references (:mod:`.evidence`, issue #3254).
442
+ """
443
+
444
+ submission: NeuronAuditSubmission
445
+ verdict: VerificationVerdict | None = None
446
+
447
+ @classmethod
448
+ def model_validate_json(cls, json_data, **kwargs) -> AuditRecord: # noqa: D102
449
+ return super().model_validate_json(evidence.resolved_text(json_data), **kwargs)
450
+
451
+ @classmethod
452
+ def model_validate(cls, obj, **kwargs) -> AuditRecord: # noqa: D102
453
+ text = evidence.resolved_dict_text(obj)
454
+ if text is None:
455
+ return super().model_validate(obj, **kwargs)
456
+ return super().model_validate_json(text, **kwargs)
457
+
458
+ @property
459
+ def is_verified(self) -> bool:
460
+ """True when a verdict exists and its status is VERIFIED."""
461
+ return self.verdict is not None and self.verdict.status == "VERIFIED"
462
+
463
+ @property
464
+ def scanned_on_builtin_sample(self) -> bool:
465
+ """Return whether the scan read only the 552-token built-in sample.
466
+
467
+ That sample is too small for a meaningful max activation, so follow-ups rescan
468
+ the unit instead of reusing the scan.
469
+ """
470
+ provenance = self.submission.provenance
471
+ return provenance is not None and provenance.corpus == BUILTIN_CORPUS
472
+
473
+ @property
474
+ def self_reviewed(self) -> bool:
475
+ """Return whether the proposer's model also reviewed the submission.
476
+
477
+ Self-review is recorded and badged, not gated (§2). It is also true when the
478
+ record calls the review independent but spells one model two ways (``[1m]``).
479
+ """
480
+ if self.verdict is None:
481
+ return False
482
+ if self.verdict.metrics is not None and not self.verdict.metrics.independent_review:
483
+ return True
484
+ return same_agent(self.verdict.reviewer_agent_model, self.submission.submitting_agent_model)
@@ -0,0 +1,49 @@
1
+ """Schemas for the WordNet specificity check: a category's words against its siblings'.
2
+
3
+ A "cities" unit might really be a "places" unit. To tell the two apart, the harness
4
+ measures the unit on two lists of words. The first is words that WordNet puts under the
5
+ claimed category (its hyponyms). The second is words from the category's siblings
6
+ (other hyponyms of its hypernyms). Each word sits in a few fixed carrier sentences.
7
+ Peak words of the scan are left out of both lists, so every word is held out. Reported,
8
+ not gated (docs/PROTOCOL.md §4). Neither agent sees it.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Literal
14
+
15
+ from pydantic import Field
16
+
17
+ from openinterp.core.strict import StrictModel, UnitFloat
18
+
19
+
20
+ class WordStrength(StrictModel):
21
+ """One word's strength: the mean over the carriers of its peak over the word's tokens."""
22
+
23
+ word: str = Field(min_length=1)
24
+ strength: float = Field(ge=0.0)
25
+
26
+
27
+ class WordnetSpecificity(StrictModel):
28
+ """The unit on a WordNet category's words against the words of its sibling categories.
29
+
30
+ A word is active at or above ``ACTIVATION_THRESHOLD × scale``. ``auroc`` is the
31
+ chance that a category word is stronger than a sibling word, ties counting half. It
32
+ is 1 when the unit picks out the category. It is 0.5 when the unit can't tell the
33
+ category from its siblings.
34
+ """
35
+
36
+ synset: str = Field(description="The category, e.g. 'city.n.01'.")
37
+ lemma: str
38
+ source: Literal["proposer", "peak_words"] = Field(
39
+ description="'proposer': the draft's wordnet_synset. 'peak_words': the proposer named "
40
+ "none, so the largest category of the scan's peak words."
41
+ )
42
+ sibling_synsets: list[str]
43
+ carriers: list[str] = Field(min_length=1, description="Templates; {} is the word.")
44
+ scale: float = Field(gt=0.0, description="The unit's top activation in the scan.")
45
+ category: list[WordStrength]
46
+ siblings: list[WordStrength]
47
+ category_active: UnitFloat
48
+ siblings_active: UnitFloat
49
+ auroc: UnitFloat