mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-06-20 20:23:36 +08:00
* 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>
This commit is contained in:
156
s05_todo_write/README.en.md
Normal file
156
s05_todo_write/README.en.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# s05: TodoWrite — An Agent Without a Plan Drifts Off Course
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → `s05` → [s06](../s06_subagent/) → s07 → ... → s20
|
||||
|
||||
> *"An agent without a plan goes wherever the wind blows"* — List the steps first, then execute. Complex tasks are less likely to miss steps.
|
||||
>
|
||||
> **Harness Layer**: Planning — Let the Agent think before it acts.
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
Give the Agent a complex task: "Rename all Python files to snake_case, run tests, and fix failures."
|
||||
|
||||
The Agent starts working, renames 3 files, runs a test, finds 2 failures, starts fixing. While fixing, it forgets the original goal was "rename to snake_case", the test failures have consumed all its attention.
|
||||
|
||||
The longer the conversation, the worse it gets: tool results keep filling the context, diluting the system prompt's influence. A 10-step refactoring: after steps 1-3, the Agent starts improvising because steps 4-10 have been pushed out of its attention.
|
||||
|
||||
---
|
||||
|
||||
## The Solution
|
||||
|
||||

|
||||
|
||||
The minimal hook structure from the previous chapter is preserved, focusing on the new `todo_write` tool and reminder mechanism. `todo_write` does no actual work, can't read files or run commands, it simply lets the Agent organize its thoughts before diving in.
|
||||
|
||||
The dispatch mechanism is unchanged; the new tool is still routed through `TOOL_HANDLERS[block.name]`. However, to demonstrate the todo reminder, a counter was added to the loop: after 3 consecutive rounds without calling `todo_write`, a reminder is injected.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
**The todo_write tool**, accepts a list with statuses, persists to `.tasks/current_todos.json` (teaching version writes to disk for observability), and displays progress in the terminal:
|
||||
|
||||
```python
|
||||
def run_todo_write(todos: list) -> str:
|
||||
tasks_file = TASKS_DIR / "current_todos.json"
|
||||
tasks_file.write_text(json.dumps(todos, indent=2, ensure_ascii=False))
|
||||
|
||||
lines = ["\n## Current Tasks"]
|
||||
for t in todos:
|
||||
icon = {"pending": " ", "in_progress": "▸", "completed": "✓"}[t["status"]]
|
||||
lines.append(f" [{icon}] {t['content']}")
|
||||
print("\n".join(lines))
|
||||
return f"Updated {len(todos)} tasks"
|
||||
```
|
||||
|
||||
The tool definition joins the other 5 in the dispatch map:
|
||||
|
||||
```python
|
||||
TOOLS = [
|
||||
{"name": "bash", ...},
|
||||
{"name": "read_file", ...},
|
||||
{"name": "write_file", ...},
|
||||
{"name": "edit_file", ...},
|
||||
{"name": "glob", ...},
|
||||
# s05: new entry
|
||||
{"name": "todo_write", "description": "Create and manage a task list ...",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {"type": "string"},
|
||||
"status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
TOOL_HANDLERS["todo_write"] = run_todo_write
|
||||
```
|
||||
|
||||
**Nag reminder**, when the model hasn't called `todo_write` for 3 consecutive rounds, a reminder is automatically injected (teaching mechanism; CC source has no fixed round-count logic):
|
||||
|
||||
```python
|
||||
if rounds_since_todo >= 3 and messages:
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": "<reminder>Update your todos.</reminder>",
|
||||
})
|
||||
rounds_since_todo = 0
|
||||
```
|
||||
|
||||
Typical flow when the Agent receives a task: first call `todo_write` to list all steps (all `pending`) → pick one step, set it to `in_progress` → complete it, set to `completed` → look at the next `pending` → continue. After 3 rounds without `todo_write`, the loop appends a reminder before the next LLM call.
|
||||
|
||||
**Key insight**: todo_write doesn't give the Agent any additional **execution capability**. What it adds is **planning capability**.
|
||||
|
||||
---
|
||||
|
||||
## Changes from s04
|
||||
|
||||
| Component | Before (s04) | After (s05) |
|
||||
|-----------|-------------|-------------|
|
||||
| Tool count | 5 (bash, read, write, edit, glob) | 6 (+todo_write) |
|
||||
| Planning | None | Stateful TODO list + nag reminder |
|
||||
| SYSTEM prompt | Generic prompt | Added "plan before executing" guidance |
|
||||
| Loop | Unchanged | Dispatch unchanged, added rounds_since_todo counter and reminder injection |
|
||||
|
||||
---
|
||||
|
||||
## Try It
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s05_todo_write/code.py
|
||||
```
|
||||
|
||||
Try these prompts:
|
||||
|
||||
1. `Refactor s05_todo_write/example/hello.py: add type hints, docstrings, and a main guard` (should list 3 steps first, then execute)
|
||||
2. `Create a Python package under s05_todo_write/example/demo_pkg with __init__.py, utils.py, and tests/test_utils.py`
|
||||
3. `Review Python files under s05_todo_write/example and fix any style issues`
|
||||
|
||||
What to watch for: Was the first tool call `todo_write`? How many TODO steps were listed? Did statuses move from `pending` to `in_progress` / `completed` during execution?
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
|
||||
The Agent can plan now. But if a task is too large, say "refactor the entire auth module", a TODO list alone isn't enough. That task is itself a collection of dozens of subtasks that would drown in a single conversation's context.
|
||||
|
||||
→ s06 Subagent: Break large tasks into subtasks, each handled by an independent Agent with its own clean context, no cross-contamination.
|
||||
|
||||
<details>
|
||||
<summary>Dive into CC Source Code</summary>
|
||||
|
||||
CC has two task systems coexisting (`tasks.ts:133-139`):
|
||||
|
||||
- **TodoWrite (V1)**: A simple list tool, data maintained in memory AppState (`TodoWriteTool.ts:65-103`). The teaching version writes to `.tasks/current_todos.json` for observability; the real V1 does not write to disk.
|
||||
- **Task System (V2 = s12)**: File-persisted, dependency graph, concurrency locks, ownership.
|
||||
|
||||
The switch is controlled by `isTodoV2Enabled()`. In the current source: V2 is enabled by default in interactive sessions, V1 in non-interactive (SDK) sessions; setting `CLAUDE_CODE_ENABLE_TASKS` forces V2 regardless. Note the source comment "Force-enable tasks in non-interactive mode" describes the env var path's purpose, not the default branch's return semantics.
|
||||
|
||||
The teaching version omits the `activeForm` field from the real source (`utils/todo/types.ts:8-15`). CC uses it for the UI spinner to show "what's being done"; the teaching version only has terminal output and doesn't need this field.
|
||||
|
||||
The teaching version's nag reminder (3 rounds without update triggers injection) is an educational mechanism. The CC source has no fixed "3 rounds" logic; the closest is `TodoWriteTool.ts:72-107` which appends a verification nudge when 3+ todos are all completed without a verification item.
|
||||
|
||||
Core increments of the Task System over TodoWrite:
|
||||
- File persistence (Claude config directory `tasks/{taskListId}/{taskId}.json`) instead of in-memory list
|
||||
- `blockedBy` dependency graph instead of flat list
|
||||
- `proper-lockfile` concurrency safety instead of no locking
|
||||
- Four separate tools (Create/Get/Update/List) instead of one
|
||||
- TaskCreated / TaskCompleted hooks (`TaskCreateTool.ts:80-129`, `TaskUpdateTool.ts:231-260`) for external system integration
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
156
s05_todo_write/README.ja.md
Normal file
156
s05_todo_write/README.ja.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# s05: TodoWrite — 計画なき Agent は途中で道を外れる
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → `s05` → [s06](../s06_subagent/) → s07 → ... → s20
|
||||
|
||||
> *"計画なき agent は風の向くままに"* — まず手順を列挙してから実行。長いタスクで見落としが減る。
|
||||
>
|
||||
> **Harness レイヤー**: 計画 — Agent が行動する前に考えさせる。
|
||||
|
||||
---
|
||||
|
||||
## 課題
|
||||
|
||||
Agent に複雑なタスクを与える:「全 Python ファイルを snake_case にリネームし、テストを実行し、失敗を修正して。」
|
||||
|
||||
Agent は作業を開始する。3 つのファイルをリネーム、テストを実行、2 つの失敗を発見、修正を開始。修正しているうちに、本来の目的が「snake_case にリネーム」だったことを忘れる。テストの失敗に注意を全て持っていかれる。
|
||||
|
||||
会話が長くなるほど悪化する:ツールの結果がコンテキストを埋め続け、システムプロンプトの影響力が希釈される。10 ステップのリファクタリング:ステップ 1-3 を終えた時点で Agent は即興で動き始める。ステップ 4-10 は既に注意の外に追い出されているから。
|
||||
|
||||
---
|
||||
|
||||
## ソリューション
|
||||
|
||||

|
||||
|
||||
前章の最小フック構造を保持し、本章では新規の `todo_write` ツールとリマインダー機構に注目する。`todo_write` は実際の作業を何もしない。ファイルを読めない、コマンドを実行できない。Agent が手を動かす前に思考を整理できるようにするだけ。
|
||||
|
||||
ディスパッチ機構は変わらず、新ツールも `TOOL_HANDLERS[block.name]` を経由する。ただし、todo リマインダーのデモのため、ループにカウンターを追加した:連続 3 ラウンド `todo_write` を呼び出さないとリマインダーが注入される。
|
||||
|
||||
---
|
||||
|
||||
## 仕組み
|
||||
|
||||
**todo_write ツール**、ステータス付きのリストを受け取り、`.tasks/current_todos.json` に永続化(教育版は観察用にディスクに書き込む)、端末に進捗を表示する:
|
||||
|
||||
```python
|
||||
def run_todo_write(todos: list) -> str:
|
||||
tasks_file = TASKS_DIR / "current_todos.json"
|
||||
tasks_file.write_text(json.dumps(todos, indent=2, ensure_ascii=False))
|
||||
|
||||
lines = ["\n## Current Tasks"]
|
||||
for t in todos:
|
||||
icon = {"pending": " ", "in_progress": "▸", "completed": "✓"}[t["status"]]
|
||||
lines.append(f" [{icon}] {t['content']}")
|
||||
print("\n".join(lines))
|
||||
return f"Updated {len(todos)} tasks"
|
||||
```
|
||||
|
||||
ツール定義は他の 5 つと一緒にディスパッチマップに追加される:
|
||||
|
||||
```python
|
||||
TOOLS = [
|
||||
{"name": "bash", ...},
|
||||
{"name": "read_file", ...},
|
||||
{"name": "write_file", ...},
|
||||
{"name": "edit_file", ...},
|
||||
{"name": "glob", ...},
|
||||
# s05: 新規追加
|
||||
{"name": "todo_write", "description": "Create and manage a task list ...",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {"type": "string"},
|
||||
"status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
TOOL_HANDLERS["todo_write"] = run_todo_write
|
||||
```
|
||||
|
||||
**Nag リマインダー**、モデルが連続 3 ラウンド `todo_write` を呼び出さないとき、リマインダーが自動的に注入される(教育用機構、CC ソースコードに固定ラウンド数のロジックはない):
|
||||
|
||||
```python
|
||||
if rounds_since_todo >= 3 and messages:
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": "<reminder>Update your todos.</reminder>",
|
||||
})
|
||||
rounds_since_todo = 0
|
||||
```
|
||||
|
||||
Agent がタスクを受け取った後の典型的な流れ:まず `todo_write` を呼び出して全手順を列挙(全て `pending`)→ 一つの手順に取り掛かり、`in_progress` に変更 → 完了したら `completed` に変更 → 次の `pending` を見る → 続行。3 ラウンド `todo_write` がない場合、次の LLM 呼び出し前にリマインダーが追加される。
|
||||
|
||||
**重要な洞察**:todo_write は Agent に**実行能力**を何も追加しない。追加するのは**計画能力**だ。
|
||||
|
||||
---
|
||||
|
||||
## s04 からの変更
|
||||
|
||||
| コンポーネント | 変更前 (s04) | 変更後 (s05) |
|
||||
|--------------|-------------|-------------|
|
||||
| ツール数 | 5 (bash, read, write, edit, glob) | 6 (+todo_write) |
|
||||
| 計画能力 | なし | ステータス付き TODO リスト + Nag リマインダー |
|
||||
| SYSTEM プロンプト | 汎用プロンプト | 「先に計画してから実行」のガイダンスを追加 |
|
||||
| ループ | 不変 | ディスパッチは不変、rounds_since_todo カウンターとリマインダー注入を追加 |
|
||||
|
||||
---
|
||||
|
||||
## 試してみよう
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s05_todo_write/code.py
|
||||
```
|
||||
|
||||
以下のプロンプトを試してみよう:
|
||||
|
||||
1. `Refactor s05_todo_write/example/hello.py: add type hints, docstrings, and a main guard`(まず 3 手順を列挙してから実行するはず)
|
||||
2. `Create a Python package under s05_todo_write/example/demo_pkg with __init__.py, utils.py, and tests/test_utils.py`
|
||||
3. `Review Python files under s05_todo_write/example and fix any style issues`
|
||||
|
||||
観察のポイント:最初のツール呼び出しは `todo_write` か? TODO は何手順列挙されたか? 実行中にステータスが `pending` から `in_progress` / `completed` に変わったか?
|
||||
|
||||
---
|
||||
|
||||
## 次へ
|
||||
|
||||
Agent は計画できるようになった。しかしタスクが大きすぎる場合、例えば「認証モジュール全体をリファクタリング」、TODO リストだけでは不十分。そのタスク自体が数十のサブタスクの集合体で、同じ会話のコンテキストに押し込めると溢れてしまう。
|
||||
|
||||
→ s06 Subagent:大きなタスクをサブタスクに分割し、それぞれを独立した Agent に任せる。それぞれが独自のクリーンなコンテキストを持ち、相互汚染がない。
|
||||
|
||||
<details>
|
||||
<summary>CC ソースコードを深掘り</summary>
|
||||
|
||||
CC には二つのタスクシステムが共存している(`tasks.ts:133-139`):
|
||||
|
||||
- **TodoWrite(V1)**:シンプルなリストツール、データはメモリ AppState で管理(`TodoWriteTool.ts:65-103`)。教育版は観察用に `.tasks/current_todos.json` に書き込むが、実際の V1 はディスクに書き込まない
|
||||
- **Task System(V2 = s12)**:ファイル永続化、依存グラフ、並行ロック、ownership
|
||||
|
||||
切り替えは `isTodoV2Enabled()` で制御される。現在のソースコードの実装:対話型セッションでは V2 がデフォルトで有効、非対話型セッション(SDK)では V1 がデフォルトで有効。`CLAUDE_CODE_ENABLE_TASKS` 環境変数を設定するとセッション種別に関わらず V2 が強制有効になる。ソースコメント「Force-enable tasks in non-interactive mode」は環境変数パスの用途を説明しており、デフォルト分岐の戻り値のセマンティクスとは異なるため注意。
|
||||
|
||||
教育版は実際のソースコードにある `activeForm` フィールドを省略している(`utils/todo/types.ts:8-15`)。CC は UI スピナーに「何をしているか」を表示するために使用するが、教育版は端末出力のみでこのフィールドは不要。
|
||||
|
||||
教育版の Nag リマインダー(3 ラウンド未更新で注入)は教育用機構。CC ソースコードに固定「3 ラウンド」のロジックはなく、最も近いのは `TodoWriteTool.ts:72-107` で 3 つ以上の todo が全て完了しているのに verification 項目がない場合に verification nudge を追加する処理。
|
||||
|
||||
Task System の TodoWrite に対する核心的な増分:
|
||||
- メモリリストではなくファイル永続化(Claude 設定ディレクトリ下 `tasks/{taskListId}/{taskId}.json`)
|
||||
- 平坦なリストではなく `blockedBy` 依存グラフ
|
||||
- ロックなしではなく `proper-lockfile` による並行安全性
|
||||
- 一つのツールではなく四つの独立ツール(Create/Get/Update/List)
|
||||
- TaskCreated / TaskCompleted フック(`TaskCreateTool.ts:80-129`、`TaskUpdateTool.ts:231-260`)による外部システム統合
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
|
||||
156
s05_todo_write/README.md
Normal file
156
s05_todo_write/README.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# s05: TodoWrite — 没有计划的 Agent,做着做着就偏了
|
||||
|
||||
[中文](README.md) · [English](README.en.md) · [日本語](README.ja.md)
|
||||
|
||||
s01 → s02 → s03 → s04 → `s05` → [s06](../s06_subagent/) → s07 → ... → s20
|
||||
|
||||
> *"没有计划的 agent 走哪算哪"* — 先列步骤再动手,长任务更不容易漏项。
|
||||
>
|
||||
> **Harness 层**: 规划 — 让 Agent 在动手之前先想清楚。
|
||||
|
||||
---
|
||||
|
||||
## 问题
|
||||
|
||||
给 Agent 一个复杂任务:"把所有 Python 文件改成 snake_case 命名,然后跑测试,修好失败。"
|
||||
|
||||
Agent 开始干活,改了 3 个文件,跑了个测试,发现 2 个失败,开始修。修着修着,它忘了最初是"改成 snake_case",测试失败把注意力全吸走了。
|
||||
|
||||
对话越长越严重:工具结果不断填满上下文,系统提示的影响力被稀释。一个 10 步重构,做完 1-3 步就开始即兴发挥,因为 4-10 步已经被挤出注意力了。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||

|
||||
|
||||
保留上一章的最小 hook 结构,重点看新增的 `todo_write` 工具和 reminder 机制。`todo_write` 本身不做任何实际工作,不能读文件、不能跑命令,只是让 Agent 在动手之前先理清思路。
|
||||
|
||||
dispatch 机制不变,新工具仍然走 `TOOL_HANDLERS[block.name]` 分发。但为了演示 todo reminder,循环里加了一个计数器:连续 3 轮没调 `todo_write` 就注入一条提醒。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
**todo_write 工具**,接收一个带状态的列表,持久化到 `.tasks/current_todos.json`(教学版写盘以便观察),同时在终端显示进度:
|
||||
|
||||
```python
|
||||
def run_todo_write(todos: list) -> str:
|
||||
tasks_file = TASKS_DIR / "current_todos.json"
|
||||
tasks_file.write_text(json.dumps(todos, indent=2, ensure_ascii=False))
|
||||
|
||||
lines = ["\n## Current Tasks"]
|
||||
for t in todos:
|
||||
icon = {"pending": " ", "in_progress": "▸", "completed": "✓"}[t["status"]]
|
||||
lines.append(f" [{icon}] {t['content']}")
|
||||
print("\n".join(lines))
|
||||
return f"Updated {len(todos)} tasks"
|
||||
```
|
||||
|
||||
工具定义和其他 5 个工具一起加入 dispatch map:
|
||||
|
||||
```python
|
||||
TOOLS = [
|
||||
{"name": "bash", ...},
|
||||
{"name": "read_file", ...},
|
||||
{"name": "write_file", ...},
|
||||
{"name": "edit_file", ...},
|
||||
{"name": "glob", ...},
|
||||
# s05: 新增一条
|
||||
{"name": "todo_write", "description": "Create and manage a task list ...",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {"type": "string"},
|
||||
"status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
TOOL_HANDLERS["todo_write"] = run_todo_write
|
||||
```
|
||||
|
||||
**Nag reminder**,模型连续 3 轮没调 `todo_write` 时,自动注入一条提醒(教学版机制,CC 源码中没有这个固定轮数逻辑):
|
||||
|
||||
```python
|
||||
if rounds_since_todo >= 3 and messages:
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": "<reminder>Update your todos.</reminder>",
|
||||
})
|
||||
rounds_since_todo = 0
|
||||
```
|
||||
|
||||
Agent 收到任务后的典型流程:先调 `todo_write` 列出所有步骤(全 `pending`)→ 做一个步骤,改成 `in_progress` → 做完改成 `completed` → 看下一个 `pending` → 继续。连续 3 轮没有调用 `todo_write` 时,循环会在下一次 LLM 调用前追加一条 reminder。
|
||||
|
||||
**关键洞察**:todo_write 不给 Agent 增加任何**执行能力**。它增加的是**规划能力**。
|
||||
|
||||
---
|
||||
|
||||
## 相对 s04 的变更
|
||||
|
||||
| 组件 | 之前 (s04) | 之后 (s05) |
|
||||
|------|-----------|-----------|
|
||||
| 工具数量 | 5 (bash, read, write, edit, glob) | 6 (+todo_write) |
|
||||
| 规划能力 | 无 | 带状态的 TODO 列表 + nag reminder |
|
||||
| SYSTEM 提示 | 通用提示 | 加入 "先计划再执行" 引导 |
|
||||
| 循环 | 不变 | dispatch 不变,新增 rounds_since_todo 计数器和 reminder 注入 |
|
||||
|
||||
---
|
||||
|
||||
## 试一下
|
||||
|
||||
```sh
|
||||
cd learn-claude-code
|
||||
python s05_todo_write/code.py
|
||||
```
|
||||
|
||||
试试这些 prompt:
|
||||
|
||||
1. `Refactor s05_todo_write/example/hello.py: add type hints, docstrings, and a main guard`(先列 3 步再执行)
|
||||
2. `Create a Python package under s05_todo_write/example/demo_pkg with __init__.py, utils.py, and tests/test_utils.py`
|
||||
3. `Review Python files under s05_todo_write/example and fix any style issues`
|
||||
|
||||
观察重点:第一次工具调用是不是 `todo_write`?TODO 列了几步?执行过程中状态有没有从 `pending` 变成 `in_progress` / `completed`?
|
||||
|
||||
---
|
||||
|
||||
## 接下来
|
||||
|
||||
Agent 能计划了。但如果一个任务太大,比如"重构整个认证模块",光靠 TODO 列表不够。这个任务本身就是几十个小任务的集合,放在同一个对话里会被上下文淹没。
|
||||
|
||||
s06 Subagent → 把大任务拆成子任务,每个子任务派一个独立的 Agent。它们有自己的干净上下文,不会互相污染。
|
||||
|
||||
<details>
|
||||
<summary>深入 CC 源码</summary>
|
||||
|
||||
CC 中有两套任务系统并存(`tasks.ts:133-139`):
|
||||
|
||||
- **TodoWrite(V1)**:一个简单的列表工具,数据在内存 AppState 中维护(`TodoWriteTool.ts:65-103`)。教学版写盘到 `.tasks/current_todos.json` 是为了可观察性,真实 V1 不写盘
|
||||
- **Task System(V2 = s12)**:文件持久化、依赖图、并发锁、ownership
|
||||
|
||||
切换由 `isTodoV2Enabled()` 控制。当前源码的实现逻辑:交互式会话中 V2 默认启用,非交互式会话(SDK)中 V1 默认启用;设置 `CLAUDE_CODE_ENABLE_TASKS` 环境变量可强制启用 V2。注意源码注释 "Force-enable tasks in non-interactive mode" 描述的是 env var 路径的用途,和默认分支的返回值语义不同,阅读时需区分。
|
||||
|
||||
教学版省略了真实源码中的 `activeForm` 字段(`utils/todo/types.ts:8-15`)。CC 用它给 UI spinner 展示"正在做什么",教学版只有终端输出,不需要这个字段。
|
||||
|
||||
教学版的 nag reminder(3 轮未更新就注入提醒)是教学机制。CC 源码中没有固定的"3 轮"逻辑,更接近的是 `TodoWriteTool.ts:72-107` 中当 3 个以上 todo 全部完成但没有 verification 项时,追加 verification nudge。
|
||||
|
||||
Task System 相比 TodoWrite 的核心增量:
|
||||
- 文件持久化(Claude 配置目录下 `tasks/{taskListId}/{taskId}.json`)而非内存列表
|
||||
- `blockedBy` 依赖图而非平铺列表
|
||||
- `proper-lockfile` 并发安全而非无锁
|
||||
- 四个独立工具(Create/Get/Update/List)而非一个
|
||||
- TaskCreated / TaskCompleted hooks(`TaskCreateTool.ts:80-129`、`TaskUpdateTool.ts:231-260`)供外部系统集成
|
||||
|
||||
</details>
|
||||
|
||||
<!-- translation-sync: zh@v1, en@v0, ja@v0 -->
|
||||
287
s05_todo_write/code.py
Normal file
287
s05_todo_write/code.py
Normal file
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
s05: TodoWrite — add a planning tool on top of s04 hooks.
|
||||
|
||||
+---------+ +-------+ +------------------+
|
||||
| User | ---> | LLM | ---> | TOOL_HANDLERS |
|
||||
| prompt | | | | bash |
|
||||
+---------+ +---+---+ | read_file |
|
||||
^ | write_file |
|
||||
| result | edit_file |
|
||||
+---------+ glob |
|
||||
todo_write ← NEW
|
||||
+------------------+
|
||||
|
|
||||
.tasks/current_todos.json
|
||||
|
|
||||
if rounds_since_todo >= 3:
|
||||
inject <reminder>
|
||||
|
||||
Changes from s04:
|
||||
+ todo_write tool + run_todo_write() implementation
|
||||
+ Nag reminder (inject reminder after 3 rounds without todo update)
|
||||
+ SYSTEM prompt includes "plan before execute" guidance
|
||||
+ rounds_since_todo counter in agent_loop
|
||||
Loop unchanged: new tool auto-dispatches via TOOL_HANDLERS.
|
||||
|
||||
Run: python s05_todo_write/code.py
|
||||
Needs: pip install anthropic python-dotenv + ANTHROPIC_API_KEY in .env
|
||||
"""
|
||||
|
||||
import os, subprocess, json
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import readline
|
||||
readline.parse_and_bind('set bind-tty-special-chars off')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from anthropic import Anthropic
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(override=True)
|
||||
if os.getenv("ANTHROPIC_BASE_URL"):
|
||||
os.environ.pop("ANTHROPIC_AUTH_TOKEN", None)
|
||||
|
||||
WORKDIR = Path.cwd()
|
||||
TASKS_DIR = WORKDIR / ".tasks"; TASKS_DIR.mkdir(exist_ok=True)
|
||||
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
|
||||
MODEL = os.environ["MODEL_ID"]
|
||||
|
||||
# s05 change: SYSTEM prompt adds planning guidance
|
||||
SYSTEM = (
|
||||
f"You are a coding agent at {WORKDIR}. "
|
||||
"Before starting any multi-step task, use todo_write to plan your steps. "
|
||||
"Update status as you go."
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# FROM s02-s04 (unchanged): Tool Implementations
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def safe_path(p: str) -> Path:
|
||||
path = (WORKDIR / p).resolve()
|
||||
if not path.is_relative_to(WORKDIR):
|
||||
raise ValueError(f"Path escapes workspace: {p}")
|
||||
return path
|
||||
|
||||
def run_bash(command: str) -> str:
|
||||
try:
|
||||
r = subprocess.run(command, shell=True, cwd=WORKDIR,
|
||||
capture_output=True, text=True, timeout=120)
|
||||
out = (r.stdout + r.stderr).strip()
|
||||
return out[:50000] if out else "(no output)"
|
||||
except subprocess.TimeoutExpired:
|
||||
return "Error: Timeout (120s)"
|
||||
|
||||
def run_read(path: str, limit: int | None = None) -> str:
|
||||
try:
|
||||
lines = safe_path(path).read_text().splitlines()
|
||||
if limit and limit < len(lines):
|
||||
lines = lines[:limit] + [f"... ({len(lines) - limit} more lines)"]
|
||||
return "\n".join(lines)
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def run_write(path: str, content: str) -> str:
|
||||
try:
|
||||
file_path = safe_path(path)
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.write_text(content)
|
||||
return f"Wrote {len(content)} bytes to {path}"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def run_edit(path: str, old_text: str, new_text: str) -> str:
|
||||
try:
|
||||
file_path = safe_path(path)
|
||||
text = file_path.read_text()
|
||||
if old_text not in text:
|
||||
return f"Error: text not found in {path}"
|
||||
file_path.write_text(text.replace(old_text, new_text, 1))
|
||||
return f"Edited {path}"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
def run_glob(pattern: str) -> str:
|
||||
import glob as g
|
||||
try:
|
||||
results = []
|
||||
for match in g.glob(pattern, root_dir=WORKDIR):
|
||||
if (WORKDIR / match).resolve().is_relative_to(WORKDIR):
|
||||
results.append(match)
|
||||
return "\n".join(results) if results else "(no matches)"
|
||||
except Exception as e:
|
||||
return f"Error: {e}"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# NEW in s05: todo_write tool — plan only, no execution
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
def run_todo_write(todos: list) -> str:
|
||||
# validate required fields
|
||||
for i, t in enumerate(todos):
|
||||
if "content" not in t or "status" not in t:
|
||||
return f"Error: todos[{i}] missing 'content' or 'status'"
|
||||
if t["status"] not in ("pending", "in_progress", "completed"):
|
||||
return f"Error: todos[{i}] has invalid status '{t['status']}'"
|
||||
tasks_file = TASKS_DIR / "current_todos.json"
|
||||
tasks_file.write_text(json.dumps(todos, indent=2, ensure_ascii=False))
|
||||
lines = ["\n\033[33m## Current Tasks\033[0m"]
|
||||
for t in todos:
|
||||
icon = {"pending": " ", "in_progress": "\033[36m▸\033[0m", "completed": "\033[32m✓\033[0m"}[t["status"]]
|
||||
lines.append(f" [{icon}] {t['content']}")
|
||||
print("\n".join(lines))
|
||||
return f"Updated {len(todos)} tasks"
|
||||
|
||||
TOOLS = [
|
||||
{"name": "bash", "description": "Run a shell command.",
|
||||
"input_schema": {"type": "object", "properties": {"command": {"type": "string"}}, "required": ["command"]}},
|
||||
{"name": "read_file", "description": "Read file contents.",
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}},
|
||||
{"name": "write_file", "description": "Write content to a file.",
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "content": {"type": "string"}}, "required": ["path", "content"]}},
|
||||
{"name": "edit_file", "description": "Replace exact text in a file once.",
|
||||
"input_schema": {"type": "object", "properties": {"path": {"type": "string"}, "old_text": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["path", "old_text", "new_text"]}},
|
||||
{"name": "glob", "description": "Find files matching a glob pattern.",
|
||||
"input_schema": {"type": "object", "properties": {"pattern": {"type": "string"}}, "required": ["pattern"]}},
|
||||
# s05: new tool
|
||||
{"name": "todo_write", "description": "Create and manage a task list for your current coding session.",
|
||||
"input_schema": {"type": "object", "properties": {"todos": {"type": "array", "items": {"type": "object", "properties": {"content": {"type": "string"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}}, "required": ["content", "status"]}}}, "required": ["todos"]}},
|
||||
]
|
||||
|
||||
TOOL_HANDLERS = {
|
||||
"bash": run_bash, "read_file": run_read, "write_file": run_write,
|
||||
"edit_file": run_edit, "glob": run_glob, "todo_write": run_todo_write,
|
||||
}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# FROM s04 (unchanged): Hook System
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
HOOKS = {"UserPromptSubmit": [], "PreToolUse": [], "PostToolUse": [], "Stop": []}
|
||||
|
||||
def register_hook(event: str, callback):
|
||||
HOOKS[event].append(callback)
|
||||
|
||||
def trigger_hooks(event: str, *args):
|
||||
for callback in HOOKS[event]:
|
||||
result = callback(*args)
|
||||
if result is not None:
|
||||
return result
|
||||
return None
|
||||
|
||||
# s04 hooks preserved
|
||||
DENY_LIST = ["rm -rf /", "sudo", "shutdown", "reboot", "mkfs", "dd if="]
|
||||
|
||||
def permission_hook(block):
|
||||
"""PreToolUse: deny list check."""
|
||||
if block.name == "bash":
|
||||
for p in DENY_LIST:
|
||||
if p in block.input.get("command", ""):
|
||||
print(f"\n\033[31m⛔ Blocked: '{p}'\033[0m")
|
||||
return "Permission denied"
|
||||
return None
|
||||
|
||||
def log_hook(block):
|
||||
"""PreToolUse: log tool calls."""
|
||||
print(f"\033[90m[HOOK] {block.name}\033[0m")
|
||||
return None
|
||||
|
||||
def context_inject_hook(query: str):
|
||||
"""UserPromptSubmit: log working directory."""
|
||||
print(f"\033[90m[HOOK] UserPromptSubmit: working in {WORKDIR}\033[0m")
|
||||
return None
|
||||
|
||||
def summary_hook(messages: list):
|
||||
"""Stop: print tool call count."""
|
||||
tool_count = sum(1 for m in messages
|
||||
for b in (m.get("content") if isinstance(m.get("content"), list) else [])
|
||||
if isinstance(b, dict) and b.get("type") == "tool_result")
|
||||
print(f"\033[90m[HOOK] Stop: session used {tool_count} tool calls\033[0m")
|
||||
return None
|
||||
|
||||
register_hook("UserPromptSubmit", context_inject_hook)
|
||||
register_hook("PreToolUse", permission_hook)
|
||||
register_hook("PreToolUse", log_hook)
|
||||
register_hook("Stop", summary_hook)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
# agent_loop — same as s04 + nag reminder counter
|
||||
# ═══════════════════════════════════════════════════════════
|
||||
|
||||
rounds_since_todo = 0
|
||||
|
||||
def agent_loop(messages: list):
|
||||
global rounds_since_todo
|
||||
while True:
|
||||
# s05: nag reminder — inject if model hasn't updated todos for 3 rounds
|
||||
if rounds_since_todo >= 3 and messages:
|
||||
messages.append({"role": "user",
|
||||
"content": "<reminder>Update your todos.</reminder>"})
|
||||
rounds_since_todo = 0
|
||||
|
||||
response = client.messages.create(
|
||||
model=MODEL, system=SYSTEM, messages=messages,
|
||||
tools=TOOLS, max_tokens=8000,
|
||||
)
|
||||
messages.append({"role": "assistant", "content": response.content})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
force = trigger_hooks("Stop", messages)
|
||||
if force:
|
||||
messages.append({"role": "user", "content": force})
|
||||
continue
|
||||
return
|
||||
|
||||
rounds_since_todo += 1
|
||||
results = []
|
||||
for block in response.content:
|
||||
if block.type != "tool_use":
|
||||
continue
|
||||
|
||||
blocked = trigger_hooks("PreToolUse", block)
|
||||
if blocked:
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id,
|
||||
"content": str(blocked)})
|
||||
continue
|
||||
|
||||
handler = TOOL_HANDLERS.get(block.name)
|
||||
output = handler(**block.input) if handler else f"Unknown: {block.name}"
|
||||
|
||||
trigger_hooks("PostToolUse", block, output)
|
||||
|
||||
# s05: reset nag counter when todo_write is called
|
||||
if block.name == "todo_write":
|
||||
rounds_since_todo = 0
|
||||
|
||||
results.append({"type": "tool_result", "tool_use_id": block.id,
|
||||
"content": output})
|
||||
|
||||
messages.append({"role": "user", "content": results})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("s05: TodoWrite — plan before execute, nag if you forget")
|
||||
print("Type a question, press Enter. Type q to quit.\n")
|
||||
|
||||
history = []
|
||||
while True:
|
||||
try:
|
||||
query = input("\033[36ms05 >> \033[0m")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if query.strip().lower() in ("q", "exit", ""):
|
||||
break
|
||||
trigger_hooks("UserPromptSubmit", query)
|
||||
history.append({"role": "user", "content": query})
|
||||
agent_loop(history)
|
||||
for block in history[-1]["content"]:
|
||||
if getattr(block, "type", None) == "text":
|
||||
print(block.text)
|
||||
print()
|
||||
6
s05_todo_write/example/hello.py
Normal file
6
s05_todo_write/example/hello.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def greet(name):
|
||||
message = "Hello, " + name
|
||||
print(message)
|
||||
|
||||
|
||||
greet("Claude")
|
||||
93
s05_todo_write/images/todo-overview.en.svg
Normal file
93
s05_todo_write/images/todo-overview.en.svg
Normal file
@@ -0,0 +1,93 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
|
||||
</marker>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="800" height="420" fill="#fafbfc" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<rect x="0" y="0" width="800" height="48" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="40" width="800" height="8" fill="url(#header)"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">TodoWrite — Loop Unchanged, One More Tool Auto-Dispatched</text>
|
||||
|
||||
<!-- ===== s04 Preserved (gray) ===== -->
|
||||
<text x="50" y="76" fill="#94a3b8" font-size="11" font-weight="600">s04 Preserved</text>
|
||||
|
||||
<!-- messages -->
|
||||
<rect x="40" y="90" width="100" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="90" y="117" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- → LLM -->
|
||||
<line x1="140" y1="112" x2="188" y2="112" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
|
||||
<!-- No → Return -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="258" y="156" fill="#16a34a" font-size="9" font-weight="600">No</text>
|
||||
<rect x="195" y="164" width="100" height="28" rx="14" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="245" y="182" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">Return Result</text>
|
||||
|
||||
<!-- Yes → PreToolUse -->
|
||||
<line x1="300" y1="112" x2="348" y2="112" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="320" y="104" fill="#d97706" font-size="9" font-weight="600">Yes</text>
|
||||
|
||||
<!-- PreToolUse (s04) -->
|
||||
<rect x="350" y="88" width="100" height="48" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="400" y="110" fill="#166534" font-size="9" font-weight="600" text-anchor="middle">trigger_hooks</text>
|
||||
<text x="400" y="124" fill="#166534" font-size="8" text-anchor="middle">PreToolUse</text>
|
||||
|
||||
<!-- → TOOL_HANDLERS -->
|
||||
<line x1="450" y1="112" x2="498" y2="112" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== s05 New: todo_write ===== -->
|
||||
<!-- TOOL_HANDLERS box (expanded, includes todo_write) -->
|
||||
<rect x="500" y="74" width="120" height="140" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-width="2" stroke-dasharray="6,3"/>
|
||||
<text x="560" y="94" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
|
||||
<!-- s04 preserved tools -->
|
||||
<rect x="512" y="102" width="96" height="22" rx="4" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="560" y="117" fill="#1e3a5f" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
|
||||
<rect x="512" y="130" width="96" height="22" rx="4" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="560" y="145" fill="#1e3a5f" font-size="9" text-anchor="middle">edit · glob</text>
|
||||
|
||||
<!-- s05 new: todo_write -->
|
||||
<rect x="512" y="158" width="96" height="22" rx="4" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="560" y="173" fill="#166534" font-size="9" font-weight="700" text-anchor="middle">todo_write</text>
|
||||
<text x="560" y="232" fill="#16a34a" font-size="11" font-weight="600" text-anchor="middle">s05 New</text>
|
||||
|
||||
<text x="560" y="196" fill="#64748b" font-size="8" text-anchor="middle">→ .tasks/current_todos.json</text>
|
||||
|
||||
<!-- Loop back -->
|
||||
<path d="M 620 112 L 660 112 L 660 260 L 90 260 L 90 134" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="370" y="280" fill="#64748b" font-size="10" text-anchor="middle">Results appended to messages[], loop continues</text>
|
||||
|
||||
<!-- ===== Nag Reminder ===== -->
|
||||
<rect x="100" y="310" width="600" height="56" rx="8" fill="#fffbeb" stroke="#d97706" stroke-width="1"/>
|
||||
<text x="120" y="332" fill="#92400e" font-size="11" font-weight="700">Nag Reminder</text>
|
||||
<text x="120" y="352" fill="#92400e" font-size="10">Model hasn't called todo_write for 3 rounds → auto-inject <reminder>Update your todos.</reminder></text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="100" y="384" width="600" height="28" rx="6" fill="#f1f5f9"/>
|
||||
<rect x="120" y="392" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="140" y="402" fill="#334155" font-size="10">s04 Preserved (loop, hooks, 5 base tools)</text>
|
||||
<rect x="400" y="392" width="12" height="12" rx="2" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
|
||||
<text x="420" y="402" fill="#334155" font-size="10">s05 New (todo_write + nag reminder)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
93
s05_todo_write/images/todo-overview.ja.svg
Normal file
93
s05_todo_write/images/todo-overview.ja.svg
Normal file
@@ -0,0 +1,93 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
|
||||
</marker>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- 背景 -->
|
||||
<rect width="800" height="420" fill="#fafbfc" rx="8"/>
|
||||
|
||||
<!-- タイトル -->
|
||||
<rect x="0" y="0" width="800" height="48" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="40" width="800" height="8" fill="url(#header)"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="15" font-weight="700" text-anchor="middle">TodoWrite — ループ不変、ツール一つ追加で自動ディスパッチ</text>
|
||||
|
||||
<!-- ===== s04 保持(灰色) ===== -->
|
||||
<text x="50" y="76" fill="#94a3b8" font-size="11" font-weight="600">s04 保持</text>
|
||||
|
||||
<!-- messages -->
|
||||
<rect x="40" y="90" width="100" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="90" y="117" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- → LLM -->
|
||||
<line x1="140" y1="112" x2="188" y2="112" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
|
||||
<!-- No → 返却 -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="258" y="156" fill="#16a34a" font-size="9" font-weight="600">No</text>
|
||||
<rect x="195" y="164" width="100" height="28" rx="14" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="245" y="182" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">結果を返す</text>
|
||||
|
||||
<!-- Yes → PreToolUse -->
|
||||
<line x1="300" y1="112" x2="348" y2="112" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="320" y="104" fill="#d97706" font-size="9" font-weight="600">Yes</text>
|
||||
|
||||
<!-- PreToolUse (s04) -->
|
||||
<rect x="350" y="88" width="100" height="48" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="400" y="110" fill="#166534" font-size="9" font-weight="600" text-anchor="middle">trigger_hooks</text>
|
||||
<text x="400" y="124" fill="#166534" font-size="8" text-anchor="middle">PreToolUse</text>
|
||||
|
||||
<!-- → TOOL_HANDLERS -->
|
||||
<line x1="450" y1="112" x2="498" y2="112" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== s05 新規:todo_write ===== -->
|
||||
<!-- TOOL_HANDLERS 枠(拡大、todo_write を含む) -->
|
||||
<rect x="500" y="74" width="120" height="140" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-width="2" stroke-dasharray="6,3"/>
|
||||
<text x="560" y="94" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
|
||||
<!-- s04 保持のツール -->
|
||||
<rect x="512" y="102" width="96" height="22" rx="4" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="560" y="117" fill="#1e3a5f" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
|
||||
<rect x="512" y="130" width="96" height="22" rx="4" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="560" y="145" fill="#1e3a5f" font-size="9" text-anchor="middle">edit · glob</text>
|
||||
|
||||
<!-- s05 新規:todo_write -->
|
||||
<rect x="512" y="158" width="96" height="22" rx="4" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="560" y="173" fill="#166534" font-size="9" font-weight="700" text-anchor="middle">todo_write</text>
|
||||
<text x="560" y="232" fill="#16a34a" font-size="11" font-weight="600" text-anchor="middle">s05 新規</text>
|
||||
|
||||
<text x="560" y="196" fill="#64748b" font-size="8" text-anchor="middle">→ .tasks/current_todos.json</text>
|
||||
|
||||
<!-- ループバック -->
|
||||
<path d="M 620 112 L 660 112 L 660 260 L 90 260 L 90 134" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="370" y="280" fill="#64748b" font-size="10" text-anchor="middle">結果を messages[] に追加、ループ継続</text>
|
||||
|
||||
<!-- ===== Nag リマインダー ===== -->
|
||||
<rect x="100" y="310" width="600" height="56" rx="8" fill="#fffbeb" stroke="#d97706" stroke-width="1"/>
|
||||
<text x="120" y="332" fill="#92400e" font-size="11" font-weight="700">Nag リマインダー(催促機構)</text>
|
||||
<text x="120" y="352" fill="#92400e" font-size="10">モデルが連続 3 ラウンド todo_write 未呼び出し → 自動注入 <reminder>Update your todos.</reminder></text>
|
||||
|
||||
<!-- 凡例 -->
|
||||
<rect x="100" y="384" width="600" height="28" rx="6" fill="#f1f5f9"/>
|
||||
<rect x="120" y="392" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="140" y="402" fill="#334155" font-size="10">s04 保持(ループ、フック、5 つの基本ツール)</text>
|
||||
<rect x="400" y="392" width="12" height="12" rx="2" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
|
||||
<text x="420" y="402" fill="#334155" font-size="10">s05 新規(todo_write + Nag リマインダー)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
93
s05_todo_write/images/todo-overview.svg
Normal file
93
s05_todo_write/images/todo-overview.svg
Normal file
@@ -0,0 +1,93 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 420" font-family="system-ui, -apple-system, sans-serif">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#555"/>
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
||||
</marker>
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#16a34a"/>
|
||||
</marker>
|
||||
<linearGradient id="header" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#1e3a5f"/>
|
||||
<stop offset="100%" stop-color="#2563eb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- 背景 -->
|
||||
<rect width="800" height="420" fill="#fafbfc" rx="8"/>
|
||||
|
||||
<!-- 标题 -->
|
||||
<rect x="0" y="0" width="800" height="48" fill="url(#header)" rx="8"/>
|
||||
<rect x="0" y="40" width="800" height="8" fill="url(#header)"/>
|
||||
<text x="400" y="31" fill="#fff" font-size="16" font-weight="700" text-anchor="middle">TodoWrite — 循环不变,多一个工具自动分发</text>
|
||||
|
||||
<!-- ===== s04 保留(灰色) ===== -->
|
||||
<text x="50" y="76" fill="#94a3b8" font-size="11" font-weight="600">s04 保留</text>
|
||||
|
||||
<!-- messages -->
|
||||
<rect x="40" y="90" width="100" height="44" rx="8" fill="#f0f4ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="90" y="117" fill="#1e3a5f" font-size="12" font-weight="600" text-anchor="middle">messages[]</text>
|
||||
|
||||
<!-- → LLM -->
|
||||
<line x1="140" y1="112" x2="188" y2="112" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
|
||||
|
||||
<!-- LLM -->
|
||||
<rect x="190" y="86" width="110" height="52" rx="8" fill="#fff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="245" y="108" fill="#1e3a5f" font-size="13" font-weight="700" text-anchor="middle">LLM</text>
|
||||
<text x="245" y="126" fill="#64748b" font-size="9" text-anchor="middle">stop_reason=tool_use?</text>
|
||||
|
||||
<!-- 否 → 返回 -->
|
||||
<line x1="245" y1="138" x2="245" y2="162" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="258" y="156" fill="#16a34a" font-size="9" font-weight="600">否</text>
|
||||
<rect x="195" y="164" width="100" height="28" rx="14" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="245" y="182" fill="#166534" font-size="11" font-weight="600" text-anchor="middle">返回结果</text>
|
||||
|
||||
<!-- 是 → PreToolUse -->
|
||||
<line x1="300" y1="112" x2="348" y2="112" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
<text x="320" y="104" fill="#d97706" font-size="9" font-weight="600">是</text>
|
||||
|
||||
<!-- PreToolUse (s04) -->
|
||||
<rect x="350" y="88" width="100" height="48" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="400" y="110" fill="#166534" font-size="9" font-weight="600" text-anchor="middle">trigger_hooks</text>
|
||||
<text x="400" y="124" fill="#166534" font-size="8" text-anchor="middle">PreToolUse</text>
|
||||
|
||||
<!-- → TOOL_HANDLERS -->
|
||||
<line x1="450" y1="112" x2="498" y2="112" stroke="#555" stroke-width="2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ===== s05 新增:todo_write ===== -->
|
||||
<!-- TOOL_HANDLERS 框(扩大,包含 todo_write) -->
|
||||
<rect x="500" y="74" width="120" height="140" rx="10" fill="#f0fdf4" stroke="#16a34a" stroke-width="2" stroke-dasharray="6,3"/>
|
||||
<text x="560" y="94" fill="#166534" font-size="10" font-weight="700" text-anchor="middle">TOOL_HANDLERS</text>
|
||||
|
||||
<!-- s04 保留的工具 -->
|
||||
<rect x="512" y="102" width="96" height="22" rx="4" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="560" y="117" fill="#1e3a5f" font-size="9" text-anchor="middle">bash · read · write</text>
|
||||
|
||||
<rect x="512" y="130" width="96" height="22" rx="4" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="560" y="145" fill="#1e3a5f" font-size="9" text-anchor="middle">edit · glob</text>
|
||||
|
||||
<!-- s05 新增:todo_write -->
|
||||
<rect x="512" y="158" width="96" height="22" rx="4" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="560" y="173" fill="#166534" font-size="9" font-weight="700" text-anchor="middle">todo_write</text>
|
||||
<text x="560" y="232" fill="#16a34a" font-size="11" font-weight="600" text-anchor="middle">s05 新增</text>
|
||||
|
||||
<text x="560" y="196" fill="#64748b" font-size="8" text-anchor="middle">→ .tasks/current_todos.json</text>
|
||||
|
||||
<!-- 回环 -->
|
||||
<path d="M 620 112 L 660 112 L 660 260 L 90 260 L 90 134" fill="none" stroke="#555" stroke-width="2" marker-end="url(#arrow)" stroke-dasharray="6,3"/>
|
||||
<text x="370" y="280" fill="#64748b" font-size="10" text-anchor="middle">结果追加到 messages[],循环继续</text>
|
||||
|
||||
<!-- ===== Nag Reminder ===== -->
|
||||
<rect x="100" y="310" width="600" height="56" rx="8" fill="#fffbeb" stroke="#d97706" stroke-width="1"/>
|
||||
<text x="120" y="332" fill="#92400e" font-size="11" font-weight="700">Nag Reminder(催更机制)</text>
|
||||
<text x="120" y="352" fill="#92400e" font-size="10">模型连续 3 轮没调 todo_write → 自动注入 <reminder>Update your todos.</reminder></text>
|
||||
|
||||
<!-- 图例 -->
|
||||
<rect x="100" y="384" width="600" height="28" rx="6" fill="#f1f5f9"/>
|
||||
<rect x="120" y="392" width="12" height="12" rx="2" fill="#f0f4ff" stroke="#2563eb" stroke-width="1"/>
|
||||
<text x="140" y="402" fill="#334155" font-size="10">s04 保留(循环、钩子、5 个基础工具)</text>
|
||||
<rect x="400" y="392" width="12" height="12" rx="2" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
|
||||
<text x="420" y="402" fill="#334155" font-size="10">s05 新增(todo_write + nag reminder)</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
Reference in New Issue
Block a user