レッスン4: 構造化された状態: エージェントがタスクの進捗をどう記憶するか
学習目標:
- タスクの進捗を会話の文章に埋め込むことが信頼性に欠ける理由と、なぜそれが構造化された状態にならなければならないかを説明できる
- TODOが作成から削除まで経る完全なライフサイクルを述べることができる
- ゼロから始めるコストと、中断した場所から再開するコストの違いを区別できる
- タスクの状態がセッション履歴に存在すべきか、別のチェックポイントに書き込むべきかを判断できる
前提: レッスン3を終えて、外部メモリの2つのモードを理解している | 前: << レッスン3 | 次: レッスン5 >>
プロセスが再起動した後、エージェントはどのステップまで到達したかを知っているか
エージェントが複数ステップのタスクを実行しています: モジュールのリファクタリングで、4つのステップに分解されます — "型定義を更新する"、"呼び出し元を更新する"、"テストを実行する"、"ドキュメントを更新する"。2番目のステップを終えたところで、予期しない再起動によってプロセスが中断されます。再起動後、同じタスク記述を受け取ります。エージェントは最初のステップからやり直すべきでしょうか、それとも最初の2つのステップは既に完了していることを知っていて、ステップ3から直接続けられるでしょうか?
答えは一つのことに帰着します: タスクの進捗が構造化された状態として記録されているか、会話の文章の山に散らばっているかです。「型定義は既に更新済み」がモデルの以前の返信の1行の自然言語に過ぎず、数十のメッセージに埋もれているなら、ホストコードには「どのステップにいるか」という具体的な事実を信頼性をもって引き出す方法がありません。しかし、その進捗が固定されたフィールドを持つTODOリストとして表現されている場合 — すべてのタスクが明示的なステータスマーカーを持つ形です — ホストコードはそれを直接読み取れます: ステップ1と2は完了済み、ステップ3は未開始。
それがこのレッスンが扱う中核的な問いです。レッスン1、2、3はすべてコンテンツの管理についてでした — 会話履歴、メモリファイル。このレッスンは進捗をどう表現するかについてです。中断の後、エージェントまたはそのホストアプリケーションが、タスクがどこまで進んだかを正確に知るために。
TODOのライフサイクル: 作成、開始、完了、削除
Claude Codeのタスク追跡ツールを例に取ります。公式ドキュメントは、実行中にTODOが経る完全なライフサイクルを示しています — 4つのステップです: 1
- 作成: Claudeはタスクを特定したときにTODOをpendingとして追加します
- 開始: Claudeは作業を開始するときにTODOをin_progressに設定します
- 完了: Claudeはタスクが正常に終了したときにcompletedとマークします
- 削除: Claudeは不要になったTODOをTaskUpdateコールでstatus: "deleted"に設定して削除します1
これらの4つのステップは、自然言語の曖昧な「完了しました」や「取り組んでいます」ではありません。4つの明示的なステータス値です: pending、in_progress、completed、そして削除のためのdeleted。すべてのステータス変更は、モデルが返信テキストで何気なく述べるのではなく、明示的なツール呼び出しを通じて行われます。
ドキュメントはまた、このメカニズムが実際に会話にどう現れるかも説明しています: "In a session that has the task-tracking tools, Claude keeps a written todo list, updating each item's status as it works. You see each change in the message stream as a structured tool call."1(タスク追跡ツールを持つセッションでは、Claudeは書かれたTODOリストを保持し、作業しながら各アイテムのステータスを更新します。各変更はメッセージストリームの中で構造化されたツール呼び出しとして見えます。)この文は重要な区別を明確にしています: 進捗は会話の中に受動的に「反映される」のではなく、それ自体で識別・解析できるツール呼び出しとして能動的に書き込まれます。それが構造化された状態と、文章に散らばった進捗記述との本当の違いです。
チェックポイント: 復旧を最初からやり直す以外のものにする
構造化されたTODOリストを手に入れたら、次の問いは: リスト自体はどこに存在するのでしょうか?それがこのセッションの会話履歴の中にのみ存在するなら、セッションが本当に終了した瞬間(短い中断ではなく、レッスン3の「セッションが終了すると、ウィンドウ内のすべてが消える」という考え方のような完全な終了)、進捗記録もそれと共に消えます。根本的に、APIはステートレスです: "The Messages API is stateless, which means that you always send the full conversational history to the API."2(Messages APIはステートレスです。つまり、常に完全な会話履歴をAPIに送信します。)
それがチェックポイントが解決する問題です: ある時点でのタスクの状態 — どのステップが完了し、どのステップが現在で、どのステップが残っているか — をデータの一部に書き込み、セッションの寿命の外側のどこかに永続化します。チェックポイントはレッスン3の外部メモリと同じ基本メカニズムを使います(ファイルに書き込み、後で読み戻す);違いは、チェックポイントは「覚える価値のある知識」を保存するのではなく、「タスクがどこまで進んだか」を保存することです — 実行を再開するために直接使える種類の状態です。
チェックポイントがあれば、復旧可能性がついに成立します: プロセス再起動後、エージェントは「どこまでやったか」を推測する必要がありません。最新のチェックポイントを読み、「ステップ1と2は完了済み、ステップ3はin_progress」と見て、ステップ1と2をやり直す代わりにステップ3から続けます。
ここでのコスト比較は具体的です。リファクタリングタスクの「型定義を更新する」ステップが冪等(再実行しても同じ結果を生む)なら、ゼロからやり直しても無駄な時間がかかるだけです。しかし、あるステップが「マイグレーション記録をデータベースに挿入する」のような非冪等な操作なら、最初からやり直すと2つの重複した記録を挿入し、データを破損させる可能性さえあります。チェックポイントが節約するのは時間だけではありません — そのような非冪等な操作を誤って再実行するリスクです。
構造化された状態は中断を生き延びられるものにするために存在する
このレッスンの冒頭のシナリオに戻ります: プロセス再起動後、エージェントは最初からやり直すべきでしょうか、中断された場所から再開すべきでしょうか?今や明確に答えられます — 2つのことがなされたかどうかに依存します。タスクの進捗が(会話の文章に散らばるのではなく)構造化された状態として表現されたか、そしてその状態が(この1つのセッションの履歴の中にのみ存在するのではなく)チェックポイントに書き込まれたか?両方が必要です。構造化された状態があってもチェックポイントがなければ、状態はセッション終了時に消えます;チェックポイントがあっても構造化された状態がなければ、チェックポイントに書き込まれたもの自体が曖昧な自然言語であり、それを読み戻しても「タスクがどのステップに到達したか」を確実に知ることはできません。
このレッスンは、進捗をどう表現し保存するかについてでした。次のレッスンは、同じくらい重要な別の問いに向かいます: この保存された状態とメモリは攻撃者の標的になりうるのでしょうか — 攻撃者がチェックポイントやメモリファイルにコンテンツを書き込めるとしたら何が起こるのでしょうか?
まとめ
- タスクの進捗がホストコードによって確実に読み取られるには、構造化された状態にならなければなりません;自然言語の返信に散らばった進捗記述は「今どのステップにいるか」を安定して解析できません
- TODOの完全なライフサイクルは4つのステップです — 作成(pending)、開始(in_progress)、完了(completed)、削除(deleted) — そして各ステップはメッセージストリームで観察できる明示的な構造化されたツール呼び出しを通じて行われます
- 構造化されていることと永続的であることは別のことです: 構造化は「プログラムが読み取れるか」を解決し、チェックポイントは「プロセス再起動やセッション終了後も状態がそこにあるか」を解決します — 両方が必要です
- チェックポイントはある時点でのタスクの状態をセッションの寿命の外側のどこかに書き込み、復旧を「最初からやり直す」ではなく「中断された場所から続ける」に変え、特に非冪等な操作の誤った再実行を回避します
- 構造化された状態は、外部メモリと同様に、永続化されると独自の攻撃対象になります — 次のレッスンのトピックです;保存するほど、保持しなければならない境界が増えます
>> レッスン5: メモリの境界と安全性