Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

タスクのスケジュール

最終更新 Markdown で表示Agent セットアップ

将来のタスクをスケジュールします。数秒後、特定の日時、または繰り返しの 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: "[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 スケジュールはデフォルトで冪等です。同じ 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 オブジェクト

スケジュールを作成または取得すると、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();
	}
}

AI 支援のスケジューリング

SDK には、自然言語のスケジュール要求を AI で解析するユーティリティがあります。

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

Scheduling と Queue と Workflows

機能 Queue Scheduling 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 - 任意のリトライ設定。詳細は 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 });
	}
}

scheduleEvery()

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) でキャンセルします

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>

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

keepAliveWhile()

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 idle
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 idle

AIChatAgent

AIChatAgent は、ストリーミング応答中に自動で keepAlive() を呼びます。AIChatAgent を使う場合、自分で追加する必要はありません。すべての LLM ストリームは、デフォルトでアイドル退避から保護されます。

keepAlive を使うタイミング

シナリオ keepAlive を使うか
AIChatAgent 経由の LLM 応答ストリーミング 不要 — すでに組み込み
カスタム Agent 内の長時間計算 使う
遅い外部 API 呼び出しの待機 使う
複数ステップのツール実行 使う
短いリクエスト / レスポンスハンドラー 不要
スケジューリングまたは Workflows によるバックグラウンド処理 不要 — アラームがすでに DO をアクティブに保つ

制限

  • 最大タスク数: SQLite ストレージで制限されます(各タスクは 1 行)。実用上の上限は、エージェントあたり数万件です。
  • タスクサイズ: 各タスク(ペイロードを含む)は最大 2 MB です。
  • 最小遅延: 0 秒(次のアラームティックで実行)
  • cron の精度: 分単位(秒単位ではない)
  • インターバルの精度: 秒単位
  • cron ジョブ: 実行後、次の発生時刻へ自動で再スケジュールされます
  • インターバルジョブ: 実行後、now + intervalSeconds へ再スケジュールされます。まだ実行中ならスキップされます

次のステップ

プッシュ通知

スケジューリングと web-push で、ブラウザーのプッシュ通知を送ります。

Workflows の実行

耐久性のある複数ステップのバックグラウンド処理です。

Agents API

Agents SDK の完全な API リファレンスです。

役に立ちましたか?