0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【2026年6月版】Context Engineering 完全入門(第7回) - TODOツールによる進捗追跡とタスク忘れ防止

0
Last updated at Posted at 2026-07-18

はじめに

本記事は「Context Engineering 完全入門」シリーズの第 7 回です。今回は TODO ツール を取り上げます。

多段タスクをこなすエージェントに共通する課題として「ドリフト(脱線)」があります。最初に立てた計画がツール出力の蓄積によってコンテキストの中盤に埋もれ、エージェントが後半のタスクを忘れてしまう現象です。本記事では、この問題に対処するための TODO ツールの設計と、学術的に裏付けられた「Recitation Pattern」の実装についてご紹介します。

前回:第 6 回 Memory + 自己更新
次回:第 8 回 Skills & AGENTS.md

🗺️ 単独でも読めます:Plan Persistence 問題と Recitation Pattern は本記事だけで理解・実装できます。

📝 本記事は、生成 AI で作成した草案をベースに、筆者が加筆・修正し、技術的な正確性を確認した上で公開しています。

📝 本記事の要点

  • 問題: 多段タスクで「計画が context の中盤に押し流される」 Plan Persistence 問題。エージェントが Step 4 以降を忘れる
  • 解決: Claude Code V2 互換の 4 ツール構成 + Recitation Pattern(3 ラウンドに 1 回、計画を末尾再掲)
  • 実装の要点: TodoStore で永続化、RecitationContextProvider で計画を末尾 attention 領域に再注入(arXiv 2606.22953 の理論的根拠)

急ぐ方は §1 の Plan Persistence 説明§4-4 RecitationContextProvider が実装の核です。

🎯 本記事の対象者

  • 多段タスクで「エージェントが脱線する」経験がある
  • Claude Code が V1 → V2 で TODO を大改修した理由を知りたい
  • 「ドリフトしないエージェント」を実装したい
  • 「Recitation Pattern」の学術的根拠を理解したい
  • TODO ツールと Recitation を組み合わせて Plan Persistence 問題に対処したい

1. ⭐ 原則

TODO ツール設計の方針を整理すると以下の通りです。#1・#2 は本節、#3 は §2 で詳しく解説します。

# 原則 概要 参照項目
1 計画を構造化して保持する タスクを状態管理し、進捗を明示的に追跡する §1
2 進行中(in_progress)は同時 1 件に制限する 並行実行によるドリフトを防止する §1
3 計画を定期的に再掲する(Recitation) Plan Persistence 問題(計画が context から消える)への対策 §2

Memory(第 6 回)との関係:自己状態管理の 2 本目

第 6 回のメモリー機能が 「何を覚えておくか」(知識・規約・進捗の保存)を扱ったのに対し、本記事の TODO は 「何を実行するか」(現在の実行計画の維持)を扱います。両者は競合ではなく 補完関係 にあり、メモリー機能はセッションを跨いだ長期的な状態管理を担うのに対し、TODO はセッション内での短期的な進捗管理を担います。

第 6 回では AGENTS.md / PROGRESS.md / Session Memory / TODO の 4 分類を提示しました。本記事では、その中の TODO に焦点を当て、多段タスクでのドリフト防止に特化した設計を解説します。

問題:エージェントの drift(脱線)

shareAI-lab/learn-claude-code のレッスン文書より:

"(訳) 進捗の明示的な追跡がなければ、エージェントは多段タスクで集中力を失います。ツールの実行結果が会話履歴に蓄積されるにつれ、最初に立てた計画は目立たなくなっていきます。10 ステップのリファクタリングでは、Step 1〜3 までは完了できても、Step 4〜10 がコンテキストの中で埋もれてしまうため、途中でドリフト(脱線)が発生してしまいます。"

TODO ツールが解決するのは「長いツール出力で原計画がコンテキストの中段になってしまいLLMから参照されにくくなる」という Lost-in-the-Middle 問題そのものです。

Plan Persistence:TODO ツールの理論的根拠

2026 年 6 月の研究論文(arXiv 2606.22953「Plan Persistence in LLM Agents」)で、「LLM Agent は計画を context-resident として持ち、コンテキストから消えると計画も消える」 ことが定量的に実証されました:

"(訳) 計画立案直後は、LLM の attention 内で計画信号が 0.453 のピーク値を示しますが、次のアクション実行と結果観察のわずか 1 ステップの間に、その信号強度は 4.1 倍減衰します。"

これが 「Recitation Pattern」(Manus 論文で 2025/7 提唱)の理論的根拠です。

6 つのベストプラクティス

原則 1〜3 を実装する際の具体的なベストプラクティスは以下の通りです。

# ベストプラクティス 概要
1 3+ ステップ・複雑なタスクで自動起動 単純なタスクでの誤起動を防ぐ
2 in_progress は同時 1 つだけ 並行実行によるドリフト防止
3 完了は即時マーク(バッチ更新禁止) 進捗の可視化と正確性
4 3 ラウンド更新なしで Nag リマインダー注入 更新忘れの検知
5 ファイル永続化(Claude Code V2 が解決した課題) セッション跨ぎでの状態保持
6 Recitation:定期的に末尾に再掲する(Plan Persistence 対策) 計画の attention 維持

3 軸評価:TODO 実装の設計軸

TODO 実装を 持続性 × 更新粒度 × 並行制御の 3 軸 で評価すると、各実装の特徴が明確になります。

  • 持続性:セッション終了で消えるか、ファイル永続化するか
  • 更新粒度:全リスト置換か、単一タスク更新か
  • 並行制御:同時 in_progress を許容するか

この 3 軸で主要な実装を評価すると、業務系での適性が判断しやすくなります。詳細は §5 のケーススタディで解説します。

2. 📐 パターン

本節では、TODO ツールを設計するための実装ベースのパターンを扱います。まず §2-1 で VS Code Copilot Chat と Claude Code の TODO 実装(本記事の実装参考元)を確認し、§2-2 で Claude Code V2 互換の 4 ツール構成 + Nag Reminder + Recitation という実装パターン、§2-3 で業務系システムへの適用時の制約を整理します。

2-1. VS Code / Claude Code の TODO 実装(実装参考元)

本記事の Microsoft Agent Framework (MAF) 実装(§4)は、VS Code Copilot Chat と Claude Code V2 の TODO 実装を参考にしています。ここでは、それぞれの仕様を確認します。

VS Code Copilot Chat の manage_todo_list

microsoft/vscode-copilot-chatmanageTodoListTool.tsxわずか 31 行 / 1.42 KB という非常にシンプルな実装です。

要素 内容
操作 read(取得)/ write(完全置換)
TodoItem id + title + description + status
status not-started / in-progress / completed
警告 項目数 < 3 で「小さすぎる」警告

Claude Code の V1 → V2 大改修

"(訳) TypeScript Agent SDK 0.3.142 および Claude Code v2.1.142 以降、セッションでは TodoWrite の代わりに、構造化された Task ツール群(TaskCreateTaskUpdateTaskGetTaskList)が使用されます。"

V2 の利点:

  • ファイル永続化:セッションを跨いで状態保持
  • マルチエージェント共有可能:他プロセスから読める
  • 細かい操作:単一タスクのみ更新可能
  • 後方互換:CLAUDE_CODE_ENABLE_TASKS=0 で V1 互換モードに戻せる

2-2. Claude Code V2 互換の実装パターン

Claude Code V2 の 4 ツール構成をベースに、Nag Reminder と Recitation を組み合わせることで、Plan Persistence 問題に対応する完全な TODO システムを構築します。

4 ツール構成(V2 互換)

ツール 操作対象 用途
TaskCreate 単一タスク 新しいタスクの作成
TaskUpdate 単一タスク ステータス更新(in_progress / completed など)
TaskGet 単一タスク 特定タスクの詳細取得
TaskList 全タスク 全体の進捗確認

VS Code の manage_todo_list(read / write の 2 操作)と比較すると、単一タスクの操作と全体取得を分離することで、トークン削減・並行制御・LLM の精度向上 の 3 つの恩恵が得られます。

Nag Reminder + Recitation の組み合わせ

  • Nag Reminder:3 ラウンド TODO 更新がない場合に「未完了タスクがあります」の警告を末尾注入
  • Recitation:3 ラウンドに 1 回、現在の計画を末尾に再掲(Plan Persistence 対策)

2-3. 業務系システムへの適用時の制約

Claude Code V2 の TODO 実装は個人利用のコーディング支援を前提としているため、業務系システムでは以下の制約を考慮する必要があります。

Claude Code V2 の前提 業務系システムでの制約
ローカルファイル(.agent-todos.json)で永続化 クラウド環境ではファイル永続化が困難、DB / Blob Storage への保存が一般的
単一ユーザー(単一エージェントプロセス)を前提 マルチテナント SaaS ではテナント分離が必須
TODO ファイルを直接変更(ユーザーも編集可) 業務系では変更履歴の監査ログが必要な場合がある
セッション終了後もファイルが残る GDPR / 個人情報保護法対応で TTL や削除ポリシーが必要

業務系での考慮点

業務系で TODO ツールを本番運用する場合、第 6 回で扱ったメモリー機能と同じく 「動的性(エージェントの自由な更新)」を維持しつつ「統制性(監査・テナント分離・削除ポリシー)」を担保する ことが重要です。具体的な永続化戦略の詳細は補章 C「業界研究の業務系翻訳ガイド」で扱います。

3. 💡 実装の方針と設計判断

サンプルコードの実装方針は以下の通りです。

設計判断 1:なぜ 4 つのツール(V2 仕様)に分けるか

VS Code の manage_todo_list(read / write の 2 操作)ではなく、Claude Code V2 の 4 ツール構成を採用します。単一タスクの操作(Create / Update / Get)と全体取得(List)を分離することで、トークン削減・並行制御・LLM の精度向上の 3 つの恩恵を受けられます。

設計判断 2:Update(id, InProgress) で「他の InProgress を pending に戻す」

「同時 in_progress 1 つだけ」ルールを TodoStore.Update 内で強制します。LLM が「複数の in_progress」を作っても、データ層で正しい状態に修正するため、アンチパターンが物理的に発生しません。

設計判断 3:Nag Reminder を AIContextProvider で実装

3 ラウンド未更新で自動的に警告を注入します。ツールとして実装すると LLM が呼ばないと意味がないため、Provider で確実に注入します。

設計判断 4:Recitation を独立した Provider にする

Reminder は「催促」、Recitation は「再掲」と責務が異なります。インターバルも違うため(Reminder は 3 ラウンド、Recitation はより頻繁)、独立した Provider にすることで柔軟に組み合わせられます。

設計判断 5:ファイル永続化を JSON で行う

.agent-todos.json 1 ファイルなのでデバッグが容易で、人が直接編集でき、Git で diff が見えます。ただし業務系では §2-3 で述べた通り、DB や Blob Storage への保存が推奨されます。

4. 💻 実装:C# / Microsoft Agent Framework (MAF)

以下のサンプルコードは、TODO ツールと Recitation Pattern の 概念を理解するための実装例 です。本番の業務系システムでは、ローカルファイルではなく Cosmos DB / DynamoDB などの永続化バックエンドを利用することが推奨されます。業務系での永続化戦略の詳細は補章 C で扱う予定です。

4-1. データ構造

💻 TodoItem — TODOの状態・優先度・日時を表すデータ構造(C#・約25行、クリックで展開)
using System.ComponentModel;
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

namespace ContextEngineering.Todo;

// 🔑 Claude Code V2 互換:cancelled も含む
public enum TodoStatus { Pending, InProgress, Completed, Cancelled }
public enum Priority { High, Medium, Low }

public record TodoItem
{
    public required string Id { get; init; }
    public required string Title { get; init; }
    public string Description { get; init; } = "";
    // 🔑 Status は mutable(Update で変更)
    public TodoStatus Status { get; set; } = TodoStatus.Pending;
    public Priority Priority { get; set; } = Priority.Medium;
    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
    public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}

4-2. 永続化付きストア

実装の要点:

  • Update(id, InProgress) で「他の InProgress を pending に強制ダウングレード」して同時 1 つルールをデータ層で物理的に強制
  • 起動時に ITodoStorage 経由で永続化データを読み込み、操作のたびに保存
  • AttentionCheck() で 3 ラウンド未更新を検知(Nag Reminder)
  • GetCurrentPlanRecitation() で pending/in_progress のみを priority 順に整形(Recitation 用)
💻 ITodoStorage — TODO永続化バックエンドのサンプル実装(C#・約6行、クリックで展開)

永続化の抽象化(インターフェース定義):

// 🔑 サンプルでは JSON ファイル実装、本番では DB / Blob Storage 等に置換
public interface ITodoStorage
{
    Task<IReadOnlyList<TodoItem>> LoadAsync(CancellationToken ct = default);
    Task SaveAsync(IReadOnlyList<TodoItem> items, CancellationToken ct = default);
}
💻 TodoStore — 永続化・単一InProgress制御・Recitationのサンプル実装(C#・約110行、クリックで展開)
public class TodoStore
{
    private readonly ITodoStorage _storage;
    private readonly List<TodoItem> _items = new();
    private int _roundsSinceUpdate;

    private TodoStore(ITodoStorage storage) => _storage = storage;

    /// <summary>起動時に永続化データをロードして <see cref="TodoStore"/> を構築する。</summary>
    public static async Task<TodoStore> OpenAsync(
        ITodoStorage storage, CancellationToken cancellationToken = default)
    {
        var store = new TodoStore(storage);
        var loaded = await storage.LoadAsync(cancellationToken);
        store._items.AddRange(loaded);
        return store;
    }

    public async Task<TodoItem> CreateAsync(
        string title, string description = "", Priority priority = Priority.Medium,
        CancellationToken cancellationToken = default)
    {
        var item = new TodoItem
        {
            Id = Guid.NewGuid().ToString("N")[..8],
            Title = title,
            Description = description,
            Priority = priority,
        };
        _items.Add(item);
        // 🔑 操作したらカウンタリセット(Reminder の起点)
        _roundsSinceUpdate = 0;
        await PersistAsync(cancellationToken);
        return item;
    }

    public async Task<TodoItem?> UpdateAsync(
        string id, TodoStatus status, CancellationToken cancellationToken = default)
    {
        // 🔑 ベストプラクティス 2: 同時 in_progress は 1 つだけ(データ層で物理的に強制)
        if (status == TodoStatus.InProgress)
        {
            foreach (var other in _items.Where(o => o.Id != id
                                                    && o.Status == TodoStatus.InProgress))
            {
                // 🔑 他の in_progress を pending に強制ダウングレード
                // → LLM が誤って複数 in_progress にしても自動修復
                other.Status = TodoStatus.Pending;
                other.UpdatedAt = DateTime.UtcNow;
            }
        }

        var target = _items.FirstOrDefault(i => i.Id == id);
        if (target == null) return null;

        target.Status = status;
        target.UpdatedAt = DateTime.UtcNow;
        _roundsSinceUpdate = 0;
        await PersistAsync(cancellationToken);
        return target;
    }

    public TodoItem? Get(string id) => _items.FirstOrDefault(i => i.Id == id);
    public IReadOnlyList<TodoItem> ListAll() => _items.AsReadOnly();

    /// <summary>
    /// Nag Reminder:3 ラウンド更新がなければ警告メッセージを返す。
    /// </summary>
    public string? AttentionCheck()
    {
        // 🔑 LLM 呼び出しごとにカウントアップ
        _roundsSinceUpdate++;
        if (_roundsSinceUpdate < 3) return null;

        var pending = _items.Count(i => i.Status != TodoStatus.Completed
                                         && i.Status != TodoStatus.Cancelled);
        return pending > 0
            ? $"[Reminder] You have {pending} incomplete tasks."
            : null;
    }

    /// <summary>
    /// Recitation:Plan Persistence 対策の中核。
    /// </summary>
    public string GetCurrentPlanRecitation()
    {
        // 🔑 pending / in_progress のみを priority 順で
        // → completed タスクは再掲不要
        var pending = _items
            .Where(i => i.Status == TodoStatus.Pending || i.Status == TodoStatus.InProgress)
            .OrderBy(i => i.Status == TodoStatus.InProgress ? 0 : 1)
            .ThenBy(i => i.Priority)
            .ToList();

        if (pending.Count == 0) return "";

        var sb = new System.Text.StringBuilder();
        sb.AppendLine("# Current Plan (Recitation for context maintenance)");
        foreach (var item in pending)
        {
            // 🔑 in_progress には → マーカー
            // → LLM が「今やっているタスク」を視覚的に認識
            var marker = item.Status == TodoStatus.InProgress ? "→" : " ";
            sb.AppendLine($"{marker} [{item.Status}] {item.Title}");
        }
        return sb.ToString();
    }

    private Task PersistAsync(CancellationToken cancellationToken)
        => _storage.SaveAsync(_items.AsReadOnly(), cancellationToken);
}

設計判断の核心:同時 1 つルールの強制

LLM が複数の InProgress を作っても、データ層で自動修復します:

💻 InProgressを同時1件に制限する更新ロジック(C#・約15行、クリックで展開)
public async Task<TodoItem?> UpdateAsync(string id, TodoStatus status, CancellationToken cancellationToken = default)
{
    // 🔑 ベストプラクティス 2: 同時 in_progress は 1 つだけ(物理的に強制)
    if (status == TodoStatus.InProgress)
    {
        foreach (var other in _items.Where(o => o.Id != id
                                                && o.Status == TodoStatus.InProgress))
        {
            other.Status = TodoStatus.Pending;  // ← 強制ダウングレード
            other.UpdatedAt = DateTime.UtcNow;
        }
    }
    // ... 続く
}

4-3. TODO ツール(Claude Code V2 互換 4 ツール)

💻 TodoTools — Claude Code V2互換4ツールのサンプル実装(C#・約85行、クリックで展開)
public class TodoTools
{
    private readonly TodoStore _store;

    // 🔑 TodoStatus/Priority を数値ではなく文字列でシリアライズする
    // → LLM にとって "0" より "Pending" の方が誤読なく理解できる(第 2 回のメタデータ完全性と同じ考え方)
    private static readonly JsonSerializerOptions Options = new()
    {
        Converters = { new JsonStringEnumConverter() },
    };

    public TodoTools(TodoStore store) => _store = store;

    [Description(
        // 🔑 「3+ steps の複雑なタスクで使え」と明示
        // → 簡単なタスクで誤起動を防ぐ
        // 🔑 「N ステップ」と依頼された場合は、1 つの親タスクにまとめず
        // このツールを N 回呼んで各ステップを個別タスクとして登録することを明示
        // → TaskList/TaskUpdate で各ステップの進捗を個別に追跡できるようにするため
        "Create a new task. Use for complex multi-step tasks (3+ steps). " +
        "IMPORTANT: If the user asks for N steps/subtasks, call this tool N times " +
        "(once per individual step) to create N separate tasks, rather than creating " +
        "a single parent task with the steps only described in the description field. " +
        "This allows each step's progress to be tracked independently via TaskUpdate/TaskList."
    )]
    public async Task<string> TaskCreateAsync(
        [Description("Task title")] string title,
        [Description("Description")] string description = "",
        [Description("Priority")] Priority priority = Priority.Medium)
        => JsonSerializer.Serialize(await _store.CreateAsync(title, description, priority), Options);

    [Description(
        "Update task status. Mark as InProgress when starting, " +
        "Completed IMMEDIATELY after finishing. " +
        // 🔑 強調:1 つだけルール
        "IMPORTANT: Only ONE task should be InProgress at a time."
    )]
    public async Task<string> TaskUpdateAsync(
        [Description("Task ID")] string id,
        [Description("New status")] TodoStatus status)
    {
        var item = await _store.UpdateAsync(id, status);
        return item != null
            ? JsonSerializer.Serialize(item, Options)
            : JsonSerializer.Serialize(new { error = $"Task {id} not found" });
    }

    [Description("Get details of a single task by ID.")]
    public string TaskGet([Description("Task ID")] string id)
    {
        var item = _store.Get(id);
        return item != null
            ? JsonSerializer.Serialize(item, Options)
            : JsonSerializer.Serialize(new { error = $"Task {id} not found" });
    }

    [Description("List all tasks with their current status.")]
    public string TaskList()
    {
        var items = _store.ListAll();
        var counts = Enum.GetValues<TodoStatus>()
            .ToDictionary(s => s.ToString(), s => items.Count(i => i.Status == s));

        var result = new
        {
            items,
            counts,
            // 🔑 警告:タスクが少なすぎる場合
            // → 「直接やった方が早い」というシグナル
            warning = items.Count is > 0 and < 3
                ? "Few tasks (< 3). Consider doing trivial tasks directly."
                : null,
        };

        return JsonSerializer.Serialize(result,
            new JsonSerializerOptions { WriteIndented = true, Converters = { new JsonStringEnumConverter() } });
    }
}

4-4. Recitation Context Provider(Plan Persistence 対策)

💻 RecitationContextProvider / TodoReminderProvider — 計画再掲と未更新通知のサンプル実装(C#・約65行、クリックで展開)
/// <summary>
/// Plan Persistence 対策:定期的に計画を末尾に再掲。
/// arXiv 2606.22953 の知見を MAF に実装。
/// </summary>
public class RecitationContextProvider : AIContextProvider
{
    private readonly TodoStore _store;
    private int _turnsSinceRecitation;
    private readonly int _recitationInterval;

    public RecitationContextProvider(TodoStore store, int recitationInterval = 3)
        : base(null, null)
    {
        _store = store;
        _recitationInterval = recitationInterval;
    }

    protected override ValueTask<AIContext> ProvideAIContextAsync(
        InvokingContext context, CancellationToken cancellationToken = default)
    {
        _turnsSinceRecitation++;

        // 🔑 N ラウンドに 1 回だけ再掲
        // → 毎回再掲するとトークン浪費
        if (_turnsSinceRecitation < _recitationInterval)
            return new ValueTask<AIContext>(new AIContext());

        var recitation = _store.GetCurrentPlanRecitation();
        if (string.IsNullOrEmpty(recitation))
            return new ValueTask<AIContext>(new AIContext());

        // 🔑 カウンタリセット
        _turnsSinceRecitation = 0;

        // 🔑 system role で「末尾に」再掲
        // → LLM の attention は末尾に集中するので、計画維持が効く
        return new ValueTask<AIContext>(new AIContext
        {
            Messages = [new ChatMessage(ChatRole.System, recitation)],
        });
    }
}

/// <summary>
/// Nag Reminder:3 ラウンド未更新で警告。
/// </summary>
public class TodoReminderProvider : AIContextProvider
{
    private readonly TodoStore _store;
    public TodoReminderProvider(TodoStore store) : base(null, null) => _store = store;

    protected override ValueTask<AIContext> ProvideAIContextAsync(
        InvokingContext context, CancellationToken cancellationToken = default)
    {
        var reminder = _store.AttentionCheck();
        if (reminder == null) return new ValueTask<AIContext>(new AIContext());

        return new ValueTask<AIContext>(new AIContext
        {
            Messages = [new ChatMessage(ChatRole.System, reminder)],
        });
    }
}

4-5. エージェント組み立て

TodoStore の読み込みは非同期 I/O(ITodoStorage.LoadAsync)になったため、コンストラクタではなく await TodoStore.OpenAsync(storage) で事前に構築してから渡す設計にしています。

💻 Example.BuildAgentWithTodo — TODO・Reminder・Recitation対応エージェントのサンプル実装(C#・約45行、クリックで展開)
public static class Example
{
    public static AIAgent BuildAgentWithTodo(IChatClient chatClient, TodoStore store)
    {
        var todoTools = new TodoTools(store);

        // 🔑 LLM に対する操作指示
        // → ワークフローを明確化することで誤用を防ぐ
        var instructions =
            "Agent that manages complex multi-step tasks.\n\n" +
            "WORKFLOW:\n" +
            "1. For complex tasks (3+ steps), call TaskCreate for each subtask.\n" +
            "2. Call TaskUpdate(id, InProgress) BEFORE starting work.\n" +
            "3. Call TaskUpdate(id, Completed) IMMEDIATELY after finishing.\n" +
            "4. Only ONE task should be InProgress at a time.\n" +
            "5. Use TaskList to verify progress periodically.";

        return chatClient.AsAIAgent(new ChatClientAgentOptions
        {
            ChatOptions = new ChatOptions
            {
                Instructions = instructions,
                // 🔑 Tools は ChatOptions に登録する
                Tools = new List<AITool>
                {
                    AIFunctionFactory.Create(todoTools.TaskCreateAsync),
                    AIFunctionFactory.Create(todoTools.TaskUpdateAsync),
                    AIFunctionFactory.Create(todoTools.TaskGet),
                    AIFunctionFactory.Create(todoTools.TaskList),
                },
            },
            AIContextProviders =
            [
                // 🔑 2 つの Provider を組み合わせる
                new TodoReminderProvider(store),       // 3 ラウンドで Reminder
                new RecitationContextProvider(store),  // Plan Persistence 対策
            ],
        });
    }
}

5. 📊 ケーススタディ

主要ツールの TODO 実装比較

主要ツールの TODO 実装を、§1 で定義した 持続性・更新粒度・並行制御の 3 軸 で比較すると、ツールごとの明確な差が現れます。

ツール ツール数 持続性 更新粒度 並行制御 Recitation 業務系適性
VS Code Copilot Chat(manage_todo_list) 1 セッション内 完全置換 緩い(複数 in_progress 可) 未対応
Codex CLI 0 ×
Claude Code V2(Task 4 ツール) 4 ファイル永続化 個別更新可能 厳密(1 つだけ) 一部対応
MAF TodoStore + Recitation(本記事) 4 + 2 Provider ファイル永続化(業務系では DB 推奨) 個別更新可能 厳密(データ層で強制) 完全対応

教訓:

  • VS Code Copilot Chat はシンプルさを重視した設計で、31 行という最小実装ながら基本的な TODO 管理は可能です。ただし完全置換方式のため、並行制御が緩く、大規模タスクでのドリフト防止には不十分です
  • Codex CLI は TODO ツールを持たないため、多段タスクの進捗管理は LLM の自律性に依存します。長時間タスクや複雑なフロー制御には不向きです
  • Claude Code V2 は運用性重視の設計で、4 ツール構成 + ファイル永続化により、V1 の課題(インメモリ、完全置換)を解決しました。ただし Recitation は「一部対応」レベルで、Plan Persistence 問題の完全解決には追加実装が必要です
  • MAF TodoStore + Recitation(本記事) は Claude Code V2 の 4 ツール構成を踏襲しつつ、Recitation Pattern を完全実装 している点が特徴です。データ層での並行制御強制(Update メソッド内での自動ダウングレード)と、RecitationContextProvider による定期的な計画再掲により、Plan Persistence 問題を安定的に解決します

つまり、「TODO 管理と Recitation を組み合わせて初めて Plan Persistence 問題を安定的に解決できる」 ということが、本記事の核心的な主張です。個人利用なら Claude Code V2 レベルで十分ですが、長時間・多段タスクを扱う業務系エージェントでは、Recitation の完全実装が長期運用の鍵となります。

自動起動の判断条件

TODO ツールを いつ使うべきか の判断は、タスクの複雑度に応じて決定します。過剰な TODO 使用は逆にオーバーヘッドを生むため、以下の判断基準を明確にすることが重要です。

使うべき場面:

  • 3+ ステップを要する複雑なマルチステップタスク
  • ユーザーが複数のタスクを列挙した
  • 慎重な計画が必要な non-trivial なタスク

使うべきでない場面:

  • 単一の簡単なタスク
  • 3 ステップ未満の trivial なタスク
  • 純粋な会話・情報提供

教訓:

TODO ツールは「使えば使うほど良い」ものではなく、タスクの複雑度に応じた選択的利用 が重要です。単純なタスクで TODO を使うと、TaskCreateTaskUpdate(InProgress) → 作業 → TaskUpdate(Completed) という一連のオーバーヘッドが逆に効率を下げます。第 5 回のサブエージェント設計と同じく、適切なタスクに適切なツールを使う判断 が業務系での長期運用の鍵となります。

6. ⚠️ アンチパターン

本記事で扱った TODO ツール設計の要点を、実装時のセルフレビュー用チェックリスト(アンチパターン)としてまとめました。
実装時は本表を「やってはいけないことリスト」としてご参照ください。特に #3・#5・#6 は長時間タスクでのドリフト防止と Plan Persistence 問題への対応に直結する重要項目です。

# アンチパターン 問題点
1 すべてのタスクで TODO を使う 過剰なオーバーヘッドが発生する
2 完了をバッチで更新する drift を誘発する
3 複数 in_progress を放置 並行実行でドリフトが発生しやすくなる
4 失敗中なのに completed にする 後から問題に気づけなくなる
5 Persistent な保存をしない セッション間で進捗が消えてゼロから計画立て直しになる
6 Recitation を実装しない Plan Persistence 問題で計画が context から消える
7 Reminder を毎ラウンド入れる トークン浪費と「警告慣れ」で LLM が無視するようになる

おわりに

本記事では、多段タスクでのドリフト防止のための TODO ツールと、Plan Persistence 問題に対応する Recitation Pattern の実装をご紹介しました。特に 持続性・更新粒度・並行制御の 3 軸 で TODO 実装を評価し、「3 ラウンドに 1 回、計画を末尾に再掲する」という Recitation の実装が、学術論文(arXiv 2606.22953)でも裏付けられた効果的な手法であることを解説しました。

第 6 回のメモリー機能と本記事の TODO ツールは、エージェントの自己状態管理における 補完関係 にあります。Memory がセッションを跨いだ長期的な状態(知識・規約・進捗)を扱うのに対し、TODO はセッション内での短期的な実行計画を扱います。両者を組み合わせることで、長時間・複数セッションにわたるタスクでも、エージェントが学習と作業を継続できる設計が可能になります。

次回は「Skills & AGENTS.md」を解説します。ここまでのランタイム制御・自己状態管理の仕組みを、マークダウンファイルとして静的に宣言する設計パターンについてご紹介します。

参考文献

VS Code / Claude Code 関連

  1. microsoft/vscode-copilot-chat manageTodoListTool.tsx
  2. Claude Code Docs「Todo Lists (Agent SDK)」
  3. Claude Code Docs「Tools reference」
  4. shareAI-lab/learn-claude-code「s05: TodoWrite」

コミュニティ実装

  1. GitHub「tintinweb/pi-manage-todo-list」
  2. GitHub「digitarald/vscode-agent-todos」

学術論文

  1. Mehta, A., & Datta, A. (Snowflake AI Research, 2026). "Plans Don't Persist: Why Context Management Is Load Bearing for LLM Agents." arXiv:2606.22953

業界研究

  1. Manus AI (2025/7/18). "Context Engineering for AI Agents: Lessons from Building Manus" — Recitation Pattern (Recite Objectives)

Appendix. 本シリーズの全記事(クロスリファレンス)

  1. 第 1 回(総論)
  2. 第 2 回(ツール設計)
  3. 第 3 回(結果ハンドリング)
  4. 第 4 回(履歴圧縮)
  5. 第 5 回(サブエージェント)
  6. 第 6 回(Memory + 自己更新)
  7. 第 7 回(TODO ツール、本記事)
  8. 第 8 回(Skills & AGENTS.md)
  9. 補章 A(VS Code Copilot Chat 1.109 機能調査)
  10. 補章 B(Claude Code Task Tools V2 移行の教訓)
  11. 補章 C(業界研究の業務系翻訳ガイド)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?