将来のタスクをスケジュールします。数秒後、特定の日時、または繰り返しの cron スケジュールです。スケジュールしたタスクはエージェントの再起動後も残り、SQLite に永続化されます。
スケジュールしたタスクは、ユーザーからのリクエストやメッセージと同じことができます。リクエストの送信、データベースのクエリ、メールの送信、状態の読み書きです。Agent 上の通常のメソッドを呼び出せます。
スケジューリングには 4 つのモードがあります。
| モード | 構文 | 用途 |
|---|---|---|
| Delayed | this.schedule(60, ...) |
60 秒後に実行 |
| Scheduled | this.schedule(new Date(...), ...) |
特定の時刻に実行 |
| Cron | this.schedule("0 8 * * *", ...) |
繰り返しスケジュールで実行 |
| Interval | this.scheduleEvery(30, ...) |
30 秒ごとに実行 |
内部では、適切な時刻にエージェントを起こすために Durable Object alarms を使います。タスクは 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: "user@example.com" });
// 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: "user@example.com" });
// 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 式の文字列を渡します。
// 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 スケジュールはデフォルトで冪等です。同じ cron 式、コールバック、ペイロードで schedule() を何度呼んでも、重複は作らず既存のスケジュールを返します。そのため onStart() での設定が安全です。
固定間隔(秒)でタスクを実行するには scheduleEvery() を使います。cron と異なり、1 分未満の精度と任意の間隔に対応します。
// 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 | Interval |
|---|---|---|
| 最小粒度 | 1 分 | 1 秒 |
| 任意の間隔 | 不可(cron パターンに合わせる必要がある) | 可 |
| 固定スケジュール | 可(例: 「毎日午前 8 時」) | 不可(開始時点からの相対) |
| 重複実行の防止 | なし | あり(組み込み) |
冪等性:
scheduleEvery() は、コールバック名、間隔、ペイロードの組み合わせで冪等です。同じ引数で何度呼んでも、重複スケジュールは作りません。Durable Object の起動ごとに走る onStart() から呼んでも安全です。
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");
// ...
}
}用途:
- 1 分未満のポーリング(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 オブジェクトが返ります。
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 には、自然言語のスケジュール要求を AI で解析するユーティリティがあります。
自然言語をスケジューリングパラメータへ解析するためのシステムプロンプトを返します。
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}`);
}
}解析したスケジューリングデータを検証する 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 | Scheduling | Workflows |
|---|---|---|---|
| 実行タイミング | 即時(FIFO) | 将来の時刻 | 将来の時刻 |
| 実行 | 順次 | スケジュールした時刻 | 複数ステップ |
| リトライ | 組み込み | 組み込み | 自動 |
| 永続化 | SQLite | SQLite | Workflow エンジン |
| 繰り返し | なし | あり(cron) | なし(スケジューリングを使う) |
| 複雑なロジック | なし | なし | あり |
| 人の承認 | なし | なし | あり |
Queue を使う場合:
- 応答をブロックせずにバックグラウンド処理が必要
- できるだけ早く実行したいが、ブロックしたくない
- 順序が重要(FIFO)
Scheduling を使う場合:
- 特定の時刻に実行する必要がある
- 繰り返しジョブ(cron)が必要
- 遅延実行(デバウンス、リトライ)
Workflows を使う場合:
- 依存関係のある複数ステップの処理
- バックオフ付きの自動リトライ
- 人が介在する承認
- 長時間実行のタスク(数分から数時間)
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- 任意のリトライ設定。詳細は Retries を参照してくださいoptions.idempotent- コールバック + ペイロードで重複を排除します。cron スケジュールはデフォルトtrue、遅延および Date ベースのスケジュールはfalseです
戻り値: タスク詳細を含む Schedule オブジェクト
冪等性:
cron スケジュールはデフォルトで冪等です。同じコールバック、cron 式、ペイロードで schedule("0 * * * *", "tick") を何度呼んでも、重複は作らず既存のスケジュールを返します。上書きするには idempotent: false を指定します。
遅延および Date ベースのスケジュールでは、同じ重複排除(コールバック + ペイロードで照合)を有効にするには idempotent: true を指定します。onStart() で schedule() を呼ぶときに特に有用です。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 });
}
}async scheduleEvery<T>(
intervalSeconds: number,
callback: keyof this,
payload?: T,
options?: { retry?: RetryOptions }
): Promise<Schedule<T>>固定間隔で繰り返し実行するタスクをスケジュールします。
パラメータ:
intervalSeconds- 実行間隔の秒数(0 より大きい必要があります)callback- 呼び出すメソッド名payload- コールバックへ渡すデータ(JSON シリアライズ可能である必要があります)options.retry- 任意のリトライ設定。詳細は Retries を参照してください。
戻り値: type: "interval" の Schedule オブジェクト
動作:
- 最初の実行は
intervalSeconds後です(即時ではありません) - 次の実行時点でコールバックがまだ動いている場合はスキップされます(重複実行の防止)
- コールバックがエラーを投げても、インターバルは続きます
- インターバル全体を止めるには
cancelSchedule(id)でキャンセルします
async getScheduleById(id: string): Promise<Schedule<unknown> | undefined>ID でスケジュールしたタスクを取得します。見つからない場合は undefined を返します。トップレベルのエージェントとサブエージェントの両方で使えます。
async listSchedules(criteria?: {
id?: string;
type?: "scheduled" | "delayed" | "cron" | "interval";
timeRange?: { start?: Date; end?: Date };
}): Promise<Schedule<unknown>[]>条件に一致するスケジュールしたタスクを取得します。トップレベルのエージェントとサブエージェントの両方で使えます。
getSchedule<T>(id: string): Schedule<T> | undefined非推奨です。ID でスケジュールしたタスクを同期的に取得します。トップレベルのエージェントでのみ動作します。代わりに await this.getScheduleById(id) を使ってください。
getSchedules<T>(criteria?: {
id?: string;
type?: "scheduled" | "delayed" | "cron" | "interval";
timeRange?: { start?: Date; end?: Date };
}): Schedule<T>[]非推奨です。条件に一致するスケジュールしたタスクを同期的に取得します。トップレベルのエージェントでのみ動作します。代わりに await this.listSchedules(criteria) を使ってください。
async cancelSchedule(id: string): Promise<boolean>スケジュールしたタスクをキャンセルします。キャンセルできた場合は true、見つからない場合は false を返します。
async keepAlive(): Promise<() => void>30 秒のアラームベースのハートビート参照を保持し、非アクティブによる Durable Object の退避を防ぎます。呼び出すとハートビートを解放する disposer 関数を返します。disposer は冪等で、複数回呼んでも安全です。
作業が終わったら必ず disposer を呼んでください。呼ばないとハートビートが無期限に続きます。
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();
}async keepAliveWhile<T>(fn: () => Promise<T>): Promise<T>Durable Object を生存させたまま、非同期関数を実行します。ハートビートは関数の実行前に自動で開始し、完了時(成功でも例外でも)に停止します。関数の戻り値を返します。
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 は、非アクティブ期間のあと(通常、受信リクエスト、WebSocket メッセージ、アラームがない状態が 70〜140 秒)に退避されます。長時間の処理(LLM 応答のストリーミング、外部 API の待機、複数ステップの計算)の途中で、エージェントが退避されることがあります。
keepAlive() は、メモリ上のハートビート参照を保持し、Durable Object のアラームシステムを直接使ってこれを防ぎます。アラームの発火自体が、非アクティブタイマーをリセットします。
- ハートビートは、アラームシステムが単一のアラスロットで多重化するため、独自のスケジュールと衝突しません。
- スケジュール行は作られず、ハートビートは
listSchedules()から見えません。 - 同時の複数
keepAlive()呼び出しは参照カウントを使うため、1 つの disposer が別の呼び出し元のハートビートを解放しません。 - サブエージェント内では、ファセットに独立したアラスロットがないため、
keepAlive()はそのハートビート参照をトップレベルの親へ委譲します。
各 keepAlive() 呼び出しは、独立した disposer を返します。
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 は、ストリーミング応答中に自動で keepAlive() を呼びます。AIChatAgent を使う場合、自分で追加する必要はありません。すべての LLM ストリームは、デフォルトでアイドル退避から保護されます。
| シナリオ | keepAlive を使うか |
|---|---|
AIChatAgent 経由の LLM 応答ストリーミング |
不要 — すでに組み込み |
| カスタム Agent 内の長時間計算 | 使う |
| 遅い外部 API 呼び出しの待機 | 使う |
| 複数ステップのツール実行 | 使う |
| 短いリクエスト / レスポンスハンドラー | 不要 |
| スケジューリングまたは Workflows によるバックグラウンド処理 | 不要 — アラームがすでに DO をアクティブに保つ |
- 最大タスク数: SQLite ストレージで制限されます(各タスクは 1 行)。実用上の上限は、エージェントあたり数万件です。
- タスクサイズ: 各タスク(ペイロードを含む)は最大 2 MB です。
- 最小遅延: 0 秒(次のアラームティックで実行)
- cron の精度: 分単位(秒単位ではない)
- インターバルの精度: 秒単位
- cron ジョブ: 実行後、次の発生時刻へ自動で再スケジュールされます
- インターバルジョブ: 実行後、
now + intervalSecondsへ再スケジュールされます。まだ実行中ならスキップされます