Skip to content

SDK API

The Python SDK lives in nullrunio/nullrun-sdk-python. Package name on PyPI: nullrun.

pip install nullrun            # core only
pip install "nullrun[langgraph]"
pip install "nullrun[agents]"  # openai-agents
pip install "nullrun[all]"     # every optional extra

Auto-instrumentation for httpx-based libraries (openai, anthropic, openai-agents, …) is on by default once init() runs — see Auto-instrumentation.

Top-level

from nullrun import init, protect, workflow, span, agent, track_llm, track_tool, track_event
Symbol Purpose
init(api_key=None, *, api_url=None, debug=False) Initialise the SDK singleton. api_key is required (read from NULLRUN_API_KEY if not passed). The HMAC secret, batch size, flush interval, and fallback mode are not parameters here — set them via env vars or by constructing NullRunRuntime directly.
@protect Wrap a function for gate enforcement (budget pre-flight + kill/pause check + sensitive-tool decision). Takes no kwargs.
@sensitive Parameterless decorator. Marks a function as a sensitive tool — @protect will pre-check runtime.execute(...) before the body runs. Fails CLOSED on transport error (ADR-008), regardless of NULLRUN_FALLBACK_MODE. Chain with @protect in either order; the recommended form is @sensitive outside so add_sensitive_tool(fn.__name__) runs before the wrapper is built.
workflow(name=None) Context manager. Sets the workflow_id contextvar that @protect and track_* attach to events.
span(name=None) Context manager for nested trace spans.
agent(name=None) Context manager for agent identity.
track_llm(*, model, input_tokens, output_tokens, latency_ms=None, cost_cents=None, **extra) Manual escape hatch for non-HTTP LLM calls.
track_tool(*, name, args=None, result=None, latency_ms=None, **extra) Manual tool-call tracking.
track_event(*, type, **payload) Catch-all for custom events.

All track_* functions return None; they buffer into the event batch and flush on the next @protect call or flush_interval_ms.

Exceptions

All raised from nullrun.breaker.exceptions:

Class When Notes
BreakerError Base for all SDK errors Subclass of Exception
NullRunAuthenticationError Missing / invalid X-API-Key, bad HMAC 401 / 403
NullRunTransportError Gateway unreachable Carries .source (NETWORK_ERROR / GATEWAY_ERROR / BREAKER_OPEN / AUTH_ERROR) and .endpoint
RateLimitError HTTP 429 Subclass of NullRunTransportError; carries .retry_after, .upgrade_url
BreakerTransportError Transport misconfiguration Subclass of BreakerError
InsecureTransportError HTTP used where HTTPS required Subclass of BreakerTransportError
CostLimitExceeded Local breaker tripped (not a gateway call) Loop / cost / retry storm caught by local detector
ApprovalRequired Sensitive tool requires explicit approval flow Caller must trigger the approval UI
BreakerTimeout Gateway timeout Auto-retried with backoff
NullRunBlockedException Generic policy block Inspect .message and .details
LoopDetectedException Loop pattern detected Subclass of NullRunBlockedException
RetryStormException Consecutive failures > threshold Subclass of NullRunBlockedException
RateLimitExceededException Local rate signal Subclass of NullRunBlockedException; distinct from gateway RateLimitError
WorkflowPausedException Paused via control plane Resume via WS / API, then retry
WorkflowKilledException Killed via control plane Base class
WorkflowKilledInterrupt Kill arrived mid-call Subclass of BaseException — catch before except Exception

Catch-all pattern

import nullrun
from nullrun import WorkflowKilledInterrupt, init, protect, workflow

# NullRunBlockedException lives in `nullrun.breaker.exceptions` — the
# `nullrun.breaker` package re-exports only the base error vocabulary
# (BreakerError, BreakerTransportError, CostLimitExceeded, ApprovalRequired,
# BreakerTimeout, CircuitBreaker, CBState). Importing the policy-block
# exception from `nullrun.breaker` would raise ImportError.
from nullrun.breaker.exceptions import NullRunBlockedException

init(api_key="nr_live_...")

with workflow("my-agent"):
    @protect
    def step():
        ...

    try:
        step()
    except WorkflowKilledInterrupt:
        raise                    # kill contract — re-raise if you can't resume
    except NullRunBlockedException as exc:
        ...                      # budget / loop / retry / sensitive
    except nullrun.breaker.exceptions.RateLimitError as exc:
        time.sleep(exc.retry_after)

See also