大きな画面での利用をおすすめします — アニメーションと操作がより快適になります。
入力

タスクシステム

タスクシステムは目標を依存関係付きの小さな仕事へ分け、グラフを保存し、完了ごとに進捗も保存します。依存がすべて終わったタスクだけを選び、プロセス再起動時は記録を読んで続行します。このチェックポイントモデルでは、中断中のタスクだけをやり直し、保存済みの完了項目は繰り返しません。

計画が 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 で、途中にプロセス停止を模擬します。右のパネルは現在の実行状態とは別の、最後の保存内容です。

  1. 初期化。 目標は「パーサー機能を出荷する」。構造はまだありません。
  2. 分解。 t1 → t2/t3 → t4 → t5 の 5 タスク、5 辺ができ、すべて pending です。
  3. 保存。 save("tasks.json") で 5 行の pending がチェックポイントへ入ります。
  4. t1 を選択。 依存がない唯一の項目で、紫の running になります。
  5. t1 完了。 ノードが緑になり、同じステップで保存行も done。完了設定と保存の隣接 2 行をまとめて示します。
  6. 解放。 t1 からの 2 辺が緑になり、t2、t3 が準備済み集合へ入ります。
  7. t2 を選択。 両方選べますが、小さい ID を優先します。
  8. t2 完了。 ファイルは t1、t2 が done。t4 は片方の依存しか満たさず、まだ待ちます。
  9. t3 を選択。 テスト作成を開始します。
  10. クラッシュ。 t3 の途中でプロセスが終了し、実行グラフと上部表示が消えます。保存は t1、t2 done、t3 pending のままです。画面の running は保存されていませんでした。
  11. 再起動。 最初に load("tasks.json")。t1、t2 は緑の状態から戻り、再分解も再実行もしません。
  12. 再開。 読んだ状態から next_ready を計算すると t3 です。保存済み完了のやり直しは 0 回、未記録の t3 を再試行します。
  13. t3 完了、t4 解放。 合流点の両依存がそろいます。
  14. 残りを実行。 t4 の選択、完了、保存で t5 を解放し、t5 も選択、完了、保存します。
  15. 終了。 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 の再開、エージェントのタスクファイルも関連する問題を扱います。本例の共通構造は、計画と進捗を保存し、依存で仕事を選び、記録から復旧することです。