Working examples for the nullrun Python SDK.
Each example is a self-contained, runnable file. The intent is to show the smallest possible change to add enforcement to a common agent framework.
pip install nullrun
export NULLRUN_API_KEY=nr_live_...Get an API key from the NullRun dashboard.
nullrun.init(api_key=...) patches the underlying HTTP transport and any
imported agent framework (openai, openai-agents, langgraph,
autogen, …) automatically. You get cost tracking without changing your
call sites; @protect is the gate layer (budget / kill / pause) that
runs before the call.
For frameworks that ship an extra, install with the matching optional
dependency (nullrun[langgraph], nullrun[openai], nullrun[llama-index],
nullrun[crewai], nullrun[autogen], etc.). The openai-agents SDK is
auto-detected at runtime without a separate extra.
| File | Framework | What it shows |
|---|---|---|
raw_openai_basic.py |
raw OpenAI | @protect + @guarded on a single LLM call |
openai_agents_basic.py |
OpenAI Agents SDK | @protect + @guarded on a multi-step agent run |
langgraph_basic.py |
LangGraph | Auto-instrumented StateGraph (recommended) |
langgraph_manual_wrapper.py |
LangGraph | nullrun.toolbox.langgraph.wrapper for re-compiled graphs |
cost_cap_demo.py |
any | Hard budget cap that halts the agent |
export NULLRUN_API_KEY=nr_live_...
python examples/raw_openai_basic.pyAll examples are read-only — they do not modify org state, policies, or
keys on your account. They do emit track events to the gateway
(auto-instrumented HTTP traffic from init()), so a cost_attribution
or examples tag in the dashboard will pick them up.
Every example ends with nullrun.shutdown() in a finally block. This
sends a clean WebSocket close frame so the backend does not log
"Connection reset without closing handshake" on long-running scripts.
No-op if init() was never called.
The examples intentionally avoid any try/except NullRunError block.
Three one-liners from the SDK do the work:
nullrun.init_or_die(api_key=...)— wrapsnullrun.init()so a missingNULLRUN_API_KEYproduces a clean exit, not a raw traceback.@nullrun.guarded(decorator) — wrap a function so anyNullRunErroris translated intoformat_user_message(exc)on stderr andsys.exit(1).with nullrun.handle():(context manager) — same behaviour for a region of code.
All three propagate WorkflowKilledInterrupt (a BaseException)
unchanged — kill signals must reach the top of the agent loop.
Non-NullRun exceptions also propagate so user bugs surface as honest
tracebacks.
For observability, register a hook with nullrun.on_error(...). The
hook fires before every NullRunError raise with the structured
fields (error_code, retryable, user_action, docs_url,
stage, workflow_id) — pair it with @guarded for Sentry /
dashboards:
import nullrun
@nullrun.on_error
def _to_sentry(err, ctx):
sentry_sdk.capture_exception(err, extra={
"error_code": err.error_code,
"retryable": err.retryable,
"stage": ctx.stage,
"workflow_id": ctx.workflow_id,
})If you need to branch on a specific error_code (operator dashboards,
per-code retry policies), reach for nullrun.NullRunError.error_code
and the structured fields — see the
error code catalogue
for the full list. But for the common "run an agent and print a
friendly message on failure" case, init_or_die + @guarded /
handle is enough.
PRs welcome. Keep each example under 80 lines. No external state beyond the NullRun API key.