fix: align TodoWrite memory model with task system docs

This commit is contained in:
Gui-Yue
2026-05-21 23:31:30 +08:00
parent 1baf1aca5a
commit 6d48bc978b
17 changed files with 85 additions and 72 deletions

View File

@@ -32,19 +32,21 @@ The dispatch mechanism is unchanged; the new tool is still routed through `TOOL_
## 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:
**The todo_write tool** accepts a list with statuses, keeps it in the current process memory, and displays progress in the terminal:
```python
CURRENT_TODOS: list[dict] = []
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))
global CURRENT_TODOS
CURRENT_TODOS = todos
lines = ["\n## Current Tasks"]
for t in todos:
for t in CURRENT_TODOS:
icon = {"pending": " ", "in_progress": "", "completed": ""}[t["status"]]
lines.append(f" [{icon}] {t['content']}")
print("\n".join(lines))
return f"Updated {len(todos)} tasks"
return f"Updated {len(CURRENT_TODOS)} tasks"
```
The tool definition joins the other 5 in the dispatch map:
@@ -135,7 +137,7 @@ The Agent can plan now. But if a task is too large, say "refactor the entire aut
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.
- **TodoWrite (V1)**: A simple list tool, data maintained in memory AppState (`TodoWriteTool.ts:65-103`). The teaching version also keeps it in process memory and clears it on exit.
- **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.

View File

@@ -32,19 +32,21 @@ Agent は作業を開始する。3 つのファイルをリネーム、テスト
## 仕組み
**todo_write ツール**、ステータス付きのリストを受け取り、`.tasks/current_todos.json` に永続化(教育版は観察用にディスクに書き込む)、端末に進捗を表示する:
**todo_write ツール**、ステータス付きのリストを受け取り、現在のプロセスメモリに保持し、端末に進捗を表示する:
```python
CURRENT_TODOS: list[dict] = []
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))
global CURRENT_TODOS
CURRENT_TODOS = todos
lines = ["\n## Current Tasks"]
for t in todos:
for t in CURRENT_TODOS:
icon = {"pending": " ", "in_progress": "", "completed": ""}[t["status"]]
lines.append(f" [{icon}] {t['content']}")
print("\n".join(lines))
return f"Updated {len(todos)} tasks"
return f"Updated {len(CURRENT_TODOS)} tasks"
```
ツール定義は他の 5 つと一緒にディスパッチマップに追加される:
@@ -135,7 +137,7 @@ Agent は計画できるようになった。しかしタスクが大きすぎ
CC には二つのタスクシステムが共存している(`tasks.ts:133-139`
- **TodoWriteV1**:シンプルなリストツール、データはメモリ AppState で管理(`TodoWriteTool.ts:65-103`)。教育版は観察用に `.tasks/current_todos.json` に書き込むが、実際の V1 はディスクに書き込まない
- **TodoWriteV1**:シンプルなリストツール、データはメモリ AppState で管理(`TodoWriteTool.ts:65-103`)。教育版もプロセスメモリに保持し、終了時に消える
- **Task SystemV2 = s12**ファイル永続化、依存グラフ、並行ロック、ownership
切り替えは `isTodoV2Enabled()` で制御される。現在のソースコードの実装:対話型セッションでは V2 がデフォルトで有効、非対話型セッションSDKでは V1 がデフォルトで有効。`CLAUDE_CODE_ENABLE_TASKS` 環境変数を設定するとセッション種別に関わらず V2 が強制有効になる。ソースコメント「Force-enable tasks in non-interactive mode」は環境変数パスの用途を説明しており、デフォルト分岐の戻り値のセマンティクスとは異なるため注意。

View File

@@ -32,19 +32,21 @@ dispatch 机制不变,新工具仍然走 `TOOL_HANDLERS[block.name]` 分发。
## 工作原理
**todo_write 工具**,接收一个带状态的列表,持久化到 `.tasks/current_todos.json`(教学版写盘以便观察),同时在终端显示进度:
**todo_write 工具**,接收一个带状态的列表,保存在当前进程内存中,同时在终端显示进度:
```python
CURRENT_TODOS: list[dict] = []
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))
global CURRENT_TODOS
CURRENT_TODOS = todos
lines = ["\n## Current Tasks"]
for t in todos:
for t in CURRENT_TODOS:
icon = {"pending": " ", "in_progress": "", "completed": ""}[t["status"]]
lines.append(f" [{icon}] {t['content']}")
print("\n".join(lines))
return f"Updated {len(todos)} tasks"
return f"Updated {len(CURRENT_TODOS)} tasks"
```
工具定义和其他 5 个工具一起加入 dispatch map
@@ -135,7 +137,7 @@ s06 Subagent → 把大任务拆成子任务,每个子任务派一个独立的
CC 中有两套任务系统并存(`tasks.ts:133-139`
- **TodoWriteV1**:一个简单的列表工具,数据在内存 AppState 中维护(`TodoWriteTool.ts:65-103`)。教学版写盘到 `.tasks/current_todos.json` 是为了可观察性,真实 V1 不写盘
- **TodoWriteV1**:一个简单的列表工具,数据在内存 AppState 中维护(`TodoWriteTool.ts:65-103`)。教学版也保存在进程内存里,退出后清空
- **Task SystemV2 = s12**文件持久化、依赖图、并发锁、ownership
切换由 `isTodoV2Enabled()` 控制。当前源码的实现逻辑:交互式会话中 V2 默认启用非交互式会话SDK中 V1 默认启用;设置 `CLAUDE_CODE_ENABLE_TASKS` 环境变量可强制启用 V2。注意源码注释 "Force-enable tasks in non-interactive mode" 描述的是 env var 路径的用途,和默认分支的返回值语义不同,阅读时需区分。
@@ -153,4 +155,4 @@ Task System 相比 TodoWrite 的核心增量:
</details>
<!-- translation-sync: zh@v1, en@v0, ja@v0 -->
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->

View File

@@ -12,7 +12,7 @@ s05: TodoWrite — add a planning tool on top of s04 hooks.
todo_write ← NEW
+------------------+
|
.tasks/current_todos.json
in-memory current_todos
|
if rounds_since_todo >= 3:
inject <reminder>
@@ -28,7 +28,7 @@ Run: python s05_todo_write/code.py
Needs: pip install anthropic python-dotenv + ANTHROPIC_API_KEY in .env
"""
import os, subprocess, json
import os, subprocess
from pathlib import Path
try:
@@ -45,9 +45,9 @@ 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"]
CURRENT_TODOS: list[dict] = []
# s05 change: SYSTEM prompt adds planning guidance
SYSTEM = (
@@ -122,20 +122,20 @@ def run_glob(pattern: str) -> str:
# ═══════════════════════════════════════════════════════════
def run_todo_write(todos: list) -> str:
global CURRENT_TODOS
# 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))
CURRENT_TODOS = todos
lines = ["\n\033[33m## Current Tasks\033[0m"]
for t in todos:
for t in CURRENT_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"
return f"Updated {len(CURRENT_TODOS)} tasks"
TOOLS = [
{"name": "bash", "description": "Run a shell command.",

View File

@@ -73,7 +73,7 @@
<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>
<text x="560" y="196" fill="#64748b" font-size="8" text-anchor="middle">in-memory TODO list</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"/>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -73,7 +73,7 @@
<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>
<text x="560" y="196" fill="#64748b" font-size="8" text-anchor="middle">メモリ内 TODO リスト</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"/>

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -73,7 +73,7 @@
<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>
<text x="560" y="196" fill="#64748b" font-size="8" text-anchor="middle">进程内 TODO 列表</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"/>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB