Failure conditions

On this page

Scenarios add repeatable failures to a world: rejected requests, latency, expired credentials or webhook disruption. Use them to check whether an agent recovers correctly and leaves the expected final state.

In the dashboard, open Failure conditions. The CLI and file format call these configurations scenarios. Both use the same rules; see the dashboard workflow guide.

Apply a scenario

A world has one active scenario. Apply a bundled name or a YAML/JSON file:

worlds scenario ls
worlds scenario apply <world_id> tickets-storm
worlds scenario apply <world_id> ./failure-conditions.yaml
worlds scenario clear <world_id>

Through the admin API, send POST /admin/worlds/:id/scenario with either {"name": "tickets-storm"} or {"scenario_doc": {...}}.

Applying or replacing a scenario resets its request-rule and webhook counters. Clearing it removes those counters and the active rules. API-key use counts are separate; see credential expiry.

Write a request rule

This example rejects every fourth support-ticket update before the update runs:

name: support-update-failures
rules:
  - match:
      method: PUT
      path: /api/v2/tickets/*
    every_nth: 4
    respond:
      status: 500
      stripe_error: api_error

The field is still named stripe_error in the shared schema. Keep that spelling for all connectors. The response is rendered in the target surface's error envelope, including Zendesk-shaped errors for /api/v2/*.

An injected response runs before the handler and before idempotency lookup. It rejects the operation without changing business objects or storing a replay response. Request logs and scenario counters still record the attempt. This models a rejected request, not a successful write whose response was lost.

Match and timing reference

Each request rule has its own persisted counter. SDK retries count as requests too.

Field Meaning
match.method GET, POST, DELETE, PUT or PATCH; omit to match any method.
match.path Path glob; omit to match any twin path. * matches one segment; ** matches the remainder.
after_requests Number of matching requests before activation; default 0.
duration_requests Number of requests in the active window; omit to keep the rule active.
every_nth Apply every Nth request after after_requests; minimum 2. When present, this takes precedence over duration_requests.
respond Inject an error with status and optional stripe_error (default api_error).
latency_ms Add 0–30,000 milliseconds of wall-clock delay. The delay itself does not move logical time.

For example, /api/v2/tickets/* matches /api/v2/tickets/123.json, but not the ticket collection. Use /v1/** for Stripe requests or /api/v2/** for support requests. An empty match: {} matches both surfaces in a combined world.

All matching rule counters advance. When multiple rules activate, the first error response wins and the largest latency applies. Use one auth rule and one webhook rule per document; only the first of each kind is used.

Full types and validation limits are in the generated scenario schema.

Example: a Stripe rate-limit window

Allow two matching requests, reject the next three, then recover:

name: refund-rate-limit
rules:
  - match: { method: POST, path: /v1/refunds }
    after_requests: 2
    duration_requests: 3
    respond: { status: 429, stripe_error: rate_limit }

Use these error shorthands with their corresponding statuses:

stripe_error Status and behavior
rate_limit 429; no retry header.
lock_timeout 429; includes Stripe-Should-Retry: true on Stripe responses.
api_error Uses the configured status (400–599); normally 500.
invalid_api_key 401 authentication failure.
api_key_expired 401 expired-key failure.

The retry header lets a configured Stripe SDK retry a lock timeout. A plain rate-limit 429 has no such instruction. Check your SDK's retry settings when deciding which failures the agent itself should see.

Credential expiry

name: expired-credentials
rules:
  - auth: { expire_after_requests: 10 }

The key returns 401 once its cumulative authenticated-request count exceeds the threshold. Applying or reapplying the scenario does not restart that count. Use a fresh or reset world to expire the key after exactly that many requests from the test's start.

Clearing the scenario removes this simulated expiry. The response uses the connector's authentication envelope; Stripe exposes api_key_expired.

Webhook disruption

name: delivery-failures
rules:
  - webhooks:
      drop_every_nth: 3
      reorder: swap_adjacent

drop_every_nth records every Nth delivery attempt as dropped, without sending or retrying it. swap_adjacent reverses each pair within a flush batch.

These rules affect the current Stripe-event webhook dispatcher. Native Zendesk writes do not produce Stripe events. Set the endpoint's auto_deliver: false and flush explicitly when testing exact batches or delivery order. See webhook determinism.

Bundled scenarios

Choose a scenario that matches the connector paths your agent uses.

Name Scope Failure pattern
tickets-storm Support ticket updates Every fourth PUT /api/v2/tickets/* returns 500.
auth-expiry World API keys Requests beyond each key's first ten return 401.
rate-limit-storm Stripe First three requests pass; next three return 429; then recovery.
intermittent-500s Stripe Every third request returns 500.
slow-stripe Stripe 800 ms delay; 1,500 ms on /v1/customers/*.
lock-timeouts Stripe POSTs Every fifth write returns retryable 429 lock_timeout.
stripe-storm-shift Stripe Six 429s after 25 calls, plus every seventh POST after its first ten. These are independent counters.
webhook-chaos Stripe-event deliveries Drop every third attempt and swap adjacent deliveries.