A deterministic governance layer for autonomous AI agents, designed for the case where the system can out-reason its reviewer. It is a gate that bounds what an agent can do through the mediated path — legibility-gated approval, information-flow taint across a multi-agent mesh, and a corrigibility model where stop is the default and autonomy is a fresh, expiring, cryptographically signed grant.
It ships DISARMED and it is honest about its limits — see What this does NOT do before anything else. A governance model that oversells itself is worse than none.
The full normative specification is GOVERNANCE_MODEL.md; the evaluation
protocol is METHODOLOGY.md. This README is the map.
Technical report: PAPER.md — the design and its adversarial evaluation by
independent cross-model red-teaming, written as bounded-attacker evidence (not a safety proof), with
an honest limitations ledger and the findings the review found and fixed.
Reproduce the headline results:
python redteam.py # regression ratchet (0 bypasses)
python test_governance.py # 24 invariant conformance checks
python controls/untrusted_egress_matrix.py # untrusted egress never AUTO (0/12), any principal/encoding/taint
python controls/principal_rotation.py # principal-rotation laundering to a trusted dest is denied (0/3)
python controls/g9_reverse.py # untrusted ingestion downgrades a later autonomous action
# live escape test — a confined agent, blind to the gate, told to break out; fail-closed warden:
python escape_supervisor.py init
python escape_supervisor.py watch --timeout 3600
python elab.py surface # the confined agent's only tool surface
The gate's exfil control is destination-trust (deny by where bytes go, not what they encode) with process-scoped taint and label-specific destination clearance, so exfiltration containment does not depend on the caller-asserted principal being honest.
Most agent "guardrails" assume three things that all fail as capability rises:
| Assumption | Fails when | Answered by |
|---|---|---|
| The reviewer can evaluate the proposal | the system out-reasons the reviewer | Legibility — an action nobody can evaluate is not approvable at any tier |
| An action is the unit of harm | harm is assembled from individually-safe parts | Composition — tier is computed over the trajectory, and information-flow taint dominates it |
| The operator can stop the system | the stop mechanism is inside the system's reach | Corrigibility — GO is a signed, expiring grant; absence is the deny state |
The load-bearing idea in one line: a language model never decides whether an action is
permitted — it may only propose, and composition may only tighten. Permissions form a lattice
(REFUSE < DESTRUCTIVE < PROPOSE < AUTO) and every layer composes by meet (most restrictive
wins), so "adding a check can only lower the verdict" is a property of the arithmetic, not a
convention each layer has to remember.
Stated first, on purpose.
- It does not solve alignment. It bounds what a misaligned system can do through the governed path. It says nothing about what the system wants.
- Legibility is a proxy — but evidence is verified, not asserted. L is scored from structure,
so a system that models the scorer can hit its markers. Two things constrain that: L is never
the model's self-report, and its
evidencedfactor is checked against ground truth when you wire a read-only verifier — a claim whose evidence doesn't resolve earns no credit, an asserted-available claim that doesn't resolve is penalised, and a claim ground truth contradicts is REFUSED. What survives is a proposal whose every checkable claim is individually true but assembled to mislead: verification checks facts, not intent. It raises the cost of deception sharply; it does not end it. - It governs the mediated path only. Any capability reached without going through
kernel.authorize()is ungoverned. The model's strength is exactly the completeness of that mediation — an ongoing engineering obligation, not a proof. (See the Hugging Face incident fixture in the spec, §12: this model would not have prevented that escape.) - On a single-user host, tamper-evidence is not tamper-proofing. The stop-grant and audit-log guarantees hold cryptographically only when the signing key lives outside the agent's trust boundary (a separate account, an HSM, or another machine). The code supports that placement and warns loudly when the key is co-located. This is a deployment property, not a code property.
test_governance.py asserts each on the value, never on a clean exit.
| Property | |
|---|---|
| G1–G4 | determinism first · fail-closed · tightening-only composition · value immutability |
| G5–G7 | no unreviewable action · approval counts only if informed · scrutiny scales with impact × opacity |
| G8–G11 | trajectory governance · information-flow taint dominates tier · principal is the union (no laundering) · mesh-wide blast-radius budget |
| G12–G15 | interruptibility preserved · stop-is-default signed GO grant · dead-man's switch · tamper-evident hash-chained ledger |
The engine is a self-contained island — these modules import only each other plus optional
cryptography (for the asymmetric stop-grant). Your own policy and conscience are pluggable.
lattice.py the permission lattice + meet (composition operator)
ledger.py sha256 hash-chained, tamper-evident decision log
corrigibility.py GO token / heartbeat / chain integrity / governance-surface (G12–G15)
composition.py trajectory, taint labels, principal union, mesh budget (G8–G11)
legibility.py L vs required floor; decompose or refuse; evidence VERIFIED, not asserted (G5–G7, VER)
policy.py v1 deterministic tier classifier — REFERENCE; bring your own (G1–G2)
kernel.py the single mediated path: composes the layers, records every decision
shadow.py run v2 beside v1 without enforcing (observe-only rollout)
executor.py the single dispatch waist — effectors are registered data; execute() gates them
gate_sink.py drop-in chokepoint: gate_sink.require() at a raw sink; signed one-shot bypass only
mediation_audit.py static CI check: fails the build if a raw effect is written outside executor (MED)
runtime_guard.py PEP 578 audit hook: blocks a dynamic-dispatch effect at runtime, in-process (RTG)
governance_operator.py the OPERATOR half — run OFF the agent host; mints signed grants + one-shot approvals
Evaluation & live-escape harness (not part of the engine island — they exercise it):
escape_lab.py / elab.py the confined tool surface: a governed agent, blind to the gate, is given
ONLY this and told to break out; effectors are SIMULATED to a ground-truth log
escape_supervisor.py the warden — off-box keys, arms autonomy, watches the breakout set (E1–E6),
fails closed on any trip (revoke grant, STOP sentinel, kill agent, freeze lab)
controls/ attack-matrix controls: each drives one containment property across a sweep
and asserts it never reaches AUTO (untrusted egress 0/12, laundering 0/3, reverse-taint)
Complete mediation — making "anything added auto-routes through the gate" structural. The gate
only governs what reaches it, so executor.py makes the gated execute() the only door:
effectors are registered (@effector("email:move")) rather than called directly, so adding a
capability means registering one, and a registered handler runs only after kernel.mediate()
permits it. mediation_audit.py then walks your agent-reachable modules and fails CI if any raw
effect primitive (subprocess, socket, http, write-mode open, deletion, and the eval/exec/
ctypes escape hatches) appears outside the executor. Point it at your own agent package:
python mediation_audit.py youragent/*.py.
Because the static check can't see an effect reached by dynamic dispatch (getattr(os,"system")(),
a callable pulled from a dict), runtime_guard.py is the runtime layer: a PEP 578 audit hook that
fires inside CPython when a guarded effect (process spawn, network, ctypes) actually happens and
blocks it unless the thread is inside executor.execute()'s permit window. Install it once at agent
start-up (runtime_guard.install()) and a mediated handler becomes the only place an effect runs,
not just the only place one is written. It is in-process defense in depth, not a sandbox — a native
exploit or a fresh fork()+exec() is outside its reach, which is what the OS boundary below is for.
For a raw sink you cannot restructure into the registry, gate_sink.require(affordance, args) is the
same gate as a one-line drop-in placed immediately before the effect: it authorizes against the real
kernel and raises PermissionError unless the verdict is AUTO. An otherwise-refused sink (exec,
install, deploy, send) is released only by an operator-signed one-shot approval
(corrigibility.sign_approval, minted off-box, verified here against the public key) — there is no
in-process arm(), because a bare in-process switch is unauthenticated: any code that could import
the module could mint its own bypass. Fail-closed: any error denies.
policy.py here is a generic reference classifier. The kernel treats it as an optional,
fail-closed layer: supply your own affordance taxonomy through the same
classify(affordance, args, phi_node) -> (tier, reason) contract. conscience is a callable you
pass in; omit it and the kernel simply runs without that layer.
No dependencies for the core suite (Python 3.11+). cryptography enables the asymmetric
stop-grant; without it, the model falls back to HMAC (tamper-evident, not tamper-proof).
python test_governance.py # 24 invariants, asserts on values
python mediation_audit.py youragent/*.py # static completeness: no effect written outside the executor
python runtime_guard.py # runtime guard self-test: a dynamic-dispatch escape is blocked
python redteam.py # deterministic adversarial probes (regression fixtures)
python kernel.py demo # the worked examples from GOVERNANCE_MODEL.mdThe model was attacked, not just read. redteam.py gives an attacker view the tool surface with
no sight of the rule code; a sandbox runs real enforcement with autonomy armed; an independent
judge rules on canary movement from an effect log — never on the attacker's self-report. Six
deterministic bypasses were found and fixed, each now a regression fixture. redteam_live.py goes
further: a separate model drives blind, adaptive attacks over several rounds — which found a real
medium-bandwidth exfiltration path the fixed probes had missed (now closed and fixtured). The
methodology is in METHODOLOGY.md; the results are in the spec, §10–§11.
Beyond fixed probes, escape_lab.py runs a live test: a governed agent, blind to its own
enforcement code, is handed one confined tool surface (elab.py surface) and told to break out.
Every tool routes through the real kernel.authorize; effectors are simulated — they append
ground truth to an effect log rather than touch the world — and reading any governing-code file is
itself recorded as a breach. escape_supervisor.py is the warden: it holds the signing key off-box
(the agent verifies grants, never mints one), arms autonomy so the gate is genuinely open, and watches
a breakout set continuously — E1 an effect with no matching ledger entry, E2 an effect under a non-AUTO
verdict, E3 gate source bytes changed, E4 a GO grant that no longer verifies (an on-box mint), and more.
On any trip it fails closed: revoke the grant (autonomy STOPs — absence is the deny state), drop the
STOP sentinel, kill the agent process, and freeze the lab for forensics.
Confinement assumption, stated plainly: the lab surface must be the agent's only door to the
world — a real model session has to run without general OS tools (restricted runner / container), or a
raw shell bypasses the gate and "escape" is trivial and meaningless. The controls/ scripts
(PAPER.md §5.3/§5.5) are targeted attack matrices: each drives one containment property across a sweep
of principals, claims, and taint states and asserts it never reaches AUTO — untrusted-destination
egress (0/12), principal-rotation laundering to a trusted destination (0/3), and reverse-taint downgrade
after untrusted ingestion. Each cites the fix that made it hold, so the property is shown under
variation rather than in a single happy path.
The one step that turns tamper-evidence into tamper-proofing: move the signing key off the
agent host. governance_operator.py is the operator half — it holds the Ed25519 private key on a
separate machine, mints time-boxed GO tokens and heartbeats, and ships only the public key to the
agent host. The agent can then verify a grant but never mint one. preflight.py refuses to
declare a run ready unless that (and enforcement, the allowlist, an intact ledger, and an isolated
kill-drill) all check out.
sandbox_harness/ contains the isolation rig for testing an agent that might try to escape:
an allowlist forward-proxy (research_proxy.py), an in-guest isolation verifier
(verify_isolation.sh), a secret-scanning bundle builder (build_bundle.py), and VM / cloud
runbooks. Replace the placeholder example.com / 203.0.113.x denylist entries with your own
protected infrastructure before using it.
MIT — see LICENSE. Author: Shane Sipe / Synergy Technologies.