* feat: s01-s14 docs quality overhaul — tool pipeline, single-agent, knowledge & resilience Rewrite code.py and README (zh/en/ja) for s01-s14, each chapter building incrementally on the previous. Key fixes across chapters: - s01-s04: agent loop, tool dispatch, permission pipeline, hooks - s05-s08: todo write, subagent, skill loading, context compact - s09-s11: memory system, system prompt assembly, error recovery - s12-s14: task graph, background tasks, cron scheduler All chapters CC source-verified. Code inherits fixes forward (PROMPT_SECTIONS, json.dumps cache, real-state context, can_start dep protection, etc.). * feat: s15-s19 docs quality overhaul — multi-agent platform: teams, protocols, autonomy, worktree, MCP tools Rewrite code.py and README (zh/en/ja) for s15-s19, the multi-agent platform chapters. Each chapter inherits all previous fixes and adds one mechanism: - s15: agent teams (TeamCreate, teammate threads, shared task list) - s16: team protocols (plan approval, shutdown handshake, consume_inbox) - s17: autonomous agents (idle polling, auto-claim, consume_lead_inbox) - s18: worktree isolation (git worktree, bind_task, cwd switching, safety) - s19: MCP tools (MCPClient, normalize_mcp_name, assemble_tool_pool, no cache) All appendix source code references verified against CC source. Config priority corrected: claude.ai < plugin < user < project < local. * fix: 5 regressions across s05-s19 — glob safety, todo validation, memory extraction, protocol types, dep crash - s05-s09: glob results now filter with is_relative_to(WORKDIR) (inherited from s02) - s06-s08: todo_write validates content/status required fields (inherited from s05) - s09: extract_memories uses pre-compression snapshot instead of compacted messages - s16: submit_plan docstring clarifies protocol-only (not code-level gate) - s17-s19: match_response restores type mismatch validation (from s16) - s17-s19: claim_task deps list handles missing dep files without crashing * fix: s12 Todo V2 logic reversal, s14/s15 cron range validation, s18/s19 worktree name validation - s12 README (zh/en/ja): fix Todo V2 direction — interactive defaults to Task, non-interactive/SDK defaults to TodoWrite. Fix env var name to CLAUDE_CODE_ENABLE_TASKS (not TODO_V2). - s14/s15: add _validate_cron_field with per-field range checks (minute 0-59, hour 0-23, dom 1-31, month 1-12, dow 0-6), step > 0, range lo <= hi. Replace old try/except validation that only caught exceptions. - s18/s19: add validate_worktree_name() to remove_worktree and keep_worktree, not just create_worktree. * fix: align s16-s19 teaching tool consistency * fix pr265 chapter diagrams * Add comprehensive s20 harness chapter * Fix chapter smoke test regressions * Clarify README tutorial track transition --------- Co-authored-by: Haoran <bill-billion@outlook.com>
14 KiB
s12: Task System — 大きな目標を小さなタスクに分割
s01 → ... → s10 → s11 → s12 → s13 → s14 → ... → s20
"大きな目標を小さなタスクに分け、順序付け、永続化" — ファイル永続化タスクグラフ、マルチ Agent 協調の基盤。
Harness 層: タスク — 永続化された目標、復旧可能な進捗。
課題
Agent がプロジェクトを受けた:データベース構築、API 実装、テスト追加。s05 の TodoWrite でリストを作り、まず API を書き始め、途中でデータベーステーブルがないことに気づいて戻る。テスト追加時に API インターフェースのシグネチャがまた変わっている...
屋根を先に建てて基礎を後から打つことはできない。タスクには順序がある。タスクの依存関係は有向非巡回グラフ(DAG)を形成すべき;教学版は blockedBy チェックのみをデモし、循環検出は実装していない。
s05 の TodoWrite はリスト。依存関係も永続化もなく、会話が終わればリストも消える。必要なのはタスクシステム:各タスクは JSON ファイル、タスク間に blockedBy 依存関係、ディスク上でセッションをまたいで永続化。
ソリューション
教学版は基本 agent loop を維持し、タスクシステムに集中するため S11 の完全なエラーリカバリ(RecoveryState、バックオフ、エスカレーション、reactive compact、フォールバックモデル)を省略。追加:5 つの新規タスクツール + .tasks/ ディレクトリによる永続化 + blockedBy 依存チェック。タスクシステムとエラーリカバリは独立したレイヤー:CC ソースコードでは utils/tasks.ts は CRUD のみ、query.ts の with_retry/RecoveryState がエラーリカバリを担当し、互いに非結合。
TodoWrite vs Task System:
| TodoWrite (s05) | Task System (s12) | |
|---|---|---|
| ストレージ | メモリ内リスト | .tasks/ JSON ファイル |
| 依存関係 | なし | blockedBy 依存グラフ |
| 永続性 | 会話終了で消失 | セッション横断 |
| マルチ Agent | なし | owner フィールド |
| ステータス | checked / unchecked | pending → in_progress → completed |
仕組み
Task: データ構造
各タスクは JSON ファイル、.tasks/ ディレクトリに保存:
@dataclass
class Task:
id: str
subject: str
description: str
status: str # pending | in_progress | completed
owner: str | None # Agent 名(マルチ Agent シナリオ)
blockedBy: list[str] # 依存タスク ID のリスト
ID は timestamp + random hex で生成、シンプルだが十分。CC は順次 ID + highwatermark ファイルで ID 再利用を防止する、より厳密な設計。
create_task: タスク作成
def create_task(subject: str, description: str = "",
blockedBy: list[str] | None = None) -> Task:
task = Task(
id=f"task_{int(time.time())}_{random_hex(4)}",
subject=subject, description=description,
status="pending", owner=None,
blockedBy=blockedBy or [],
)
save_task(task)
return task
作成時に自動的に save_task で .tasks/{id}.json に書き込み。blockedBy で依存を宣言、例えば "API を書く" の blockedBy は ["task_schema"]。
can_start: 依存チェック
タスクは blockedBy がすべて completed になってからでないと開始できない:
def can_start(task_id: str) -> bool:
task = load_task(task_id)
for dep_id in task.blockedBy:
if not _task_path(dep_id).exists():
return False # missing dependency = blocked
dep = load_task(dep_id)
if dep.status != "completed":
return False
return True
can_start は claim_task の事前チェック:blockedBy に一つでも completed でないものがあれば、認識不可。存在しない依存は blocked として扱い、誤った ID 参照時のクラッシュを防ぐ。
claim_task: タスク認識
Agent がタスクに取り掛かる時、claim_task を呼び出し:owner を設定、ステータスを pending → in_progress に変更。owner フィールドは誰が作業中かを記録し、マルチ Agent シナリオで重複認識を防止:
def claim_task(task_id: str, owner: str = "agent") -> str:
task = load_task(task_id)
if task.status != "pending":
return f"Task {task_id} is {task.status}, cannot claim"
if not can_start(task_id):
deps = [d for d in task.blockedBy
if load_task(d).status != "completed"]
return f"Blocked by: {deps}"
task.owner = owner
task.status = "in_progress"
save_task(task)
return f"Claimed {task_id} ({task.subject})"
タスクが既に他者に認識されている(status != "pending")、または依存が未完了(can_start が False)の場合、認識を拒否。
complete_task: 完了とアンロック
タスク完了後、completed に設定。同時に他の全タスクを走査し、直前にアンロックされた下流タスクを特定:
def complete_task(task_id: str) -> str:
task = load_task(task_id)
task.status = "completed"
save_task(task)
# アンロックされた下流タスクを検索
unblocked = [t.subject for t in list_tasks()
if t.status == "pending" and t.blockedBy
and can_start(t.id)]
msg = f"Completed {task_id} ({task.subject})"
if unblocked:
msg += f"\nUnblocked: {', '.join(unblocked)}"
return msg
"schema" 完了後、"endpoints" と "docs" の can_start が True を返し、開始可能になる。
get_task: 完全な詳細を確認
list_tasks は 1 行サマリのみ表示。get_task は description と依存関係の詳細を含む完全なタスク JSON を返す。セッションをまたいで復旧する際、Agent は完全な説明を読んで作業を継続する必要がある:
def get_task(task_id: str) -> str:
task = load_task(task_id)
return json.dumps(asdict(task), indent=2)
状態マシン: 2 つのアクション、3 つの状態
pending ──claim──→ in_progress ──complete──→ completed
ここで claim / complete はアクション、pending / in_progress / completed は状態:
- claim_task:
pending→in_progress。owner を設定し、作業を開始。 - complete_task:
in_progress→completed。タスクを完了済みにし、下流をアンロック。
CC には in_progress → pending の release パスがない。teammate が終了または shutdown した場合、CC は未完了タスクの owner をクリアし、status を pending にリセットし、他の agent が再認識できるようにする。教学版はこの復旧パスを省略。
組み合わせて実行
# 依存関係のあるタスクを作成
schema = create_task("setup database schema")
endpoints = create_task("create API endpoints", blockedBy=[schema.id])
tests = create_task("write tests", blockedBy=[endpoints.id])
docs = create_task("write docs", blockedBy=[schema.id])
# Agent が最初に実行可能なタスクを認識
claim_task(schema.id) # ✓ Claimed(依存なし)
complete_task(schema.id) # ✓ Completed → endpoints, docs をアンロック
claim_task(endpoints.id) # ✓ Claimed(schema 完了済み)
complete_task(endpoints.id) # ✓ Completed → tests をアンロック
claim_task(docs.id) # ✓ Claimed(schema 完了済み)
complete_task(docs.id) # ✓ Completed
claim_task(tests.id) # ✓ Claimed(endpoints 完了済み)
complete_task(tests.id) # ✓ Completed
各 create_task が JSON ファイルを書き込み、各 claim_task / complete_task がファイルを更新。セッションをまたいでも .tasks/ ディレクトリが残り、Agent はファイルを読んで進捗を復旧。
s11 からの変更
| コンポーネント | 変更前 (s11) | 変更後 (s12) |
|---|---|---|
| タスク管理 | なし | Task dataclass + 5 ツール |
| 新規型 | — | Task(id, subject, description, status, owner, blockedBy) |
| ストレージ | 永続化なし | .tasks/{id}.json セッション横断 |
| 依存関係 | なし | blockedBy グラフ + can_start チェック |
| ツール | bash, read_file, write_file (3) | + create_task, list_tasks, get_task, claim_task, complete_task (8) |
| ライフサイクル | — | pending → in_progress → completed(release ロールバックなし) |
試してみる
cd learn-claude-code
python s12_task_system/code.py
以下のプロンプトを試してください:
Create tasks: setup database schema, create API endpoints (depends on schema), write tests (depends on endpoints), write docs (depends on schema)List all tasks and their statusesClaim the first unblocked task and complete itList tasks again — which ones are now unblocked?
観察ポイント:.tasks/ ディレクトリに JSON ファイルが生成されているか?タスク完了後、ブロックされていたタスクがアンロックされているか?
次の章
タスクグラフができた。しかし、一部のタスクは長時間かかる — 全テスト実行やサーバーデプロイなど。Agent は LLM をトークン課金で呼び出しており、遅い操作を待つ余裕はない。
s13 Background Tasks → 遅い操作はバックグラウンドへ。Agent は他のタスクの処理を続け、バックグラウンドの完了を通知で受け取る。
CC ソースコード深掘り
以下は CC ソースコード
utils/tasks.ts(862 行)、tools/TaskCreateTool/TaskCreateTool.ts(138 行)、tools/TaskUpdateTool/TaskUpdateTool.ts(406 行)、tools/TaskGetTool/TaskGetTool.ts(128 行)、tools/TaskListTool/TaskListTool.ts(116 行)、hooks/useTaskListWatcher.ts(221 行)の完全分析に基づく。
一、TaskRecord の完全フィールド
チュートリアルでは id、subject、status、owner、blockedBy のみ解説。CC は実際に 9 フィールドを持つ(utils/tasks.ts:76-89):
| フィールド | 型 | 用途 |
|---|---|---|
id |
string | 昇順整数 ID |
subject |
string | 短いタイトル |
description |
string | 自由形式の説明 |
activeForm |
string? | 現在進行形、in_progress 時にスピナーに表示 |
owner |
string? | 割り当てられた agent ID |
status |
pending/in_progress/completed | ライフサイクル |
blocks |
string[] | このタスクがブロックするタスク ID(下流) |
blockedBy |
string[] | このタスクをブロックするタスク ID(上流) |
metadata |
Record? | 任意の拡張キーバリューペア |
保存場所:~/.claude/tasks/{taskListId}/{id}.json。タスクごとに 1 ファイル。
二、TodoWrite のアップグレードではなく、2 つの独立システム
CC では Task System と TodoWrite は共存し、isTodoV2Enabled() で切り替え(utils/tasks.ts:133)— 対話セッションはデフォルトで Task (V2)、非対話/SDK セッションは TodoWrite。環境変数 CLAUDE_CODE_ENABLE_TASKS で Task を強制有効化可能。Task は TodoWrite にない機能を持つ:ファイルロック並行保護、依存関係強制、ownership、fs.watch リアクティブ監視、ライフサイクルフック。
三、並行認識のロック機構
claimTask()(utils/tasks.ts:541-612)は二重ロックで競合を防止:
タスクファイルロック:proper-lockfile で {taskId}.json をロック(最大 30 リトライ、指数バックオフ 5-100ms)。ロック内:
- タスクを再読込(TOCTOU 防止)
- 既に他者が認識済み →
already_claimed - 既に完了済み →
already_resolved - 上流が未完了 →
blocked - owner を設定
リストレベルロック(agent busy チェック時):.lock ファイル、全タスクを原子的に走査し該当 agent が他の open task を持つか確認。
注意:教学版は認識と作業開始を 1 ステップに統合(claim = owner 設定 + in_progress);実際の CC の claimTask は主に owner 競合を解決し、owner のみを設定して status は変更しない。status の更新は TaskUpdate が担当。
四、高水位標による ID 再利用防止
.highwatermark ファイルが過去に割り当てられた最大タスク ID を記録。タスクが削除されても ID は再利用されない。
五、4 つの Task ツール
CC のタスクシステムは 4 つのツールを持つ(チュートリアルの汎用 Task ツールとは異なる):TaskCreate、TaskGet、TaskUpdate、TaskList。すべて isConcurrencySafe: true と shouldDefer: true が設定(ツールスキーマは初期プロンプトに含まれず、ToolSearch 後にのみ可視)。
教学版の create_task(blockedBy=...) は作成時に直接依存を宣言する合理な簡略化。実際の CC の TaskCreate は subject/description/activeForm/metadata のみを受け付け、依存関係は TaskUpdate の addBlocks/addBlockedBy で管理される。