Calculate bounded retry delays, apply jitter and provider minimum waits, and configure a snapshotted GetRatchet custom policy.
Exponential backoff with jitter in TypeScript and Python
Backoff separates failed attempts in time. Jitter spreads those attempts so workers that fail together do not all retry together. Neither mechanism proves that an operation is safe to repeat; destination-side idempotency is a separate requirement.
Calculate a bounded delay
After failed attempt number n, starting at one:
base = min(maxDelayMs, initialDelayMs × multiplier ** (n - 1))
jittered = round(base × (1 - jitterPercent/100 + random × 2 × jitterPercent/100))
selected = min(86_400_000, max(jittered, retryAfterMs or 0))random is a sample from zero through one. In GetRatchet, the policy cap applies before jitter. With a 600-second cap and 20% jitter, the policy delay can therefore reach 720 seconds. The independent 24-hour safety limit applies after selecting the final delay. The worker-supplied minimum is never jittered down.
Consider a custom policy with six total attempts, initial delay 10 seconds, multiplier 2, cap 600 seconds and jitter 20%:
| Failed attempt | Base delay | Jitter range | Next execution |
|---|---|---|---|
| 1 | 10 seconds | 8–12 seconds | Attempt 2 |
| 2 | 20 seconds | 16–24 seconds | Attempt 3 |
| 3 | 40 seconds | 32–48 seconds | Attempt 4 |
| 4 | 80 seconds | 64–96 seconds | Attempt 5 |
| 5 | 160 seconds | 128–192 seconds | Attempt 6 |
| 6 | None | None | Exhausted; no automatic attempt 7 |
With zero jitter, those five waits total 310 seconds. Actual completion time also includes handler execution, polling and queue gates. This is not a completion-time guarantee. If the provider requests 60 seconds after attempt 1, select 60 seconds; after attempt 5, a 60-second hint loses to the 128–192-second normal delay.
Test the calculation independently
These pure examples use already validated policy values. The optional sample makes a test deterministic; a real scheduler should use fresh randomness and persist the selected due time rather than recalculate it on every poll.
function delayMs(attempt: number, random: number, retryAfterMs = 0) {
const base = Math.min(600_000, 10_000 * 2 ** (attempt - 1));
return Math.min(86_400_000, Math.max(Math.round(base * (0.8 + random * 0.4)), retryAfterMs));
}
console.assert(delayMs(1, 0) === 8_000);
console.assert(delayMs(1, 1) === 12_000);
console.assert(delayMs(1, 0, 60_000) === 60_000);import math
def delay_ms(attempt, sample, retry_after_ms=0):
base = min(600000, 10000 * 2 ** (attempt - 1))
# Match JavaScript's rounding for nonnegative values, including .5 ties.
jittered = math.floor(base * (0.8 + sample * 0.4) + 0.5)
return min(86400000, max(jittered, retry_after_ms))
assert delay_ms(1, 0) == 8000
assert delay_ms(1, 1) == 12000
assert delay_ms(1, 0, 60000) == 60000Configure GetRatchet instead of another retry loop
Basic and Pro support a complete custom policy. Set it in the endpoint policy editor or through the endpoint policy API:
{
"preset": "CUSTOM",
"maxAttempts": 6,
"initialDelayMs": 10000,
"multiplier": 2,
"maxDelayMs": 600000,
"jitterPercent": 20
}For an endpoint update, include the current expectedVersion, defaultTimeoutMs, maxConcurrent and nullable rateLimitPerMinute alongside these fields. Read the endpoint policy first: the version guard protects against overwriting another administrator's update. For tool publication, use retryPreset: "CUSTOM" and put this object in retryPolicy, alongside the required contract fields.
Validation accepts 1–10 attempts; an integer initial delay of 1,000–1,800,000 milliseconds; a multiplier of 1–4; an integer maximum delay from the initial delay through 86,400,000 milliseconds; and jitter of 0–50%. Unknown fields are rejected. CUSTOM requires all fields; built-in presets forbid custom fields.
The built-in policies remain useful defaults:
| Preset | Total attempts | Initial delay | Multiplier | Cap before jitter | Jitter |
|---|---|---|---|---|---|
| STANDARD | 5 | 30 seconds | 2 | 15 minutes | ±20% |
| AGGRESSIVE | 8 | 5 seconds | 2 | 5 minutes | ±20% |
| RELAXED | 3 | 2 minutes | 2 | 30 minutes | ±20% |
Explicit enqueue maxAttempts overrides the default count. A selected tool contract policy takes precedence over the endpoint policy. The full resolved policy is snapshotted when the job is enqueued. Editing the endpoint later does not rewrite queued jobs or their scheduled due times.
The worker reference documents enqueue, claim, report, and policy configuration together, including the gates that can delay a scheduled attempt.
Avoid multiplying retries across layers: six durable attempts containing five HTTP client attempts can produce 30 provider requests. If the HTTP library performs retries, account for them deliberately. Prefer one durable scheduling policy and keep any transport retries tightly bounded. An open circuit still blocks claims, including custom policies with short delays. See HTTP error classification for Retry-After parsing and per-error decisions.