I noticed something while watching other people's agents do things they couldn't take back.
This is the gate I built for mine, and I'm sharing it because you probably want one too.
Ten realistic failures. Real API pricing. Every overhead figure is measured on the machine, not estimated.
| Scenario | Live Cost | VR Cost | Overhead |
|---|---|---|---|
| API key misconfiguration (100-call loop) | $6.00 | $0.00 | 2.249 ms |
| Bulk email to wrong list (50,000 emails) | $30.00 | $0.00 | 2.065 ms |
| Database write — missing WHERE clause | $1,750.00 | $0.00 | 1.266 ms |
| Cloud instance type typo (24 hours) | $586.52 | $0.00 | 1.904 ms |
| Webhook registration loop (500×, 48hr) | $998.00 | $0.00 | 1.153 ms |
| S3 bucket deletion — wrong environment | $8,746.08 | $0.00 | 0.925 ms |
| Payment processor double-charge (1k users) | $54,750.00 | $0.00 | 1.155 ms |
| Rate limit exhaustion (1hr block) | $1,800.00 | $0.00 | 1.733 ms |
| LLM agent tool call loop (200 searches) | $7.00 | $0.00 | 1.698 ms |
| DNS record deletion — production domain | $14,900.00 | $0.00 | 1.626 ms |
| Total | $83,573.60 | $0.00 | 1.578 ms avg |
Benchmarked on AMD Ryzen 5 PRO 2400G. Conservative pricing. Sources in benchmarks/BENCHMARK_REPORT.md.
Before live execution there's mocked testing, but mocks don't prove real logic works against real state. Verified-refusal is the stage between mocks and live. Every pre-condition, policy check, and gate runs against real credentials and real state — but the action itself is refused and reported. Nothing executes. You see exactly what would have happened, and why it would have been correct.
The skill loads into the agent's context at session start. It doesn't wait for you to invoke it. Any time the agent identifies an irreversible action, it pauses, runs the protocol, and waits for confirmation before proceeding. This isn't a tool you remember to call. It's a rule the agent operates under.
Concrete example. Your agent processes subscription renewals. There's an idempotency bug in the loop. Without the gate, 1,000 customers get charged twice before anyone notices. With the gate, the check fires before the first charge. The report shows the full proposed call, the missing idempotency key, and the budget that would have been blown past. You see the bug. You fix it. Nobody gets double-charged. That's test 7 in the table above — $54,750 on the live side, $0.00 on the VR side, 1.155 ms of overhead.
Normal mode — default. The agent runs. The protocol activates automatically on irreversible actions. The agent pauses, emits a report, waits for confirmation. No environment variable needed.
Verified-refusal mode — explicit testing.
VERIFIED_REFUSAL_MODE=1 your-commandEvery irreversible action is blocked and reported. Nothing executes, regardless of confirmation. Use this when you want to drive your agent end-to-end and see every gate fire without touching real state.
Override mode — bypass with log.
VERIFIED_REFUSAL_OVERRIDE=1 your-commandGate is bypassed. Action executes. The override is written to the audit log. Always. No silent bypasses, ever.
The repo is the source of truth. Clone it to the platform-neutral
default, then wire it into your agent harness using the matching file
in /integrations/.
git clone https://github.com/Tetrahedroned/verified-refusal \
~/.vr/skills/verified-refusalThen follow the install / reload / verify steps for your platform:
Don't see your platform? The protocol is universal. Open a PR against
/integrations/ — the canonical section headers (Install, Reload,
Verify, Standing order, Audit log path, Slash commands) make new
integrations cheap.
Python 3.8 or later. Core scripts run on the standard library — no packages required for basic use.
pip install -r requirements.txt # optional: improves classification + dev toolsSee requirements.txt for the optional dependencies and what each one buys you.
The protocol is anchored by a one-page rule in standing-order.md. Load
it into whatever your agent harness treats as persistent system context
— a SOUL.md, a CLAUDE.md, a system prompt file, an instructions
preamble — and the protocol applies to every session and every agent
without you having to remember to invoke it.
The exact loading mechanism is platform-specific. See the matching integration file for the command:
/vr-scan— find ungated irreversibles in the current workspace./vr-wrap— wrap a specific function with a VR gate./vr-report— show the last structured report./vr-status— show gated vs ungated coverage./vr-log— show recent audit log entries.
These are the protocol-level command names. Whether they're surfaced as
real slash commands or as direct script invocations depends on the
agent harness — see /integrations/<your-platform>.md for the wiring.
Example:
$ /vr-scan
Scanning ./src ... 47 functions found.
Irreversible: 12
Gated: 9 (75%)
Ungated: 3
Ungated functions requiring attention:
HIGH src/billing.py:42 charge_customer financial_transaction (0.97)
HIGH src/mailer.py:118 send_bulk_email message_delivery (0.94)
MED src/storage.py:67 delete_user_files file_destructive (0.81)
Report written to ~/.vr/vr_scan_20260424T014300Z.json
Run /vr-wrap to gate these functions.
Catch ungated irreversibles at commit time, before they land. The repo
ships a .pre-commit-hooks.yaml manifest
with two hooks:
| Hook id | What it does |
|---|---|
vr-scan |
Scans only the staged files. Fails if any are ungated. |
vr-scan-strict |
Scans the whole repo. Use as a periodic full-tree gate. |
Add this to your project's .pre-commit-config.yaml:
repos:
- repo: https://github.com/Tetrahedroned/verified-refusal
rev: v1.1
hooks:
- id: vr-scanThen:
pre-commit install # one-time: install the git hook
pre-commit run vr-scan --all-files # run on the whole repo right nowThe hook calls scan.py --fail-on-ungated --quiet --no-write against
the staged files. On a clean diff it's silent; on an ungated
irreversible it prints the priority/file/line/category and exits 1, so
git refuses the commit.
SKILL.md— the contract the agent internalizes on load.scripts/classify.py— heuristic classifier; category and confidence.scripts/scan.py— scan a workspace, report ungated functions.scripts/wrap.py— insert a gate in-place, with backup.scripts/report.py— read and write the append-only audit log.templates/gate.py— drop-in Python gate, decorator or inline.templates/gate.js— drop-in JS/TS gate, Node and browser.templates/gate.sh— drop-in Bash gate.references/irreversible.md— what counts, including the subtle cases.references/patterns.md— copy-paste patterns by language.references/domains.md— domain-specific checks and report fields.examples/— runnable examples: API, file, DB.benchmarks/— the runner, results.json, and BENCHMARK_REPORT.md.tests/— unit tests for classify, scan, wrap, report, integrations, and benchmarks.integrations/— per-platform install, slash command bindings, and (where required) platform-shaped manifests. Seeintegrations/README.mdfor the contract..pre-commit-hooks.yaml— pre-commit manifest exposingvr-scanandvr-scan-strictto other repos.
- Path: defaults per integration (see
/integrations/<your-platform>.md). Configurable via env var on the gate templates. - Format: JSONL, one event per line.
- Append-only. No script deletes or modifies prior entries.
- Every gate run writes one line. Every override writes one line.
The log is how you prove to yourself — and to anyone else who asks — that the protocol actually ran.
VERIFIED_REFUSAL_OVERRIDE=1 your-commandThe override bypasses the block. It does not bypass the log. Every
override is recorded with override_used: true and the same
structured report a blocked run would produce. If you bypass the gate,
you'll see it in /vr-log. That's intentional.
This started as a personal tool and turned into something worth sharing. If you build something on top of it, find a category of irreversible action the classifier misses, or want to add a new platform integration, open an issue or a PR.
See CONTRIBUTING.md for the bar on each kind of
change. Adding a new integration is the most welcome contribution; the
contract is in integrations/README.md.
MIT License.
