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

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

Планируйте задачи для выполнения в будущем: будь то через несколько секунд, в конкретную дату и время или по повторяющемуся расписанию cron. Запланированные задачи переживают перезапуск агента и сохраняются в SQLite.

Запланированные задачи могут делать всё то же, что и запрос или сообщение пользователя: отправлять запросы, обращаться к базам данных, отправлять электронные письма, читать и записывать состояние. Запланированные задачи могут вызывать любой обычный метод вашего Agent.

Обзор

Система планирования поддерживает четыре режима:

Режим Синтаксис Сценарий использования
Отложено this.schedule(60, ...) Запуск через 60 секунд
Запланировано this.schedule(new Date(...), ...) Запуск в заданное время
Cron this.schedule("0 8 * * *", ...) Запуск по повторяющемуся расписанию
Интервал this.scheduleEvery(30, ...) Запуск каждые 30 секунд

Под капотом планирование использует Будильники Durable Object чтобы будить агента в нужный момент. Задачи хранятся в таблице SQLite и выполняются по порядку.

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

import { Agent } from "agents";

export class ReminderAgent extends Agent {
	async onRequest(request) {
		const url = new URL(request.url);

		// Schedule in 30 seconds
		await this.schedule(30, "sendReminder", {
			message: "Check your email",
		});

		// Schedule at specific time
		await this.schedule(new Date("2025-02-01T09:00:00Z"), "sendReminder", {
			message: "Monthly report due",
		});

		// Schedule recurring (every day at 8am)
		await this.schedule("0 8 * * *", "dailyDigest", {
			userId: url.searchParams.get("userId"),
		});

		return new Response("Scheduled!");
	}

	async sendReminder(payload) {
		console.log(`Reminder: ${payload.message}`);
		// Send notification, email, etc.
	}

	async dailyDigest(payload) {
		console.log(`Sending daily digest to ${payload.userId}`);
		// Generate and send digest
	}
}
import { Agent } from "agents";

export class ReminderAgent extends Agent {
	async onRequest(request: Request) {
		const url = new URL(request.url);

		// Schedule in 30 seconds
		await this.schedule(30, "sendReminder", {
			message: "Check your email",
		});

		// Schedule at specific time
		await this.schedule(new Date("2025-02-01T09:00:00Z"), "sendReminder", {
			message: "Monthly report due",
		});

		// Schedule recurring (every day at 8am)
		await this.schedule("0 8 * * *", "dailyDigest", {
			userId: url.searchParams.get("userId"),
		});

		return new Response("Scheduled!");
	}

	async sendReminder(payload: { message: string }) {
		console.log(`Reminder: ${payload.message}`);
		// Send notification, email, etc.
	}

	async dailyDigest(payload: { userId: string }) {
		console.log(`Sending daily digest to ${payload.userId}`);
		// Generate and send digest
	}
}

Режимы планирования

Отложенное выполнение

Передайте число, чтобы запланировать выполнение задачи с задержкой в секунды:

// Run in 10 seconds
await this.schedule(10, "processTask", { taskId: "123" });

// Run in 5 minutes (300 seconds)
await this.schedule(300, "sendFollowUp", { email: "[email protected]" });

// Run in 1 hour
await this.schedule(3600, "checkStatus", { orderId: "abc" });
// Run in 10 seconds
await this.schedule(10, "processTask", { taskId: "123" });

// Run in 5 minutes (300 seconds)
await this.schedule(300, "sendFollowUp", { email: "[email protected]" });

// Run in 1 hour
await this.schedule(3600, "checkStatus", { orderId: "abc" });

Варианты использования:

Запланированное выполнение

Передайте Date объект, чтобы запланировать задачу на конкретное время:

// Run tomorrow at noon
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(12, 0, 0, 0);
await this.schedule(tomorrow, "sendReminder", { message: "Meeting time!" });

// Run at a specific timestamp
await this.schedule(new Date("2025-06-15T14:30:00Z"), "triggerEvent", {
	eventId: "conference-2025",
});

// Run in 2 hours using Date math
const twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000);
await this.schedule(twoHoursFromNow, "checkIn", {});
// Run tomorrow at noon
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(12, 0, 0, 0);
await this.schedule(tomorrow, "sendReminder", { message: "Meeting time!" });

// Run at a specific timestamp
await this.schedule(new Date("2025-06-15T14:30:00Z"), "triggerEvent", {
	eventId: "conference-2025",
});

// Run in 2 hours using Date math
const twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000);
await this.schedule(twoHoursFromNow, "checkIn", {});

Варианты использования:

Повторяющееся (cron)

Передайте строку cron-выражения для повторяющихся расписаний:

// Every day at 8:00 AM
await this.schedule("0 8 * * *", "dailyReport", {});

// Every hour
await this.schedule("0 * * * *", "hourlyCheck", {});

// Every Monday at 9:00 AM
await this.schedule("0 9 * * 1", "weeklySync", {});

// Every 15 minutes
await this.schedule("*/15 * * * *", "pollForUpdates", {});

// First day of every month at midnight
await this.schedule("0 0 1 * *", "monthlyCleanup", {});
// Every day at 8:00 AM
await this.schedule("0 8 * * *", "dailyReport", {});

// Every hour
await this.schedule("0 * * * *", "hourlyCheck", {});

// Every Monday at 9:00 AM
await this.schedule("0 9 * * 1", "weeklySync", {});

// Every 15 minutes
await this.schedule("*/15 * * * *", "pollForUpdates", {});

// First day of every month at midnight
await this.schedule("0 0 1 * *", "monthlyCleanup", {});

Синтаксис cron: minute hour day month weekday

Поле Значения Специальные символы
Минута 0-59 * , - /
Час 0-23 * , - /
День месяца 1-31 * , - /
Месяц 1-12 * , - /
День недели 0-6 (0=воскресенье) * , - /

Типичные паттерны:

"* * * * *"; // Every minute
"*/5 * * * *"; // Every 5 minutes
"0 * * * *"; // Every hour (on the hour)
"0 0 * * *"; // Every day at midnight
"0 8 * * 1-5"; // Weekdays at 8am
"0 0 * * 0"; // Every Sunday at midnight
"0 0 1 * *"; // First of every month
"* * * * *"; // Every minute
"*/5 * * * *"; // Every 5 minutes
"0 * * * *"; // Every hour (on the hour)
"0 0 * * *"; // Every day at midnight
"0 8 * * 1-5"; // Weekdays at 8am
"0 0 * * 0"; // Every Sunday at midnight
"0 0 1 * *"; // First of every month

Варианты использования:

Расписания cron по умолчанию идемпотентны: вызов schedule() с тем же cron-выражением, колбэком и полезной нагрузкой несколько раз возвращает существующее расписание вместо создания дубликата. Это делает cron-расписания безопасными для настройки в onStart().

Интервал

Используйте scheduleEvery() чтобы запускать задачу через фиксированные интервалы (в секундах). В отличие от cron, интервалы поддерживают точность до долей минуты и произвольную длительность:

// Poll every 30 seconds
await this.scheduleEvery(30, "poll", { source: "api" });

// Health check every 45 seconds
await this.scheduleEvery(45, "healthCheck", {});

// Sync every 90 seconds (1.5 minutes - cannot be expressed in cron)
await this.scheduleEvery(90, "syncData", { destination: "warehouse" });
// Poll every 30 seconds
await this.scheduleEvery(30, "poll", { source: "api" });

// Health check every 45 seconds
await this.scheduleEvery(45, "healthCheck", {});

// Sync every 90 seconds (1.5 minutes - cannot be expressed in cron)
await this.scheduleEvery(90, "syncData", { destination: "warehouse" });

Основные отличия от cron:

Возможность Cron Интервал
Минимальная гранулярность 1 минута 1 секунда
Произвольные интервалы Нет (должно соответствовать шаблону cron) Да
Фиксированное расписание Да (например, «каждый день в 8:00») Нет (относительно начала)
Предотвращение перекрытия Нет Да (встроено)

Идемпотентность:

scheduleEvery() идемпотентен по комбинации имени колбэка, интервала и полезной нагрузки: повторные вызовы с одними и теми же аргументами не создают дублирующихся расписаний. Поэтому его можно безопасно вызывать в onStart(), который выполняется при каждом пробуждении Durable Object:

class MyAgent extends Agent {
	async onStart() {
		// Safe to call on every wake — only one schedule is created
		await this.scheduleEvery(30, "poll", { source: "api" });
	}
}
class MyAgent extends Agent {
	async onStart() {
		// Safe to call on every wake — only one schedule is created
		await this.scheduleEvery(30, "poll", { source: "api" });
	}
}

Другой интервал или полезная нагрузка создаёт новое, независимое расписание.

Предотвращение перекрытия:

Если колбэк выполняется дольше заданного интервала, следующее выполнение пропускается, а не ставится в очередь. Это предотвращает неконтролируемый расход ресурсов:

class PollingAgent extends Agent {
	async poll() {
		// If this takes 45 seconds and interval is 30 seconds,
		// the next poll is skipped (with a warning logged)
		const data = await slowExternalApi();
		await this.processData(data);
	}
}

// Set up 30-second interval
await this.scheduleEvery(30, "poll", {});
class PollingAgent extends Agent {
	async poll() {
		// If this takes 45 seconds and interval is 30 seconds,
		// the next poll is skipped (with a warning logged)
		const data = await slowExternalApi();
		await this.processData(data);
	}
}

// Set up 30-second interval
await this.scheduleEvery(30, "poll", {});

Когда происходит пропуск, в логах появится предупреждение:

Skipping interval schedule abc123: previous execution still running

Устойчивость к ошибкам:

Если колбэк выбрасывает исключение, интервал продолжает работать, сбой происходит только в этом конкретном выполнении:

class SyncAgent extends Agent {
	async syncData() {
		// Even if this throws, the interval keeps running
		const response = await fetch("https://api.example.com/data");
		if (!response.ok) throw new Error("Sync failed");
		// ...
	}
}
class SyncAgent extends Agent {
	async syncData() {
		// Even if this throws, the interval keeps running
		const response = await fetch("https://api.example.com/data");
		if (!response.ok) throw new Error("Sync failed");
		// ...
	}
}

Варианты использования:

Управление запланированными задачами

Получить расписание

Получите запланированную задачу по её ID:

const schedule = await this.getScheduleById(scheduleId);

if (schedule) {
	console.log(
		`Task ${schedule.id} will run at ${new Date(schedule.time * 1000)}`,
	);
	console.log(`Callback: ${schedule.callback}`);
	console.log(`Type: ${schedule.type}`); // "scheduled" | "delayed" | "cron" | "interval"
} else {
	console.log("Schedule not found");
}
const schedule = await this.getScheduleById(scheduleId);

if (schedule) {
	console.log(
		`Task ${schedule.id} will run at ${new Date(schedule.time * 1000)}`,
	);
	console.log(`Callback: ${schedule.callback}`);
	console.log(`Type: ${schedule.type}`); // "scheduled" | "delayed" | "cron" | "interval"
} else {
	console.log("Schedule not found");
}

Список расписаний

Запрашивайте запланированные задачи с необязательными фильтрами:

// Get all scheduled tasks
const allSchedules = await this.listSchedules();

// Get only cron jobs
const cronJobs = await this.listSchedules({ type: "cron" });

// Get tasks in the next hour
const upcoming = await this.listSchedules({
	timeRange: {
		start: new Date(),
		end: new Date(Date.now() + 60 * 60 * 1000),
	},
});

// Get a specific task by ID
const specific = await this.listSchedules({ id: "abc123" });

// Combine filters
const upcomingCronJobs = await this.listSchedules({
	type: "cron",
	timeRange: {
		start: new Date(),
		end: new Date(Date.now() + 24 * 60 * 60 * 1000),
	},
});
// Get all scheduled tasks
const allSchedules = await this.listSchedules();

// Get only cron jobs
const cronJobs = await this.listSchedules({ type: "cron" });

// Get tasks in the next hour
const upcoming = await this.listSchedules({
	timeRange: {
		start: new Date(),
		end: new Date(Date.now() + 60 * 60 * 1000),
	},
});

// Get a specific task by ID
const specific = await this.listSchedules({ id: "abc123" });

// Combine filters
const upcomingCronJobs = await this.listSchedules({
	type: "cron",
	timeRange: {
		start: new Date(),
		end: new Date(Date.now() + 24 * 60 * 60 * 1000),
	},
});

Отменить расписание

Удалите запланированную задачу до её выполнения:

const cancelled = await this.cancelSchedule(scheduleId);

if (cancelled) {
	console.log("Schedule cancelled successfully");
} else {
	console.log("Schedule not found (may have already executed)");
}
const cancelled = await this.cancelSchedule(scheduleId);

if (cancelled) {
	console.log("Schedule cancelled successfully");
} else {
	console.log("Schedule not found (may have already executed)");
}

Пример: отменяемые напоминания

class ReminderAgent extends Agent {
	async setReminder(userId, message, delaySeconds) {
		const schedule = await this.schedule(delaySeconds, "sendReminder", {
			userId,
			message,
		});

		// Store the schedule ID so user can cancel later
		this.sql`
      INSERT INTO user_reminders (user_id, schedule_id, message)
      VALUES (${userId}, ${schedule.id}, ${message})
    `;

		return schedule.id;
	}

	async cancelReminder(scheduleId) {
		const cancelled = await this.cancelSchedule(scheduleId);

		if (cancelled) {
			this.sql`DELETE FROM user_reminders WHERE schedule_id = ${scheduleId}`;
		}

		return cancelled;
	}

	async sendReminder(payload) {
		// Send the reminder...

		// Clean up the record
		this.sql`DELETE FROM user_reminders WHERE user_id = ${payload.userId}`;
	}
}
class ReminderAgent extends Agent {
	async setReminder(userId: string, message: string, delaySeconds: number) {
		const schedule = await this.schedule(delaySeconds, "sendReminder", {
			userId,
			message,
		});

		// Store the schedule ID so user can cancel later
		this.sql`
      INSERT INTO user_reminders (user_id, schedule_id, message)
      VALUES (${userId}, ${schedule.id}, ${message})
    `;

		return schedule.id;
	}

	async cancelReminder(scheduleId: string) {
		const cancelled = await this.cancelSchedule(scheduleId);

		if (cancelled) {
			this.sql`DELETE FROM user_reminders WHERE schedule_id = ${scheduleId}`;
		}

		return cancelled;
	}

	async sendReminder(payload: { userId: string; message: string }) {
		// Send the reminder...

		// Clean up the record
		this.sql`DELETE FROM user_reminders WHERE user_id = ${payload.userId}`;
	}
}

Объект Schedule

При создании или получении расписания вы получаете Schedule объект:

type Schedule<T> = {
	id: string; // Unique identifier
	callback: string; // Method name to call
	payload: T; // Data passed to the callback
	time: number; // Unix timestamp (seconds) of next execution
} & (
	| { type: "scheduled" } // One-time at specific date
	| { type: "delayed"; delayInSeconds: number } // One-time after delay
	| { type: "cron"; cron: string } // Recurring (cron expression)
	| { type: "interval"; intervalSeconds: number } // Recurring (fixed interval)
);

Пример:

const schedule = await this.schedule(60, "myTask", { foo: "bar" });

console.log(schedule);
// {
//   id: "abc123xyz",
//   callback: "myTask",
//   payload: { foo: "bar" },
//   time: 1706745600,
//   type: "delayed",
//   delayInSeconds: 60
// }
const schedule = await this.schedule(60, "myTask", { foo: "bar" });

console.log(schedule);
// {
//   id: "abc123xyz",
//   callback: "myTask",
//   payload: { foo: "bar" },
//   time: 1706745600,
//   type: "delayed",
//   delayInSeconds: 60
// }

Паттерны

Повторное планирование из колбэков

Для динамических повторяющихся расписаний планируйте следующий запуск прямо внутри колбэка:

class PollingAgent extends Agent {
	async startPolling(intervalSeconds) {
		await this.schedule(intervalSeconds, "poll", { interval: intervalSeconds });
	}

	async poll(payload) {
		try {
			const data = await fetch("https://api.example.com/updates");
			await this.processUpdates(await data.json());
		} catch (error) {
			console.error("Polling failed:", error);
		}

		// Schedule the next poll (regardless of success/failure)
		await this.schedule(payload.interval, "poll", payload);
	}

	async stopPolling() {
		// Cancel all polling schedules
		const schedules = await this.listSchedules({ type: "delayed" });
		for (const schedule of schedules) {
			if (schedule.callback === "poll") {
				await this.cancelSchedule(schedule.id);
			}
		}
	}
}
class PollingAgent extends Agent {
	async startPolling(intervalSeconds: number) {
		await this.schedule(intervalSeconds, "poll", { interval: intervalSeconds });
	}

	async poll(payload: { interval: number }) {
		try {
			const data = await fetch("https://api.example.com/updates");
			await this.processUpdates(await data.json());
		} catch (error) {
			console.error("Polling failed:", error);
		}

		// Schedule the next poll (regardless of success/failure)
		await this.schedule(payload.interval, "poll", payload);
	}

	async stopPolling() {
		// Cancel all polling schedules
		const schedules = await this.listSchedules({ type: "delayed" });
		for (const schedule of schedules) {
			if (schedule.callback === "poll") {
				await this.cancelSchedule(schedule.id);
			}
		}
	}
}

Повтор с экспоненциальной задержкой

class RetryAgent extends Agent {
	async attemptTask(payload) {
		try {
			await this.doWork(payload.taskId);
			console.log(
				`Task ${payload.taskId} succeeded on attempt ${payload.attempt}`,
			);
		} catch (error) {
			if (payload.attempt >= payload.maxAttempts) {
				console.error(
					`Task ${payload.taskId} failed after ${payload.maxAttempts} attempts`,
				);
				return;
			}

			// Exponential backoff: 2^attempt seconds (2s, 4s, 8s, 16s...)
			const delaySeconds = Math.pow(2, payload.attempt);

			await this.schedule(delaySeconds, "attemptTask", {
				...payload,
				attempt: payload.attempt + 1,
			});

			console.log(`Retrying task ${payload.taskId} in ${delaySeconds}s`);
		}
	}

	async doWork(taskId) {
		// Your actual work here
	}
}
class RetryAgent extends Agent {
	async attemptTask(payload: {
		taskId: string;
		attempt: number;
		maxAttempts: number;
	}) {
		try {
			await this.doWork(payload.taskId);
			console.log(
				`Task ${payload.taskId} succeeded on attempt ${payload.attempt}`,
			);
		} catch (error) {
			if (payload.attempt >= payload.maxAttempts) {
				console.error(
					`Task ${payload.taskId} failed after ${payload.maxAttempts} attempts`,
				);
				return;
			}

			// Exponential backoff: 2^attempt seconds (2s, 4s, 8s, 16s...)
			const delaySeconds = Math.pow(2, payload.attempt);

			await this.schedule(delaySeconds, "attemptTask", {
				...payload,
				attempt: payload.attempt + 1,
			});

			console.log(`Retrying task ${payload.taskId} in ${delaySeconds}s`);
		}
	}

	async doWork(taskId: string) {
		// Your actual work here
	}
}

Агенты с автоматическим самоуничтожением

Вы можете безопасно вызывать this.destroy() изнутри запланированного колбэка:

class TemporaryAgent extends Agent {
	async onStart() {
		// Self-destruct in 24 hours
		await this.schedule(24 * 60 * 60, "cleanup", {});
	}

	async cleanup() {
		// Perform final cleanup
		console.log("Agent lifetime expired, cleaning up...");

		// This is safe to call from a scheduled callback
		await this.destroy();
	}
}
class TemporaryAgent extends Agent {
	async onStart() {
		// Self-destruct in 24 hours
		await this.schedule(24 * 60 * 60, "cleanup", {});
	}

	async cleanup() {
		// Perform final cleanup
		console.log("Agent lifetime expired, cleaning up...");

		// This is safe to call from a scheduled callback
		await this.destroy();
	}
}

Планирование с помощью ИИ

SDK включает утилиты для разбора запросов на планирование на естественном языке с помощью ИИ.

getSchedulePrompt()

Возвращает системный промпт для разбора естественного языка в параметры расписания:

import { getSchedulePrompt, scheduleSchema } from "agents";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";

class SmartScheduler extends Agent {
	async parseScheduleRequest(userInput) {
		const result = await generateObject({
			model: openai("gpt-4o"),
			system: getSchedulePrompt({ date: new Date() }),
			prompt: userInput,
			schema: scheduleSchema,
		});

		return result.object;
	}

	async handleUserRequest(input) {
		// Parse: "remind me to call mom tomorrow at 3pm"
		const parsed = await this.parseScheduleRequest(input);

		// parsed = {
		//   description: "call mom",
		//   when: {
		//     type: "scheduled",
		//     date: "2025-01-30T15:00:00Z"
		//   }
		// }

		if (parsed.when.type === "scheduled" && parsed.when.date) {
			await this.schedule(new Date(parsed.when.date), "sendReminder", {
				message: parsed.description,
			});
		} else if (parsed.when.type === "delayed" && parsed.when.delayInSeconds) {
			await this.schedule(parsed.when.delayInSeconds, "sendReminder", {
				message: parsed.description,
			});
		} else if (parsed.when.type === "cron" && parsed.when.cron) {
			await this.schedule(parsed.when.cron, "sendReminder", {
				message: parsed.description,
			});
		}
	}

	async sendReminder(payload) {
		console.log(`Reminder: ${payload.message}`);
	}
}
import { getSchedulePrompt, scheduleSchema } from "agents";
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";

class SmartScheduler extends Agent {
	async parseScheduleRequest(userInput: string) {
		const result = await generateObject({
			model: openai("gpt-4o"),
			system: getSchedulePrompt({ date: new Date() }),
			prompt: userInput,
			schema: scheduleSchema,
		});

		return result.object;
	}

	async handleUserRequest(input: string) {
		// Parse: "remind me to call mom tomorrow at 3pm"
		const parsed = await this.parseScheduleRequest(input);

		// parsed = {
		//   description: "call mom",
		//   when: {
		//     type: "scheduled",
		//     date: "2025-01-30T15:00:00Z"
		//   }
		// }

		if (parsed.when.type === "scheduled" && parsed.when.date) {
			await this.schedule(new Date(parsed.when.date), "sendReminder", {
				message: parsed.description,
			});
		} else if (parsed.when.type === "delayed" && parsed.when.delayInSeconds) {
			await this.schedule(parsed.when.delayInSeconds, "sendReminder", {
				message: parsed.description,
			});
		} else if (parsed.when.type === "cron" && parsed.when.cron) {
			await this.schedule(parsed.when.cron, "sendReminder", {
				message: parsed.description,
			});
		}
	}

	async sendReminder(payload: { message: string }) {
		console.log(`Reminder: ${payload.message}`);
	}
}

scheduleSchema

Zod-схема для валидации разобранных данных расписания. Использует размеченное объединение по when.type чтобы каждый вариант содержал только нужные ему поля:

import { scheduleSchema } from "agents";

// The schema is a discriminated union:
// {
//   description: string,
//   when:
//     | { type: "scheduled", date: string }       // ISO 8601 date string
//     | { type: "delayed", delayInSeconds: number }
//     | { type: "cron", cron: string }
//     | { type: "no-schedule" }
// }
import { scheduleSchema } from "agents";

// The schema is a discriminated union:
// {
//   description: string,
//   when:
//     | { type: "scheduled", date: string }       // ISO 8601 date string
//     | { type: "delayed", delayInSeconds: number }
//     | { type: "cron", cron: string }
//     | { type: "no-schedule" }
// }

Планирование, Queue и Workflows

Возможность Queue Планирование Workflows
Когда Немедленно (FIFO) Будущее время Будущее время
Выполнение Последовательно В запланированное время Многошаговый
Повторы Встроено Встроено Автоматически
Персистентность SQLite SQLite Движок Workflow
Повторяющееся Нет Да (cron) Нет (используйте планирование)
Сложная логика Нет Нет Да
Подтверждение человеком Нет Нет Да

Используйте Queue, если:

Используйте Scheduling, если:

Используйте Workflows, если:

Справочник по API

schedule()

async schedule<T>(
  when: Date | string | number,
  callback: keyof this,
  payload?: T,
  options?: { retry?: RetryOptions; idempotent?: boolean }
): Promise<Schedule<T>>

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

Параметры:

Возвращает: Есть Schedule объект с деталями задачи

Идемпотентность:

Расписания cron по умолчанию идемпотентны: вызов schedule("0 * * * *", "tick") несколько раз с одним и тем же колбэком, cron-выражением и полезной нагрузкой возвращает существующее расписание вместо создания дубликата. Задайте idempotent: false чтобы переопределить это.

Для отложенных и основанных на дате расписаний установите idempotent: true чтобы включить такое же поведение дедупликации (сопоставление по колбэку и полезной нагрузке). Это особенно полезно при вызове schedule() в onStart() чтобы избежать накопления дублирующихся строк при перезапусках Durable Object:

class MyAgent extends Agent {
	async onStart() {
		// Without idempotent: true, this creates a new row on every DO restart
		await this.schedule(3600, "hourlyCleanup", {}, { idempotent: true });
	}
}
class MyAgent extends Agent {
	async onStart() {
		// Without idempotent: true, this creates a new row on every DO restart
		await this.schedule(3600, "hourlyCleanup", {}, { idempotent: true });
	}
}

scheduleEvery()

async scheduleEvery<T>(
  intervalSeconds: number,
  callback: keyof this,
  payload?: T,
  options?: { retry?: RetryOptions }
): Promise<Schedule<T>>

Запланируйте задачу, которая будет повторяться через фиксированный интервал.

Параметры:

Возвращает: Есть Schedule объект с type: "interval"

Поведение:

getScheduleById()

async getScheduleById(id: string): Promise<Schedule<unknown> | undefined>

Получить запланированную задачу по ID. Возвращает undefined если не найдено. Этот метод работает как для агентов верхнего уровня, так и для дочерних агентов.

listSchedules()

async listSchedules(criteria?: {
  id?: string;
  type?: "scheduled" | "delayed" | "cron" | "interval";
  timeRange?: { start?: Date; end?: Date };
}): Promise<Schedule<unknown>[]>

Получить запланированные задачи, соответствующие критериям. Этот метод работает как в агентах верхнего уровня, так и в дочерних агентах.

getSchedule()

getSchedule<T>(id: string): Schedule<T> | undefined

Устарело. Синхронно получает запланированную задачу по ID. Этот метод работает только в агентах верхнего уровня. Используйте await this.getScheduleById(id) взамен.

getSchedules()

getSchedules<T>(criteria?: {
  id?: string;
  type?: "scheduled" | "delayed" | "cron" | "interval";
  timeRange?: { start?: Date; end?: Date };
}): Schedule<T>[]

Устарело. Синхронно получает запланированные задачи, соответствующие критериям. Этот метод работает только в агентах верхнего уровня. Используйте await this.listSchedules(criteria) взамен.

cancelSchedule()

async cancelSchedule(id: string): Promise<boolean>

Отменить запланированную задачу. Возвращает true если отменено, false если не найдено.

keepAlive()

async keepAlive(): Promise<() => void>

Предотвращает вытеснение Durable Object из-за неактивности, удерживая ссылку на heartbeat на основе 30-секундного будильника. Возвращает функцию освобождения, которая снимает heartbeat при вызове. Функция освобождения идемпотентна: вызывать её несколько раз безопасно.

Всегда вызывайте disposer по завершении работы, иначе heartbeat будет продолжаться бесконечно.

const dispose = await this.keepAlive();
try {
	// Long-running work that must not be interrupted
	const result = await longRunningComputation();
	await sendResults(result);
} finally {
	dispose();
}
const dispose = await this.keepAlive();
try {
	// Long-running work that must not be interrupted
	const result = await longRunningComputation();
	await sendResults(result);
} finally {
	dispose();
}

keepAliveWhile()

async keepAliveWhile<T>(fn: () => Promise<T>): Promise<T>

Запустите асинхронную функцию, сохраняя Durable Object активным. heartbeat автоматически запускается до выполнения функции и останавливается после её завершения (независимо от того, завершилась ли она успешно или с исключением). Возвращает значение, которое вернула функция.

Это рекомендуемый способ использования keepAlive : гарантирует очистку.

const result = await this.keepAliveWhile(async () => {
	const data = await longRunningComputation();
	return data;
});
const result = await this.keepAliveWhile(async () => {
	const data = await longRunningComputation();
	return data;
});

Поддержание активности агента

Durable Objects вытесняются из памяти после периода бездействия (обычно от 70 до 140 секунд без входящих запросов, сообщений WebSocket или alarm). Во время долгих операций, таких как потоковая передача ответов LLM, ожидание внешних API или выполнение многошаговых вычислений, агент может быть вытеснен прямо в процессе работы.

keepAlive() предотвращает это, удерживая ссылку на heartbeat в памяти и напрямую используя систему alarm Durable Object. Само срабатывание alarm сбрасывает таймер бездействия.

Несколько одновременных вызывающих сторон

Каждый keepAlive() вызов возвращает независимую функцию освобождения:

const dispose1 = await this.keepAlive();
const dispose2 = await this.keepAlive();

// Both heartbeats are active
dispose1(); // Only cancels the first heartbeat
// Agent is still alive via dispose2's heartbeat

dispose2(); // Now the agent can go idle
const dispose1 = await this.keepAlive();
const dispose2 = await this.keepAlive();

// Both heartbeats are active
dispose1(); // Only cancels the first heartbeat
// Agent is still alive via dispose2's heartbeat

dispose2(); // Now the agent can go idle

AIChatAgent

AIChatAgent автоматически вызывает keepAlive() во время потоковых ответов. Вам не нужно добавлять это самостоятельно при использовании AIChatAgent : каждый поток LLM по умолчанию защищён от вытеснения из-за простоя.

Когда использовать keepAlive

Сценарий Используйте keepAlive?
Потоковая передача ответов LLM через AIChatAgent Нет: это уже встроено
Долгие вычисления в пользовательском Agent Да
Ожидание медленного внешнего вызова API Да
Многошаговое выполнение инструментов Да
Короткие обработчики типа request-response Нет: не требуется
Фоновая работа с помощью планирования или workflows Нет: будильники уже поддерживают DO в активном состоянии

Лимиты

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

Push-уведомления

Отправляйте push-уведомления браузера с помощью планирования и web-push.

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

Немедленная обработка фоновых задач.

Запуск Workflows

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

Agents API

Полный справочник API для Agents SDK.