Safety rails (0.5.2)
Safety rails are cooperative guards on instrumented work. Use them with
chaos_call, chaos_acall, chaos_stream, chaos_astream, and explicit provider
patches. The package remains standalone; SDKs and sibling projects are optional.
from agentic_chaos import CircuitBreaker, ChaosAbortedError, chaos_call, chaos_session
with chaos_session([], breaker=CircuitBreaker(
max_cost_usd=5.0, max_failure_rate=0.5, window_size=20, min_calls=5,
max_duration_seconds=60, max_injections=100,
)) as session:
try:
result = chaos_call(lambda: "offline result")
session.record_usage(input_tokens=100, output_tokens=50,
input_usd_per_million=1, output_usd_per_million=2)
except ChaosAbortedError:
print(session.report_fields())
Limits and accounting
- Cost aborts at or above
max_cost_usd. Callsession.record_cost(cost_usd)orrecord_usage(...)with measured tokens and supplied per-million prices. No provider prices or response usage are guessed. An unreported cost cannot trigger this limit. Accounting after a call can overshoot the budget by that call's cost. Record reservations before a call if pre-dispatch budgeting is needed. - Failure rate aborts above
max_failure_rate, using the lastwindow_sizecompleted wrapped operations, aftermin_callscompletions. An escaping exception counts as failure; degradation and stream cuts do not imply a failed target operation. Stream consumption counts once, not once per chunk. Stream-provider creation is a separate guarded operation. Cancellation counts as a failed completion when its worker finishes. This is operational exception rate, not recovery quality or a resilience score. max_injectionsreserves capacity before fault execution and commits the count only when a fault event is produced. Eventless completions release their reservation. Built-in pass-through predicates skip admission. The next eligible injection attempt aborts. It does not count preview records. Slow-stream delay faults count once per stream even if they delay multiple chunks.- Duration is elapsed monotonic time checked at admission, dispatch, stream iteration/delivery boundaries, and completion. It is cooperative: blocking target calls, upstream reads, and bounded fault sleeps can exceed the duration before the next check. There is no timer that kills arbitrary code.
- Limits validate finite values and positive integer windows/counts. Failure
rates lie in
[0, 1];min_callscannot exceedwindow_size. Breaker configuration is copied into fresh state on each session entry.
Abort is sticky. Catching ChaosAbortedError does not re-enable dispatch.
Shared locked state covers async workers and inherited tasks; queued async
provider work checks again on the event loop before entering its target.
A session that has ended rejects inherited instrumented work.
Already-running calls can finish after abort. in_flight_at_abort records how
many wrapped operations were running when the threshold fired; in_flight
records outstanding operations when the report is captured. Reports are snapshots,
not a guarantee that external work stopped. Direct calls and custom fault code
that bypass these wrappers remain outside this boundary. The containment tests
include a deliberately bypassed direct call as a negative control.
Dry-run
agentic-chaos chaos run examples/chaos_customer_support_demo.py \
--inject rate_limit_storm --dry-run --save preview.json
agentic-chaos agent run my_agent.py --profile staging --profile-file chaos.toml \
--dry-run --max-duration-seconds 30 --save preview.json
chaos_session(..., dry_run=True) uses the same configured selection and seeded
probability, tool/edge predicates, and finite burst/turn state. Selection RNG and
preview counters use private copies; original fault state stays unchanged.
Custom pass-through faults should implement eligible() and, if stateful,
advance_preview() (use independent scalar preview state); their trigger is
never executed to discover eligibility during dry-run. It executes targets and delivers original stream chunks without
fault mutation, delay, error, or skipped calls. This can still make real API calls
and spend money. Preview records live in planned_events (would_inject) rather
than chaos_events, so consumers do not mistake a plan for an observed failure.
For streams, a plan is recorded when consumption reaches the configured fault
boundary. An unconsumed stream has no plan. Multiple faults still require explicit
call-site selection. Safety limits also apply during preview.
Both CLI run commands support --max-cost-usd, --max-failure-rate,
--failure-window, --min-calls, --max-duration-seconds, --max-injections,
and --dry-run. They save evidence even when the target raises or catches an
abort; an aborted command exits nonzero.
Reports and sibling compatibility
Build a native report with the session's additive safety fields:
from datetime import datetime, timezone
from agentic_chaos.models import ChaosReport
report = ChaosReport(name="experiment", start_time=datetime.now(timezone.utc),
chaos_events=session.events_as_json(), **session.report_fields())
The fields are aborted, abort_reason (threshold, limit, observed value,
in_flight_at_abort), dry_run, planned_events, cost_usd, injections, and
in_flight. Old reports load with defaults. The native event shape is unchanged.
AIOS export preserves these fields under the existing
io.deepagentlabs.agentic-chaos.safety extension and maps abort to Run
cancelled; preview adds no synthetic occurrence or reliability evidence.
No new canonical AIOS type is invented.
Compatibility tests exercise Lens's Workflow parser, Tower's native report
reader, MCP's saved-report AIOS export, and AIOS schema/semantic validation.
Existing consumers accept additive fields, but their native views need not display
abort/preview metadata. MCP's existing run_experiment does not expose the new
CLI flags or populate safety fields automatically. Use the Python API or CLI for
safety-controlled execution; the saved-report export preserves its metadata.
No sibling runtime dependency or patch release is required for these compatible
artifacts. The development lock includes Lens 0.6.1 and Evals 0.7.1; CI checks out
current Lens, Tower, MCP, and AIOS sources.
Run uv run python examples/chaos_safety_demo.py for the offline example.
Regenerate the demo with
uv run --with pillow python scripts/generate_safety_demo.py.
