← Создание агентов на Cloudflare / agents / runtime / execution
Повторы
Повторяйте неудачные операции с экспоненциальной задержкой и джиттером. Agents SDK предоставляет встроенную поддержку повторов для запланированных задач, задач из очереди и this.retry() метод для вашего собственного кода.
Обзор
Временные сбои обычны при вызовах внешних API, взаимодействии с другими сервисами или выполнении фоновых задач. Система повторных попыток обрабатывает их автоматически:
- Экспоненциальная задержка : каждая повторная попытка ждёт дольше предыдущей
- Джиттер : случайные задержки предотвращают проблему массового наплыва запросов (так называемый thundering herd)
- Настраиваемый : настраивает число попыток, задержки и лимиты для каждого места вызова
- Встроено : операции расписания, очереди и workflow повторяются автоматически
быстрый старт
Используйте this.retry() чтобы повторять любую асинхронную операцию:
import { Agent } from "agents";
export class MyAgent extends Agent {
async fetchWithRetry(url) {
const response = await this.retry(async () => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
return response;
}
}import { Agent } from "agents";
export class MyAgent extends Agent {
async fetchWithRetry(url: string) {
const response = await this.retry(async () => {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
});
return response;
}
}По умолчанию this.retry() повторяет попытку до трёх раз с экспоненциальной задержкой и джиттером.
this.retry()
retry() метод доступен для каждого Agent экземпляр. По умолчанию он повторяет вызов переданной функции при любой выброшенной ошибке.
async retry<T>(
fn: (attempt: number) => Promise<T>,
options?: RetryOptions & {
shouldRetry?: (err: unknown, nextAttempt: number) => boolean;
}
): Promise<T>Параметры:
fn: асинхронная функция, которую нужно повторить. Получает номер текущей попытки (нумерация с 1).options: необязательная конфигурация повторных попыток (см. RetryOptions ниже). Параметры проверяются заранее: некорректные значения сразу вызывают исключение.options.shouldRetry: необязательный предикат, вызываемый с выброшенной ошибкой и номером следующей попытки. Вернитеfalseчтобы немедленно прекратить повторные попытки. Если значение не задано, повторяются все ошибки.
Возвращает: результат fn в случае успеха.
Выбрасывает: последнюю ошибку, если все попытки завершились неудачей, или shouldRetry возвращает false.
Примеры
Базовая повторная попытка:
const data = await this.retry(() => fetch("https://api.example.com/data"));const data = await this.retry(() => fetch("https://api.example.com/data"));Пользовательские параметры повторных попыток:
const data = await this.retry(
async () => {
const res = await fetch("https://slow-api.example.com/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{
maxAttempts: 5,
baseDelayMs: 500,
maxDelayMs: 10000,
},
);const data = await this.retry(
async () => {
const res = await fetch("https://slow-api.example.com/data");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{
maxAttempts: 5,
baseDelayMs: 500,
maxDelayMs: 10000,
},
);Использование номера попытки:
const result = await this.retry(async (attempt) => {
console.log(`Attempt ${attempt}...`);
return await this.callExternalService();
});const result = await this.retry(async (attempt) => {
console.log(`Attempt ${attempt}...`);
return await this.callExternalService();
});Выборочная повторная попытка с shouldRetry:
Используйте shouldRetry чтобы прекращать повторные попытки при определённых ошибках. Предикат получает как саму ошибку, так и номер следующей попытки:
const data = await this.retry(
async () => {
const res = await fetch("https://api.example.com/data");
if (!res.ok) throw new HttpError(res.status, await res.text());
return res.json();
},
{
maxAttempts: 5,
shouldRetry: (err, nextAttempt) => {
// Do not retry 4xx client errors — our request is wrong
if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
return false;
}
return true; // retry everything else (5xx, network errors, etc.)
},
},
);const data = await this.retry(
async () => {
const res = await fetch("https://api.example.com/data");
if (!res.ok) throw new HttpError(res.status, await res.text());
return res.json();
},
{
maxAttempts: 5,
shouldRetry: (err, nextAttempt) => {
// Do not retry 4xx client errors — our request is wrong
if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
return false;
}
return true; // retry everything else (5xx, network errors, etc.)
},
},
);Повторы в расписаниях
При создании расписания передайте параметры повторных попыток:
// Retry up to 5 times if the callback fails
await this.schedule(
"processTask",
60,
{ taskId: "123" },
{
retry: { maxAttempts: 5 },
},
);
// Retry with custom backoff
await this.schedule(
new Date("2026-03-01T09:00:00Z"),
"sendReport",
{},
{
retry: {
maxAttempts: 3,
baseDelayMs: 1000,
maxDelayMs: 30000,
},
},
);
// Cron with retries
await this.schedule(
"0 8 * * *",
"dailyDigest",
{},
{
retry: { maxAttempts: 3 },
},
);
// Interval with retries
await this.scheduleEvery(
30,
"poll",
{ source: "api" },
{
retry: { maxAttempts: 5, baseDelayMs: 200 },
},
);// Retry up to 5 times if the callback fails
await this.schedule(
"processTask",
60,
{ taskId: "123" },
{
retry: { maxAttempts: 5 },
},
);
// Retry with custom backoff
await this.schedule(
new Date("2026-03-01T09:00:00Z"),
"sendReport",
{},
{
retry: {
maxAttempts: 3,
baseDelayMs: 1000,
maxDelayMs: 30000,
},
},
);
// Cron with retries
await this.schedule(
"0 8 * * *",
"dailyDigest",
{},
{
retry: { maxAttempts: 3 },
},
);
// Interval with retries
await this.scheduleEvery(
30,
"poll",
{ source: "api" },
{
retry: { maxAttempts: 5, baseDelayMs: 200 },
},
);Если колбэк выбрасывает исключение, он повторяется согласно параметрам повторных попыток. Если все попытки завершаются неудачей, ошибка записывается в лог и перенаправляется через onError(). Расписание всё равно удаляется (для одноразовых задач) либо пересоздаётся заново (для cron- и interval-расписаний), независимо от того, успешно завершилось выполнение или нет.
Повторы в очередях
При добавлении задачи в очередь передайте параметры повторных попыток:
await this.queue(
"sendEmail",
{ to: "[email protected]" },
{
retry: { maxAttempts: 5 },
},
);
await this.queue("processWebhook", webhookData, {
retry: {
maxAttempts: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
},
});await this.queue(
"sendEmail",
{ to: "[email protected]" },
{
retry: { maxAttempts: 5 },
},
);
await this.queue("processWebhook", webhookData, {
retry: {
maxAttempts: 3,
baseDelayMs: 500,
maxDelayMs: 5000,
},
});Если колбэк выбрасывает исключение, повторная попытка выполняется до того, как задача будет удалена из очереди. После исчерпания всех попыток задача удаляется из очереди, а ошибка записывается в лог.
Проверка
Параметры повторных попыток проверяются заранее при вызове this.retry(), queue(), schedule(), или scheduleEvery(). Недопустимые параметры вызывают исключение сразу, а не приводят к сбою позже, во время выполнения:
// Throws immediately: "retry.maxAttempts must be >= 1"
await this.queue("sendEmail", data, {
retry: { maxAttempts: 0 },
});
// Throws immediately: "retry.baseDelayMs must be > 0"
await this.schedule(
60,
"process",
{},
{
retry: { baseDelayMs: -100 },
},
);
// Throws immediately: "retry.maxAttempts must be an integer"
await this.retry(() => fetch(url), { maxAttempts: 2.5 });
// Throws immediately: "retry.baseDelayMs must be <= retry.maxDelayMs"
// because baseDelayMs: 5000 exceeds the default maxDelayMs: 3000
await this.queue("sendEmail", data, {
retry: { baseDelayMs: 5000 },
});// Throws immediately: "retry.maxAttempts must be >= 1"
await this.queue("sendEmail", data, {
retry: { maxAttempts: 0 },
});
// Throws immediately: "retry.baseDelayMs must be > 0"
await this.schedule(
60,
"process",
{},
{
retry: { baseDelayMs: -100 },
},
);
// Throws immediately: "retry.maxAttempts must be an integer"
await this.retry(() => fetch(url), { maxAttempts: 2.5 });
// Throws immediately: "retry.baseDelayMs must be <= retry.maxDelayMs"
// because baseDelayMs: 5000 exceeds the default maxDelayMs: 3000
await this.queue("sendEmail", data, {
retry: { baseDelayMs: 5000 },
});Валидация сначала объединяет частичные параметры со значениями по умолчанию на уровне класса или встроенными значениями, а затем проверяет ограничения между полями. Это означает { baseDelayMs: 5000 } перехватывается сразу, когда разрешённый maxDelayMs равно 3000, а не завершается ошибкой позже, во время выполнения.
Поведение по умолчанию
Даже без явных настроек повторных попыток запланированные и поставленные в очередь колбэки повторяются с разумными значениями по умолчанию:
| Параметр | По умолчанию |
|---|---|
maxAttempts |
3 |
baseDelayMs |
100 |
maxDelayMs |
3000 |
Эти значения по умолчанию применяются к this.retry(), queue(), schedule(), а также scheduleEvery(). Параметры, заданные в конкретном месте вызова, имеют приоритет над ними.
Значения по умолчанию на уровне класса
Переопределите настройки по умолчанию для всего агента через static options:
class MyAgent extends Agent {
static options = {
retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 },
};
}class MyAgent extends Agent {
static options = {
retry: { maxAttempts: 5, baseDelayMs: 200, maxDelayMs: 5000 },
};
}Укажите только те поля, которые хотите изменить: незаданные поля примут встроенные значения по умолчанию:
class MyAgent extends Agent {
// Only override maxAttempts; baseDelayMs (100) and maxDelayMs (3000) stay default
static options = {
retry: { maxAttempts: 10 },
};
}class MyAgent extends Agent {
// Only override maxAttempts; baseDelayMs (100) and maxDelayMs (3000) stay default
static options = {
retry: { maxAttempts: 10 },
};
}Значения по умолчанию на уровне класса используются как запасной вариант, если место вызова не задаёт параметры повторных попыток. Параметры, заданные в конкретном месте вызова, всегда имеют приоритет:
// Uses class-level defaults (10 attempts)
await this.retry(() => fetch(url));
// Overrides to 2 attempts for this specific call
await this.retry(() => fetch(url), { maxAttempts: 2 });// Uses class-level defaults (10 attempts)
await this.retry(() => fetch(url));
// Overrides to 2 attempts for this specific call
await this.retry(() => fetch(url), { maxAttempts: 2 });Чтобы отключить повторные попытки для конкретной задачи, задайте maxAttempts: 1:
await this.schedule(
60,
"oneShot",
{},
{
retry: { maxAttempts: 1 },
},
);await this.schedule(
60,
"oneShot",
{},
{
retry: { maxAttempts: 1 },
},
);RetryOptions
interface RetryOptions {
/** Maximum number of attempts (including the first). Must be an integer >= 1. Default: 3 */
maxAttempts?: number;
/** Base delay in milliseconds for exponential backoff. Must be > 0 and <= maxDelayMs. Default: 100 */
baseDelayMs?: number;
/** Maximum delay cap in milliseconds. Must be > 0. Default: 3000 */
maxDelayMs?: number;
}Задержка между повторными попытками использует экспоненциальная задержка с полным джиттером:
delay = random(0, min(2^attempt * baseDelayMs, maxDelayMs))Это означает, что первые повторные попытки выполняются быстро (часто менее чем за 200 мс), а более поздние постепенно откладываются, чтобы не перегружать отказавший сервис. Случайный разброс (джиттер) не даёт нескольким агентам повторять попытку точно в один и тот же момент.
Как это работает
Стратегия задержки
Система повторных попыток использует стратегию «Full Jitter» из AWS Architecture Blog ↗. При 3 попытках с настройками по умолчанию:
| Попытка | Верхняя граница | Фактическая задержка |
|---|---|---|
| 1 | min(2^1 * 100, 3000) = 200ms | random(0, 200ms) |
| 2 | min(2^2 * 100, 3000) = 400ms | random(0, 400ms) |
| 3 | (без повтора, последняя попытка) | , |
С maxAttempts: 5 и baseDelayMs: 500:
| Попытка | Верхняя граница | Фактическая задержка |
|---|---|---|
| 1 | min(2 * 500, 3000) = 1000ms | random(0, 1000ms) |
| 2 | min(4 * 500, 3000) = 2000ms | random(0, 2000ms) |
| 3 | min(8 * 500, 3000) = 3000ms | random(0, 3000ms) |
| 4 | min(16 * 500, 3000) = 3000ms | random(0, 3000ms) |
| 5 | (без повтора, последняя попытка) | , |
Повторные попытки MCP-сервера
При добавлении MCP-сервера можно настроить параметры повторных попыток подключения и переподключения:
await this.addMcpServer("github", "https://mcp.github.com", {
retry: { maxAttempts: 5, baseDelayMs: 1000, maxDelayMs: 10000 },
});await this.addMcpServer("github", "https://mcp.github.com", {
retry: { maxAttempts: 5, baseDelayMs: 1000, maxDelayMs: 10000 },
});Эти параметры сохраняются устойчиво и применяются, когда:
- Восстановление подключений сервера после гибернации
- Установление соединений после завершения OAuth
По умолчанию: 3 попытки, базовая задержка 500 мс, максимальная задержка 5 с.
Паттерны
Повтор с логированием
class MyAgent extends Agent {
async resilientTask(payload) {
try {
const result = await this.retry(
async (attempt) => {
if (attempt > 1) {
console.log(`Retrying ${payload.url} (attempt ${attempt})...`);
}
const res = await fetch(payload.url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{ maxAttempts: 5 },
);
console.log("Success:", result);
} catch (e) {
console.error("All retries failed:", e);
}
}
}class MyAgent extends Agent {
async resilientTask(payload: { url: string }) {
try {
const result = await this.retry(
async (attempt) => {
if (attempt > 1) {
console.log(`Retrying ${payload.url} (attempt ${attempt})...`);
}
const res = await fetch(payload.url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
},
{ maxAttempts: 5 },
);
console.log("Success:", result);
} catch (e) {
console.error("All retries failed:", e);
}
}
}Повтор с fallback-вариантом
class MyAgent extends Agent {
async fetchData() {
try {
return await this.retry(
() => fetch("https://primary-api.example.com/data"),
{ maxAttempts: 3, baseDelayMs: 200 },
);
} catch {
// Primary failed, try fallback
return await this.retry(
() => fetch("https://fallback-api.example.com/data"),
{ maxAttempts: 2 },
);
}
}
}class MyAgent extends Agent {
async fetchData() {
try {
return await this.retry(
() => fetch("https://primary-api.example.com/data"),
{ maxAttempts: 3, baseDelayMs: 200 },
);
} catch {
// Primary failed, try fallback
return await this.retry(
() => fetch("https://fallback-api.example.com/data"),
{ maxAttempts: 2 },
);
}
}
}Сочетание повторов с планированием
Для операций, восстановление которых может занять много времени (минуты или часы), сочетайте this.retry() для немедленных повторов с this.schedule() для отложенных повторов:
class MyAgent extends Agent {
async syncData(payload) {
const attempt = payload.attempt ?? 1;
try {
// Immediate retries for transient failures (seconds)
await this.retry(() => this.fetchAndProcess(payload.source), {
maxAttempts: 3,
baseDelayMs: 1000,
});
} catch (e) {
if (attempt >= 5) {
console.error("Giving up after 5 scheduled attempts");
return;
}
// Schedule a retry in 5 minutes for longer outages
const delaySeconds = 300 * attempt;
await this.schedule(delaySeconds, "syncData", {
source: payload.source,
attempt: attempt + 1,
});
console.log(`Scheduled retry ${attempt + 1} in ${delaySeconds}s`);
}
}
}class MyAgent extends Agent {
async syncData(payload: { source: string; attempt?: number }) {
const attempt = payload.attempt ?? 1;
try {
// Immediate retries for transient failures (seconds)
await this.retry(() => this.fetchAndProcess(payload.source), {
maxAttempts: 3,
baseDelayMs: 1000,
});
} catch (e) {
if (attempt >= 5) {
console.error("Giving up after 5 scheduled attempts");
return;
}
// Schedule a retry in 5 minutes for longer outages
const delaySeconds = 300 * attempt;
await this.schedule(delaySeconds, "syncData", {
source: payload.source,
attempt: attempt + 1,
});
console.log(`Scheduled retry ${attempt + 1} in ${delaySeconds}s`);
}
}
}Ограничения
- Без очереди недоставленных сообщений. Если поставленная в очередь или запланированная задача исчерпывает все попытки повтора, она удаляется. Реализуйте собственное сохранение состояния, если вам нужно отслеживать неудачные задачи.
- Задержки при повторных попытках блокируют Agent. Во время отсрочки Durable Object активен, но простаивает. Для коротких задержек (менее 3 секунд) это нормально. Для более длительного восстановления используйте
this.schedule()взамен. - Повторные попытки в очереди приводят к блокировке по принципу head-of-line. Элементы очереди обрабатываются последовательно. Если один элемент подолгу повторяется с большими задержками, это блокирует все последующие элементы. Если нужна независимая логика повторов, используйте
this.retry()внутри callback, а не через параметры повтора для отдельной задачи уqueue(). - Без circuit breaker. Система повторных попыток не отслеживает частоту ошибок между вызовами. Если сервис постоянно недоступен, каждая задача независимо исчерпывает свой бюджет попыток.
shouldRetryдоступен только наthis.retry(). ЗаголовокshouldRetryпредикат нельзя использовать сschedule()илиqueue()потому что функции нельзя сериализовать в базу данных. Для запланированных и поставленных в очередь задач обрабатывайте неповторяемые ошибки внутри самого колбэка.