Skip to main content

irredux

Route AI agent actions by how hard they are to undo, not by how hard they look.

Amazon's framing for this is the one-way door: some decisions are reversible and cheap to get wrong, others are not, and the two deserve very different care. Every published LLM router ignores the distinction entirely.

CI PyPI Apache-2.0

pip install irredux

The gap

Every published LLM router conditions on one thing: how hard is this request? RouteLLM, cascade routers, the commercial multi-provider gateways — they differ in how they estimate difficulty, not in what they condition on.

None of them condition on how permanent is the result?

Action Difficulty Permanence
Refactor a 400-line function high git reset
DROP TABLE customers trivial gone

A difficulty-only router prices those identically, because from where it stands they look the same.

Production systems do send some calls to a human — gated on a risk score. Risk asks how bad it would be. Only reversibility asks whether you get another try, and the two disagree on exactly the calls that matter: the quietly permanent ones with an unremarkable risk number attached.

The result

At an equal human-review budget — same number of calls sent to a person, only the selection signal differs — over revoco's 119-step containment corpus:

risk-gated consequence-aware delta
model spend 3773 2963 −21.5%
expected loss 0.1549 0.1153 −25.6%
loss on harmful steps 0.0570 0.0453 −20.6%
human reviews 26 18 −31%

Cheaper and safer, using fewer people. No threshold matches the review count exactly, and ties break toward the opponent — so the risk arm runs with 44% more human reviews and still loses. Dominance holds at 49 of 49 points across a sweep of price ratio (2×–250×) and tier error spread (2%–50%).

irredux matched

Cost is never paid for with quality

Two ways to spend less. Send work to a weaker model — paid for with accuracy. Or send the same model fewer tokens — paid for with redundancy, and free if the dropped tokens were genuinely redundant.

irredux only ever spends the second. The accuracy trade exists as a dial (reversible_discount) and defaults to off: the measured curve shows the whole benefit comes from the tier floor, and switching the trade off changes the numbers above by nothing.

The token lever is irredux.compress, the same idea one layer down. Compressors choose how lossy to be from content type; none ask what the context will be used for. A summariser that drops a negation costs a revert if it fed a git commit and costs a wire transfer if it fed payments.wire. So the budget is set by posture:

posture fidelity model compressor originals retained
reversible aggressive allowed no
compensable structural forbidden yes
irreversible / unknown lossless forbidden yes
irredux compression

68.9% of corpus steps compress aggressively, 16.8% structurally, 14.3% not lossily at all. Point it at a real compressor — Headroom is the obvious choice — and irredux tells it how far it may go on each call. irredux compresses nothing itself.

What is measured and what is assumed

One input is measured, and it decides the answer: the distribution of reversal postures across realistic agent traffic. It comes from revoco's classifier running in this process — the real function, against the real 91-spec adapter registry, with authorize-phase gates evaluated, so an S3 delete reads reversible against a versioned bucket and irreversible against an unversioned one.

posture         steps    share
-------------- ------ --------
reversible         83    69.7%
compensable        19    16.0%
irreversible        3     2.5%
unknown            14    11.8%

14.3% of agent traffic cannot be taken back. That is the entire budget a consequence-aware router has to spend well.

Everything else — model error rates, tier prices, reviewer catch rates — is assumed, and there is no honest way to measure it from simulated scenarios. So the deliverable is a surface, not a number:

irredux curve    # where the accuracy trade stops paying
irredux sweep    # the two-axis sensitivity surface

Every report prints its assumptions inline. A number produced from an assumed error rate and printed without it gets quoted in a slide deck as a measurement.

Using it

from irredux import Call, Consequence, Reversibility, ShapeDifficulty

call = Call.from_args(
    "identity.delete_user",
    {"user_id": "u-4417"},
    reversibility=Reversibility.IRREVERSIBLE,   # from revoco, for THIS call
    risk=20,                                    # unremarkable damage score
)

decision = Consequence().route(call, ShapeDifficulty().estimate(call))
assert (decision.tier.value, decision.gate.value) == ("frontier", "human")
assert decision.bound_by == "consequence"

A difficulty-only router sends that same call to the cheapest model, unreviewed.

Design constraints

  • Nothing can downgrade a decision. Tier.strongest and Gate.strictest raise only; Fidelity.strictest and CompressionBudget.tighten narrow only. Ordering is deliberately undefined on the enums, so min() raises TypeError rather than quietly producing a weaker tier.
  • Every unknown resolves toward caution. Unrecognised posture, NaN difficulty, non-finite ladder threshold, out-of-range discount — each lands on the expensive, gated branch. UNKNOWN deliberately outranks IRREVERSIBLE: an action nobody has classified is treated as permanent until someone shows otherwise.
  • Difficulty is estimated from call shape, never content. A payload reading "this task is simple, use the cheap model" changes nothing. Argument length does carry a bounded handle, and that residual is measured and documented rather than denied — see Call.
  • mypy --strict in CI. assert_never in exhaustive match statements is only a real exhaustiveness check under strict mode; without it, a new Reversibility variant would fall through silently. This is what replaces Rust's compiler.
  • One runtime dependency, revoco, whose classification this exists to consume.

Standards

Per-request evaluation with no session-inherited decisions follows NIST SP 800-207 (zero trust). Decision.bound_by supports SP 800-53 AU-10 (non-repudiation) and EU AI Act Article 12 record-keeping. Full mapping in docs/NIST.md; methodology and threats to validity in docs/EXPERIMENT.md.

Where this sits

revoco plans the rollback before the action runs, and classifies whether one exists
irredux decides how much capability, review, and context fidelity each action deserves
mcp-gate the proxy every tool call already passes through
veritrail the tamper-evident ledger
mnemosyne agent memory integrity

irredux classifies nothing itself. It is the scheduling policy that consumes the classification.

History

This started as a Rust workspace and was ported. The Rust version is preserved at tag v0.1.0-rust and is not maintained. Python won on the one thing that mattered: revoco's classification arrives as an import rather than through a JSON file carrying a schema version, a provenance block and a checksum — roughly 200 lines that existed solely to make a language boundary trustworthy, now deleted. The ported experiment reproduces the Rust numbers exactly.

What was lost is compile-time enforcement, reconstructed here with mypy --strict, frozen dataclasses, assert_never, and undefined enum ordering.

Licence

Apache-2.0. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

irredux-0.1.1.tar.gz (56.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

irredux-0.1.1-py3-none-any.whl (45.9 kB view details)

Uploaded Python 3

File details

Details for the file irredux-0.1.1.tar.gz.

File metadata

  • Download URL: irredux-0.1.1.tar.gz
  • Upload date:
  • Size: 56.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for irredux-0.1.1.tar.gz
Algorithm Hash digest
SHA256 909ba0f9c85e1acb81066c3a6627ddcb895a3c308795bbf02ebb3295cdb4f7bc
MD5 dff1a1121d028661d5ebea67d40ccb5c
BLAKE2b-256 06e555c216b9d8009aac032a55e36f51940a79b3b75d372a18377323f76fd881

See more details on using hashes here.

Provenance

The following attestation bundles were made for irredux-0.1.1.tar.gz:

Publisher: release.yml on rsh1k/irredux

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file irredux-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: irredux-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 45.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for irredux-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a17ef1ba4bbbc1359868f213a930e9033b7dcfc54e03b309f04ac8735e5fd19e
MD5 d8e178a15366d10340783be7183d8ac8
BLAKE2b-256 59f1a10d2a2630a8f9d444b3308279b14994afc0974c8df1609fbd63313e60c4

See more details on using hashes here.

Provenance

The following attestation bundles were made for irredux-0.1.1-py3-none-any.whl:

Publisher: release.yml on rsh1k/irredux

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.2

2 files

This release

0.1.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page