INTEGRITY Документация

Повторы

Повторяйте неудачные операции с экспоненциальной задержкой и джиттером. Agents SDK предоставляет встроенную поддержку повторов для запланированных задач, задач из очереди и this.retry() метод для вашего собственного кода.

Обзор

Временные сбои обычны при вызовах внешних API, взаимодействии с другими сервисами или выполнении фоновых задач. Система повторных попыток обрабатывает их автоматически:

быстрый старт

Используйте 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 в случае успеха.

Выбрасывает: последнюю ошибку, если все попытки завершились неудачей, или 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 },
});

Эти параметры сохраняются устойчиво и применяются, когда:

По умолчанию: 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`);
		}
	}
}

Ограничения

Следующие шаги

Планирование задач

Запланируйте задачи к будущему выполнению.

Задачи очереди

Очередь фоновых задач для немедленной обработки.

Запуск Workflows

Устойчивая многошаговая обработка с автоматическими повторными попытками.