Bounded exponential backoff for idempotent external calls.
A transient Sprites blip used to fail provisioning outright: sandbox marked
failed, conversation marked failed, user retries by hand (#168). Steps
that are safe to repeat now retry a couple of times before giving up.
The contract is idempotency, and the retry policy leans on it: an unknown error shape is retried, because for an idempotent call the cost of a pointless retry is milliseconds, while the cost of misclassifying a transient transport error as permanent is a failed conversation. Only errors that are provably permanent — an HTTP 4xx other than 429 — are not retried.
Never wrap a non-idempotent call (spawning a runtime turn, a git clone into a non-empty directory) in this.
Summary
Functions
Whether an error reason is worth retrying.
Runs fun, retrying on retriable failure with exponential backoff + jitter.
Functions
Whether an error reason is worth retrying.
The Managoat.Sandbox taxonomy classifies directly: {:unavailable, _}
and {:rate_limited, _} are transient; :not_found, {:denied, _} and
{:invalid, _} are provably permanent, as are :truncated,
:not_supported and a reported-failed restore.
Everything else — :timeout, transport exceptions, unknown shapes — is
treated as transient; see the moduledoc for why unknown defaults to retry.
Runs fun, retrying on retriable failure with exponential backoff + jitter.
Retries when fun returns {:error, reason} with a retriable reason, or
when it raises (unknown raises are treated as transient, per the moduledoc). Anything else fun returns passes through
untouched. When attempts are exhausted the last error tuple is returned, or
the last exception re-raised, so call sites keep their existing semantics.
Options:
:attempts— total attempts, default 3:base_ms— first delay, default 250 (test config sets it to 1):max_ms— delay ceiling, default 2000:label— for the retry log line:retriable?— override thetransient?/1classifier