Skip to main content

Chain-level governance

This is the design decision that makes ProofRail different from other agent governance tools. Understanding it explains why the SDK is structured the way it is and what kind of failures it catches that others don’t.

The problem with per-call governance

Most AI safety tools evaluate one tool call at a time. The agent calls search_web — the tool is allowed, no PII, looks fine. The agent calls calculate_offer for 3,000wellbelowtheconfiguredsingletransactionthreshold,allowed.Theagentcallssendemaildomainisontheallowlist,contentlooksnormal,allowed.Theagentcallsrecordcommitmentfor3,000 — well below the configured single-transaction threshold, allowed. The agent calls `send_email` — domain is on the allowlist, content looks normal, allowed. The agent calls `record_commitment` for 3,000 — again under the threshold, allowed. Another 3,000commitmentasteplaterallowed.A3,000 commitment a step later — allowed. A 4,000 commitment to close out the workflow — still under the single-transaction threshold, allowed. Six allow decisions. The workflow completes. Your company is now committed to paying a vendor $13,000. Each individual step passed its own review. The chain of steps did not.

What chain-level tracking measures

When you open a ProofRail Chain, the SDK creates a session that lives for the duration of that workflow. Every action the agent takes in that session contributes to running totals:
  • Cumulative financial exposure — sum of all amount or value fields seen across the workflow
  • External communications — count of emails sent, messages posted, API calls made to external domains
  • Records modified — count of write/update/delete actions against your data
  • Privileged actions — count of permission changes, IAM operations, role assignments
  • External domains contacted — deduplicated list of every domain the agents touched
  • Tokens used and estimated cost — running LLM spend across all calls in the workflow
Every policy evaluation sees these totals. A 4,000chargelooksfineinisolation;thesamechargeafterthreeother4,000 charge looks fine in isolation; the same charge after three other 3,000 charges is the one that should require approval. The cumulative threshold catches this; the per-transaction threshold doesn’t.

The example, in code

import asyncio
import proofrail

proofrail.init(
    api_key="prail_...",
    financial_approval_threshold_usd=5000,        # single-transaction cap
    cumulative_financial_threshold_usd=10000,     # chain cumulative cap
)

async def vendor_workflow():
    async with proofrail.Chain("vendor-purchase") as chain:

        # Research agent — no financial impact
        await chain.record_agent_action(
            agent_name="research-agent",
            action_type="tool_call",
            action_name="search_web",
            payload={"query": "office chair vendors"},
        )
        # Cumulative exposure: $0

        # Negotiation agent — proposes a price
        await chain.record_agent_action(
            agent_name="negotiation-agent",
            action_type="tool_call",
            action_name="record_commitment",
            payload={"amount_usd": 3000},
        )
        # $3,000 < $5,000 single-transaction threshold → allowed
        # Cumulative exposure: $3,000

        # Email agent — sends the offer
        await chain.record_agent_action(
            agent_name="email-agent",
            action_type="tool_call",
            action_name="send_email",
            payload={"to": "sales@vendor.com", "subject": "Purchase order"},
        )

        # Another $3,000 commitment
        await chain.record_agent_action(
            agent_name="commitment-agent",
            action_type="tool_call",
            action_name="record_commitment",
            payload={"amount_usd": 3000},
        )
        # $3,000 < $5,000 → still allowed by single-transaction rule
        # Cumulative exposure: $6,000 (still under the $10,000 chain cap)

        # Another $3,000
        await chain.record_agent_action(
            agent_name="commitment-agent",
            action_type="tool_call",
            action_name="record_commitment",
            payload={"amount_usd": 3000},
        )
        # $3,000 < $5,000 → still allowed by single-transaction rule
        # Cumulative exposure: $9,000 (still under chain cap)

        # Final $4,000 commitment
        await chain.record_agent_action(
            agent_name="commitment-agent",
            action_type="tool_call",
            action_name="record_commitment",
            payload={"amount_usd": 4000},
        )
        # $4,000 < $5,000 → would be allowed by single-transaction rule
        # BUT cumulative would reach $13,000 — exceeds $10,000 chain cap
        # → require_approval triggered, execution blocks for human review

asyncio.run(vendor_workflow())
A per-call governance tool would let every action through — none of the individual amounts exceeds the single-transaction threshold. ProofRail blocks the final action and waits for a human to approve or deny it. The approver sees the full chain context (all the prior commitments, what each agent did, the running totals) and makes an informed decision.

How chain context interacts with fast-path

ProofRail’s local fast-path evaluation handles obviously-safe actions without a backend round-trip. These actions are still added to cumulative metrics — the fast-path doesn’t bypass tracking, only the backend authorization step. This means the early search_web call in the example above resolves in under 5ms locally, but its (non-financial, no external comm) presence is still recorded. When the financial actions come through later, the backend sees the full chain state including the fast-path events. See Configuration Reference for fast-path tuning options.

Chain lifecycle

A chain is a context manager. It starts when you enter the with block and completes when you exit, normally or via exception.
async with proofrail.Chain("workflow-name", metadata={...}) as chain:
    # All actions recorded here belong to one chain
    await chain.record_agent_action(...)
    await chain.record_agent_action(...)
# Chain completes here. Receipt is generated. Audit trail is sealed.
When the chain exits, the SDK calls the chain-complete endpoint on the backend, which seals the chain and triggers receipt generation. See Audit receipts for what’s in a receipt and how to verify them.

Where to go next

Policies

How ProofRail decides what to allow, flag, or block.

Audit receipts

HMAC-signed records you can verify offline.

Configuration

Tune chain behavior — thresholds, fail modes, timeouts.

Framework adapters

Wrap LangGraph, LangChain, CrewAI, or MCP with one line.