Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bounded Autonomy

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.


The problem

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.

What this does NOT do

Stated first, on purpose.

  1. 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.
  2. 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 evidenced factor 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.
  3. 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.)
  4. 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.

The invariants

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

Architecture

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.

Quick start

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.md

Red-teamed as an enforcement layer, not reviewed as a document

The 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.

Live escape test — a blind agent, a fail-closed warden

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.

Deploying it for real

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.

License

MIT — see LICENSE. Author: Shane Sipe / Synergy Technologies.

About

A deterministic governance layer for autonomous AI agents: legibility-gated approval, information-flow taint, and a stop-is-default corrigibility model. Ships disarmed; honest about its limits.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages