← Создание агентов на Cloudflare / agents / runtime / execution
Планирование задач
Планируйте задачи для выполнения в будущем: будь то через несколько секунд, в конкретную дату и время или по повторяющемуся расписанию 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");
// ...
}
}Варианты использования:
- Опрос с интервалом менее минуты (каждые 10, 30, 45 секунд)
- Интервалы, которые не соответствуют cron (каждые 90 секунд, каждые 7 минут)
- Опрос API с ограничением частоты запросов и точным управлением
- Синхронизация данных в реальном времени
Управление запланированными задачами
Получить расписание
Получите запланированную задачу по её 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, если:
- Вам нужна фоновая обработка без блокировки ответа
- Задачи должны выполняться как можно скорее, но не обязаны блокировать выполнение
- Порядок имеет значение (FIFO)
Используйте Scheduling, если:
- Задачи должны выполняться в определённое время
- Вам нужны повторяющиеся задания (cron)
- Отложенное выполнение (устранение дребезга, повторные попытки)
Используйте Workflows, если:
- Многошаговые процессы с зависимостями
- Автоматические повторные попытки с задержкой
- Подтверждения с участием человека
- Долгие задачи (от минут до часов)
Справочник по API
schedule()
async schedule<T>(
when: Date | string | number,
callback: keyof this,
payload?: T,
options?: { retry?: RetryOptions; idempotent?: boolean }
): Promise<Schedule<T>>Запланируйте задачу к будущему выполнению.
Параметры:
when- Когда выполнять:number(задержка в секундах),Date(конкретное время), илиstring(cron-выражение)callback- Имя вызываемого методаpayload- данные, передаваемые в колбэк (должны быть сериализуемыми в JSON)options.retry- Необязательная конфигурация повторных попыток. См. Повторы для подробностейoptions.idempotent- дедупликация по колбэку и полезной нагрузке. Значение по умолчанию:trueдля расписаний cron,falseдля отложенных расписаний и расписаний на основе Date
Возвращает: Есть 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>>Запланируйте задачу, которая будет повторяться через фиксированный интервал.
Параметры:
intervalSeconds- Количество секунд между запусками (должно быть больше 0)callback- Имя вызываемого методаpayload- данные, передаваемые в колбэк (должны быть сериализуемыми в JSON)options.retry- Необязательная конфигурация повторных попыток. См. Повторы для получения подробностей.
Возвращает: Есть Schedule объект с type: "interval"
Поведение:
- Первое выполнение происходит после
intervalSeconds(не сразу) - Если колбэк всё ещё выполняется к моменту следующего запланированного запуска, этот запуск пропускается (защита от наложения)
- Если колбэк выбрасывает исключение, интервал продолжает работать
- Отмена с помощью
cancelSchedule(id)чтобы остановить весь интервал
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 сбрасывает таймер бездействия.
- Heartbeat не конфликтует с вашими собственными расписаниями, поскольку система alarm мультиплексирует их через единый слот alarm.
- Строки расписания не создаются, а heartbeat невидим для
listSchedules(). - Несколько одновременных
keepAlive()вызовы используют счётчик ссылок, поэтому один disposer не освобождает heartbeat другого вызывающего. - Внутри дочерних агентов
keepAlive()делегирует эту ссылку на heartbeat родительскому агенту верхнего уровня, поскольку у граней нет собственных слотов 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 idleconst 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 idleAIChatAgent
AIChatAgent автоматически вызывает keepAlive() во время потоковых ответов. Вам не нужно добавлять это самостоятельно при использовании AIChatAgent : каждый поток LLM по умолчанию защищён от вытеснения из-за простоя.
Когда использовать keepAlive
| Сценарий | Используйте keepAlive? |
|---|---|
Потоковая передача ответов LLM через AIChatAgent |
Нет: это уже встроено |
| Долгие вычисления в пользовательском Agent | Да |
| Ожидание медленного внешнего вызова API | Да |
| Многошаговое выполнение инструментов | Да |
| Короткие обработчики типа request-response | Нет: не требуется |
| Фоновая работа с помощью планирования или workflows | Нет: будильники уже поддерживают DO в активном состоянии |
Лимиты
- Максимальное количество задач: Ограничено хранилищем SQLite (каждая задача представляет собой строку). Практический предел составляет десятки тысяч на агента.
- Размер задачи: Каждая задача (включая payload) может занимать до 2 МБ.
- Минимальная задержка: 0 секунд (запускается при следующем тике alarm)
- Точность cron: С точностью до минуты (не до секунд)
- Точность интервала: Второй уровень
- Задания cron: После выполнения автоматически перепланируется на следующее наступление
- Интервальные задачи: После выполнения перепланируется на
now + intervalSeconds; пропускается, если всё ещё выполняется