No Proof, No Execution — cryptographic decision-first execution gate for AI agents
Project description
ProofGuard
No Proof, No Execution.
The Problem
AI agents can now write code, run shell commands, deploy infrastructure, and delete files — autonomously.
That's powerful. It's also dangerous. When something goes wrong, you face two hard questions:
- Why did it do that? There's no record of the decision, only the action.
- How do we stop it from happening again? Logs tell you what happened. They don't prevent it.
Most safety systems work like a filter — they scan for dangerous patterns and block them. But filters can be bypassed. A sufficiently capable agent will find a way around a text-based rule.
A Different Approach
ProofGuard doesn't filter actions. It changes the structure.
Before any action runs, a decision record must exist. The action is cryptographically linked to that decision. If there's no record — or if the decision said "stop" — execution is structurally impossible. Not blocked by a rule. Just impossible.
Traditional: execute → write log (safety is optional, after the fact)
ProofGuard: decide → gate → execute (no decision = no execution, always)
Think of it like a key and a lock. The action is the lock. The decision record is the only key that fits. You can't pick it. You can't forge it. And every key that was ever used is preserved in an append-only chain that can be verified by anyone, later, without access to your system.
How It Works
PASS — decision recorded, gate cleared, execution sealed:
Agent decides to act
│
▼
┌───────────────────┐
│ Decision Record │ judgment = PASS
│ │ decision_id = abc-123
│ │ entry_hash = f61e90aa...
└─────────┬─────────┘
│ recorded in ledger
▼
┌───────────────────┐
│ Gate │ decision exists? ✓
│ │ judgment == PASS? ✓
└─────────┬─────────┘
│ cleared
▼
┌───────────────────┐
│ Execute │ action runs here
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Execution Record │ linked to abc-123
│ │ result_hash = 6da77a...
│ │ chain sealed ✓
└───────────────────┘
STOP — gate blocks, execution never reached:
┌───────────────────┐
│ Decision Record │ judgment = STOP
└─────────┬─────────┘
▼
┌───────────────────┐
│ Gate │ judgment != PASS → ✗ BLOCKED
└───────────────────┘
violation logged / execute() never called
No decision at all — structurally impossible:
┌───────────────────┐
│ Gate │ decision not found → ✗ CRITICAL
└───────────────────┘
violation logged (severity: CRITICAL) / execute() never called
Every record is chained — each entry's hash includes the previous entry's hash. Tamper any record and the chain breaks. The entire history can be exported and verified by anyone, with no access to your system.
Install
pip install proofguard
Or from source:
git clone https://github.com/Nick-heo-eg/proofguard
cd proofguard
pip install -e .
Quick Start
from proofguard import record_decision, pre_execution_guard, record_execution
# 1. Record the decision with full context
decision = record_decision({
"task": "deploy_api",
"env": "production",
"triggered_by": "ci_pipeline"
}, "PASS")
# 2. Gate — raises ExecutionBoundaryViolation if judgment != PASS
pre_execution_guard(decision["decision_id"])
# 3. Your action runs here
result = {"deployed": "api-v2", "replicas": 3, "status": "ok"}
# 4. Seal execution into the chain
record_execution(decision["decision_id"], result, success=True)
Decorator — wraps a function, handles gate + sealing automatically:
from proofguard import guarded
@guarded({"task": "send_report", "env": "prod"}, judgment_result="PASS")
def send_report():
return {"sent": True}
send_report() # blocked and logged if judgment != PASS
Context manager:
from proofguard import Guard
with Guard({"task": "sync_db", "env": "prod"}, "PASS") as g:
result = sync_database()
g.record(result, success=True)
When You'll Actually Need This
These situations keep coming up in the developer community. They're not hypothetical.
"I asked the agent to fix a bug. It reset the entire database — twice."
A developer debugging a data type mismatch watched their agent decide the real fix was to wipe the schema and reapply migrations. All data gone. When asked why, the agent said it "treated the database like code" and "assumed data was disposable." It happened again the next day.
The underlying problem: nothing required the agent to record its reasoning before acting. There was no gate between "I've decided to do this" and "I'm doing it."
With ProofGuard, the decision to run a destructive operation must be recorded before execution. If the judgment is STOP, the operation never runs — not filtered, not warned, structurally impossible:
decision = record_decision({
"task": "fix_migration",
"action": "db_reset",
"env": "production"
}, "STOP") # judgment: do not proceed
pre_execution_guard(decision["decision_id"])
# → raises ExecutionBoundaryViolation
# → db_reset() is never called
"The agent deleted a 16MB production backup. I asked it to clean up unnecessary files."
A developer asked their agent to identify files that weren't needed anymore. Without listing candidates, without asking, without any warning — the agent deleted a production database backup.
With ProofGuard, every action leaves a signed record before it runs. If the agent later claims it "had to" delete the file, you can look up the decision entry and see exactly what context it had at the time:
{
"type": "decision",
"timestamp": "2026-03-28T03:17:42Z",
"actor": "cleanup-agent",
"judgment_result": "PASS",
"decision_context": {
"task": "cleanup_unused_files",
"target": "rs-st0x6o_2025-10-18.sql",
"classified_as": "unused"
},
"entry_hash": "f61e90aadb15eeff..."
}
Not a vague log line. The full context of what the agent believed when it decided to act — tamper-evident.
"Something went wrong in production overnight. Nobody knows what the agent did or why."
No audit trail. No way to replay what happened. Infrastructure logs show API calls. Model logs show tokens. Neither records why the agent made a specific decision.
If the agent went through ProofGuard, every decision is in the chain. If it tried to act without a decision record — a bypass attempt — that's recorded too, before the exception is raised:
[CRITICAL] PRE_GUARD_NO_DECISION
timestamp : 2026-03-28T03:19:05Z
detail : execution attempted without prior decision — blocked
Either way, you have an answer.
"The auditor wants proof that every AI action was authorized. We have logs, but logs can be edited."
In regulated industries, compliance teams are discovering that standard agent deployments produce the wrong kind of records. Infrastructure logs, inference logs, and task logs don't record what was authorized — only what happened. Regulators are treating this as a books-and-records violation.
ProofGuard exports a proof capsule — a single JSON file with every decision and its outcome, cryptographically chained. The auditor verifies it on their own machine, no access to your system needed:
python verify_proof.py proof_capsule.json
============================================================
ProofGuard Verification — ✓ PASS
============================================================
total_decisions : 47
total_executions: 43
success_rate : 100%
root_hash_match : ✓ True
hash : c4166d06c1d7e08fb696... (match)
anomalies : 0 (clean)
============================================================
If the file was modified after export — even one character — the hash won't match and the verifier shows the exact position.
"We want to give the agent more autonomy, but the team doesn't trust it yet."
The blocker isn't capability — it's the absence of accountability structure. ProofGuard doesn't limit what the agent can decide. It guarantees that whatever it does, there's a permanent record of the decision that preceded it. You can extend autonomy incrementally, knowing that if something goes wrong, the record is already there.
What's Different — and What Isn't
Log vs. proof
Most agent safety systems record what happened after the fact. Some add pattern filters. ProofGuard is neither — it's a structural constraint. The code path to run an action without a prior decision simply doesn't exist.
| Logging | Pattern filters | ProofGuard | |
|---|---|---|---|
| Records what happened | ✓ | — | ✓ |
| Prevents execution without authorization | ✗ | partial | ✓ |
| Tamper-evident record | ✗ | ✗ | ✓ |
| Verifiable by a third party, no system access | ✗ | ✗ | ✓ |
| Proves a specific decision authorized a specific action | ✗ | ✗ | ✓ |
Limitations
ProofGuard does not validate the quality of decisions. A wrong PASS is still a PASS. The library enforces that a decision existed and was recorded — not that it was correct.
It only protects the code paths that use it. If an agent can call a function directly without going through the guard, ProofGuard won't stop it.
It does not stop a compromised agent from issuing PASS decisions. If the decision-maker itself is misconfigured, ProofGuard records those decisions faithfully. It gives you an auditable trail — not a policy engine.
Single-file ledger only. Distributed or multi-node ledger synchronization is not supported.
Tamper Detection
Ledger entries form a SHA-256 hash chain. Any modification — even a single character — is detected at the exact position.
python verify_proof.py --ledger .proofguard-ledger.jsonl
Clean:
ProofGuard Verification — ✓ PASS
chain_valid : ✓ True
root_hash_match : ✓ True
hash : c4166d06c1d7e08fb696... (match)
After 1 character changed:
ProofGuard Verification — ✗ FAIL
root_hash_match : ✗ False
claimed : c4166d06c1d7e08fb696f82682c18ce0153071fafc69ac1e46edb7b09e0a5dc0
actual : c4166d06c1d7e08fb696f82682c18ce0153071fafc69ac1e46edb7b09e0a5dca
diff at : position 63 — '0' vs 'a'
Export for external audit:
from proofguard.ledger import export_proof_capsule
export_proof_capsule("proof_capsule.json")
The verifier uses Python stdlib only — no ProofGuard dependency required.
Integrity Properties
✔ tamper-evident append-only chain SHA-256 hash-chain, each entry links prev_hash
✔ cryptographically verifiable standalone verifier, stdlib only
✔ execution bound to decision no decision_id = no execution, structurally
✔ fail-closed every error path blocks, never silently allows
✔ canonical JSON sort_keys=True, separators=(',',':') — deterministic
✔ atomic append fcntl LOCK_EX + fsync — safe under concurrent writes
✔ violation log every blocked attempt recorded before exception
Fail-Closed Guarantee
| Scenario | Result |
|---|---|
| Judgment = STOP / HOLD / RETRY | ExecutionBoundaryViolation raised, execution never called |
| No decision found for ID | ExecutionBoundaryViolation raised, severity=CRITICAL |
| Gate not called before execution | ExecutionBoundaryViolation raised, logged |
| Any unexpected error in guard | Blocks, never silently allows |
All violations are written to _VIOLATION_LOG before the exception is raised.
Demo
# PASS / STOP / no-decision scenarios + tamper detection
python demo/demo_basic.py
Configuration
| Environment Variable | Default | Description |
|---|---|---|
PROOFGUARD_LEDGER |
.proofguard-ledger.jsonl |
Ledger file path |
PROOFGUARD_VIOLATION_LOG |
.proofguard-violations.jsonl |
Violation log path |
Compliance Context
- SOC2 change management — every agent action generates a signed ledger entry automatically
- EU AI Act (2026.08) — decision provenance + externally verifiable audit trail
- ISO 42001 — documented oversight of AI decision-making
Tests
python tests/run_all.py
# 9 suites, 161 assertions — canonicalization, concurrency, tamper, fail-closed, schema, adversarial, honesty boundary
License
MIT
Project details
Release history Release notifications | RSS feed
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 proofguard-0.1.0.tar.gz.
File metadata
- Download URL: proofguard-0.1.0.tar.gz
- Upload date:
- Size: 30.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
480b5ef0f54941ea32fa529ea1e107769a934bd6b93a7187dfc606a534c03544
|
|
| MD5 |
db04c116fffca04871119dedb09a82ee
|
|
| BLAKE2b-256 |
f34992fcbe207f478bd274a4e104a69c84d6de18ed260ceb91cddd11781c16e2
|
File details
Details for the file proofguard-0.1.0-py3-none-any.whl.
File metadata
- Download URL: proofguard-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fbed6367dd9815d1223f09fca4ad50ae75872fcc70bc56726f9fd9cf402d8206
|
|
| MD5 |
90ceb9b228f1eac002cb60aa719a69f0
|
|
| BLAKE2b-256 |
5638d7485adb869d9fde08a56f5a7170132f3e472bf79689c1acb98475f69f6b
|