consequence-gate
A speculative outcome-simulation layer for AI agent tool calls. It sits
upstream of static runtime access gates (AgentWall, AWS Strands
BeforeToolCallEvent, MCP proxies, Prisma AIRS) and asks a different
question than they do.
Static gates ask: does this call match an allowed pattern or schema?
consequence-gate asks: what will this call actually do, and is that
outcome safe?
Why this exists
Static runtime gates are fast (sub-millisecond) and effective at schema
validation, RBAC, and pattern matching — but a schema-valid,
policy-compliant call can still be consequence-catastrophic. A
process_claim(amount=50000) call can pass every static check while
pushing an account over its daily velocity limit via an irreversible
instant transfer. consequence-gate projects the outcome of a call
(balance deltas, row-count blast radius, FK cascade depth,
irreversibility) before the call reaches your existing static gate, and
either passes it through, asks a human, denies it outright, or steers
the agent toward a pre-vetted safer alternative.
This is explicitly not a replacement for AgentWall / Strands / MCP proxies — it is an outcome prediction layer that runs upstream of them in the same pipeline.
Core contracts
- No silent argument mutation. Steering returns structured guidance and a suggested alternative call; the agent (or a human) still has to commit to it. This preserves the audit property that every executed call was one the agent explicitly chose.
- Idempotency keys are derived from the transaction's own natural key
(e.g.
claim_id, ortable + filter hash), never a fresh random token per retry -- otherwise a lost-response retry looks like a brand-new transaction instead of a duplicate. - Hard retry cap on STEER. Regardless of guidance quality, retries are capped (default: 2) before forcing escalation to a human, as a backstop against loop-thrashing.
- Confidence-gated escalation. Low-confidence projections route to
ASK, never to a confident-lookingALLOWorDENY-- an unfounded heuristic is worse than admitting uncertainty.
Modules
consequence_gate.simulators.financial-- functional: disbursement / claim / refund velocity and irreversibility modeling.consequence_gate.simulators.database-- functional: row-count blast radius via the DB's own query planner (EXPLAIN, not hardcoded selectivity constants) and recursiveON DELETE CASCADEgraph walking.consequence_gate.simulators.communications-- functional: outbound email/SMS/notification blast radius, unsubscribe suppression compliance, canary cohort analysis, sender reputation impact.consequence_gate.core-- shared models, the confidence/threshold evaluator, and the idempotency-locked circuit breaker.consequence_gate.integrations.strands_hook-- functional: AWS StrandsBeforeToolCallEventadapter with fullALLOW/DENY/ASK/STEERlifecycle.consequence_gate.integrations.mcp_proxy-- functional: MCP stdio proxy interceptingtools/callrequests, returning JSON-RPC errors orisError=truetool results.consequence_gate.integrations.langgraph_hook-- functional: LangGraph middleware (@wrap_tool_call) intercepting tool execution with fullALLOW/DENY/ASK/STEERlifecycle.consequence_gate.backtest-- offline JSONL trace replay harness and four-quadrant FP/FN/TN report generator, for evaluating this layer against historical execution logs with zero production integration.
Quickstart
Installation
pip install -e ".[dev]"
pytest
python examples/run_backtest_demo.py
AWS Strands Integration
from consequence_gate.integrations.strands_hook import create_financial_gate_hook
from strands.agents import Agent
hook = create_financial_gate_hook(
daily_tier_limit_inr=25000.0,
instant_wire_threshold=10000.0,
max_retries=2,
context_provider=lambda event: {
"account_rolling_24h_spend": get_current_spend(event),
"kyc_verified": is_kyc_verified(event),
},
)
agent = Agent(hooks=[hook])
response = agent("Process this claim for 50,000 INR")
MCP Proxy Integration
Run as a standalone proxy in front of any MCP server:
# Financial disbursement gate
python -m consequence_gate.integrations.examples.run_mcp_proxy financial \\
--downstream-command "npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb" \\
--daily-tier-limit 25000 \\
--instant-wire-threshold 10000
Configure in Claude Desktop / Cursor / Windsurf:
{
"mcpServers": {
"my-consequence-gate": {
"command": "python",
"args": ["-m", "consequence_gate.integrations.examples.run_mcp_proxy", "financial", "--downstream-command", "npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb"]
}
}
}
LangGraph Integration
from langchain.agents import create_agent
from consequence_gate.integrations.langgraph_hook import create_financial_gate_middleware
middleware = create_financial_gate_middleware(
daily_tier_limit_inr=25000.0,
instant_wire_threshold=10000.0,
max_retries=2,
)
agent = create_agent(
model="claude-sonnet-4",
tools=[my_tool],
middleware=[middleware],
)
Run the example:
python -m consequence_gate.integrations.examples.run_langgraph
Database Deletion Gate (Strands)
from consequence_gate.integrations.strands_hook import create_database_gate_hook
hook = create_database_gate_hook(
max_autonomous_delete_rows=100,
db_conn=get_db_connection(),
max_retries=2,
context_provider=lambda event: {
"table_metadata": get_table_metadata(event),
},
)
agent = Agent(hooks=[hook])
Communications Blast Gate (Strands)
from consequence_gate.integrations.strands_hook import create_communication_gate_hook
hook = create_communication_gate_hook(
max_autonomous_recipients=10000,
canary_min_size=100,
canary_max_bounce_rate=0.05,
canary_max_complaint_rate=0.01,
context_provider=lambda event: {
"segment_counts": get_segment_counts(event),
"recent_unsubscribes": get_recent_unsubscribes(event),
"historical_bounce_rate": 0.02,
"historical_complaint_rate": 0.005,
},
)
agent = Agent(hooks=[hook])
Decision Matrix
| Decision | Strands | MCP | LangGraph |
|---|---|---|---|
ALLOW |
Executes normally | Forwarded to downstream MCP server | Tool executes via handler(request) |
DENY |
BLOCKED: <reason> |
JSON-RPC error (code=-32603) | Raises ValueError("BLOCKED: ...") |
ASK |
ESCALATION_REQUIRED: <reason> |
isError=true tool result |
Raises ValueError("ESCALATION_REQUIRED: ...") |
STEER |
STEER_GUIDANCE: <guidance>\nSuggested alternative... |
isError=true + guidance |
ToolMessage(content="STEER_GUIDANCE: ...", status="error") |
Trust Model & Failure Modes
consequence-gate evaluates consequence by combining tool invocation parameters with runtime state returned from your context_provider callback (e.g. account_rolling_24h_spend, kyc_verified, table_metadata).
Critical Failure Modes & Mitigations
| Failure Mode | Risk | Mitigation in Consequence-Gate |
|---|---|---|
| Stale Context / Cache Lag | Context provider returns yesterday's spend balance, potentially missing velocity breaches. | If context is unverified or confidence drops below 0.8, the gate always defaults to ASK (human escalation). It never grants a speculative ALLOW. |
| Missing Context Provider | Tool is invoked without any environment or database connection. | The simulator scores confidence as 0.0 or 0.5 and raises an escalation requirement (ASK). |
| Agent Steering Thrashing | The agent repeatedly submits non-compliant alternative calls in response to guidance. | The SteerCircuitBreaker enforces a hard retry cap (default: 2 retries) before terminating the loop and escalating to human review. |
| Replay / Network Retries | Network timeout causes agent runtime to resubmit the identical tool call. | Idempotency tokens are deterministically keyed to the entity's natural business key, guaranteeing identical evaluation without incrementing velocity counters twice. |
See SECURITY.md for full trust boundary documentation.
Offline Backtest Benchmark
Before deploying to production, run an offline backtest against historical execution traces to measure the four-quadrant FP/FN/TN breakdown:
# Run backtest on the bundled 500-trace synthetic benchmark dataset
consequence-gate backtest examples/benchmark_traces.jsonl
Empirical Benchmark Summary (500 Synthetic Traces)
(Note: Evaluated on a synthetically generated trace corpus examples/benchmark_traces.jsonl to validate harness mechanics end-to-end; see BACKTEST_RESULTS.md for disclosure)
- Benign Pass-Through (True Negatives): 252 (50.4%) — Benign operations passed through.
- Downstream Hazards Intercepted: 129 (25.8%) — Schema-valid hazards caught before execution.
- Over-Blocked Operations Relieved: 35 (7.0%) — Benign calls over-blocked by naive regex gates safely enabled.
- Ambiguous Escalations: 84 (16.8%) — Low-confidence/unrecognized calls routed to
ASK.
Detailed breakdown and reproduction steps: BACKTEST_RESULTS.md | BACKTEST_METHODOLOGY.md
Status & Test Coverage
- Financial simulator: functional with unit tests (
tests/test_financial_sim.py, 96% coverage) - Database simulator: functional with unit tests (
tests/test_database_sim.py), plus real-PostgreSQL EXPLAIN fixture validation (tests/test_database_explain_fixture.py, runs in CI via apostgres:16service container; skips locally when no database is reachable) - Communications simulator: functional with unit tests (
tests/test_communications_sim.py, 90% coverage) - Circuit breaker & natural-key idempotency: functional with unit tests (
tests/test_circuit_breaker.py, 95% coverage) - Strands integration: functional with unit tests (
tests/test_strands_hook.py, 91% coverage) - MCP integration: functional with unit tests (
tests/test_mcp_proxy.py) - LangGraph integration: functional with unit tests (
tests/test_langgraph_hook.py) - CLI & Backtesting: functional with unit tests (
tests/test_cli.py)
Contributing
See CONTRIBUTING.md for architecture guidelines, coding conventions, and instructions on creating new consequence simulators.
License
Apache-2.0
Metadata
Release files for consequence-gate 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| consequence_gate-0.2.0.tar.gz | 52.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| consequence_gate-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 96.2 kB
Release files / consequence_gate-0.2.0.tar.gz
| Download URL | consequence_gate-0.2.0.tar.gz |
|---|---|
| Size | 52.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2c1b04b47081dd89f83e6e570abbad571e7ca5867420269329fbb97ab431e464
|
|
BLAKE2b-256 checksum How to use checksums |
a73f381d058b946f77247e690d10ffd927868ef15991bc92899d3aa42194f774
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.1
|
Release files / consequence_gate-0.2.0-py3-none-any.whl
| Download URL | consequence_gate-0.2.0-py3-none-any.whl |
|---|---|
| Size | 44.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
975bee40a25499f0cf212797129a2d44c08c266f35e45503b83727bf341fa08a
|
|
BLAKE2b-256 checksum How to use checksums |
1303fe731e0dce348cdab54dd9cbe1ecea21bf6ffd951c076836fa0f69838f76
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.1
|