公開されているページや API を 15 分ごとにチェックし、エージェントに結果を JSON で報告させて、状態が変わったときだけ Slack に投稿します。チェックは Ephemeral がスケジュールどおりに実行し、結果の読み取りとアラートを出すかどうかの判断は、あなたが用意する小さなスクリプトが行います。
1 ページを取得するチェックには、約 20 秒かかります。費用の大半はコンピュートで 1 秒あたり $0.0002 で課金され、これにページ取得の $0.001 が加わります。モデルの利用料はそのモデル自体の価格で課金され、選んだモデルによって変わるため、最初の数回の実行で cost_micros を確認し、そこからスケジュールやモデルを調整してください。
必要なものを用意する
- スケジュールを作成できるのはオーナーと管理者だけなので、管理者ロールの API キー。ロールとキーを参照してください。
- 監視する、1 つのドメイン上のページまたは API。ここでは
https://status.example.comとします。 - Slack の Incoming Webhook URL、または独自のアラートサービスのアドレス。
- 数分おきにスクリプトを実行できる場所。cron、スケジュール実行される CI ジョブ、サーバーレスのタイマーなどです。
ワークフローを保存する
ワークフローはページを取得し、決まった形式の JSON で答えます。Ephemeral は出力スキーマを強制しません。実行の出力は自由なテキストです。そのため、プロンプトでは JSON だけを返すよう求め、スクリプト側では手順 4 で慎重にパースします。
curl -X PUT $EPHEMERAL_URL/v1/workflows/check-status-page \ -H "Authorization: Bearer $EPHEMERAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Fetch the page at input.url with web_fetch. Reply with one JSON object and nothing else: {\"ok\": boolean, \"http_status\": number, \"summary\": string}. Set ok to true only when the response status is 200 and the page reports no ongoing incident, outage or degraded service; resolved past incidents do not count. summary is one sentence of at most 200 characters.", "web": { "allow": ["status.example.com"], "search": false }, "shell": false, "timeout_seconds": 90 }'Web アクセスは
status.example.comに限定し、検索はオフなので、チェックはそのサイト以外を読めません。チェックする URL は入力として渡されるため、同じワークフローを、このドメイン上の複数のスケジュールで使えます。スケジュールを作成する
まずタイムテーブルをプレビューします。次の 5 回の開始時刻が返され、何も保存されません。
curl -X POST $EPHEMERAL_URL/v1/schedules/preview \ -H "Authorization: Bearer $EPHEMERAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "cron": "*/15 * * * *", "timezone": "UTC" }'次に、スケジュールを作成します。入力と予算は、すべての実行で同じです。
curl -X POST $EPHEMERAL_URL/v1/schedules \ -H "Authorization: Bearer $EPHEMERAL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow": "check-status-page", "name": "Status page, every 15 minutes", "cron": "*/15 * * * *", "input": { "url": "https://status.example.com" }, "max_cost_micros": 100000 }'スケジュールの
idを控えておいてください。予算の 100,000 マイクロ ($0.10) は、開始のたびにクレジットから確保され、実行が精算されると解放されます。この予算は、タイムアウトまでのコンピュート料金をまかなえる必要があり、ここでは 90 秒分の $0.018 です。次の開始時刻になってもチェックが実行中の場合は、skip_if_runningがデフォルトでtrueのため、その開始はスキップされます。今すぐ 1 回開始して結果を読む
タイムテーブルを待つ必要はありません。
POST /v1/schedules/{id}/runは、スケジュールの入力と予算でワークフローを今すぐ開始し、スケジュールの ID を持つ実行とともに202を返します。 続いて、スケジュールの実行を新しい順に一覧表示します。curl -X POST $EPHEMERAL_URL/v1/schedules/$SCHEDULE_ID/run \ -H "Authorization: Bearer $EPHEMERAL_API_KEY" curl "$EPHEMERAL_URL/v1/runs?schedule=$SCHEDULE_ID&limit=1" \ -H "Authorization: Bearer $EPHEMERAL_API_KEY"outputは、JSON を保持する文字列です。モデルは指示にかかわらず、JSON をコードフェンスで囲んだり、前後に 1 文を加えたりすることがあります。そのため、以下のスクリプトでは、最初の{から最後の}までを取り出してパースします。変化があったときだけアラートを出す
スケジュールされた実行はコールバックを送らないため、スクリプトがスケジュールの最新の実行を取得します。最後に確認した実行と最後の状態をファイルに記憶し、すでに確認した実行や、まだ実行中のものは無視して、前回と状態が異なるときだけ Slack に投稿します。チェックが失敗した場合や、読み取れない結果が返った場合は、それ自体が
unknownという状態として扱われます。そのため、監視の不具合が、正常なページのように見えることはありません。import { readFile, writeFile } from "node:fs/promises"; const { EPHEMERAL_URL, EPHEMERAL_API_KEY, SCHEDULE_ID, SLACK_WEBHOOK_URL } = process.env; const ACTIVE = new Set(["reserving", "queued", "starting", "running"]); const stateFile = "monitor-state.json"; const state = JSON.parse(await readFile(stateFile, "utf8").catch(() => "{}")); const response = await fetch(`${EPHEMERAL_URL}/v1/runs?schedule=${SCHEDULE_ID}&limit=1`, { headers: { Authorization: `Bearer ${EPHEMERAL_API_KEY}` }, }); if (!response.ok) throw new Error(`Ephemeral returned ${response.status}`); const [run] = (await response.json()).items; if (!run || ACTIVE.has(run.status) || run.id === state.run) process.exit(0); // nothing new to judge let label = "unknown"; // the check itself did not produce an answer let summary = run.error ?? `The run ${run.status}.`; if (run.status === "succeeded") { try { const result = JSON.parse(run.output.match(/\{[\s\S]*\}/)[0]); // tolerate text or a code fence around the JSON label = result.ok === true ? "ok" : "down"; summary = result.summary; } catch { summary = "The check did not return valid JSON."; } } if (label !== (state.label ?? "ok")) { await fetch(SLACK_WEBHOOK_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: `Status page: ${label}. ${summary} (${run.id})` }), }); } await writeFile(stateFile, JSON.stringify({ run: run.id, label }));4 つの環境変数を設定したうえで、たとえば cron の
*/5 * * * *で、5 分おきに実行します。最初のチェックは、ページが正常なら何も投稿しません。それ以降は、downからokへの復旧を含め、状態が変わるたびに 1 回投稿します。
知っておくとよいこと
| 項目 | 動作 |
|---|---|
| チェックの重複 | skip_if_running がオンの場合、前回の実行が続いている間は開始がスキップされ、スケジュールの last_error に記録されます。重複しても問題がない場合に限り、false に設定してください。 |
| 停止するスケジュール | クレジットが尽きた場合などで、開始が 5 回連続で失敗すると、スケジュールは自動的にオフになり、新しい実行は作られなくなります。ときどき GET /v1/schedules/{id} で enabled と last_error を確認するか、クレジットをチャージして enabled を true に設定してください。 |
| 間隔の変更 | 新しい cron を指定して PATCH /v1/schedules/{id} を呼び出します。次の開始は、現在時刻から数えます。 |
| 一時停止 | ephemeral schedules pause ID と resume、または PATCH の enabled を使います。 |
callback を指定して POST /v1/runs で実行を開始します。受信側は、 支払い調査のレシピにあるものと同じです。ただしその場合、Ephemeral の重複防止とタイムテーブルは使えなくなるため、スケジュールは、結果をこちらから送り出す必要のない処理のために使ってください。