タスクシステム
タスクシステムは目標を依存関係付きの小さな仕事へ分け、グラフを保存し、完了ごとに進捗も保存します。依存がすべて終わったタスクだけを選び、プロセス再起動時は記録を読んで続行します。このチェックポイントモデルでは、中断中のタスクだけをやり直し、保存済みの完了項目は繰り返しません。
計画が messages[] などのプロセスメモリだけにあると、終了時に完了記録を失い得ます。外部の成果物が残っても、新プロセスには完了済みの項目が分かりません。本例の同じクラッシュで、disk は完了実行 5 回、memory は 7 回です。差の 2 回は t1、t2 の再実行です。
tasks.json は 5 タスク、依存辺、状態を保持します。分解後に計画を保存し、各 task.status = "done" の後で進捗を保存します。再起動は load を先に試し、記録がなければ decompose します。
エージェントループへスケジューラーを加えた仕組みです。サブエージェントが文脈間で仕事を分けるのに対し、ここではプロセスの寿命をまたいで続けます。
依存で順序を決め、完了を保存する
disk のコードで重要なのは next_ready(graph) と完了後の save です。
next_ready は依存がすべて done の項目だけを返します。t1「モジュールの土台作成」が t2「パーサー実装」と t3「テスト作成」を解放し、t4「テストスイート実行」は t2 と t3 の両方を待ちます。t5「リリースノート」は t4 を待ちます。t2 だけ終わっても t4 は準備済み集合へ入りません。t2、t3 が同時に準備済みになるのは正しく、単一ワーカーは小さい ID を選びます。複数ワーカーなら並行処理できます。
load("tasks.json") は decompose の前、save は各完了の後です。分解時と完了時だけ保存するため、チェックポイントには running がありません。「完了を記録済み」と「未記録」を区別します。中断中のタスクはファイル上 pending なので再試行します。実際の仕事はすでに一部の副作用を持つ場合があり、再実行には冪等性や復旧手順が必要です。pending は外界に何も起きていない証明ではありません。
memory は同じグラフと選択規則を使い、永続的な読み込みと保存だけを省きます。
タスクシステムのステップ
初期設定は disk で、途中にプロセス停止を模擬します。右のパネルは現在の実行状態とは別の、最後の保存内容です。
- 初期化。 目標は「パーサー機能を出荷する」。構造はまだありません。
- 分解。 t1 → t2/t3 → t4 → t5 の 5 タスク、5 辺ができ、すべて pending です。
- 保存。
save("tasks.json")で 5 行の pending がチェックポイントへ入ります。 - t1 を選択。 依存がない唯一の項目で、紫の running になります。
- t1 完了。 ノードが緑になり、同じステップで保存行も done。完了設定と保存の隣接 2 行をまとめて示します。
- 解放。 t1 からの 2 辺が緑になり、t2、t3 が準備済み集合へ入ります。
- t2 を選択。 両方選べますが、小さい ID を優先します。
- t2 完了。 ファイルは t1、t2 が done。t4 は片方の依存しか満たさず、まだ待ちます。
- t3 を選択。 テスト作成を開始します。
- クラッシュ。 t3 の途中でプロセスが終了し、実行グラフと上部表示が消えます。保存は t1、t2 done、t3 pending のままです。画面の running は保存されていませんでした。
- 再起動。 最初に
load("tasks.json")。t1、t2 は緑の状態から戻り、再分解も再実行もしません。 - 再開。 読んだ状態から
next_readyを計算すると t3 です。保存済み完了のやり直しは 0 回、未記録の t3 を再試行します。 - t3 完了、t4 解放。 合流点の両依存がそろいます。
- 残りを実行。 t4 の選択、完了、保存で t5 を解放し、t5 も選択、完了、保存します。
- 終了。 5 タスク、完了実行 5 回、中断試行 1 回。この設定は中断したタスクだけを再開し直しました。
同じクラッシュで in-memory にすると、グラフと選択順は同じでも、再起動時に読む記録がありません。全 5 項目を pending で作り直し、t1、t2 を again 表示付きで繰り返します。完了実行は 7 回です。
scenario を smooth にすると、クラッシュなしの両方式は同じ実行結果です。crash after を 0〜4 へ動かすと、disk の完了実行は常に 5 回で、中断した試行だけを再実行します。memory は 5〜9 回へ増え、失った完了記録 1 件につき 1 回やり直します。0 は完了前なので損失なし、4 は 4 件を二度実行します。
ループ内で保存する理由
decompose 後の 1 回だけでは、5 件とも pending の計画しか残りません。進捗を失えば全作業をやり直します。完了後の save が進捗を保存します。本例の理想化したチェックポイントは、実行中の 1 タスクより前の完了を保持します。
この単純な再開方法に永続 running は不要です。所有者や復旧規則なしに running だけ保存すると、再起動後に別の実行者がまだ作業中か判別できません。本番は running を保存しても構いませんが、リース、所有者、停止後の復旧を設計します。本例は完了だけを記録し、中断作業を再試行する方式です。
予定時刻を準備条件へ追加すれば時刻指定にもつなげられますが、依存の検査は残せます。繰り返し、重複実行、予定の取り逃しには追加方針が必要で、条件 1 つの置換で完全なスケジューラーにはなりません。
各仕事が短く再実行も安ければ、永続化の管理費用に見合わない場合があります。価値は、クラッシュで失う進捗の費用に依存します。
境界条件
- 完了と保存の間で停止。 2 行は原子的でないので、効果が発生済みのタスクを再実行する可能性があります。一時ファイルと rename は保存ファイルの完全性を守っても、外部副作用と完了記録を一緒にコミットしません。冪等性、トランザクション調整、明示的な復旧手順が必要です。ジャーナルの保証も設計次第です。
- 準備済み 2 件、ワーカー 1 台。 t2、t3 間の順序はありません。どちらを先にしてもよく、保証されるのは t4 が両方を待つことです。
- 依存の循環。 t2 が t3、t3 が t2 を待つと、pending のまま
next_readyは何も返せません。実分解器は保存前に非循環を検証します。 - 途中で目標変更。 保存は古い分解です。再起動はそれを忠実に再開します。再計画時は古い記録を明示的に置換または版管理します。
よくある間違い
- 計画だけ保存する。 初期保存には完了がなく、再起動は全件を繰り返します。save を完了ループ内へ置きます。
- 保存より停止前の画面を信じる。 t3 は running でしたが、記録は pending です。復旧は永続状態と定めた規則に従います。
- 実行器の暗黙知で順序を守る。
t4.deps = [t2, t3]をデータにすれば、同じグラフの保存、読み込み、共有でも制約を保持できます。 - 開始しただけで done にする。 再開は done を飛ばすので、途中の仕事を永久に省く危険があります。
- 起動ごとにグラフを再生成。 有効な保存があれば先に load します。無条件の再計画は復旧の根拠を失わせます。
モデルの簡略化
DAG、依存検査、完了ごとの保存、クラッシュ、再開は決定的に計算します。ファイルも別のメモリチェックポイントで模擬し、利用者のディスクには書きません。名前と実行は固定で、実際にモジュールは作りません。実エージェントの分解はモデルが行い得ます。ツール呼び出しと ReAct ループはその記録を示します。
本例の decompose は固定なので再計画も同じグラフです。実モデルは別の分解を選び得るため、この再実行数を現実の復旧費用の上限や下限にはできません。
本番には原子的ファイル保存、ジャーナル、副作用と記録の間の復旧、再試行、タイムアウト、優先順位、並行ワーカーの取得、循環検査も必要です。Airflow、Temporal などのワークフロー、CI の再開、エージェントのタスクファイルも関連する問題を扱います。本例の共通構造は、計画と進捗を保存し、依存で仕事を選び、記録から復旧することです。
続けて学ぶ
同じ手法につながるレッスンや、次の概念への入口です。
鍵マークのあるレッスンの操作には有料アクセスが必要です。解説はすべて無料で読めます。
誤りや気になる点を見つけましたか? フィードバックを送る。