Schedule tasks·续1
// 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), }, });
### Cancel a schedule
Remove a scheduled task before it executes:
```js
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}`;
}
}
The Schedule object
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
// }
Patterns
Rescheduling from callbacks
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 = 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);
}
}
}
}
Exponential backoff retry
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
}
}
Self-destructing agents
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();
}
}
Note
When destroy() is called from within a scheduled task, the Agent SDK defers the destruction to ensure the scheduled callback completes successfully. The Agent instance will be evicted immediately after the callback finishes executing.
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:
import { getSchedulePrompt, scheduleSchema } from "agents/schedules/parser";
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/schedules/parser";
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
A Zod schema for validating parsed scheduling data. Uses a discriminated union on when.type so each variant only contains the fields it needs:
import { scheduleSchema } from "agents/schedules/parser";
// 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/schedules/parser";
// 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" }
// }
Note
Dates are returned as ISO 8601 strings (not Date objects) for compatibility with both Zod v3 and v4 JSON schema generation.
Scheduling vs Queue vs Workflows
| Feature | Queue | Scheduling | Workflows |
|---|---|---|---|
| When | Immediately (FIFO) | Future time | Future time |
| Execution | Sequential | At scheduled time | Multi-step |
| Retries | Built-in | Built-in | Automatic |
| Persistence | SQLite | SQLite | Workflow engine |
| Recurring | No | Yes (cron) | No (use scheduling) |
| Complex logic | No | No | Yes |
| Human approval | No | No | Yes |
