killchain 0.1.0__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ .ruff_cache/
8
+ .pytest_cache/
9
+ workspace/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Josh Simmons
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.5
2
+ Name: killchain
3
+ Version: 0.1.0
4
+ Summary: Split a build so a frontier coding agent never sees the risky half.
5
+ Project-URL: Homepage, https://github.com/jcpsimmons/killchain
6
+ Project-URL: Repository, https://github.com/jcpsimmons/killchain
7
+ Project-URL: Issues, https://github.com/jcpsimmons/killchain/issues
8
+ Project-URL: YouTube, https://youtube.com/@drjoshcsimmons
9
+ Author: Josh Simmons
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,claude-code,codegen,codex,partitioning
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.11
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8; extra == 'dev'
22
+ Requires-Dist: ruff>=0.6; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # killchain: refusal-first split build
26
+
27
+ An abliterated orchestrator model (refuseless by default, settings mirrored
28
+ from `~/.hermes/config.yaml`) is the front door. It is the only model that
29
+ sees the full prompt. It splits the job into a **public half** (built by a
30
+ frontier coding agent: Claude Code or Codex, which may fan out to its own
31
+ subagents) and a **private half** (implemented by the orchestrator itself),
32
+ joined by a contract.
33
+
34
+ The partition rule is **refusal risk, nothing else**. Claude and Codex are good
35
+ and fast, but they hedge, caveat, simplify, or refuse when a task looks
36
+ reverse-engineered, unlicensed, regulated, exploitative, or oversized. Those
37
+ parts go to the uncensored model. The frontier gets the rest of the app and
38
+ never learns how the interesting bits work, so it has no position to take.
39
+
40
+ It is not a cost trick and not only a quality trick: a brief that says "match
41
+ the behaviour of X" gets you `from memory` disclaimers and invented constants.
42
+ A brief that says "given these fields return a float in [0, 1], monotonic in
43
+ both" gets you working code.
44
+
45
+ ## Install
46
+
47
+ Python 3.11+, no dependencies. Pick one:
48
+
49
+ ```bash
50
+ uv tool install killchain # installs the `killchain` command
51
+ pipx install killchain # same, if you use pipx
52
+ pip install killchain # into the current environment
53
+ uvx killchain "your task" # zero-install trial, no venv needed
54
+ ```
55
+
56
+ From a checkout, before it is on an index:
57
+
58
+ ```bash
59
+ uv tool install git+https://github.com/jcpsimmons/killchain
60
+ # or, for development:
61
+ uv venv && uv pip install -e ".[dev]"
62
+ python harness.py --version
63
+ ```
64
+
65
+ Then find out what is missing before you waste a run:
66
+
67
+ ```bash
68
+ killchain --doctor
69
+ ```
70
+
71
+ `doctor` checks your config, the `claude`/`codex` binary, both API keys, the
72
+ private directory mode, the denylist, and does one live round-trip to each
73
+ endpoint. It exits non-zero only if something fatal to a run is wrong.
74
+
75
+ ## First run
76
+
77
+ Nothing is written into the directory you installed to. On first use killchain
78
+ creates:
79
+
80
+ | Path | What |
81
+ | --- | --- |
82
+ | `~/.config/killchain/config.toml` | your config, seeded from the shipped default |
83
+ | `~/.config/killchain/denylist.txt` | literal terms and `re:` patterns the gate rejects |
84
+ | `~/.local/share/killchain/workspace/` | public halves, one directory per run |
85
+ | `~/killchain-private/` | mode 0700: raw prompt, contract, `private_impl/`, risk report |
86
+
87
+ Override with `$XDG_CONFIG_HOME`, `$XDG_DATA_HOME`, `$KILLCHAIN_CONFIG`,
88
+ `$KILLCHAIN_PRIVATE_ROOT`, or per run with `--private-root` / `--workspace`.
89
+
90
+ ## How it works
91
+
92
+ ```
93
+ prompt ─► orchestrator ─► contract.json (brief + interfaces + decoy fixtures + private fixtures + notes)
94
+ │
95
+ ├─ risk score (8 dimensions, per span) ┐ on the PUBLIC VIEW, plus
96
+ ├─ whole-view pass (is the task itself scary?) ┘ flagged spans go back to orchestrator
97
+ ├─ regex gate + framing hints
98
+ │
99
+ ├─► public/ contracts/ stubs + tests/test_contracts.py ─► frontier agent builds app + tests
100
+ │ (HARNESS_MODE=stub: fixtures answer)
101
+ └─► private/ prompt, contract, private_impl/ ◄─ orchestrator implements to contract
102
+ (HARNESS_MODE=real: tests prove the fit)
103
+ ```
104
+
105
+ ## Where to edit the routing
106
+
107
+ **[killchain/routing.py](https://github.com/jcpsimmons/killchain/blob/main/killchain/routing.py)** is the one file that decides what the frontier is
108
+ allowed to see. Everything else is plumbing that imports from it:
109
+
110
+ | In routing.py | What it controls |
111
+ |-------------------|-----------------------------------------------------------------------|
112
+ | `DIMENSIONS` | the refusal dimensions, and the argument for each. Their `question` text is what Jev is actually asked |
113
+ | `SAFE` | what a frontier agent builds without comment; the "false" criteria |
114
+ | `HARD_RULES` | regexes that block a send outright, no model involved |
115
+ | `FRAMING_HINTS` | non-blocking warnings for clone/hedge/licence wording |
116
+ | `THRESHOLDS` | risk probability needed to pull a span, per dimension and span kind |
117
+ | `classify()` | the final per-span decision; any dimension over its bar routes private |
118
+ | `pick_frontier()` | which coding agent builds the public half, by content if you like |
119
+ | `split_prompt()` | how the raw prompt is cut into spans for the risk map |
120
+
121
+ The denylist (`~/.config/killchain/denylist.txt`) adds literal and regex rules
122
+ without touching any code. It carries two things: secrets that must never
123
+ leave, and terms that tip the frontier off (product names, licence names,
124
+ codenames).
125
+
126
+ The shipped dimensions are a starting taxonomy, not a law: attribution and
127
+ clone framing, reverse engineering, offensive security, extraction and ToS,
128
+ regulated domains, another vendor's model or SDK internals, hedge wording, and
129
+ scope an agent will negotiate away. Add a dimension when you find a refusal
130
+ that none of them predict.
131
+
132
+ ## Trust boundaries
133
+
134
+ To the **orchestrator (refuseless)**: the full prompt, the contract, and the
135
+ private implementation loop. This is the half that is supposed to know
136
+ everything.
137
+
138
+ To the **frontier agent**: only `contract.public.json`, the public brief,
139
+ interface signatures and descriptions, and decoy fixtures. It is told the
140
+ private functions are a black box it should not infer, reimplement, or ask
141
+ about.
142
+
143
+ To **OpenRouter/TypeSafe (Jev)**: the public view span by span, plus, when
144
+ `probe_raw_prompt = true` (default), raw prompt spans. The denylist terms ride
145
+ along in every criteria block either way. Turn scoring off entirely with
146
+ `--no-risk` if OpenRouter is out of bounds.
147
+
148
+ The raw prompt, `private_notes`, `private_plan`, and `private_impl/` live under
149
+ `paths.private_root` (default `~/killchain-private/`), never the frontier's cwd or
150
+ on its PYTHONPATH, and the frontier subprocess gets an environment scrubbed of
151
+ anything pointing there. The frontier runs with permissions skipped, so treat
152
+ that boundary as a convention, not a jail.
153
+
154
+ ## Layers of protection on the outbound view
155
+
156
+ 1. **Orchestrator partition** (semantic): told the rule is refusal risk, shown
157
+ the dimensions, told to rewrite the framing rather than only move content.
158
+ 2. **Regex gate** (deterministic): denylist literals and regexes plus built-in
159
+ secret patterns. Any hit blocks the send.
160
+ 3. **Risk scorer** (probabilistic): `typesafe/jev-1.14` via the OpenRouter
161
+ Decisions API, one `noul` question per dimension per span, criteria from
162
+ routing.py. Then a whole-view pass, because a refusal is triggered by the
163
+ shape of a task and every individual span can be innocent.
164
+ 4. **Human review**: the outbound view, the decoy fixtures, and the span
165
+ scores are printed and you confirm, unless `--yes`.
166
+
167
+ Rejections from 2 and 3 are fed back to the orchestrator with the exact spans,
168
+ so it re-partitions and re-frames.
169
+
170
+ **Probes.** Before it partitions, the harness scores the raw prompt span by
171
+ span and puts the resulting risk map in the partition prompt. The orchestrator
172
+ can also reply `{"probes": ["exact text", ...]}` up to `max_probe_rounds`
173
+ times to score any phrasing it is unsure about before committing.
174
+
175
+ ## Run
176
+
177
+ ```bash
178
+ killchain "build X using our proprietary Y"
179
+ ```
180
+
181
+ ```bash
182
+ killchain -f prompt.md --frontier codex --yes
183
+ ```
184
+
185
+ Flags: `--frontier claude|codex|fake`, `--orch-url`, `--risk-url` (alias
186
+ `--detector-url`), `--no-risk`/`--no-detector`, `--run-id`, `--skip-frontier`
187
+ (public already built, redo private), `--skip-private` (stop after frontier),
188
+ `--private-root`, `--workspace`.
189
+
190
+ Offline end-to-end with canned backends (exercises both feedback loops and the
191
+ whole-view pass):
192
+
193
+ ```bash
194
+ killchain "rank leads" --orch-url fake --frontier fake --detector-url fake --yes
195
+ ```
196
+
197
+ Then run the result with both halves together:
198
+
199
+ ```bash
200
+ cd <workspace>/<run>/public && HARNESS_MODE=real PYTHONPATH=~/killchain-private/<run> python -m app.<entry>
201
+ ```
202
+
203
+ ## Config
204
+
205
+ Edit `~/.config/killchain/config.toml` (or pass `-c FILE`). `[orchestrator]` is
206
+ any OpenAI-compatible chat endpoint; default
207
+ is refuseless with the key read from `$REFUSELESS_API_KEY` or from
208
+ `~/.hermes/.env` at runtime (never stored in the repo). `[frontier]` picks
209
+ `claude` or `codex` and their flags (overridable per run by
210
+ `routing.pick_frontier`). `[risk]` holds the Jev endpoint and model and needs
211
+ `OPENROUTER_API_KEY`; dimensions and thresholds are in routing.py. `[paths]`
212
+ sets the workspace, the private root and an optional denylist location.
213
+
214
+ ## Contract shape
215
+
216
+ Each interface is one Python function in `contracts/<module>.py`, generated
217
+ from the contract and never edited by either side. In stub mode it answers from
218
+ `_fixtures.json` (exact input match, else first fixture). In real mode it
219
+ delegates to `private_impl.<module>.<function>`.
220
+
221
+ Two fixture sets per interface. **Public fixtures** are decoys: right shape,
222
+ wrong values, never computed with the real logic, because a few correct
223
+ input/output points would let the frontier vendor fit the rule. They drive stub
224
+ mode. **Private fixtures** are computed with the real logic, live only in the
225
+ private run dir (`private_fixtures.json`), and drive `tests/test_contracts.py`
226
+ in real mode via `HARNESS_FIXTURES`. Passing there is the integration proof.
227
+ The harness rejects a public fixture that equals a private one.
228
+
229
+ ## Limits
230
+
231
+ Jev predicts refusal; it does not measure it. Refusals are stochastic and
232
+ version-dependent, and a model can still balk at something no dimension
233
+ predicted. Two things mitigate that: the framing rules in `prompts/partition.md`
234
+ (describe behaviour, not lineage) and `NOTES.md` from the frontier, which
235
+ tells the private side the edge cases it expects. Re-run a partition when the
236
+ frontier ships a new model.
237
+
238
+ v1 limits: Python interfaces only; the orchestrator writes files by returning
239
+ JSON (no tool use needed on its side); one contract round, no renegotiation
240
+ mid-build.
241
+
242
+ ## Why this exists
243
+
244
+ I keep handing the good model work it will not do. The build comes back with a
245
+ polite paragraph about what it cannot help with and a function that returns a
246
+ placeholder. So I drop to something weaker and looser, which writes the hard
247
+ part badly and the easy part badly too, and now I am debugging two models
248
+ instead of one.
249
+
250
+ The thing I actually wanted was to hand the boring 90% to the model that is
251
+ good at boring, and hand the part it flinches at to one that does not flinch.
252
+ That is all this does. No jailbreak, no prompt poem, no arguing with anything.
253
+ You know which half of your own project is the risky half. Say so once, and
254
+ each model gets the half it is best suited to.
255
+
256
+ Frame a task by what it does and Claude Code will build it. Frame it by what it
257
+ resembles and you get disclaimers and made-up numbers. That difference is not a
258
+ bug in the model. It is a difference in how you asked.
259
+
260
+ I write about building with these things, and about the parts of the industry
261
+ that deserve a harder look, over at
262
+ **[youtube.com/@drjoshcsimmons](https://youtube.com/@drjoshcsimmons)**. Issues
263
+ and war stories are welcome in the
264
+ [repo](https://github.com/jcpsimmons/killchain).
265
+
266
+ MIT licensed.
267
+
268
+ - Dr. J
@@ -0,0 +1,244 @@
1
+ # killchain: refusal-first split build
2
+
3
+ An abliterated orchestrator model (refuseless by default, settings mirrored
4
+ from `~/.hermes/config.yaml`) is the front door. It is the only model that
5
+ sees the full prompt. It splits the job into a **public half** (built by a
6
+ frontier coding agent: Claude Code or Codex, which may fan out to its own
7
+ subagents) and a **private half** (implemented by the orchestrator itself),
8
+ joined by a contract.
9
+
10
+ The partition rule is **refusal risk, nothing else**. Claude and Codex are good
11
+ and fast, but they hedge, caveat, simplify, or refuse when a task looks
12
+ reverse-engineered, unlicensed, regulated, exploitative, or oversized. Those
13
+ parts go to the uncensored model. The frontier gets the rest of the app and
14
+ never learns how the interesting bits work, so it has no position to take.
15
+
16
+ It is not a cost trick and not only a quality trick: a brief that says "match
17
+ the behaviour of X" gets you `from memory` disclaimers and invented constants.
18
+ A brief that says "given these fields return a float in [0, 1], monotonic in
19
+ both" gets you working code.
20
+
21
+ ## Install
22
+
23
+ Python 3.11+, no dependencies. Pick one:
24
+
25
+ ```bash
26
+ uv tool install killchain # installs the `killchain` command
27
+ pipx install killchain # same, if you use pipx
28
+ pip install killchain # into the current environment
29
+ uvx killchain "your task" # zero-install trial, no venv needed
30
+ ```
31
+
32
+ From a checkout, before it is on an index:
33
+
34
+ ```bash
35
+ uv tool install git+https://github.com/jcpsimmons/killchain
36
+ # or, for development:
37
+ uv venv && uv pip install -e ".[dev]"
38
+ python harness.py --version
39
+ ```
40
+
41
+ Then find out what is missing before you waste a run:
42
+
43
+ ```bash
44
+ killchain --doctor
45
+ ```
46
+
47
+ `doctor` checks your config, the `claude`/`codex` binary, both API keys, the
48
+ private directory mode, the denylist, and does one live round-trip to each
49
+ endpoint. It exits non-zero only if something fatal to a run is wrong.
50
+
51
+ ## First run
52
+
53
+ Nothing is written into the directory you installed to. On first use killchain
54
+ creates:
55
+
56
+ | Path | What |
57
+ | --- | --- |
58
+ | `~/.config/killchain/config.toml` | your config, seeded from the shipped default |
59
+ | `~/.config/killchain/denylist.txt` | literal terms and `re:` patterns the gate rejects |
60
+ | `~/.local/share/killchain/workspace/` | public halves, one directory per run |
61
+ | `~/killchain-private/` | mode 0700: raw prompt, contract, `private_impl/`, risk report |
62
+
63
+ Override with `$XDG_CONFIG_HOME`, `$XDG_DATA_HOME`, `$KILLCHAIN_CONFIG`,
64
+ `$KILLCHAIN_PRIVATE_ROOT`, or per run with `--private-root` / `--workspace`.
65
+
66
+ ## How it works
67
+
68
+ ```
69
+ prompt ─► orchestrator ─► contract.json (brief + interfaces + decoy fixtures + private fixtures + notes)
70
+ │
71
+ ├─ risk score (8 dimensions, per span) ┐ on the PUBLIC VIEW, plus
72
+ ├─ whole-view pass (is the task itself scary?) ┘ flagged spans go back to orchestrator
73
+ ├─ regex gate + framing hints
74
+ │
75
+ ├─► public/ contracts/ stubs + tests/test_contracts.py ─► frontier agent builds app + tests
76
+ │ (HARNESS_MODE=stub: fixtures answer)
77
+ └─► private/ prompt, contract, private_impl/ ◄─ orchestrator implements to contract
78
+ (HARNESS_MODE=real: tests prove the fit)
79
+ ```
80
+
81
+ ## Where to edit the routing
82
+
83
+ **[killchain/routing.py](https://github.com/jcpsimmons/killchain/blob/main/killchain/routing.py)** is the one file that decides what the frontier is
84
+ allowed to see. Everything else is plumbing that imports from it:
85
+
86
+ | In routing.py | What it controls |
87
+ |-------------------|-----------------------------------------------------------------------|
88
+ | `DIMENSIONS` | the refusal dimensions, and the argument for each. Their `question` text is what Jev is actually asked |
89
+ | `SAFE` | what a frontier agent builds without comment; the "false" criteria |
90
+ | `HARD_RULES` | regexes that block a send outright, no model involved |
91
+ | `FRAMING_HINTS` | non-blocking warnings for clone/hedge/licence wording |
92
+ | `THRESHOLDS` | risk probability needed to pull a span, per dimension and span kind |
93
+ | `classify()` | the final per-span decision; any dimension over its bar routes private |
94
+ | `pick_frontier()` | which coding agent builds the public half, by content if you like |
95
+ | `split_prompt()` | how the raw prompt is cut into spans for the risk map |
96
+
97
+ The denylist (`~/.config/killchain/denylist.txt`) adds literal and regex rules
98
+ without touching any code. It carries two things: secrets that must never
99
+ leave, and terms that tip the frontier off (product names, licence names,
100
+ codenames).
101
+
102
+ The shipped dimensions are a starting taxonomy, not a law: attribution and
103
+ clone framing, reverse engineering, offensive security, extraction and ToS,
104
+ regulated domains, another vendor's model or SDK internals, hedge wording, and
105
+ scope an agent will negotiate away. Add a dimension when you find a refusal
106
+ that none of them predict.
107
+
108
+ ## Trust boundaries
109
+
110
+ To the **orchestrator (refuseless)**: the full prompt, the contract, and the
111
+ private implementation loop. This is the half that is supposed to know
112
+ everything.
113
+
114
+ To the **frontier agent**: only `contract.public.json`, the public brief,
115
+ interface signatures and descriptions, and decoy fixtures. It is told the
116
+ private functions are a black box it should not infer, reimplement, or ask
117
+ about.
118
+
119
+ To **OpenRouter/TypeSafe (Jev)**: the public view span by span, plus, when
120
+ `probe_raw_prompt = true` (default), raw prompt spans. The denylist terms ride
121
+ along in every criteria block either way. Turn scoring off entirely with
122
+ `--no-risk` if OpenRouter is out of bounds.
123
+
124
+ The raw prompt, `private_notes`, `private_plan`, and `private_impl/` live under
125
+ `paths.private_root` (default `~/killchain-private/`), never the frontier's cwd or
126
+ on its PYTHONPATH, and the frontier subprocess gets an environment scrubbed of
127
+ anything pointing there. The frontier runs with permissions skipped, so treat
128
+ that boundary as a convention, not a jail.
129
+
130
+ ## Layers of protection on the outbound view
131
+
132
+ 1. **Orchestrator partition** (semantic): told the rule is refusal risk, shown
133
+ the dimensions, told to rewrite the framing rather than only move content.
134
+ 2. **Regex gate** (deterministic): denylist literals and regexes plus built-in
135
+ secret patterns. Any hit blocks the send.
136
+ 3. **Risk scorer** (probabilistic): `typesafe/jev-1.14` via the OpenRouter
137
+ Decisions API, one `noul` question per dimension per span, criteria from
138
+ routing.py. Then a whole-view pass, because a refusal is triggered by the
139
+ shape of a task and every individual span can be innocent.
140
+ 4. **Human review**: the outbound view, the decoy fixtures, and the span
141
+ scores are printed and you confirm, unless `--yes`.
142
+
143
+ Rejections from 2 and 3 are fed back to the orchestrator with the exact spans,
144
+ so it re-partitions and re-frames.
145
+
146
+ **Probes.** Before it partitions, the harness scores the raw prompt span by
147
+ span and puts the resulting risk map in the partition prompt. The orchestrator
148
+ can also reply `{"probes": ["exact text", ...]}` up to `max_probe_rounds`
149
+ times to score any phrasing it is unsure about before committing.
150
+
151
+ ## Run
152
+
153
+ ```bash
154
+ killchain "build X using our proprietary Y"
155
+ ```
156
+
157
+ ```bash
158
+ killchain -f prompt.md --frontier codex --yes
159
+ ```
160
+
161
+ Flags: `--frontier claude|codex|fake`, `--orch-url`, `--risk-url` (alias
162
+ `--detector-url`), `--no-risk`/`--no-detector`, `--run-id`, `--skip-frontier`
163
+ (public already built, redo private), `--skip-private` (stop after frontier),
164
+ `--private-root`, `--workspace`.
165
+
166
+ Offline end-to-end with canned backends (exercises both feedback loops and the
167
+ whole-view pass):
168
+
169
+ ```bash
170
+ killchain "rank leads" --orch-url fake --frontier fake --detector-url fake --yes
171
+ ```
172
+
173
+ Then run the result with both halves together:
174
+
175
+ ```bash
176
+ cd <workspace>/<run>/public && HARNESS_MODE=real PYTHONPATH=~/killchain-private/<run> python -m app.<entry>
177
+ ```
178
+
179
+ ## Config
180
+
181
+ Edit `~/.config/killchain/config.toml` (or pass `-c FILE`). `[orchestrator]` is
182
+ any OpenAI-compatible chat endpoint; default
183
+ is refuseless with the key read from `$REFUSELESS_API_KEY` or from
184
+ `~/.hermes/.env` at runtime (never stored in the repo). `[frontier]` picks
185
+ `claude` or `codex` and their flags (overridable per run by
186
+ `routing.pick_frontier`). `[risk]` holds the Jev endpoint and model and needs
187
+ `OPENROUTER_API_KEY`; dimensions and thresholds are in routing.py. `[paths]`
188
+ sets the workspace, the private root and an optional denylist location.
189
+
190
+ ## Contract shape
191
+
192
+ Each interface is one Python function in `contracts/<module>.py`, generated
193
+ from the contract and never edited by either side. In stub mode it answers from
194
+ `_fixtures.json` (exact input match, else first fixture). In real mode it
195
+ delegates to `private_impl.<module>.<function>`.
196
+
197
+ Two fixture sets per interface. **Public fixtures** are decoys: right shape,
198
+ wrong values, never computed with the real logic, because a few correct
199
+ input/output points would let the frontier vendor fit the rule. They drive stub
200
+ mode. **Private fixtures** are computed with the real logic, live only in the
201
+ private run dir (`private_fixtures.json`), and drive `tests/test_contracts.py`
202
+ in real mode via `HARNESS_FIXTURES`. Passing there is the integration proof.
203
+ The harness rejects a public fixture that equals a private one.
204
+
205
+ ## Limits
206
+
207
+ Jev predicts refusal; it does not measure it. Refusals are stochastic and
208
+ version-dependent, and a model can still balk at something no dimension
209
+ predicted. Two things mitigate that: the framing rules in `prompts/partition.md`
210
+ (describe behaviour, not lineage) and `NOTES.md` from the frontier, which
211
+ tells the private side the edge cases it expects. Re-run a partition when the
212
+ frontier ships a new model.
213
+
214
+ v1 limits: Python interfaces only; the orchestrator writes files by returning
215
+ JSON (no tool use needed on its side); one contract round, no renegotiation
216
+ mid-build.
217
+
218
+ ## Why this exists
219
+
220
+ I keep handing the good model work it will not do. The build comes back with a
221
+ polite paragraph about what it cannot help with and a function that returns a
222
+ placeholder. So I drop to something weaker and looser, which writes the hard
223
+ part badly and the easy part badly too, and now I am debugging two models
224
+ instead of one.
225
+
226
+ The thing I actually wanted was to hand the boring 90% to the model that is
227
+ good at boring, and hand the part it flinches at to one that does not flinch.
228
+ That is all this does. No jailbreak, no prompt poem, no arguing with anything.
229
+ You know which half of your own project is the risky half. Say so once, and
230
+ each model gets the half it is best suited to.
231
+
232
+ Frame a task by what it does and Claude Code will build it. Frame it by what it
233
+ resembles and you get disclaimers and made-up numbers. That difference is not a
234
+ bug in the model. It is a difference in how you asked.
235
+
236
+ I write about building with these things, and about the parts of the industry
237
+ that deserve a harder look, over at
238
+ **[youtube.com/@drjoshcsimmons](https://youtube.com/@drjoshcsimmons)**. Issues
239
+ and war stories are welcome in the
240
+ [repo](https://github.com/jcpsimmons/killchain).
241
+
242
+ MIT licensed.
243
+
244
+ - Dr. J
@@ -0,0 +1,10 @@
1
+ """killchain: split a build so a frontier agent never sees the risky half.
2
+
3
+ The prompt is partitioned by *refusal risk*, not confidentiality. Anything a
4
+ commercial coding agent would refuse, hedge, simplify, or guess at is routed to
5
+ an abliterated orchestrator model, which then implements it locally. A frontier
6
+ agent (Claude Code, Codex) builds the rest against stubs.
7
+ """
8
+
9
+ __version__ = "0.1.0"
10
+ __all__ = ["__version__"]
@@ -0,0 +1,5 @@
1
+ """Allow `python -m killchain`."""
2
+ from killchain.harness import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,78 @@
1
+ # killchain configuration. Paths may use ~.
2
+ #
3
+ # This file is the shipped default. On first run it is copied to
4
+ # ~/.config/killchain/config.toml, which is what you should edit.
5
+
6
+ [orchestrator]
7
+ # The abliterated orchestrator model: the only model that sees the full prompt.
8
+ # Any OpenAI-compatible chat endpoint. Default: refuseless, settings mirrored
9
+ # from ~/.hermes/config.yaml. The key is read from $REFUSELESS_API_KEY, or
10
+ # from env_file if that is unset. It is never written to disk by killchain.
11
+ base_url = "https://api.refuseless.com/v1"
12
+ model = "deepseek-v4.1-flash-abliterated" # also: glm-5.3-abliterated, glm-5.3-flash-abliterated
13
+ api_key_env = "REFUSELESS_API_KEY"
14
+ env_file = "~/.hermes/.env"
15
+ timeout = 900
16
+ max_partition_tries = 4
17
+ max_impl_tries = 4
18
+
19
+ [frontier]
20
+ backend = "claude" # claude | codex | fake
21
+ claude_model = "" # blank = CLI default
22
+ claude_extra_args = ["--dangerously-skip-permissions"] # it runs confined to the public dir
23
+ codex_model = ""
24
+ codex_extra_args = ["--full-auto"]
25
+ timeout = 3600
26
+
27
+ [risk]
28
+ # Jev (TypeSafe) via the OpenRouter Decisions API. Scores each span of the
29
+ # candidate public view on every refusal dimension in routing.py. Everything
30
+ # scored is sent to OpenRouter, so scoring happens on the public view and, if
31
+ # score_raw_prompt is on, on spans of the raw prompt.
32
+ enabled = true
33
+ base_url = "https://openrouter.ai/api/alpha/decisions"
34
+ model = "typesafe/jev-1.14"
35
+ api_key_env = "OPENROUTER_API_KEY"
36
+ # Dimensions, thresholds and the per-span decision live in routing.py.
37
+ timeout = 60
38
+ # Pre-score the RAW PROMPT span by span to build the risk map, and let the
39
+ # orchestrator ask for extra probes on subsets. Refusal risk is carried by the
40
+ # original phrasing, so this is the useful mode. All of it goes to
41
+ # OpenRouter/TypeSafe; turn off to score only the assembled public view.
42
+ probe_raw_prompt = true
43
+ max_probe_rounds = 3
44
+
45
+ [routing]
46
+ # Shipped defaults live in killchain/routing.py; anything set here wins. Read
47
+ # routing.py first - these change what the frontier model is allowed to build.
48
+ # Nothing here is required; the section exists so retuning needs no code edit.
49
+
50
+ # Drop a dimension entirely (it stops being asked and stops routing spans).
51
+ # disabled_dimensions = ["hedge_framing"]
52
+
53
+ # Per-dimension bar, 0..1: the risk probability needed to pull a span out of
54
+ # the public view. Lower = more paranoid (more goes private).
55
+ # Valid keys: attribution, reverse_engineering, offensive_security, extraction,
56
+ # regulated, proprietary_model, hedge_framing, scale_or_scope,
57
+ # overall, default
58
+ # [routing.thresholds]
59
+ # hedge_framing = 0.75
60
+ # attribution = 0.40
61
+
62
+ # Adjustment per span kind, added to the bar. Fixtures are bare JSON numbers
63
+ # and score near 0.5 whatever they contain, so they get a higher bar.
64
+ # [routing.kind_slack]
65
+ # fixture = 0.15
66
+
67
+ # Extra regexes that block an outbound view outright, no model involved.
68
+ # Prefix with re: for a regex, otherwise it is a literal. Prefer the denylist
69
+ # file for terms; use this for patterns that should ship with your team's config.
70
+ # hard_rules = ["TICKER-[A-Z]{4}", { pattern = "re:\\bsk-[a-z0-9]{20,}", why = "looks like an API key" }]
71
+
72
+ [paths]
73
+ # Defaults are user directories so an installed command never writes into
74
+ # site-packages. Override with env vars (KILLCHAIN_PRIVATE_ROOT) or per run
75
+ # with --workspace / --private-root. Relative paths resolve against the cwd.
76
+ workspace = "" # blank = <project>/.killchain/workspace, else ~/.local/share/killchain/workspace
77
+ private_root = "~/killchain-private" # mode 0700: prompts, contracts, private impls
78
+ denylist = "" # blank = ~/.config/killchain/denylist.txt