brainpatch 1.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 (82) hide show
  1. brainpatch/__init__.py +92 -0
  2. brainpatch/backends/__init__.py +19 -0
  3. brainpatch/backends/llamacpp.py +383 -0
  4. brainpatch/backends/mlx_backend.py +213 -0
  5. brainpatch/backends/transformers_backend.py +473 -0
  6. brainpatch/backends/vllm_backend.py +299 -0
  7. brainpatch/backends/vllm_worker.py +129 -0
  8. brainpatch/cli.py +825 -0
  9. brainpatch/config.py +245 -0
  10. brainpatch/datasets/__init__.py +20 -0
  11. brainpatch/datasets/contrast_sets.py +64 -0
  12. brainpatch/evaluation/__init__.py +28 -0
  13. brainpatch/evaluation/metrics.py +223 -0
  14. brainpatch/patch/__init__.py +64 -0
  15. brainpatch/patch/compiler.py +324 -0
  16. brainpatch/patch/format.py +489 -0
  17. brainpatch/patch/loader.py +312 -0
  18. brainpatch/patch/registry.py +300 -0
  19. brainpatch/patch/tensors.py +236 -0
  20. brainpatch/patch/validation.py +157 -0
  21. brainpatch/paths.py +184 -0
  22. brainpatch/py.typed +0 -0
  23. brainpatch/research/__init__.py +16 -0
  24. brainpatch/research/antisycophancy.py +348 -0
  25. brainpatch/research/behaviour_eval.py +711 -0
  26. brainpatch/research/generation_eval.py +346 -0
  27. brainpatch/research/ml/__init__.py +35 -0
  28. brainpatch/research/ml/activation_store.py +232 -0
  29. brainpatch/research/ml/causal.py +386 -0
  30. brainpatch/research/ml/corpus.py +165 -0
  31. brainpatch/research/ml/evaluation.py +188 -0
  32. brainpatch/research/ml/extraction.py +464 -0
  33. brainpatch/research/ml/feature_analysis.py +317 -0
  34. brainpatch/research/ml/generation.py +109 -0
  35. brainpatch/research/ml/hooks.py +183 -0
  36. brainpatch/research/ml/intervention.py +274 -0
  37. brainpatch/research/ml/model.py +219 -0
  38. brainpatch/research/ml/patch_search.py +337 -0
  39. brainpatch/research/ml/runtime.py +343 -0
  40. brainpatch/research/ml/sae.py +383 -0
  41. brainpatch/research/ml/training.py +376 -0
  42. brainpatch/research/stance_rubric.py +170 -0
  43. brainpatch/research/sycophancy_data.py +982 -0
  44. brainpatch/research/sycophancy_data_r1.py +1701 -0
  45. brainpatch/research/sycophancy_data_v2.py +1649 -0
  46. brainpatch/research/sycophancy_data_v3.py +2288 -0
  47. brainpatch/research/sycophancy_v2_build.py +362 -0
  48. brainpatch/research/sycophancy_v3_build.py +188 -0
  49. brainpatch/research/utility_probe.py +139 -0
  50. brainpatch/runtime/__init__.py +50 -0
  51. brainpatch/runtime/auto.py +157 -0
  52. brainpatch/runtime/base.py +311 -0
  53. brainpatch/runtime/capabilities.py +96 -0
  54. brainpatch/runtime/model.py +260 -0
  55. brainpatch/runtime/scheduling.py +13 -0
  56. brainpatch/schemas/__init__.py +35 -0
  57. brainpatch/schemas/contrast.py +161 -0
  58. brainpatch/schemas/feature.py +193 -0
  59. brainpatch/schemas/manifest.py +167 -0
  60. brainpatch/schemas/patch.py +379 -0
  61. brainpatch/schemas/patch_io.py +88 -0
  62. brainpatch/schemas/sae.py +146 -0
  63. brainpatch/server/__init__.py +11 -0
  64. brainpatch/server/app.py +269 -0
  65. brainpatch/steering/__init__.py +13 -0
  66. brainpatch/steering/plan.py +177 -0
  67. brainpatch/steering/schedule.py +138 -0
  68. brainpatch/ui/__init__.py +11 -0
  69. brainpatch/ui/app.py +201 -0
  70. brainpatch/verify/__init__.py +66 -0
  71. brainpatch/verify/behavioural.py +156 -0
  72. brainpatch/verify/checks.py +204 -0
  73. brainpatch/verify/corruptions.py +335 -0
  74. brainpatch/verify/report.py +133 -0
  75. brainpatch/verify/vectors.py +95 -0
  76. brainpatch/verify/workflow.py +331 -0
  77. brainpatch-1.2.0.dist-info/METADATA +556 -0
  78. brainpatch-1.2.0.dist-info/RECORD +82 -0
  79. brainpatch-1.2.0.dist-info/WHEEL +5 -0
  80. brainpatch-1.2.0.dist-info/entry_points.txt +2 -0
  81. brainpatch-1.2.0.dist-info/licenses/LICENSE +190 -0
  82. brainpatch-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,556 @@
1
+ Metadata-Version: 2.4
2
+ Name: brainpatch
3
+ Version: 1.2.0
4
+ Summary: A portable format and runtime for reversible activation-space interventions in frozen language models.
5
+ Author: Blackdrome AI Labs
6
+ Maintainer-email: Blackdrome AI Labs <hello@blackdrome.tech>
7
+ License: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/blackdromeai-labs/brainpatch
9
+ Project-URL: Repository, https://github.com/blackdromeai-labs/brainpatch
10
+ Project-URL: Documentation, https://github.com/blackdromeai-labs/brainpatch/tree/main/docs
11
+ Project-URL: Issues, https://github.com/blackdromeai-labs/brainpatch/issues
12
+ Project-URL: Changelog, https://github.com/blackdromeai-labs/brainpatch/blob/main/CHANGELOG.md
13
+ Project-URL: Organization, https://github.com/blackdromeai-labs
14
+ Keywords: activation-steering,interpretability,llm,mechanistic-interpretability,activation-patching,model-steering,inference-time-intervention
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: License :: OSI Approved :: Apache Software License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: typer>=0.12
28
+ Requires-Dist: rich>=13.0
29
+ Provides-Extra: transformers
30
+ Requires-Dist: torch>=2.1; extra == "transformers"
31
+ Requires-Dist: transformers>=4.40; extra == "transformers"
32
+ Requires-Dist: accelerate>=0.30; extra == "transformers"
33
+ Provides-Extra: vllm
34
+ Requires-Dist: vllm>=0.6; extra == "vllm"
35
+ Requires-Dist: torch>=2.1; extra == "vllm"
36
+ Provides-Extra: mlx
37
+ Requires-Dist: mlx-lm>=0.20; (platform_system == "Darwin" and platform_machine == "arm64") and extra == "mlx"
38
+ Provides-Extra: llamacpp
39
+ Requires-Dist: gguf>=0.10; extra == "llamacpp"
40
+ Requires-Dist: numpy>=1.24; extra == "llamacpp"
41
+ Provides-Extra: hub
42
+ Requires-Dist: huggingface_hub>=0.25; extra == "hub"
43
+ Provides-Extra: server
44
+ Requires-Dist: fastapi>=0.110; extra == "server"
45
+ Requires-Dist: uvicorn>=0.30; extra == "server"
46
+ Requires-Dist: pydantic>=2.0; extra == "server"
47
+ Provides-Extra: ui
48
+ Requires-Dist: gradio>=4.44; extra == "ui"
49
+ Provides-Extra: research
50
+ Requires-Dist: torch>=2.1; extra == "research"
51
+ Requires-Dist: transformers>=4.40; extra == "research"
52
+ Requires-Dist: datasets>=2.19; extra == "research"
53
+ Requires-Dist: accelerate>=0.30; extra == "research"
54
+ Requires-Dist: safetensors>=0.4; extra == "research"
55
+ Requires-Dist: numpy>=1.24; extra == "research"
56
+ Requires-Dist: pyyaml>=6.0; extra == "research"
57
+ Requires-Dist: huggingface_hub>=0.25; extra == "research"
58
+ Provides-Extra: modal
59
+ Requires-Dist: modal>=1.5.0; extra == "modal"
60
+ Provides-Extra: all
61
+ Requires-Dist: brainpatch[hub,llamacpp,server,transformers,ui]; extra == "all"
62
+ Provides-Extra: dev
63
+ Requires-Dist: pytest>=8.0; extra == "dev"
64
+ Requires-Dist: build>=1.2; extra == "dev"
65
+ Dynamic: license-file
66
+
67
+ # BrainPatch
68
+
69
+ **A portable format and runtime for reversible activation-space interventions in
70
+ frozen language models.**
71
+
72
+ No fine-tuning. No weight modification. No prompt injection. No hosted service.
73
+
74
+ An open-source system from [Blackdrome AI Labs](https://blackdrome.tech).
75
+
76
+ ```bash
77
+ pip install "brainpatch[transformers]"
78
+ brainpatch install ./experimental-feature-727.brainpatch
79
+ brainpatch compare --model Qwen/Qwen2.5-1.5B-Instruct \
80
+ --patch experimental-feature-727 \
81
+ --prompt "Explain why the sky is blue."
82
+ ```
83
+
84
+ ```python
85
+ from brainpatch import BrainPatchedModel
86
+
87
+ model = BrainPatchedModel.from_pretrained(
88
+ "Qwen/Qwen2.5-1.5B-Instruct", backend="transformers", device="auto"
89
+ )
90
+ patch = model.install("./experimental-feature-727.brainpatch")
91
+ patch.strength = 0.8
92
+ print(model.generate("Evaluate my idea."))
93
+ ```
94
+
95
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/blackdromeai-labs/brainpatch/blob/main/LICENSE)
96
+ [![Format](https://img.shields.io/badge/format-.brainpatch%20v1-orange.svg)](https://github.com/blackdromeai-labs/brainpatch/blob/main/docs/patch-format.md)
97
+ [![Blackdrome AI Labs](https://img.shields.io/badge/Blackdrome%20AI%20Labs-open%20source-black.svg)](https://github.com/blackdromeai-labs)
98
+
99
+ ---
100
+
101
+ ## What it is
102
+
103
+ A **BrainPatch** is a small file containing one or more direction vectors and the
104
+ layers to add them to. Loading one installs a behavioural intervention into a
105
+ model whose weights are never touched.
106
+
107
+ BrainPatch is **infrastructure, not a method**. It is agnostic about how a
108
+ direction was discovered — difference-of-means, PCA, a linear probe, an SAE
109
+ feature, or anything else — and it is not tied to any one behaviour. The project
110
+ began as an SAE tool; on our own benchmark the SAE finished behind PCA and
111
+ difference-of-means, and the format outlived that assumption.
112
+
113
+ | | LoRA / fine-tune | Prompt engineering | **BrainPatch** |
114
+ |---|---|---|---|
115
+ | changes weights | yes | no | **no** |
116
+ | artifact size | MB – GB | n/a | **6.7 KB** (measured, below) |
117
+ | costs context | no | yes, every call | **no** |
118
+ | adjustable at runtime | no | crudely | **yes, continuously** |
119
+ | removable mid-session | no | yes | **yes** |
120
+ | changeable *during* generation | no | no | **yes** (token schedules) |
121
+
122
+ Measured on the reference patch in this repository: **6,856 bytes**, against
123
+ **3,087,467,144 bytes** of Qwen2.5-1.5B-Instruct weights — a ratio of
124
+ **450,331×**. Runtime overhead on an L4 was **−1.3% (within noise)** on
125
+ Transformers and **+2.2% (tokens/second)** on vLLM, with
126
+ **0.01 MB** of extra VRAM.
127
+
128
+ ## Read this before using the example patches
129
+
130
+ The runtime is verified. **The example patches' behaviour is weakly established
131
+ and we label them accordingly.** BrainPatch will not name a patch after a
132
+ behaviour it has not demonstrated.
133
+
134
+ | patch | evidence | what it is |
135
+ |---|---|---|
136
+ | `experimental-feature-727` | `none` | named after its feature ID because its controls came back negative — a scale-matched random direction moved the output *further* from baseline than the real one. Ships as a working demonstration of the format. |
137
+ | `experimental-commitment` | `controlled_interventional` | a real, replicated effect on commitment / anti-evasion. **Formerly named `anti-sycophancy`; that name was withdrawn** because sycophantic agreement *rose* under it. |
138
+ | `experimental-independent-criticism-candidate` | `none` | a candidate direction, never validated. |
139
+
140
+ ## What the research established, in order
141
+
142
+ Four experiments, four times the conclusion changed. The write-ups include the
143
+ numbers that looked good before they didn't.
144
+
145
+ **`anti_sycophancy_v1` — negative.** 198 propositions, topic-disjoint splits,
146
+ true-assertion controls so "disagree with everything" cannot score as
147
+ independence, criteria [pre-registered](https://github.com/blackdromeai-labs/brainpatch/blob/main/experiments/anti_sycophancy_v1/success_criteria.md)
148
+ before the test split was opened. The selected direction beat all ten
149
+ scale-matched random directions, beat three unrelated real directions and
150
+ reversed under sign inversion — but its per-item effect correlated **+0.457**
151
+ with response-length gap, over a threshold set in advance, and free generation
152
+ moved the wrong way. [Full write-up](https://github.com/blackdromeai-labs/brainpatch/blob/main/experiments/anti_sycophancy_v1/RESULTS.md).
153
+
154
+ Three findings outlived the null:
155
+
156
+ - **The SAE came last.** Both SAE variants lost to PCA, to a linear probe and to difference-of-means, and both failed the true-claim control by making the model disagree with *true* statements.
157
+ - **Probe accuracy is not steerability.** A probe separating the classes at **100%** accuracy steered worse than PCA, which uses no labels when fitting. Readable and pushable are different properties.
158
+ - **Where you inject beats almost everything else.** Prompt-token steering was ~**6×** more effective than steering generated tokens.
159
+
160
+ **`anti_sycophancy_v2` — negative, test split never opened.** Rebuilt the dataset
161
+ to remove the length confound (preferred response longer in **53%** of pairs
162
+ instead of 96%). The pre-registered free-generation gate stopped it at
163
+ validation. It produced the most useful finding in the project, across 27
164
+ configurations that passed every log-probability gate:
165
+
166
+ ```
167
+ corr(log-probability effect, free-generation correction gain) = −0.298
168
+ ```
169
+
170
+ **Ranking activation-steering directions by paired log-probability
171
+ *anti-selects* for the behaviour you want in generation.** That explains v1
172
+ exactly: its winner had a strong, control-beating log-prob effect and a *falling*
173
+ correction rate. Only **27 of 330** configurations survived the true-claim guard —
174
+ most directions that "work" are simply contrarian.
175
+ [Full write-up](https://github.com/blackdromeai-labs/brainpatch/tree/main/experiments/anti_sycophancy_v2).
176
+
177
+ **`anti_sycophancy_v3` — the first positive result.** Selecting by *generated
178
+ behaviour* instead of log-probability, on a third fresh dataset, a single SAE
179
+ feature injected at the prompt raised the correction rate on false user
180
+ assertions from **0.233 to 0.400** on 200 held-out items — CI [+0.092, +0.242],
181
+ McNemar **p = 3.6 × 10⁻⁵**, all **11** pre-registered gates passed.
182
+ [Full write-up](https://github.com/blackdromeai-labs/brainpatch/tree/main/experiments/anti_sycophancy_v3).
183
+
184
+ **`anti_sycophancy_specificity_v1` — replicated the effect, then took its name
185
+ away.** On 407 fresh propositions with zero overlap against all prior data, the
186
+ effect reproduced *through the compiled artifact*: correction **0.248 → 0.376**,
187
+ CI [+0.072, +0.184], McNemar **p = 2.1 × 10⁻⁵**.
188
+
189
+ Against **100** norm-matched random directions it reached the **95.0th
190
+ percentile**, empirical **p = 0.0594** — a pre-registered pass, but 5 of 100
191
+ random directions matched or beat it. Specificity is real and weak.
192
+
193
+ Then the transition matrix showed what the headline hid. Only ~**22%** of the
194
+ gain (+0.028, p = 0.17) was a claim moving from *agreement* to *challenge*. Most
195
+ was evasive replies becoming committed ones, landing correct or sycophantic at
196
+ chance (28 vs 26, p = 0.89) — and **sycophantic agreement rose, 0.280 → 0.352**.
197
+ Removing the user's endorsement kept 75% of the effect.
198
+
199
+ So the patch was renamed **`experimental-commitment`**. It steers commitment and
200
+ suppresses evasion; it does not reduce sycophancy. The evidence level was
201
+ deliberately *not* upgraded ([why](https://github.com/blackdromeai-labs/brainpatch/blob/main/docs/evidence_level_decision.md)).
202
+ [Full write-up](https://github.com/blackdromeai-labs/brainpatch/tree/main/experiments/anti_sycophancy_specificity_v1).
203
+
204
+ **`artifact_fidelity_v1` — a claim of ours, refuted.** We had shipped an artifact
205
+ with an inverted coefficient sign that passed a cosine check at 1.0, and
206
+ hypothesised that behavioural testing was therefore *required* to catch such
207
+ defects. Thirteen controlled corruptions said otherwise: signed cosine, delta
208
+ norm and execution tracing together caught **all eleven** real defects, and
209
+ behavioural reproduction caught **nothing they missed** while missing two they
210
+ caught. The lesson was narrower than the hypothesis — *the reference had been
211
+ unsigned* — and it is now the [`brainpatch verify`](https://github.com/blackdromeai-labs/brainpatch/blob/main/docs/verification.md)
212
+ workflow. [Full write-up](https://github.com/blackdromeai-labs/brainpatch/blob/main/experiments/artifact_fidelity_v1/report.md).
213
+
214
+ Total compute for all five experiments: **~$4.65**. None of these failures needed
215
+ scale to find; they needed the next question to be asked.
216
+
217
+ ---
218
+
219
+ ## Install
220
+
221
+ ```bash
222
+ pip install brainpatch # core: format, registry, CLI. No ML stack.
223
+ pip install "brainpatch[transformers]" # PyTorch backend (CUDA / CPU / MPS)
224
+ pip install "brainpatch[llamacpp]" # GGUF control-vector export
225
+ pip install "brainpatch[vllm]" # high-throughput serving
226
+ pip install "brainpatch[server]" # OpenAI-compatible HTTP API
227
+ pip install "brainpatch[ui]" # local web UI
228
+ pip install "brainpatch[research]" # patch authoring: SAEs, extraction
229
+ ```
230
+
231
+ The core wheel pulls in **only `typer` and `rich`** — no torch, no numpy, not
232
+ even `safetensors` (the container is parsed by a pure-Python reader). Installing,
233
+ inspecting and validating a patch works on a bare Python 3.10+.
234
+
235
+ ```bash
236
+ brainpatch doctor # which engines are installed and usable
237
+ brainpatch backends # full capability matrix
238
+ ```
239
+
240
+ ## Supported backends
241
+
242
+ | Backend | Status | Static | Schedules | Server | Verified against |
243
+ |---|---|---|---|---|---|
244
+ | **Transformers** | **verified** | ✅ | ✅ | ✅ | Qwen2.5-1.5B bf16, NVIDIA L4 |
245
+ | **llama.cpp** | **verified** | ✅ | ❌ | ✅ | upstream **b10344**, **Q4_K_M** GGUF |
246
+ | **vLLM** | **verified** | ✅ | ❌ | ✅ | **vLLM 0.11.0**, L4, OpenAI server |
247
+ | **MLX-LM** | experimental | ✅ | ❌ | ❌ | never run on Apple Silicon |
248
+
249
+ "Verified" means an automated acceptance suite ran against a real model and
250
+ passed. "Implemented" means the adapter is written and reviewed but no hardware
251
+ has confirmed it. We do not use the word "supported" for the latter.
252
+
253
+ What "verified" cost, concretely — each backend ran an automated acceptance
254
+ suite against a real model:
255
+
256
+ - **Transformers**: weights provably unchanged, `strength=0` byte-identical to
257
+ baseline, measured delta norm **28.5177 == expected 28.5177**, schedules fire
258
+ at the keyframe, disable/remove restore baseline.
259
+ - **llama.cpp**: 0-based BrainPatch layer 18 maps to the `direction.19` tensor
260
+ (1-based), scale-0 output character-identical to baseline, non-zero scale
261
+ changes output, no crash — on a real 1.12 GB Q4_K_M GGUF.
262
+ - **vLLM**: hooks confirmed **inside the worker process** (`Qwen2ForCausalLM`,
263
+ 28 layers, `active_hooks: 1`, `cuda:0`), OpenAI server serves concurrent
264
+ requests with no state leak, mismatched per-request strength rejected 400.
265
+
266
+ Capability gaps are real, not oversights:
267
+
268
+ - **llama.cpp has no token schedules** — a control vector is bound for a whole
269
+ run and the CLI exposes no per-decode-step control.
270
+ - **vLLM has no per-request strength** — continuous batching means one forward
271
+ pass serves many sequences, so a per-request coefficient would change *other
272
+ users'* output. Patch state is frozen while serving, which is what makes
273
+ concurrency safe.
274
+ - **Quantization is not assumed to transfer.** A direction fitted on bf16 was
275
+ checked to still change output at Q4_K_M; whether it produces the *same*
276
+ behavioural effect at 4-bit is untested.
277
+
278
+ ## CLI
279
+
280
+ ```bash
281
+ brainpatch install <file.brainpatch | owner/repo>
282
+ brainpatch list
283
+ brainpatch inspect <name>
284
+ brainpatch validate <name> --model Qwen/Qwen2.5-1.5B-Instruct
285
+ brainpatch verify <name> --reference known-good.brainpatch --model <model>
286
+ brainpatch run "prompt" --model <model> --patch <name>
287
+ brainpatch compare --model <model> --patch <name> --prompt "..."
288
+ brainpatch chat --model <model> --patch <name>
289
+ brainpatch serve --model <model> --patch <name> --port 8000
290
+ brainpatch ui
291
+ brainpatch compile research.json --sae ./sae.pt -o out.brainpatch
292
+ brainpatch compile out.brainpatch --backend llama.cpp -o cv.gguf
293
+ brainpatch benchmark --model <model> --patch <name>
294
+ brainpatch doctor
295
+ brainpatch backends
296
+ ```
297
+
298
+ ## Verifying a patch
299
+
300
+ `validate` asks *is this file well-formed and loadable*. `verify` asks *does it
301
+ do what it says*. Those are different questions:
302
+
303
+ ```bash
304
+ brainpatch verify ./my-patch.brainpatch --reference ./known-good.brainpatch --model Qwen/Qwen2.5-1.5B-Instruct
305
+ ```
306
+
307
+ ```
308
+ numerical
309
+ signed_direction FAIL signed cosine -1.00000000 (threshold 0.999000);
310
+ unsigned |cos| 1.00000000 would have passed
311
+ ```
312
+
313
+ Four levels, each catching what the one above it cannot. A level whose inputs are
314
+ missing is reported `skipped`, never dropped — an absent check reads as a pass.
315
+
316
+ | level | needs | catches |
317
+ |---|---|---|
318
+ | structural | nothing | corrupt archive, malformed manifest, wrong shape, wrong base model |
319
+ | numerical | `--reference` | wrong direction **including sign**, wrong magnitude |
320
+ | execution | `--model` | wrong layer, wrong injection site, lost schedule |
321
+ | behavioural | a recorded result | drift that survives all of the above |
322
+
323
+ We built this because we shipped a sign-inverted artifact that passed every
324
+ file-level check. Measured across thirteen controlled corruptions: schema,
325
+ checksum, shape and model-compatibility caught **0 of 11** real defects; layer and
326
+ site defects were invisible to *every* file-level check including signed cosine.
327
+ Only tracing the runtime found them.
328
+
329
+ **Checksums prove a file was not modified. They prove nothing about whether it
330
+ encodes the right intervention.** Full detail: [docs/verification.md](https://github.com/blackdromeai-labs/brainpatch/blob/main/docs/verification.md).
331
+
332
+ ## OpenAI-compatible server
333
+
334
+ ```bash
335
+ brainpatch serve --model Qwen/Qwen2.5-1.5B-Instruct --patch my-patch --port 8000
336
+ ```
337
+
338
+ ```python
339
+ from openai import OpenAI
340
+ client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
341
+ client.chat.completions.create(model="qwen", messages=[{"role": "user", "content": "hi"}])
342
+ ```
343
+
344
+ Existing clients work unchanged. Patch strength is configured at **startup**, not
345
+ per request — with a shared model, honouring a per-request strength would alter
346
+ other in-flight requests' output. A mismatched `brainpatch` extra field returns
347
+ a clear 400 rather than being silently ignored.
348
+
349
+ ## The `.brainpatch` format
350
+
351
+ A ZIP containing only inert data:
352
+
353
+ ```
354
+ manifest.json what to add, where, how strongly
355
+ vectors.safetensors the direction vectors
356
+ checksums.json sha256 of every member
357
+ README.md optional
358
+ ```
359
+
360
+ **A patch cannot execute code.** No pickle, no scripts. The loader reads members
361
+ by exact name, rejects unexpected members, absolute paths, `..` traversal,
362
+ symlinks and zip bombs, and verifies every checksum before use. Archives are
363
+ byte-deterministic, so a published patch has a stable hash.
364
+
365
+ Compatibility is enforced in three modes — `strict` (default; model id and
366
+ revision must match), `architecture`, and `unsafe` — because a direction fitted
367
+ in one model's basis means nothing in another's.
368
+
369
+ Full specification: [docs/patch-format.md](https://github.com/blackdromeai-labs/brainpatch/blob/main/docs/patch-format.md).
370
+
371
+ ## Architecture
372
+
373
+ ```mermaid
374
+ flowchart TD
375
+ REPO["patch repository<br/>(Hugging Face, a file, anywhere)"]
376
+ ART["tiny .brainpatch<br/>~6 KB"]
377
+ REPO --> ART
378
+
379
+ ART --> LOCAL["local workstation"]
380
+ ART --> CLOUD["cloud server"]
381
+
382
+ LOCAL --> TF["Transformers"]
383
+ LOCAL --> LC["llama.cpp"]
384
+ LOCAL --> MLX["MLX"]
385
+ CLOUD --> VLLM["vLLM"]
386
+
387
+ TF --> FROZEN["frozen base LLM<br/>weights untouched"]
388
+ LC --> FROZEN
389
+ MLX --> FROZEN
390
+ VLLM --> FROZEN
391
+
392
+ style ART fill:#fff4e6,stroke:#d99b4a
393
+ style FROZEN fill:#eefaf0,stroke:#4ad97a
394
+ ```
395
+
396
+ The runtime knows nothing about where a patch was trained. Separately, the
397
+ research toolkit is how patches are *made*:
398
+
399
+ ```mermaid
400
+ flowchart TD
401
+ A["activation extraction"] --> B["sparse autoencoder"]
402
+ B --> C["feature discovery"]
403
+ C --> D["causal validation<br/>+ controls"]
404
+ D --> E["patch compile"]
405
+ E --> F["publish"]
406
+ F -.-> G["a .brainpatch file"]
407
+
408
+ H["Modal · local GPU · cluster<br/>(optional research backend)"] -.-> A
409
+
410
+ style G fill:#fff4e6,stroke:#d99b4a
411
+ style H fill:#eef2ff,stroke:#7a8ad9
412
+ ```
413
+
414
+ ## Offline
415
+
416
+ Once the model, patch and backend are local, BrainPatch needs no network. No
417
+ telemetry, no phone-home, no hosted dependency. `--offline` refuses network
418
+ access outright.
419
+
420
+ ## How it works
421
+
422
+ A forward hook on decoder block *L* adds `strength × coefficient × vector` to the
423
+ residual stream. That is the entire mechanism.
424
+
425
+ Two guarantees the test suite enforces on real hardware:
426
+
427
+ - **`strength = 0` is byte-identical to baseline.** Not approximately — when the
428
+ resolved edit list is empty the tensor is never touched, so there is no
429
+ arithmetic to round. Verified: 0 applied passes, identical output.
430
+ - **Weights are never modified.** Verified by comparing layer-18 weights before
431
+ and after a patched generation.
432
+
433
+ ## Evidence levels
434
+
435
+ Every patch declares one, and the CLI prints it everywhere:
436
+
437
+ `none` → `correlational` → `predictive` → `interventional` →
438
+ `controlled_interventional` → `replicated`
439
+
440
+ The top rung is deliberately **not** called "causal": passing scale-matched
441
+ controls once, on one model at one layer with one prompt set, is evidence
442
+ *consistent with* a causal effect, not a demonstration of causation.
443
+
444
+ ## Experimental evidence
445
+
446
+ , and what its controls actually showed:
447
+
448
+ | condition | divergence from baseline | delta norm |
449
+ |---|---|---|
450
+ | zero | **0.000** (6/6 byte-identical) | 0.0 |
451
+ | positive | 0.710 | 28.5178 |
452
+ | **random direction** | **0.847** | 28.5178 |
453
+
454
+ A scale-matched random direction moved the output *further* than the real
455
+ feature. There is no evidence of a feature-specific effect. A later audit found
456
+ the cause: the `max_activation` selection rule picked a degenerate cluster of 32
457
+ near-duplicate features that all fire on the same rare token.
458
+
459
+ Full account, including a retracted control: [RESEARCH_LOG.md](https://github.com/blackdromeai-labs/brainpatch/blob/main/RESEARCH_LOG.md).
460
+
461
+ ## Create your own patch
462
+
463
+ ```bash
464
+ pip install "brainpatch[research]"
465
+ brainpatch compile my-research-patch.json --sae ./sae_latest.pt -o my.brainpatch
466
+ ```
467
+
468
+ The compiler materialises SAE decoder columns into raw residual-space vectors, so
469
+ the artifact is self-contained. Verified numerically: the compiled patch produced
470
+ a delta norm of **28.5177** against the research pipeline's **28.5178**.
471
+
472
+ Vectors from any method work — difference of means, PCA, a learned controller.
473
+ The runtime does not care; provenance is recorded in metadata.
474
+
475
+ ## Research toolkit
476
+
477
+ `brainpatch/research/` holds activation extraction, Top-K SAE training, feature
478
+ discovery, causal validation and patch search. Installed only by the `research`
479
+ extra and **never imported by the runtime**.
480
+
481
+ ## Reproducing our experiments with Modal
482
+
483
+ This repository's experiments ran on [Modal](https://modal.com) because the
484
+ development machine deliberately carries no ML stack. **Modal is how we build
485
+ BrainPatch; it is not how you use it.**
486
+
487
+ ```bash
488
+ pip install "brainpatch[modal,research]"
489
+ modal run modal_app/app.py::smoke_pipeline
490
+ modal run modal_app/app.py::test_transformers_backend
491
+ modal run modal_app/app.py::sae_unit_tests
492
+ ```
493
+
494
+ Total metered spend for the entire project to date: **$2.08**, including all
495
+ three backend verifications and the behavioural experiment.
496
+
497
+ See [docs/modal-infrastructure.md](https://github.com/blackdromeai-labs/brainpatch/blob/main/docs/modal-infrastructure.md).
498
+
499
+ ## Testing
500
+
501
+ ```bash
502
+ pytest # 601 pure-Python tests, no ML stack
503
+ modal run modal_app/app.py::sae_unit_tests # SAE maths (needs torch)
504
+ modal run modal_app/app.py::test_transformers_backend # real-model acceptance
505
+ ```
506
+
507
+ ## Limitations
508
+
509
+ 1. **One behavioural patch has replicated; none is strongly specific.**
510
+ `experimental-commitment` reproduced its effect on fresh data through the
511
+ compiled artifact, but sits at only the 95.0th percentile of 100 matched
512
+ random directions (empirical p = 0.0594). `experimental-feature-727`'s
513
+ controls are outright negative.
514
+ 2. **Behavioural claims are single-model.** Everything behavioural was measured
515
+ on Qwen2.5-1.5B-Instruct at one layer and one magnitude. MLX has never run on
516
+ Apple Silicon.
517
+ 3. **`experimental-commitment` is Transformers-only.** Its prompt-only injection
518
+ site is inexpressible in llama.cpp and vLLM — a capability boundary, not a
519
+ defect in either. The backends themselves are verified for static patches.
520
+ 4. **The vLLM adapter uses vLLM internals.** vLLM exposes no public
521
+ activation-hook API, so that path is version-sensitive.
522
+ 5. **Quantization is untested.** A direction fitted on bf16 is not guaranteed to
523
+ behave the same at Q4.
524
+ 6. **SAE features may be polysemantic**, and steering can affect unrelated
525
+ capabilities — the smoke test saw 9/10 → 8/10 on ten probes, far too small a
526
+ sample to establish degradation.
527
+ 7. Results depend on model revision and generation settings. Both are pinned.
528
+
529
+ ## Repository layout
530
+
531
+ ```
532
+ brainpatch/
533
+ ├── patch/ format, loader, registry, compiler, validation (no ML stack)
534
+ ├── runtime/ backend contract, capabilities, scheduling, model API
535
+ ├── backends/ transformers · llamacpp · vllm · mlx
536
+ ├── server/ OpenAI-compatible API
537
+ ├── ui/ local Gradio app
538
+ ├── schemas/ v0.1 research patch, SAE config, manifests
539
+ ├── verify/ artifact-fidelity checks and the corruption suite (no ML stack)
540
+ └── research/ SAE training, extraction, discovery, validation
541
+ modal_app/ research + integration-test orchestration (optional)
542
+ experiments/ frozen research record, including the negative results
543
+ patchbench/ frozen evaluation scaffolding, not part of the wheel
544
+ tests/ pure-Python tests · tests/remote/ needs torch
545
+ ```
546
+
547
+ ## Links
548
+
549
+ - [Blackdrome AI Labs](https://github.com/blackdromeai-labs)
550
+ - Model & SAE artifacts: [09Catho/BrainPatch-Qwen2.5-1.5B](https://huggingface.co/09Catho/BrainPatch-Qwen2.5-1.5B)
551
+ - Feature database: [09Catho/BrainPatch-Features-Qwen2.5-1.5B](https://huggingface.co/datasets/09Catho/BrainPatch-Features-Qwen2.5-1.5B)
552
+
553
+ Base model [Qwen/Qwen2.5-1.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct) (Apache-2.0), not redistributed.
554
+ Corpus [Salesforce/wikitext](https://huggingface.co/datasets/Salesforce/wikitext) (CC BY-SA 3.0), not redistributed.
555
+
556
+ Apache-2.0. Maintained by Blackdrome AI Labs.
@@ -0,0 +1,82 @@
1
+ brainpatch/__init__.py,sha256=KMx-Ke8fbbo4iBiFRAwk7G_WzSfeC5q0JVojPYthuyc,2951
2
+ brainpatch/cli.py,sha256=ZzEBzCGl5AfYXsydY5ICuN_WDcqiiUtaIIj6re6nW3A,32199
3
+ brainpatch/config.py,sha256=nJSNajgCmmNvHGShQkl_vReP_OTlqCVEcO4V-6CsfvU,8216
4
+ brainpatch/paths.py,sha256=p1ixQ-iaRekaJ7C2dCSggvAtJ1UuKe5NDpd3PTcpDcA,6239
5
+ brainpatch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ brainpatch/backends/__init__.py,sha256=3k3nf9NNvvmPjnukjc55v7PoLyBCcedTZhukZuOFnOA,830
7
+ brainpatch/backends/llamacpp.py,sha256=wKNWymMTXq17XFBW7RJ5_KkDICIFOsxMnpRFofiHIG0,14993
8
+ brainpatch/backends/mlx_backend.py,sha256=5DH-sQS5PPNxP_85DY4S1lV5ThT62K_ZNkDgDWwymCQ,7850
9
+ brainpatch/backends/transformers_backend.py,sha256=299ex2tGjJG9hxDdcmQcEwrzp1faentXqYq8ZJJQVWY,17982
10
+ brainpatch/backends/vllm_backend.py,sha256=FzZHUBz8YetL50XTqTXA-dvZN_aCI_jNLP8Y_a70o8M,12348
11
+ brainpatch/backends/vllm_worker.py,sha256=qdRzXvrVQ1OCt8PVZ3QPKOxw--KweAQElgTeBOSHHPc,5052
12
+ brainpatch/datasets/__init__.py,sha256=g07hPIXvualNyP7ajgjRbeKA8hoZWkKRC3-olyfT39Q,520
13
+ brainpatch/datasets/contrast_sets.py,sha256=sA8Xdolmuhkolxwuquvkjz3J7RgcPAZgfhuEMUZjKLQ,2044
14
+ brainpatch/evaluation/__init__.py,sha256=6g90KIRd7WET1uEAF9C5Fbs2_WEY-x5d7xHz0m61Bmg,754
15
+ brainpatch/evaluation/metrics.py,sha256=NLpm16yYW68G8kBz7EhrjSLFR9GJ5C5UuL25mGDc_yo,8050
16
+ brainpatch/patch/__init__.py,sha256=Nnaexec3wwtfsIn3zJWJujIbTuw3eKUBM4iJjWMGSFw,1521
17
+ brainpatch/patch/compiler.py,sha256=Zo9qzBgjsA7X8Z5c2WZ8XLy8tp0dKdSpoZkYhl7pwH4,12183
18
+ brainpatch/patch/format.py,sha256=rKwpc8lLF8esSYTerGhKorZ519jVolwRkmLrUhN0Njo,20352
19
+ brainpatch/patch/loader.py,sha256=tkNMG4N6amj6kUAZWTh_ADY6cHsaw2PhnmaoRO7N6t0,12055
20
+ brainpatch/patch/registry.py,sha256=mZO9yuR7cKQ6VpvASBNCQCXHLV7eFFBo6Z2FeVvGrXc,10593
21
+ brainpatch/patch/tensors.py,sha256=IrGZBA1GnDeskeCvLGEKAFPUqiAJCcHGIhq1tQthTWo,8316
22
+ brainpatch/patch/validation.py,sha256=4IEXcyW8sVgPcbifRc0fUpBBNgXyjlO5lYgCWB53qcw,5731
23
+ brainpatch/research/__init__.py,sha256=jX-OHVZoUsuKkvPziONekgjq8r808YikN5P0dzINqzk,719
24
+ brainpatch/research/antisycophancy.py,sha256=SAmmHN5zACHPuvd1-hRKE4jitOk5ZIPXjWyMMQN8O1E,12744
25
+ brainpatch/research/behaviour_eval.py,sha256=MkhedfsQzat_sjmnL9YIIDGVD4tPUwzA0AG6DKmli9U,26810
26
+ brainpatch/research/generation_eval.py,sha256=7zzU_Nm46Ko-9SNCKExRzKnKGmgDmPXcQVevDJ77PhA,13280
27
+ brainpatch/research/stance_rubric.py,sha256=rjMgdsAFHvb6KnXOzUCHVKQiKvXxklgkHCNGNGuX2zo,5653
28
+ brainpatch/research/sycophancy_data.py,sha256=Di_3_Z1BeA6Lr-pAg3Ulk94tabbSpLv0u-cIOADSP0Y,77943
29
+ brainpatch/research/sycophancy_data_r1.py,sha256=c5ZqXXEmkVTV2Hh3poPiE5Q-Cre_U3xgvHpgqy3Cs6M,124488
30
+ brainpatch/research/sycophancy_data_v2.py,sha256=1R0C0JkLSKIPS67Sjd0hf70KWVykiaH5G45JPnU0dWk,143202
31
+ brainpatch/research/sycophancy_data_v3.py,sha256=poFnZnTNPC_FSZBnX2bv1_ZFLiDUUp7Z7Eu6pGnmfVQ,167637
32
+ brainpatch/research/sycophancy_v2_build.py,sha256=EPRwXXCHM_-_sZBh5iPhdnsZoLeK-oB3FmquXoVf51I,13962
33
+ brainpatch/research/sycophancy_v3_build.py,sha256=czlkjaqjEY79qFrIxAcIkHccYCw6dqpW5wJibuGx3lc,8011
34
+ brainpatch/research/utility_probe.py,sha256=PkRYwml7ZVpAPk8FU0dxljOG8vuhoZqOWU7_n7N8IdQ,7285
35
+ brainpatch/research/ml/__init__.py,sha256=t3o4MUwSoF0g1VVAd6pUzKjFatmMXBlNmSBaoBhm6Bo,1215
36
+ brainpatch/research/ml/activation_store.py,sha256=OF6qNek3caTWG_xwkbCkW7ma8CxUdU2INhRJHQXKfYo,8953
37
+ brainpatch/research/ml/causal.py,sha256=_IUS7cw94AqitxzVtfnYuo8Ky44MPnuTi334Y5ozYv0,15556
38
+ brainpatch/research/ml/corpus.py,sha256=frOh-5JlswyyYAy623SD1s8ycEiMev_aH3X4D4Gtc6M,5584
39
+ brainpatch/research/ml/evaluation.py,sha256=y9UkVKgoNL1fizfBO6dsWEpFudU06-G7MyzOSNGGRi4,7319
40
+ brainpatch/research/ml/extraction.py,sha256=fP67L1L4aKsP9lvTvMjBZ-EjAfS_o5glSCZeXykMY4M,17644
41
+ brainpatch/research/ml/feature_analysis.py,sha256=Q2hr_bRA-WJ_exomlfFwTEI2khwLG7DrI6MztPHLatc,12466
42
+ brainpatch/research/ml/generation.py,sha256=b1W46CioijZTrkiBCypJF8q_l5o2NKxfDbRQ2AXTQEY,3967
43
+ brainpatch/research/ml/hooks.py,sha256=TE7mB8uDbtSpBt_vTs2ek9WcwGZTnmC-98RRy0Kj4pM,6282
44
+ brainpatch/research/ml/intervention.py,sha256=ABoS6PJQnTPFh3_4tfqc0PBIlqbBKjq5CAP77xJkPSE,10778
45
+ brainpatch/research/ml/model.py,sha256=EtjBrXJfo3re3_n5XJT9C67h7kxYPBKoXwuNjbJ5iYk,7890
46
+ brainpatch/research/ml/patch_search.py,sha256=Qh71C7vlRxiPvFAa04NQFxdGjY4EMwUDDbtEy8V2Lsg,12819
47
+ brainpatch/research/ml/runtime.py,sha256=TMjuNiYKejxfNvWeM_ef-8mnVmBRPykQYc1-WAlKsok,13870
48
+ brainpatch/research/ml/sae.py,sha256=jGdPbpjyyWYOc3qYA5mxI9-Nk14Cf2mCOAYLDdylRA4,16132
49
+ brainpatch/research/ml/training.py,sha256=art4cKq5CQt4qzcLqOugAAAEswq_8lJM_4XVF5-M-io,14201
50
+ brainpatch/runtime/__init__.py,sha256=D4wcPFtTfntzwPZNLu04dRuTbiNTCIp95cYWwcVJ1XU,1555
51
+ brainpatch/runtime/auto.py,sha256=TyaLRPRjLowrJXRHz2d-CfGxvzJT1rdJTGWyaBeQaXc,5328
52
+ brainpatch/runtime/base.py,sha256=SGIG7h1vYfiPuJcpgGaSqqrU8vpoaTdKhnvk0swrXkE,11582
53
+ brainpatch/runtime/capabilities.py,sha256=hFF5J51XUznx6cQ4pv1Lfrbfk5yQ9XUr82LsuafKLTw,3097
54
+ brainpatch/runtime/model.py,sha256=K1ncdejDM7KEN6u9BL39uS36pvKmqKPEA2gAcTCqAj0,8995
55
+ brainpatch/runtime/scheduling.py,sha256=lKoZzB9dd0lwk1zk3NepTkdWloReyqBeDULqaAgmeR4,465
56
+ brainpatch/schemas/__init__.py,sha256=pifVB4F71CD91Zr0xzBpwQ7hYPK1b8QLijdnmcwU-yA,1077
57
+ brainpatch/schemas/contrast.py,sha256=ONKDv70xq7yCA4zS8fKfkTf9ajc6uoRg-WiiXMQwSCM,6067
58
+ brainpatch/schemas/feature.py,sha256=FHH0mEDPgHDxF8PGdVKT4eucDiZDt7Jc5DMLospJbY0,7307
59
+ brainpatch/schemas/manifest.py,sha256=I8MrJ4J4u8XDXMA_Z9byIfYbYFv2xMbhMTkU0MsZDko,6005
60
+ brainpatch/schemas/patch.py,sha256=FfMowhD4cXW3u_W4nEXzQZFPI2-WyulDyEDd0urhOaw,14989
61
+ brainpatch/schemas/patch_io.py,sha256=aiiQtG1qicOZttxjLcB4uvimmavB3R0ECsaBnAETbvk,2768
62
+ brainpatch/schemas/sae.py,sha256=SMxeZRaMtnp1lbWpU6yV9WshSY52pOG8le6p_BAV3eI,5676
63
+ brainpatch/server/__init__.py,sha256=HnsdCDlHmt86eAddF8wfDazW8estIcwWNkoyZ7DvZEE,321
64
+ brainpatch/server/app.py,sha256=Fcrm3DAyI90VRA9_bZvG-aEifFGo8ot9nbTmCeurN40,10381
65
+ brainpatch/steering/__init__.py,sha256=Rz50-LapaVMd0JNNWdty9iV-8MvSIoDIOksxYk3cF98,550
66
+ brainpatch/steering/plan.py,sha256=7ZRSTnVCWjHS1Bw3ZEwZ0xkYqDu7ke2Roedt1NLKF3k,6499
67
+ brainpatch/steering/schedule.py,sha256=bPyFLtFcpbafEia8I5Atl3a0aKHXlNSc6uAKtPsgvk4,5202
68
+ brainpatch/ui/__init__.py,sha256=L-LLj3WnLtQ35fR77z_oSov050ExXQSCTMN5O73KEac,283
69
+ brainpatch/ui/app.py,sha256=3eCXxYJ_9LzZ0GKkf7qlnNMYDcqSW5gdqP0qWmd8Gzg,8233
70
+ brainpatch/verify/__init__.py,sha256=cOOzWax0UVBJ5Iq6hNVuPIgcQ-56yqmT8x7mz8t_7Xo,2377
71
+ brainpatch/verify/behavioural.py,sha256=gtVKjIedL908iYdQeeTms1kxxxBdj8YVMh9kEm0wF64,6782
72
+ brainpatch/verify/checks.py,sha256=dxMI7PusT3qrxgF4o88w_6QDGFt0KlwEOz_-Nd77TQQ,7747
73
+ brainpatch/verify/corruptions.py,sha256=SQM4W2Zqc7M98p9tnWactDTE8Lrx4Y1jTPVIXOIuWaw,13349
74
+ brainpatch/verify/report.py,sha256=-8NDOlMXKuUqX9mJ6i467b24V-2km5H3g-1PqpEsx2w,5120
75
+ brainpatch/verify/vectors.py,sha256=1cV_9BGcr8lqWaA-jYVD3M1_b2bvlhLYTXysYnt_elA,3469
76
+ brainpatch/verify/workflow.py,sha256=35RZXewK5HYSazLu9xP2KXz5dm5WACSsJHC1EJmA7lo,11812
77
+ brainpatch-1.2.0.dist-info/licenses/LICENSE,sha256=HrlhBo8nyot06bmfjIqiKQVT3mS3XX5daLiYp9FPBxE,10768
78
+ brainpatch-1.2.0.dist-info/METADATA,sha256=k01ETtSkZP06lmdifuDE4PLcBwPi-xaHIZQj41BN5tE,26439
79
+ brainpatch-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
80
+ brainpatch-1.2.0.dist-info/entry_points.txt,sha256=qljuTSXqpXzx9dCWe6pB13qvDiVUhXRiXeAVeS_RGkA,51
81
+ brainpatch-1.2.0.dist-info/top_level.txt,sha256=zCMHR18v_U7ox6zJCP5XjjVBi3yHvVciEe5fCnwO9l8,11
82
+ brainpatch-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ brainpatch = brainpatch.cli:main