Files
analysis_claude_code/s12_task_system/README.zh.md
2026-07-31 03:15:58 +08:00

229 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# s12: Task System — 目标太大,拆成小任务
[English](README.md) · [中文](README.zh.md) · [日本語](README.ja.md)
s01 → ... → s10 → s11 → `s12` → [s13](../s13_background_tasks/) → s14 → ... → s20 → s21
> *"大目标拆成小任务, 排好序, 持久化"* — 文件持久化的任务图, 多 agent 协作的基础。
>
> **Harness 层**: 任务 — 持久化的目标, 可恢复的进度。
---
## 问题
Agent 接到一个项目:搭数据库、写 API、加测试。它用 s05 的 TodoWrite 列了一张清单,然后开始写 API写到一半发现没数据库表回头补加测试时发现 API 接口签名又变了...
盖房子不能先盖屋顶再打地基。任务之间有先后。任务之间的前置依赖可以表示为有向无环图DAG本章用 `blockedBy` 记录这些依赖。
s05 的 TodoWrite 是当前任务的执行清单,保存在会话内存中。这里需要的是**任务系统**:每个任务是一个 JSON 文件,任务之间有 `blockedBy` 依赖,跨会话持久化在磁盘上。
---
## 解决方案
![Task System Overview](images/task-system-overview.svg)
本章新增 5 个任务工具、`.tasks/` 目录持久化和 `blockedBy` 依赖检查。
TodoWrite vs Task System
| | TodoWrite (s05) | Task System (s12) |
|---|---|---|
| 定位 | 当前任务的执行清单 | 可恢复的任务系统 |
| 存储 | 进程内 / 会话状态 | `.tasks/{id}.json` |
| 依赖 | 无 | `blockedBy` / `blocks` 依赖图 |
| 生命周期 | 当前会话 / 当前任务 | 跨会话保留 |
| 分工 | 不负责任务认领 | `owner` / claim |
| 状态 | pending / in_progress / completed | pending / in_progress / completed |
| 粒度 | Agent 自己的步骤 | 可被认领、追踪、解锁的任务 |
| 更新契约 | 整表替换 | 对单条记录执行创建、读取、更新、列举 |
---
## 工作原理
![Task DAG](images/task-dag.svg)
### Task: 数据结构
每个任务是一个 JSON 文件,存于 `.tasks/` 目录:
```python
@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` 生成。
### create_task: 创建任务
```python
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** 之后才能开始:
```python
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 场景下防止重复认领:
```python
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`。同时扫描所有其他任务,找出**刚刚被解锁**的下游任务:
```python
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` 只显示一行摘要。`get_task` 返回完整的任务 JSON包括 description 和依赖细节。跨会话恢复时Agent 需要读取完整描述才能继续工作:
```python
def get_task(task_id: str) -> str:
task = load_task(task_id)
return json.dumps(asdict(task), indent=2)
```
### 状态机: 两个动作,三个状态
```
pending ──claim──→ in_progress ──complete──→ completed
```
这里的 `claim` / `complete` 是动作,`pending` / `in_progress` / `completed` 是状态:
- **claim_task**: `pending``in_progress`。设置 owner开始工作。
- **complete_task**: `in_progress``completed`。把任务标记为完成,并解锁下游。
### 合起来跑
```python
# 创建有依赖的任务
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 个工具 |
| 新类型 | — | Taskid, 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 回退) |
---
## 试一下
```sh
cd learn-claude-code
python s12_task_system/code.py
```
试试这些 prompt
1. `Create tasks: setup database schema, create API endpoints (depends on schema), write tests (depends on endpoints), write docs (depends on schema)`
2. `List all tasks and their statuses`
3. `Claim the first unblocked task and complete it`
4. `List tasks again — which ones are now unblocked?`
观察重点:`.tasks/` 目录下是否生成了 JSON 文件?完成任务后,被阻塞的任务是否解锁?
---
## 接下来
任务图有了。但有些任务要跑很久——比如全量测试、部署到服务器。Agent 调 LLM 按量计费,不能干等一个慢操作。
s13 Background Tasks → 慢操作放后台。Agent 继续处理其他任务,后台跑完了通知它。
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->