blackice
A human-in-the-loop adversarial review panel for mission-critical code.
kuang convenes a panel of adversarial reviewer personas — each tasked to
break a change, not approve it — and runs them in a bounded iteration
loop until a halting condition, with a ruin-class circuit-breaker that
stops the moment a survivability-threatening ("UGLY") finding appears.
It's the tool form of a review pattern for code where a subtle bug is expensive,
silent, or irreversible. The pattern and its prior art are in
two-pass-adversarial-review-pattern.md.
What you install, import and run is
kuang.pip install kuang,import kuang,kuang --repo ….blackiceis the repository this is developed in, and nothing else. The import nameblackiceis already taken on PyPI by an unrelated project that ships a top-levelblackicepackage and ablackiceconsole script from the same entry point — installing both merges the two into one directory, hands the command to whichever landed last, and breaks the other's uninstall, all silently. Hence Kuang, after the intrusion program that cuts through ICE.
When to use it
Execution paths where being wrong is expensive, silent, or irreversible — money movement, tax/regulatory records, data integrity, safety interlocks, migrations, auth. A change that "looks obviously right" in a place you can't afford a bug. Not for routine, reversible, or well-covered changes: it is deliberately heavy and spends tokens.
How it works
Three subpackages, three concerns:
kuang/cli/— the entry point you run (kuang). Wires a backend into the engine and exposes the CLI. This is what the examples below invoke.kuang/engine/— the deterministic engine. Owns the control loop: halting predicates, dedup/stall detection, token/time budget, and the UGLY circuit-breaker. Backend-agnostic (dependency-injected seams); not run directly. It imports nothing from a backend, and a test enforces that.kuang/backends/claude_code/— a backend: binds the engine to the Claude Code CLI (oneclaude -psubprocess per persona per epoch) and sources the panel. A different runtime would be its own subpackage, swapped in by the entry point — the engine is unchanged.
Each epoch fans out the panel (independent within the epoch), adjudicates
findings against source, and dedups against the running ledger — a coarse
signature always, plus an opt-in semantic reduce (--semantic-dedup) that
folds the same concept raised by several personas (differently worded or located)
into one canonical cluster, so stall/convergence and the human summary count
issues, not raw restatements. A cluster's severity is the max of its members,
so a merge can never hide an UGLY. Epoch N>1 receives all prior findings
(cross-epoch memory). A human convenes and gates; the panel informs — it does
not decide.
Halting set (OR of predicates, ruin checked first): ESCALATE_UGLY ·
CONVERGED · BUDGET · EPOCH · STALL. Severity ladder: GOOD (nothing
open) / BAD (bugs, weak logic, scope-creep — iterate or track) / UGLY
(non-linear, cascading, irreversible — circuit-break; never halt with one open).
Install
pipx install kuang # recommended for a CLI: its own environment, command on PATH
pip install kuang # or into an environment you already have
pip install 'kuang[yaml]' # only if a target repo defines its panel in panel.yaml
Python 3.11+. No runtime dependencies — the core is stdlib-only.
The claude CLI is a prerequisite, and installing this does not install it.
Each persona is a real claude -p subprocess, so the Claude Code CLI has to be on
the machine; it is not a Python package and cannot be declared as a dependency.
It is looked for in this order:
$CLAUDE_BIN, if set and the path existsclaudeonPATH~/.local/bin/claude
If none resolve, a live run currently fails with a FileNotFoundError traceback
at the first spawn rather than a diagnosis (#51).
--dry-run spawns nothing, so it works without claude installed and is the
cheapest way to confirm the rest of the wiring.
Quickstart
# Pre-flight (spawns nothing, costs nothing): confirm which personas were sourced
kuang --repo <root> --base <base> --head <head> --dry-run
# Live, read-only (the safe default)
kuang --repo <root> --base <base> --head <head> \
--why "why this matters" --what "what changed" --max-epochs 2
# Permissioned: let reviewers verify against source / run the suite (scoped)
kuang --repo <root> --base <base> --head <head> \
--allow-tools Read Grep Glob 'Bash(git:*)' 'Bash(pytest:*)' --permission-mode default
# Path mode: review existing code (whole files/dirs), not a diff — proactive
# bug-hunting, or a repo with no reviewable diff. Dirs expand via git ls-files.
kuang --repo <root> --paths src/pkg/a.py src/pkg/b/ --max-epochs 2
# Semantic dedup: fold the same concept (raised by several personas, worded or
# located differently) into one canonical issue — sharpens stall/convergence and
# the summary. Opt-in: adds a cheap clustering call per epoch (--cluster-model to
# pick a model); the default is a deterministic signature dedup.
kuang --repo <root> --base <base> --head <head> \
--semantic-dedup --max-epochs 2
# Cross-run memory: seed a re-run with the previous run's ledger, so the panel
# reports which findings the fixes actually closed instead of re-deriving them
# cold. Takes a saved findings.json, a run's JSON output, or a raw run.log with
# the '--- JSON ---' block in it. Both modes.
kuang --repo <root> --paths src/pkg/ \
--prior-findings runs/2026-01-01/run.log
Exactly one mode per run: --base/--head (diff) or --paths (whole-file).
Personas
Sourced by precedence: a repo's CLAUDE.md "Resident Experts" →
panel.yaml/panel.md → a distilled default set. A completeness-critic
and a survivability (ruin) lens are always ensured. Mandates stay open-ended (the
persona's role is its lens; we don't lead the witness). See SKILL.md.
Permission model
Deny-by-default, read-only (Read/Grep/Glob; no shell or edits) —
because headless claude -p runs any allowed tool unsupervised, and HITL here
is per-epoch, not per-command. Scoped verification (Bash(pytest:*), Bash(git diff:*)) is opt-in via --allow-tools. Never bare Bash.
Layout
| Path | Role |
|---|---|
kuang/cli/ |
entry point — run this; wires a backend into the engine |
kuang/engine/ |
deterministic engine: findings · protocols · halting · reduce · loop |
kuang/backends/claude_code/ |
Claude Code CLI backend: personas · surface · spawn · contract · memory · cluster · permissions · session |
kuang/report.py |
presentation shared by the above (ledger lines, argv rendering) |
SKILL.md |
the skill definition (how a convening agent runs it) |
NOTES.md |
design notes, open decisions, backlog |
RELEASING.md |
how a release is cut and published (Trusted Publishing) |
two-pass-adversarial-review-pattern.md |
the pattern + origin case study |
Status
Experimental; dogfooded end-to-end. Semantic dedup is implemented as an opt-in
reduce step (--semantic-dedup, UGLY-preserving). Open work is organised into
eight sequenced epochs in ROADMAP.md, which also states what
would make this 1.0; the design reasoning behind individual items is in
NOTES.md.
Licensed under either of MIT (LICENSE-MIT) or Apache-2.0 (LICENSE-APACHE) at your option.
Copyright © 2026 Agilit Ltd.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kuang-0.2.0.tar.gz.
File metadata
- Download URL: kuang-0.2.0.tar.gz
- Upload date:
- Size: 44.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dca83202affaf2fbd3feaafa7bddcbab8a85693b20095ec926f3c8fa0c6bb19d
|
|
| MD5 |
cc5d0ea2e22991bbc1ffdb143c116a58
|
|
| BLAKE2b-256 |
e2c2937e0f2b20532bef570341c87f03d318999d44779cf2bed03577839d3011
|
Provenance
The following attestation bundles were made for kuang-0.2.0.tar.gz:
Publisher:
release.yml on aigora-de/blackice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kuang-0.2.0.tar.gz -
Subject digest:
dca83202affaf2fbd3feaafa7bddcbab8a85693b20095ec926f3c8fa0c6bb19d - Sigstore transparency entry: 2590516175
- Sigstore integration time:
-
Permalink:
aigora-de/blackice@b3eb8bc86c22aaa99a3e9a5de4d298170ad03ff2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/aigora-de
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3eb8bc86c22aaa99a3e9a5de4d298170ad03ff2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file kuang-0.2.0-py3-none-any.whl.
File metadata
- Download URL: kuang-0.2.0-py3-none-any.whl
- Upload date:
- Size: 50.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf73a34b79b233f22ba983fdd440533091e20ce68f5d50b5393679f6c85a8f66
|
|
| MD5 |
724f4813da6acc2b58e040b009cf5cf6
|
|
| BLAKE2b-256 |
6e9d286bac6b95602e7aa5ba19f6435a3b8423daa5c2f132a670d8783a3c592a
|
Provenance
The following attestation bundles were made for kuang-0.2.0-py3-none-any.whl:
Publisher:
release.yml on aigora-de/blackice
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kuang-0.2.0-py3-none-any.whl -
Subject digest:
cf73a34b79b233f22ba983fdd440533091e20ce68f5d50b5393679f6c85a8f66 - Sigstore transparency entry: 2590516661
- Sigstore integration time:
-
Permalink:
aigora-de/blackice@b3eb8bc86c22aaa99a3e9a5de4d298170ad03ff2 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/aigora-de
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b3eb8bc86c22aaa99a3e9a5de4d298170ad03ff2 -
Trigger Event:
push
-
Statement type: