Failure conditions
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. |