Schedule tasks
Schedule tasks to run in the future — whether that is seconds from now, at a specific date/time, or on a recurring cron schedule. Scheduled tasks survive agent restarts and are persisted to SQLite.
Scheduled tasks can do anything a request or message from a user can: make requests, query databases, send emails, read and write state. Scheduled tasks can invoke any regular method on your Agent.
The scheduling system supports four modes:
| Mode | Syntax | Use case |
|---|---|---|
| Delayed | this.schedule(60, ...) | Run in 60 seconds |
| Scheduled | this.schedule(new Date(...), ...) | Run at specific time |
| Cron | this.schedule("0 8 * * *", ...) | Run on recurring schedule |
| Interval | this.scheduleEvery(30, ...) | Run every 30 seconds |
Under the hood, scheduling uses Durable Object alarms to wake the agent at the right time. Tasks are stored in a SQLite table and executed in order.
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 }}Pass a number to schedule a task to run after a delay in seconds:
// Run in 10 secondsawait this.schedule(10, "processTask", { taskId: "123" });
// Run in 5 minutes (300 seconds)await this.schedule(300, "sendFollowUp", { email: "user@example.com" });
// Run in 1 hourawait this.schedule(3600, "checkStatus", { orderId: "abc" });// Run in 10 secondsawait this.schedule(10, "processTask", { taskId: "123" });
// Run in 5 minutes (300 seconds)await this.schedule(300, "sendFollowUp", { email: "user@example.com" });
// Run in 1 hourawait this.schedule(3600, "checkStatus", { orderId: "abc" });Use cases:
- Debouncing rapid events
- Delayed notifications ("You left items in your cart")
- Retry with backoff
- Rate limiting
Pass a Date object to schedule a task at a specific time:
// Run tomorrow at noonconst 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 timestampawait this.schedule(new Date("2025-06-15T14:30:00Z"), "triggerEvent", { eventId: "conference-2025",});
// Run in 2 hours using Date mathconst twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000);await this.schedule(twoHoursFromNow, "checkIn", {});// Run tomorrow at noonconst 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 timestampawait this.schedule(new Date("2025-06-15T14:30:00Z"), "triggerEvent", { eventId: "conference-2025",});
// Run in 2 hours using Date mathconst twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000);await this.schedule(twoHoursFromNow, "checkIn", {});Use cases:
- Appointment reminders
- Deadline notifications
- Scheduled content publishing
- Time-based triggers
Pass a cron expression string for recurring schedules:
// Every day at 8:00 AMawait this.schedule("0 8 * * *", "dailyReport", {});
// Every hourawait this.schedule("0 * * * *", "hourlyCheck", {});
// Every Monday at 9:00 AMawait this.schedule("0 9 * * 1", "weeklySync", {});
// Every 15 minutesawait this.schedule("*/15 * * * *", "pollForUpdates", {});
// First day of every month at midnightawait this.schedule("0 0 1 * *", "monthlyCleanup", {});// Every day at 8:00 AMawait this.schedule("0 8 * * *", "dailyReport", {});
// Every hourawait this.schedule("0 * * * *", "hourlyCheck", {});
// Every Monday at 9:00 AMawait this.schedule("0 9 * * 1", "weeklySync", {});
// Every 15 minutesawait this.schedule("*/15 * * * *", "pollForUpdates", {});
// First day of every month at midnightawait this.schedule("0 0 1 * *", "monthlyCleanup", {});Cron syntax: minute hour day month weekday
| Field | Values | Special characters |
|---|---|---|
| Minute | 0-59 | * , - / |
| Hour | 0-23 | * , - / |
| Day of Month | 1-31 | * , - / |
| Month | 1-12 | * , - / |
| Day of Week | 0-6 (0=Sunday) | * , - / |
Common patterns:
"* * * * *"; // 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 monthUse cases:
- Daily/weekly reports
- Periodic cleanup jobs
- Polling external services
- Health checks
- Subscription renewals
Use scheduleEvery() to run a task at fixed intervals (in seconds). Unlike cron, intervals support sub-minute precision and arbitrary durations:
// Poll every 30 secondsawait this.scheduleEvery(30, "poll", { source: "api" });
// Health check every 45 secondsawait 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 secondsawait this.scheduleEvery(30, "poll", { source: "api" });
// Health check every 45 secondsawait this.scheduleEvery(45, "healthCheck", {});
// Sync every 90 seconds (1.5 minutes - cannot be expressed in cron)await this.scheduleEvery(90, "syncData", { destination: "warehouse" });Key differences from cron:
| Feature | Cron | Interval |
|---|---|---|
| Minimum granularity | 1 minute | 1 second |
| Arbitrary intervals | No (must fit cron pattern) | Yes |
| Fixed schedule | Yes (for example, "every day at 8am") | No (relative to start) |
| Overlap prevention | No | Yes (built-in) |
Overlap prevention:
If a callback takes longer than the interval, the next execution is skipped (not queued). This prevents runaway resource usage:
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 intervalawait 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 intervalawait this.scheduleEvery(30, "poll", {});When a skip occurs, you will see a warning in logs:
Skipping interval schedule abc123: previous execution still runningError resilience:
If the callback throws an error, the interval continues — only that execution fails:
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"); // ... }}Use cases:
- Sub-minute polling (every 10, 30, 45 seconds)
- Intervals that do not map to cron (every 90 seconds, every 7 minutes)
- Rate-limited API polling with precise control
- Real-time data synchronization
Retrieve a scheduled task by its ID:
const schedule = await this.getSchedule(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.getSchedule(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");}Query scheduled tasks with optional filters:
// Get all scheduled tasksconst allSchedules = this.getSchedules();
// Get only cron jobsconst cronJobs = this.getSchedules({ type: "cron" });
// Get tasks in the next hourconst upcoming = this.getSchedules({ timeRange: { start: new Date(), end: new Date(Date.now() + 60 * 60 * 1000), },});
// Get a specific task by IDconst specific = this.getSchedules({ id: "abc123" });
// Combine filtersconst upcomingCronJobs = this.getSchedules({ type: "cron", timeRange: { start: new Date(), end: new Date(Date.now() + 24 * 60 * 60 * 1000), },});// Get all scheduled tasksconst allSchedules = this.getSchedules();
// Get only cron jobsconst cronJobs = this.getSchedules({ type: "cron" });
// Get tasks in the next hourconst upcoming = this.getSchedules({ timeRange: { start: new Date(), end: new Date(Date.now() + 60 * 60 * 1000), },});
// Get a specific task by IDconst specific = this.getSchedules({ id: "abc123" });
// Combine filtersconst upcomingCronJobs = this.getSchedules({ type: "cron", timeRange: { start: new Date(), end: new Date(Date.now() + 24 * 60 * 60 * 1000), },});Remove a scheduled task before it executes:
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)");}Example: Cancellable reminders
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}`; }}When you create or retrieve a schedule, you get a Schedule object:
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));Example:
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// }For dynamic recurring schedules, schedule the next run from within the callback:
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 = this.getSchedules({ 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 = this.getSchedules({ 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 }}You can safely call this.destroy() from within a scheduled callback:
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(); }}The SDK includes utilities for parsing natural language scheduling requests with AI.
Returns a system prompt for parsing natural language into scheduling parameters:
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}`); }}A Zod schema for validating parsed scheduling data:
import { scheduleSchema } from "agents";
// The schema shape:// {// description: string,// when: {// type: "scheduled" | "delayed" | "cron" | "no-schedule",// date?: Date, // for "scheduled"// delayInSeconds?: number, // for "delayed"// cron?: string // for "cron"// }// }import { scheduleSchema } from "agents";
// The schema shape:// {// description: string,// when: {// type: "scheduled" | "delayed" | "cron" | "no-schedule",// date?: Date, // for "scheduled"// delayInSeconds?: number, // for "delayed"// cron?: string // for "cron"// }// }| Feature | Queue | Scheduling | Workflows |
|---|---|---|---|
| When | Immediately (FIFO) | Future time | Future time |
| Execution | Sequential | At scheduled time | Multi-step |
| Retries | Manual | Manual | Automatic |
| Persistence | SQLite | SQLite | Workflow engine |
| Recurring | No | Yes (cron) | No (use scheduling) |
| Complex logic | No | No | Yes |
| Human approval | No | No | Yes |
Use Queue when:
- You need background processing without blocking the response
- Tasks should run ASAP but do not need to block
- Order matters (FIFO)
Use Scheduling when:
- Tasks need to run at a specific time
- You need recurring jobs (cron)
- Delayed execution (debouncing, retries)
Use Workflows when:
- Multi-step processes with dependencies
- Automatic retries with backoff
- Human-in-the-loop approvals
- Long-running tasks (minutes to hours)
async schedule<T>( when: Date | string | number, callback: keyof this, payload?: T): Promise<Schedule<T>>Schedule a task for future execution.
Parameters:
when- When to execute:number(seconds delay),Date(specific time), orstring(cron expression)callback- Name of the method to callpayload- Data to pass to the callback (must be JSON-serializable)
Returns: A Schedule object with the task details
async scheduleEvery<T>( intervalSeconds: number, callback: keyof this, payload?: T): Promise<Schedule<T>>Schedule a task to run repeatedly at a fixed interval.
Parameters:
intervalSeconds- Number of seconds between executions (must be greater than 0)callback- Name of the method to callpayload- Data to pass to the callback (must be JSON-serializable)
Returns: A Schedule object with type: "interval"
Behavior:
- First execution occurs after
intervalSeconds(not immediately) - If callback is still running when next execution is due, it is skipped (overlap prevention)
- If callback throws an error, the interval continues
- Cancel with
cancelSchedule(id)to stop the entire interval
async getSchedule<T>(id: string): Promise<Schedule<T> | undefined>Get a scheduled task by ID. Returns undefined if not found.
getSchedules<T>(criteria?: { id?: string; type?: "scheduled" | "delayed" | "cron" | "interval"; timeRange?: { start?: Date; end?: Date };}): Schedule<T>[]Get scheduled tasks matching the criteria.
async cancelSchedule(id: string): Promise<boolean>Cancel a scheduled task. Returns true if cancelled, false if not found.
- Maximum tasks: Limited by SQLite storage (each task is a row). Practical limit is tens of thousands per agent.
- Task size: Each task (including payload) can be up to 2MB.
- Minimum delay: 0 seconds (runs on next alarm tick)
- Cron precision: Minute-level (not seconds)
- Interval precision: Second-level
- Cron jobs: After execution, automatically rescheduled for the next occurrence
- Interval jobs: After execution, rescheduled for
now + intervalSeconds; skipped if still running
Was this helpful?
- Resources
- API
- New to Cloudflare?
- Directory
- Sponsorships
- Open Source
- Support
- Help Center
- System Status
- Compliance
- GDPR
- Company
- cloudflare.com
- Our team
- Careers
- © 2026 Cloudflare, Inc.
- Privacy Policy
- Terms of Use
- Report Security Issues
- Trademark
-