Skip to content
Cloudflare Docs

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.

Overview

The scheduling system supports four modes:

ModeSyntaxUse case
Delayedthis.schedule(60, ...)Run in 60 seconds
Scheduledthis.schedule(new Date(...), ...)Run at specific time
Cronthis.schedule("0 8 * * *", ...)Run on recurring schedule
Intervalthis.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.

Quick start

JavaScript
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
}
}

Scheduling modes

Delayed execution

Pass a number to schedule a task to run after a delay in seconds:

JavaScript
// Run in 10 seconds
await this.schedule(10, "processTask", { taskId: "123" });
// Run in 5 minutes (300 seconds)
await this.schedule(300, "sendFollowUp", { email: "user@example.com" });
// Run in 1 hour
await this.schedule(3600, "checkStatus", { orderId: "abc" });

Use cases:

  • Debouncing rapid events
  • Delayed notifications ("You left items in your cart")
  • Retry with backoff
  • Rate limiting

Scheduled execution

Pass a Date object to schedule a task at a specific time:

JavaScript
// 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", {});

Use cases:

  • Appointment reminders
  • Deadline notifications
  • Scheduled content publishing
  • Time-based triggers

Recurring (cron)

Pass a cron expression string for recurring schedules:

JavaScript
// 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 syntax: minute hour day month weekday

FieldValuesSpecial characters
Minute0-59* , - /
Hour0-23* , - /
Day of Month1-31* , - /
Month1-12* , - /
Day of Week0-6 (0=Sunday)* , - /

Common patterns:

JavaScript
"* * * * *"; // 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

Use cases:

  • Daily/weekly reports
  • Periodic cleanup jobs
  • Polling external services
  • Health checks
  • Subscription renewals

Interval

Use scheduleEvery() to run a task at fixed intervals (in seconds). Unlike cron, intervals support sub-minute precision and arbitrary durations:

JavaScript
// 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" });

Key differences from cron:

FeatureCronInterval
Minimum granularity1 minute1 second
Arbitrary intervalsNo (must fit cron pattern)Yes
Fixed scheduleYes (for example, "every day at 8am")No (relative to start)
Overlap preventionNoYes (built-in)

Overlap prevention:

If a callback takes longer than the interval, the next execution is skipped (not queued). This prevents runaway resource usage:

JavaScript
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", {});

When a skip occurs, you will see a warning in logs:

Skipping interval schedule abc123: previous execution still running

Error resilience:

If the callback throws an error, the interval continues — only that execution fails:

JavaScript
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

Managing scheduled tasks

Get a schedule

Retrieve a scheduled task by its ID:

JavaScript
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");
}

List schedules

Query scheduled tasks with optional filters:

JavaScript
// Get all scheduled tasks
const allSchedules = this.getSchedules();
// Get only cron jobs
const cronJobs = this.getSchedules({ type: "cron" });
// Get tasks in the next hour
const upcoming = this.getSchedules({
timeRange: {
start: new Date(),
end: new Date(Date.now() + 60 * 60 * 1000),
},
});
// Get a specific task by ID
const specific = this.getSchedules({ id: "abc123" });
// Combine filters
const upcomingCronJobs = this.getSchedules({
type: "cron",
timeRange: {
start: new Date(),
end: new Date(Date.now() + 24 * 60 * 60 * 1000),
},
});

Cancel a schedule

Remove a scheduled task before it executes:

JavaScript
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

JavaScript
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}`;
}
}

The Schedule object

When you create or retrieve a schedule, you get a Schedule object:

TypeScript
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:

JavaScript
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
// }

Patterns

Rescheduling from callbacks

For dynamic recurring schedules, schedule the next run from within the callback:

JavaScript
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);
}
}
}
}

Exponential backoff retry

JavaScript
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
}
}

Self-destructing agents

You can safely call this.destroy() from within a scheduled callback:

JavaScript
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();
}
}

AI-assisted scheduling

The SDK includes utilities for parsing natural language scheduling requests with AI.

getSchedulePrompt()

Returns a system prompt for parsing natural language into scheduling parameters:

JavaScript
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}`);
}
}

scheduleSchema

A Zod schema for validating parsed scheduling data:

JavaScript
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"
// }
// }

Scheduling vs Queue vs Workflows

FeatureQueueSchedulingWorkflows
WhenImmediately (FIFO)Future timeFuture time
ExecutionSequentialAt scheduled timeMulti-step
RetriesManualManualAutomatic
PersistenceSQLiteSQLiteWorkflow engine
RecurringNoYes (cron)No (use scheduling)
Complex logicNoNoYes
Human approvalNoNoYes

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)

API reference

schedule()

TypeScript
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), or string (cron expression)
  • callback - Name of the method to call
  • payload - Data to pass to the callback (must be JSON-serializable)

Returns: A Schedule object with the task details

scheduleEvery()

TypeScript
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 call
  • payload - 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

getSchedule()

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

Get a scheduled task by ID. Returns undefined if not found.

getSchedules()

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

Get scheduled tasks matching the criteria.

cancelSchedule()

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

Cancel a scheduled task. Returns true if cancelled, false if not found.

Limits

  • 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

Next steps