Runtime decision layer for tool-using AI agents
Before your agent executes a supported tool or model call, the
SDK asks the gate. allow, block, or
require_approval — backed by tool patterns,
budgets, rate limits, and human approvals.
How it fits together
flowchart LR
Agent["Your agent<br/>(Python SDK)"] -->|"@protect"| Gate
Gate -->|"budget pre-flight<br/>policy fetch"| Gateway["NullRun gateway"]
Gateway -->|"plan limit<br/>rate limit<br/>ToolBlock check"| Decision{"allow?"}
Decision -->|"yes"| Body["wrapped function runs"]
Decision -->|"no"| Block["raise NullRunBlockedException"]
Gateway -.->|"control plane<br/>(WebSocket)"| Kill["kill / pause<br/>from dashboard"]
What you get out of the box
Budget gate
Set a per-workflow cap in cents. The SDK asks the gateway
"any budget left?" before every
@protect
call — no round-trip cost when the answer is "yes". Hard
blocks on overrun; soft mode allows a bounded overrun
when an active chain is present.
Action-bound approvals
Operator approves sensitive calls via typed predicates
(
money_amount / tool_parameters).
Every approval is bound to the exact action payload via a
SHA-256 action_digest — the grant is refused
if the SDK then executes a different amount or
different arguments.
Real-time kill / pause
A WebSocket control plane pushes
killed /
paused to every connected SDK. WorkflowKilledInterrupt
is a BaseException so it reaches the top of
the agent loop, not a swallowed except Exception.
ToolBlock policy
Server-side glob-pattern rules (
mcp://payments/refund*,
bash, db.drop) decide which
canonical tool names are allowed. Always Hard:
fails closed on transport error, regardless of the budget's
enforcement_mode.
Auto-instrumentation
nullrun.init() patches OpenAI, Anthropic,
LangGraph, OpenAI Agents, Mistral, Gemini, Cohere, Bedrock,
LlamaIndex, CrewAI, and AutoGen — cost tracking without
@protect.
Decision history + audit chain
Every gate decision (allow / block / require_approval) is
recorded in
audit_events with hash-chained
content_hash + previous_hash. The
chain is recompute-verifiable on demand via GET
/api/v1/orgs/{org_id}/audit-log/verify.
Wire it up in 30 lines
sequenceDiagram
participant U as Your code
participant SDK as nullrun SDK
participant G as NullRun gateway
participant DB as Dashboard
U->>SDK: from nullrun import init, protect
U->>SDK: init(api_key="nr_live_...")
Note over SDK: fetches HMAC secret via /api/v1/auth/verify
U->>SDK: with workflow("user-123"):<br/> @protect<br/> def step(): ...
loop every @protect call
U->>SDK: step()
SDK->>G: POST /api/v1/gate (tokens=1)
G-->>SDK: {decision: "allow"}
SDK->>SDK: run wrapped function
SDK->>G: POST /api/v1/track (actual cost)
end
DB->>G: operator clicks Kill
G-->>SDK: WS push: StateChange(killed)
SDK->>U: raise WorkflowKilledInterrupt
Managed runtime, not a self-hosted deployment
NullRun runs as a managed control plane at nullrun.io.
There is no self-hosted deployment option today. The Python
SDK runs inside your process and talks to the hosted gateway
over HTTPS; the dashboard at nullrun.io hosts the
control plane. See
the docs for the SDK
surface and /about for
the runtime contract.