Changelog¶
Every released version of the nullrun Python SDK, newest first. Entries
cover what someone building on NullRun can observe: the API surface, the
behaviour a protected run exhibits, and the wire contract.
The docs are the source of truth for how each of these behaves — this page records when it changed. A version listed here is described in full in the chapters it affects; if a symbol appears in an entry, it is documented on one of those pages.
Reading version numbers
The SDK is versioned 0.MINOR.PATCH. The minor position is
where the public surface moves — a symbol appears, changes shape, or
goes away. The patch position carries behaviour fixes and
additions that do not alter the surface.
All versions below the current one are available on PyPI:
pip install nullrun==0.18.5
0.18.5 — 2026-09-26¶
The public surface is reduced to the smallest set that still does the job. Four entry points collapse into two, and a set of internals stops being importable.
Changed¶
-
nullrun.guardreplacesnullrun.handle. Same@contextmanagerbody: it catchesNullRunError, re-raisesWorkflowKilledInterruptuntouched, prints a developer-facing report, and exits non-zero on failure. See Error handling for the full context-manager set and whereguardsits among them. -
nullrun.init(fail_on_exit=True)replacesinit_or_die(). Fail-fast is now a keyword argument oninitrather than a separate function. The default isFalse, which preserves the raise-on-bad-config behaviour that embedders want. -
nullrun.shutdown()is registered automatically.init()arranges for a clean WebSocket close at process exit, so a long-running script no longer needs an explicit shutdown call. Calling it yourself is still valid and still idempotent. -
Install is a single line. Per-framework install groups are gone;
pip install nullrunis the whole instruction. The framework auto-detection in Framework integrations is unchanged.
Removed¶
These names are no longer importable from the top-level nullrun
namespace. dir(nullrun) now returns exactly:
__version__, init, protect, shutdown, on_error, guard,
NullRunError, NullRunAuthError, NullRunConfigError,
NullRunBackendError, NullRunBudgetError, NullRunToolBlockedError,
WorkflowKilledInterrupt, NullRunWorkflowKilledError,
NullRunMcpDestructiveBlockedError,
NullRunMcpReadonlyBypassBlockedError,
NullRunMcpApprovalRequiredError,
NullRunApprovalDbUnavailableError,
format_user_message, set_user_message
nullrun.status()— the snapshot is reached throughnullrun.get_runtime().status(), which returns the sameNullRunStatusdataclass.NullRunStatusitself remains importable as a type.@nullrun.guarded— the decorator form iswith nullrun.guard():.nullrun.auto_instrument,nullrun.is_auto_instrumented, and the module-leveltrack_eventalias. Theruntime.track_eventmethod is unaffected.NullRunCallbackfrom the lazy-export table. The framework integrations do not need it.
The wire contract is unchanged from 0.18.0.
0.18.2 — 2026-09-22¶
@protect is established as the single entry point. Every call routes
through the execute endpoint unconditionally — there is no opt-out and
no per-tool registry to maintain.
import nullrun
nullrun.init()
@nullrun.protect
def my_tool(query: str) -> str:
...
See Decorators & extractors for the full decorator reference and Tool policies for what the gate evaluates.
0.18.1 — 2026-09-22¶
Aimed squarely at the moment a developer first runs an example and it does not work.
-
A four-line error report on the fail-fast paths. A configuration or gate failure at startup prints what failed, where it failed (wire endpoint, status code, transport source), why it failed (the underlying exception and its machine
error_code), and what to do about it. The end-user-facing message is still the headline, so an end-user deployment still sees a single clean sentence. See Troubleshooting. -
A warning when a protected tool fires 50 times with no model activity. The usual cause is a tool wired up without the agent loop that feeds it, which otherwise bills nothing and looks like it works.
0.18.0 — 2026-09-21¶
- Approved actions are consumed on success. When an action runs after approval, the grant is closed automatically. Grants left open past their expiry no longer accumulate, so the approvals surface reflects only what is actually waiting on a human.
0.17.1 — 2026-09-15¶
- Every gate call carries its own operation id. A single id is no longer reused across calls in the same scope, which removes a class of spurious budget errors where an unrelated call inherited the identity of the first one. The error codes in Error codes are unchanged.
0.17.0 — 2026-09-12¶
-
The circuit breaker serialises sync and async callers against each other. A threaded call and an
asynciocall on the same breaker instance previously took different locks, so their state transitions could interleave. Both paths now contend on one lock. See Circuit breaker. -
The impact helpers resolve off the top-level
nullrunnamespace rather than needing a deep import into a private module. The helpers that Sensitive tools describes are what this fixes; the exception they were raising on first call is gone.
0.16.x — August–September 2026¶
The hardening series. The surface settled here; everything above is a change to it.
-
Kill propagates as an exception rather than an exit. A
WorkflowKilledInterruptraised by Kill is not swallowed by an enclosing error handler, so an agent that is stopped from the dashboard stops. See Control plane. -
Fail-closed policy fetches. A policy the gate cannot retrieve is a refusal, not a pass.
ToolBlockand aggregate rate limiting fail closed; per-key limits and budget checks fail open, because the budget layer behind them is the backstop. See Policies. -
Cost accounting is decimal, not floating point. Amounts are serialized without binary-float drift, so a reserved cost and its consumption net to zero. See Budgets.
-
Reservations are released on the exception path. A protected call that raises leaves no reservation behind.
Reports something that does not match what you are reading here? The SDK repository takes issues, and the GitHub link in the footer points at this docs repository.