better doc

This commit is contained in:
CrazyBoyM
2026-02-27 01:11:57 +08:00
parent aea8844bac
commit 665831c774
46 changed files with 1217 additions and 3505 deletions

View File

@@ -1,29 +1,14 @@
# s07: Tasks
> Tasks are persisted as JSON files with a dependency graph, so state survives context compression and can be shared across agents.
`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] s08 > s09 > s10 > s11 > s12`
> *"State survives /compact"* -- file-based state outlives context compression.
## Problem
In-memory state (for example the TodoManager from s03) is fragile under compression (s06). Once earlier turns are compacted into summaries, in-memory todo state is gone.
In-memory state (TodoManager from s03) dies when context compresses (s06). After auto_compact replaces messages with a summary, the todo list is gone. The agent can only reconstruct from summary text -- lossy and error-prone.
s06 -> s07 is the key transition:
1. Todo list state in memory is conversational and lossy.
2. Task board state on disk is durable and recoverable.
A second issue is visibility: in-memory structures are process-local, so teammates cannot reliably share that state.
## When to Use Task vs Todo
From s07 onward, Task is the default. Todo remains for short linear checklists.
## Quick Decision Matrix
| Situation | Prefer | Why |
|---|---|---|
| Short, single-session checklist | Todo | Lowest ceremony, fastest capture |
| Cross-session work, dependencies, or teammates | Task | Durable state, dependency graph, shared visibility |
| Unsure which one to use | Task | Easier to simplify later than migrate mid-run |
File-based tasks solve this: write state to disk, and it survives compression, process restarts, and eventually multi-agent sharing (s09+).
## Solution
@@ -45,29 +30,28 @@ Dependency resolution:
## How It Works
1. TaskManager provides CRUD with one JSON file per task.
1. TaskManager: one JSON file per task, CRUD with dependency graph.
```python
class TaskManager:
def create(self, subject: str, description: str = "") -> str:
task = {
"id": self._next_id,
"subject": subject,
"description": description,
"status": "pending",
"blockedBy": [],
"blocks": [],
"owner": "",
}
def __init__(self, tasks_dir: Path):
self.dir = tasks_dir
self.dir.mkdir(exist_ok=True)
self._next_id = self._max_id() + 1
def create(self, subject, description=""):
task = {"id": self._next_id, "subject": subject,
"status": "pending", "blockedBy": [],
"blocks": [], "owner": ""}
self._save(task)
self._next_id += 1
return json.dumps(task, indent=2)
```
2. Completing a task clears that dependency from other tasks.
2. Completing a task clears its ID from every other task's `blockedBy` list.
```python
def _clear_dependency(self, completed_id: int):
def _clear_dependency(self, completed_id):
for f in self.dir.glob("task_*.json"):
task = json.loads(f.read_text())
if completed_id in task.get("blockedBy", []):
@@ -85,63 +69,22 @@ def update(self, task_id, status=None,
task["status"] = status
if status == "completed":
self._clear_dependency(task_id)
if add_blocks:
task["blocks"] = list(set(task["blocks"] + add_blocks))
for blocked_id in add_blocks:
blocked = self._load(blocked_id)
if task_id not in blocked["blockedBy"]:
blocked["blockedBy"].append(task_id)
self._save(blocked)
self._save(task)
```
4. Task tools are added to the dispatch map.
4. Four task tools go into the dispatch map.
```python
TOOL_HANDLERS = {
# ...base tools...
"task_create": lambda **kw: TASKS.create(kw["subject"]),
"task_update": lambda **kw: TASKS.update(kw["task_id"],
kw.get("status")),
"task_update": lambda **kw: TASKS.update(kw["task_id"], kw.get("status")),
"task_list": lambda **kw: TASKS.list_all(),
"task_get": lambda **kw: TASKS.get(kw["task_id"]),
}
```
## Key Code
TaskManager with dependency graph (from `agents/s07_task_system.py`, lines 46-123):
```python
class TaskManager:
def __init__(self, tasks_dir: Path):
self.dir = tasks_dir
self.dir.mkdir(exist_ok=True)
self._next_id = self._max_id() + 1
def _load(self, task_id: int) -> dict:
path = self.dir / f"task_{task_id}.json"
return json.loads(path.read_text())
def _save(self, task: dict):
path = self.dir / f"task_{task['id']}.json"
path.write_text(json.dumps(task, indent=2))
def create(self, subject, description=""):
task = {"id": self._next_id, "subject": subject,
"status": "pending", "blockedBy": [],
"blocks": [], "owner": ""}
self._save(task)
self._next_id += 1
return json.dumps(task, indent=2)
def _clear_dependency(self, completed_id):
for f in self.dir.glob("task_*.json"):
task = json.loads(f.read_text())
if completed_id in task.get("blockedBy", []):
task["blockedBy"].remove(completed_id)
self._save(task)
```
From s07 onward, Task is the default for multi-step work. Todo remains for quick checklists.
## What Changed From s06
@@ -152,14 +95,6 @@ class TaskManager:
| Dependencies | None | `blockedBy + blocks` graph |
| Persistence | Lost on compact | Survives compression |
## Design Rationale
File-based state survives compaction and process restarts. The dependency graph preserves execution order even when conversation details are forgotten. This turns transient chat context into durable work state.
Durability still needs a write discipline: reload task JSON before each write, validate expected `status/blockedBy`, then persist atomically. Otherwise concurrent writers can overwrite each other.
Course-level implication: s07+ defaults to Task because it better matches long-running and collaborative engineering workflows.
## Try It
```sh
@@ -167,8 +102,6 @@ cd learn-claude-code
python agents/s07_task_system.py
```
Suggested prompts:
1. `Create 3 tasks: "Setup project", "Write code", "Write tests". Make them depend on each other in order.`
2. `List all tasks and show the dependency graph`
3. `Complete task 1 and then list tasks to see task 2 unblocked`