7.7 Retries, effect classes, and idempotency
Composed flows touch multiple services, so failures are a fact of life. RS2 makes retry declarative and safe: you describe a policy, and the runtime only auto-retries what's safe to retry, deriving idempotency keys so effects don't duplicate.
Effect classes: what may auto-retry
Every call has an effect class that governs whether it can be retried automatically. Defaults are inferred from the method; you can override per call.
| Class | Meaning | Method default |
|---|---|---|
pure |
No side effects; always safe to retry | GET/HEAD/OPTIONS |
idempotent |
Repeating is harmless | PUT/DELETE |
keyed |
Retry only with an idempotency key | — |
unsafe |
Never auto-retried | POST/PATCH |
Override on a call: { "call": { …, "effect": "keyed" } }. Marking a POST as
keyed opts it into retry because the runtime will attach a stable idempotency
key (below).
The retry policy
A declarative policy (camelCase), resolved most-specific-first: per-call retry
→ mount retry → tenant retry → runtime default (no retry).
{ "enabled": true, "maxAttempts": 4, "baseDelayMs": 250, "maxDelayMs": 5000,
"backoffMultiplier": 2, "jitter": "full",
"retryStatuses": [408, 429, 500, 502, 503, 504],
"retryOnNetworkError": true, "respectRetryAfter": true }
Unlike v1 there is no $_retry pipeline variable — retry is configured on the
step, mount, or tenant, never imperatively inside the flow.
The policy is target-agnostic: an external call (an absolute URL through the
mount's httpOut grants, 7.3) retries exactly like an internal one —
retryStatuses against the upstream's response, retryOnNetworkError for
transport failures, backoff with the upstream's Retry-After honored. A keyed
external POST sends the auto-derived Idempotency-Key header to the provider
(the Stripe convention), stable across attempts.
Auto-derived idempotency keys
This is the safety mechanism that makes retry usable for effects. For keyed/unsafe calls inside a segment, the runtime derives a stable idempotency key from the invocation and the step's position. The key is the same across retry attempts, so if a segment re-runs, a keyed effect dedupes instead of firing twice. You don't plumb keys through your pipeline — composed effects dedupe across segment retries automatically.
(For external callers, you send your own Idempotency-Key header; the runtime's
idempotency store handles replay. That's the same machinery, covered in 9.3.)
Putting it together
- A call's effect class decides eligibility (
pure/idempotentretry freely;keyedretries with a key;unsafedoesn't). - The retry policy decides how (attempts, backoff, which statuses).
- The runtime's derived keys make keyed retries safe within a segment.
The "segment" keeps coming up because it's the unit all of this operates on. The
next section explains segments and the ?$plan tool that shows them.
← Previous: 7.6 Transforms with JSONata · Manual home · Next: 7.8 Segments and ?$plan →