7.0 KiB
7.0 KiB
セッション内 Task ツール
背景
長めのタスクを LLM に進めさせる際、Claude Code / OpenCode が備える「セッション内 TODO / Task リスト」相当の機構が無いため、エージェントが自分の作業計画を構造化された形で保持・更新できない。Reasoning や text 出力の中で擬似的に TODO を書くことはできるが、
- compact を跨ぐと完全に消える
- ツール結果ではないため、状態の作成・更新・削除の規約が決まらず、意図と乖離した「やったつもり」を起こしやすい
- 安定した識別子が無いため、後から特定 entry を更新できない
この用途のために、セッション内に正規化された Task リストを保持し、compact を跨いで保存される専用ツールを導入する。
「LLM がツールを使わなくなった場合のサボり防止」を目的とした注意機構(<system-reminder> の自動注入)は本チケットの範囲外。tickets/session-todo-reminder.md で別途扱う。
方針
- 保存先は
tools層の session-lifetime 状態。Trackerと同じ生存スコープでPodが所有。Arc<Mutex<TaskStore>>ベースの store を tool に注入する - Task は差分操作。全置換ではなく
TaskCreate/TaskUpdateによって TaskStore を逐次更新する - 永続化は専用レーンを持たない。
tool_call.argumentsがセッションログに既に乗っているため、resume 時には履歴 replay の中でTaskCreate/TaskUpdateを順に再適用すれば状態が復元される
Task データモデル
各 Task entry は以下の4フィールドを持つ。
taskid: store が自動採番する整数 ID。TaskCreateごとに単調増加するstatus:pending | inprogress | completed | deletedsubject: 1行程度の短い件名description: 詳細説明
状態遷移の通常ルートは pending -> inprogress -> completed とする。完了ではなく一覧から取り下げたい場合は、物理削除ではなく TaskUpdate で status = deleted を設定する。
要件
TaskCreate ツール
- 入力は
subject/descriptionのみ - TaskStore の末尾に新規 entry を push する
taskidは TaskStore が自動で割り当てる。LLM は指定できない- 初期
statusはpending - 戻り値は作成された Task と、更新後の TaskStore スナップショット概要を summary に含める
TaskList ツール
- 入力なし
- 現在の TaskStore スナップショットを返す
taskid/status/subject/descriptionを含めるcompleted/deletedも含め、store に残っている Task をすべて列挙する
TaskGet ツール
- 入力は
taskid - 指定された Task を1件返す
- 存在しない
taskidはエラーにする
TaskUpdate ツール
- 入力は
taskidと、更新するstatus/subject/descriptionのいずれか - 少なくとも1フィールドは更新指定が必要
taskidは変更できないstatusはpending | inprogress | completed | deletedのいずれか- 存在しない
taskidはエラーにする - 削除は
status = deletedへの更新として表現し、entry は store から取り除かない - 戻り値は更新された Task と、更新後の TaskStore スナップショット概要を summary に含める
Resume 時の復元
Pod::resume/ restore の履歴 replay 中にTaskCreate/TaskUpdateのtool_call.argumentsを観測したら、TaskStore に順に再適用するTaskCreateは replay 時も自動採番を行う。ログ上の call 順序が保持されるため、通常の replay では元のtaskidが再現されるTaskUpdateは replay 済みの TaskStore に対して適用する- 専用 LogEntry / Persistence 型は追加しない(
Trackerと同じ方針) tool_call.argumentsのフォーマットが現在の schema と乖離した場合(旧バージョンのログ)は、その call を無視してよい
Compact 跨ぎ
- compact 起動時、Pod は現在の TaskStore 全スナップショットを compact worker context に渡す
- compact worker は summary を書く際、TaskStore を参照できる。未完了 Task を summary 文に取り込める情報源として参照する(強制ではない)
- compact worker に Task 編集権限は与えない(作成・更新・削除はしない)
- compact 後の新セッションでも TaskStore は空にせず、compact 前の TaskStore 全件を維持する。
completed/deletedも含めて保持する - compact 後の新セッション開始時、Pod は
mark_read_requiredと同じ system message 注入レーンに TaskStore 全スナップショットを1メッセージとして注入する - compact 完了後、Pod は新セッションの継続前に必ず
TaskListを発火させ、LLM が compact 後の TaskStore 状態を tool result として再確認できるようにする
完了条件
TaskCreate/TaskList/TaskGet/TaskUpdateが builtin tool として登録され、Pod で利用できる- LLM が
TaskCreateを呼ぶと TaskStore に pending Task が末尾追加され、自動採番されたtaskidが返る - LLM が
TaskUpdateを呼ぶと既存 Task のstatus/subject/descriptionが更新される TaskList/TaskGetで TaskStore の状態を確認できる- セッションを resume すると、履歴中の
TaskCreate/TaskUpdatereplay により TaskStore が復元される - compact を跨いでも、TaskStore 全件が維持され、新セッション冒頭の system message と
TaskList発火により再提示される - 単体テストで TaskCreate / TaskUpdate / TaskList / TaskGet の挙動、replay 復元がカバーされる
範囲外
- 注意機構(
<system-reminder>の自動注入による「サボり防止」)→tickets/session-todo-reminder.md - Task の階層・優先度・タグ
- TUI / GUI での Task 状態の可視化(ツール呼び出しのイベントは既に流れているので、クライアント側で表示するかは別軸)
- Task の永続化を専用 LogEntry に分離する設計(現方針は tool_call replay と compact 時 snapshot 維持で復元、追加レーン不要)
- 複数 Pod 間で Task を共有する仕組み
参照
- 設計指針:
CLAUDE.md(最小の構造化 / 概念の追加は不在が問題になってから) - 参考実装: Claude Code の TodoWrite、OpenCode の todo tool
- 関連:
crates/tools/src/tracker.rs(session-lifetime 状態の前例)、crates/pod/src/compact/worker.rs(auto-injection レーン) - 後続:
tickets/session-todo-reminder.md(注意機構)
Review
- 状態: Approve (再レビュー済み、blocking 解消・non-blocking 対応反映)
- レビュー詳細: ./session-todo.review.md
- 日付: 2026-05-03