スケジュールされた自律動作:ループ、Wakeup、Cron
Claude Code のスケジュールされた自律動作を極める:/loop の dynamic モード、ScheduleWakeup のタイミング設計、プロンプトキャッシュの経済学、常設自動化のための cron ジョブ。
学ぶこと
セッション 11では、人間をループに介在させずに動作するエージェントを見ました。このセッションで扱うのは、そこに欠けていた次元 — 時間です。デプロイを見張るエージェントや夜間監査を実行するエージェントは、何をするかだけでなく、いつ起きてそれをするかも決めなければなりません。そして Fable 5 時代には、その決定に現実の経済性が伴います。
このセッションを終えると、次のことがわかります:
/loopの仕組みと、「dynamic モード(動的モード)」の意味- ScheduleWakeup ツール:delay のクランプ(範囲制限)、自己スケジューリング、
stopのセマンティクス - プロンプトキャッシュの経済学 — なぜ 300 秒のスリープが最悪の選択なのか
- 何をポーリングすべきか(外部の状態)、何を決してポーリングすべきでないか(ハーネスが追跡する作業)
- 常設の cron ジョブと単発の wakeup(ウェイクアップ、起床)の違い
- なぜフォアグラウンドの sleep がブロックされるのか、代わりに何を使うべきか(Monitor、バックグラウンド Bash)
課題
コミットをプッシュして、CI がグリーンになったら Claude Code に PR をマージさせたいとします。CI には約 8 分かかります。その 8 分間、エージェントは何をすればよいのでしょうか?
素朴な選択肢はどれもだめです:
Option 1: Busy-wait Option 2: sleep 480 Option 3: poll every 60 s
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Check. Check. │ │ Block the whole │ │ 8 wake-ups, 8 │
│ Check. Check... │ │ session on a │ │ full turns, for │
│ Burns tokens │ │ foreground sleep │ │ one bit of info │
│ continuously │ │ (blocked anyway) │ │ ("done yet?") │
└──────────────────┘ └──────────────────┘ └──────────────────┘
その下には、より微妙な問題が潜んでいます:スリープもタダではないのです。Anthropic のプロンプトキャッシュの TTL は約 5 分。TTL 内に起きれば、次のターンは蓄積されたコンテキストをキャッシュから読み込みます — 安価です。TTL を過ぎて眠れば、起きるたびにコンテキスト全体がキャッシュなしで、フル価格で読み直されます。
つまり、スケジュールされた自律動作は実際には2つの問題です:メカニズムの問題(エージェントはどうやって自分自身を再開するのか?)と、経済性の問題(どの間隔が安く、どの間隔が罠なのか?)。Fable 5 時代のハーネスは、その両方に答えを用意しています。
仕組み
/loop と dynamic モード
/loop はタスクを繰り返し実行します。固定間隔を与えることもできますし、dynamic モード — 固定間隔なし — で走らせることもできます。dynamic モードでは、モデルは ScheduleWakeup というツールで自らペースを決めます:各イテレーションの終わりに自分自身の再開をスケジュールし、同じループプロンプトを自分に渡し直すのです。
Dynamic loop lifecycle:
┌────────────┐ ScheduleWakeup ┌────────────┐
│ Iteration N │ ──(delaySeconds)──▶ │ sleep │
└────────────┘ └─────┬──────┘
▲ │ harness re-invokes
│ same loop prompt │ with the loop prompt
└──────────────────────────────────┘
Exit: ScheduleWakeup with stop: true → loop ends
間隔は設定値ではなく、モデルが毎ラウンド下す決定です。CI の監視と夜間の後片付けジョブではまったく異なるペース配分が必要であり、いまどちらの状況にいるかを知っているのは、ループの中のエージェントだけです。
ScheduleWakeup のセマンティクス
重要なフィールドは2つです:
delaySeconds— [60, 3600] の範囲にクランプされます。10 秒を要求すれば 60 秒になり、2 時間を要求すれば 3600 秒になります。下限は暴走的なタイトループを止め、上限はループが少なくとも 1 時間に 1 回はチェックインすることを保証します。stop: true— ループを終了します。目標に到達したとき(あるいは到達不可能なとき)、エージェントはスケジューリングをやめ、ループはきれいに終了します。
ScheduleWakeup(delaySeconds: 270) # resume in 4.5 minutes
ScheduleWakeup(stop: true) # goal reached — end the loop
プロンプトキャッシュの崖
あなたが選ぶすべての delay を左右すべき数字がこれです:プロンプトキャッシュの寿命は約 5 分(300 秒)。この1つの事実だけで、delay の範囲は3つのレジーム(領域)に分かれます:
delaySeconds chosen
60 ──────── 270 ─ 300 ─────────── 1200 ──────── 1800 ────── 3600
│ WARM ZONE │ CLIFF │ DEAD ZONE │ COMMITTED SLEEP ZONE │
│ cache hit │ worst │ cache miss, │ cache miss, but one │
│ every wake │ of │ yet you're │ miss buys 20–60 min │
│ │ both │ still waking│ of silence — worth it │
│ │ │ frequently │ │
- 約 270 秒未満 — キャッシュが切れる前に起きるので、毎回の wake がコンテキストをウォームな状態で読みます。これはアクティブなポーリングのためのレジームで、(290 ではなく)約 270 にするのは崖の手前にマージンを残すためです。
- 約 300 秒 — 両方の悪いとこ取り:キャッシュを失うのにちょうど十分な長さで、起きるたびにキャッシュなしのフル読み込みを支払う — それでいて 5 分ごとに起き続けます。
- 1200 秒以上 — 覚悟を決めたスリープ。wake はキャッシュミスになりますが、1 回のミスで 20〜60 分間まったく走らない時間が買えます。アイドル時のチェックには 1200〜1800 秒がまともなデフォルトです。
アンチパターンは、崖と覚悟を決めたスリープの間のデッドゾーンです:コールドリードの価格を、ウォームポーリングの頻度で支払うことになります。レジームは意図的に選びましょう — これはセッション 21 のコスト最適化の思考を、時間軸に適用したものです。
ポーリングするのは外部の状態 — ハーネスが追跡する作業は決してポーリングしない
2つ目の規律:そもそも何がポーリングに値するのかを知ることです。
ハーネス自身が追跡する作業 — バックグラウンドの Bash コマンド、起動したエージェント、実行中のワークフロー(セッション 8で扱った仕組み)— は、完了時に自動的にエージェントを再起動します。これをポーリングするのは純粋な無駄です:どのみちハーネスが教えてくれたはずだからです。ハーネスが追跡する作業に対してスケジュールする価値のある唯一の wakeup は、長めのフォールバック — 何かがハングした場合に備えた、遠い先の 1 回きりのチェックだけです。
ポーリングに値するのは外部の状態です:CI の実行、デプロイのロールアウト、リモートのキュー — ハーネスの視界の外で変化するもの。delay はその状態が変化する速さに合わせましょう:8 分のパイプラインに 60 秒間隔のポーリングは要りませんし、1 時間かけて掃けるキューに 5 分間隔のポーリングも要りません。
cron ジョブと単発の wakeup
ScheduleWakeup は単発です:一度だけ発火し、ループは毎ラウンド再スケジュールしなければなりません。常設のジョブ — 夜間レポートや定期ヘルスチェック — のために、ハーネスには cron ツールがあります:CronCreate、CronList、CronDelete が、どのループからも独立して持続する定期スケジュールジョブを管理します。
どちらのメカニズムも自律ループを駆動し、ハーネスはそれらのターンをセンチネルプロンプトでマークします — cron モードなら <<autonomous-loop>>、ScheduleWakeup モードなら <<autonomous-loop-dynamic>> です。これによってエージェントは、人間に答えているのではなく、無人のループとして走っているのだと認識できます。トランスクリプトにこれらのセンチネルが現れることはありますが、自分で管理する必要はありません。
フォアグラウンドの sleep は禁止
補助的な2つのルールで全体像が完成します:
- フォアグラウンドの sleep はブロックされます。
sleep 480でターンを停留させることはできません。条件を待つには、セッションをブロックする代わりに Monitor ツール — ステータスに対する until ループ — を使います。 - バックグラウンド Bash は
run_in_backgroundでコマンドをデタッチ実行します(&は不要)。ターンをまたいで走り続け、終了時にエージェントを再起動します — だからこそ、決してポーリングしないのです。
ハンズオン
実際の CI 監視のペースを設計してみましょう。コミットをプッシュしたところで、パイプラインは過去実績で約 8 分かかります。ループの仕事:起きて、PR のチェックを確認し、グリーンになったらマージし、停止する。
まず、3つの候補スケジュールを比較します:
Pipeline duration: ~480 s (8 min)
Strategy A: 60 s polls W─W─W─W─W─W─W─W─✓ 8 wakes, all warm
8 turns for 1 bit of information
Strategy B: one 480 s sleep ────────cold────✓ 1 wake, full uncached
context re-read at the moment you act
Strategy C: two 270 s sleeps ───warm───┬──warm──✓ 2 wakes, BOTH cached
(270 s) (540 s ≈ 9 min — CI done)
勝者は戦略 C です:各スリープが約 300 秒のキャッシュ TTL を下回るため、2 回の wake はどちらもコンテキストをウォームに読み、2 ターンというオーバーヘッドは戦略 A の 4 分の 1 で済みます。ループのイテレーションは次のようになります:
# Iteration 1 (t = 0): just pushed. CI will take ~8 min.
gh pr checks 142 # → pending
# Too early to poll tightly. Sleep just under the cache TTL.
# ScheduleWakeup(delaySeconds: 270)
# Iteration 2 (t = 270 s): warm wake — context read from cache.
gh pr checks 142 # → still running (expected; ~4.5 min elapsed)
# ScheduleWakeup(delaySeconds: 270)
# Iteration 3 (t = 540 s): warm wake again. ~9 min elapsed.
gh pr checks 142 # → all checks passed
gh pr merge 142 --squash
# Goal reached:
# ScheduleWakeup(stop: true)
注目に値する判断が2つあります:
- もしパイプラインに 25 分かかるなら、ウォームポーリングは意味を失います。もう一方のレジームに振り切りましょう — 単発の
delaySeconds: 1500スリープです。1 回のキャッシュミスで、待ち時間全体が買えます。 - もし「パイプライン」がバックグラウンド Bash で起動したローカルのテストスイートなら、そもそもポーリングをスケジュールしません — コマンドが終了すれば、ハーネスが再起動してくれます。せいぜい、ハングに備えて遠い先のフォールバックを 1 つ(
delaySeconds: 1800)設定する程度です。
そしてループが最終的に行動する場面では、セッション 11の規範が適用されます:可逆でスコープ内のアクション(頼まれた PR をマージする)は確認なしで進み、破壊的またはスコープを変える決定こそ、自律ループが立ち止まって報告すべきところです。
何が変わったか
| 以前 | Fable 5 時代 |
|---|---|
| 自律動作 = 付き添いが必要なフォアグラウンドセッション | ループが ScheduleWakeup で自分の wakeup をスケジュール |
| 一度決めたら固定のポーリング間隔 | イテレーションごと、コストレジームごとに delay を選択 |
シェルで sleep 480 | フォアグラウンドの sleep はブロック;条件待ちは Monitor |
| 自分のバックグラウンドタスクまで含めて全部ポーリング | ハーネス追跡の作業は自動再起動;ポーリングは外部状態のみ |
| 定期ジョブは外部の cron に配線 | CronCreate/CronList/CronDelete によるハーネス内の常設ジョブ |
| スリープ時間は後回しの考慮事項 | 約 5 分のキャッシュ TTL が、それを値札付きの決定に変えた |
キーインサイト
いつ起きるべきかを知っているエージェントは、決して眠らないエージェントよりも安い。
このセッションのすべて — クランプ、TTL、センチネル — は1つの考えに奉仕しています:スケジュールされた自律動作では、間隔そのものが答えの一部だということです。300 秒眠るループと 270 秒眠るループはほとんど同じに見えますが、価格の崖の反対側に立っています。約 5 分のキャッシュ TTL が残す良いレジームはちょうど2つ — ウォームを保つ(約 270 秒未満、活発に収束中)か、スリープに振り切る(1200 秒以上、アイドル中)か — であり、そして最も安いスケジュールはスケジュールしないことです。ハーネスが追跡する作業は、自分から報告しに戻ってくるのですから。
固定の cron は「これを毎晩実行して」に答えます。動的な wakeup は、より難しい問い —「この待ち時間には、どれだけの長さの価値があるのか?」— に答えます。モデルに時計を持たせることで手に入るのは、その判断力なのです。
次のセッション
セッション 32 は 新しいツールベルト:Artifacts、タスク、型付き出力 でモジュールを締めくくります — Fable 5 時代のユーザー向けツール群:ホストされる Artifacts ページ、TodoWrite を置き換えたタスクシステム、型付きのレビュー所見、そしてバックグラウンド作業が追跡されるタスクとして表面化する仕組みを学びます。