7. Tasks
"Break big goals into small tasks, order them, persist to disk"
New to this?
What is a task graph (DAG)?
A Directed Acyclic Graph where each task can list other tasks it depends on (blockedBy). Task 4 can't start until tasks 2 and 3 are done. This models real-world dependencies between work items.
Why persist tasks to disk instead of memory?
Context compression (Context Compact session) can wipe the in-memory todo list. Tasks saved as JSON files on disk survive compression, crashes, and even multi-agent handoffs — any agent can pick up where another left off.
What does 'ready' mean for a task?
A task is ready when its status is 'pending' AND all tasks in its blockedBy list are 'completed'. The TaskManager computes ready tasks automatically so the agent just asks 'what can I do now?'
The Problem
The TodoWrite session’s TodoManager is a flat checklist in memory: no ordering, no dependencies, no status beyond done-or-not. Real goals have structure — task B depends on task A, tasks C and D can run in parallel, task E waits for both C and D.
Without explicit relationships, the agent can’t tell what’s ready, what’s blocked, or what can run concurrently. And because the list lives only in memory, context compression (Context Compact session) wipes it clean.
The Solution
Promote the checklist into a task graph persisted to disk. Each task is a JSON file with status and dependencies (blockedBy). The graph answers three questions at any moment:
- What’s ready? — tasks with
pendingstatus and emptyblockedBy - What’s blocked? — tasks waiting on unfinished dependencies
- What’s done? —
completedtasks, whose completion automatically unblocks dependents
.tasks/
task_1.json {"id":1, "status":"completed"}
task_2.json {"id":2, "blockedBy":[1], "status":"pending"}
task_3.json {"id":3, "blockedBy":[1], "status":"pending"}
task_4.json {"id":4, "blockedBy":[2,3], "status":"pending"}
Task graph (DAG):
1 --> 2 --> 4
1 --> 3 --> 4
When task 1 completes: 2 and 3 become ready.
When 2 and 3 complete: 4 becomes ready.
How It Works
- Each task is a JSON file. The TaskManager reads all files and builds the graph in memory.
import json
from pathlib import Path
class TaskManager:
def __init__(self, tasks_dir: str = ".tasks"):
self.dir = Path(tasks_dir)
self.dir.mkdir(exist_ok=True)
def _load_all(self) -> list:
tasks = []
for f in sorted(self.dir.glob("task_*.json")):
tasks.append(json.loads(f.read_text()))
return tasks
def create(self, title: str, blocked_by: list = None) -> dict:
tasks = self._load_all()
new_id = max((t["id"] for t in tasks), default=0) + 1
task = {"id": new_id, "title": title,
"status": "pending", "blockedBy": blocked_by or []}
path = self.dir / f"task_{new_id}.json"
path.write_text(json.dumps(task, indent=2))
return task
def complete(self, task_id: int) -> str:
path = self.dir / f"task_{task_id}.json"
task = json.loads(path.read_text())
task["status"] = "completed"
path.write_text(json.dumps(task, indent=2))
return f"Task {task_id} completed."
def ready(self) -> list:
tasks = self._load_all()
done_ids = {t["id"] for t in tasks if t["status"] == "completed"}
return [
t for t in tasks
if t["status"] == "pending"
and all(dep in done_ids for dep in t.get("blockedBy", []))
]
- The agent gets three tools:
create_task,complete_task, andlist_ready_tasks.
TASK_MANAGER = TaskManager()
TOOL_HANDLERS = {
# ...base tools...
"create_task": lambda **kw: str(TASK_MANAGER.create(
kw["title"], kw.get("blocked_by", []))),
"complete_task": lambda **kw: TASK_MANAGER.complete(kw["task_id"]),
"list_ready_tasks": lambda **kw: json.dumps(TASK_MANAGER.ready(), indent=2),
}
- The agent’s workflow becomes: create the task graph, then work through ready tasks one by one.
Agent: "I need to refactor auth module."
1. create_task("Read current auth code") -> id=1
2. create_task("Write new AuthManager", blocked_by=[1]) -> id=2
3. create_task("Update tests", blocked_by=[2]) -> id=3
4. create_task("Update docs", blocked_by=[2]) -> id=4
list_ready_tasks() -> [task 1]
[works on task 1]
complete_task(1)
list_ready_tasks() -> [task 2]
...
What Changed From Context Compact
| Component | Before (Context Compact) | After (Tasks) |
|---|---|---|
| Planning | In-memory checklist | Disk-persisted task graph |
| Dependencies | None | blockedBy list |
| Parallelism | Sequential only | Explicit parallel-ready detection |
| Survives | Nothing | Compression, crashes, handoffs |
Key Takeaway
Persisting tasks to disk is what makes the agent’s plans durable. The task graph encodes not just what to do but in what order and what can run in parallel. Combined with the Context Compact session’s compression, the agent can work on truly large goals across many turns without losing track.
Interactive Code Walkthrough
1class TaskManager:2 def __init__(self, tasks_dir: str = ".tasks"):3 self.dir = Path(tasks_dir)4 self.dir.mkdir(exist_ok=True)5 6 def _load_all(self) -> list:7 tasks = []8 for f in sorted(self.dir.glob("task_*.json")):9 tasks.append(json.loads(f.read_text()))10 return tasks11 12 def create(self, title: str, blocked_by: list = None) -> dict:13 tasks = self._load_all()14 new_id = max((t["id"] for t in tasks), default=0) + 115 task = {"id": new_id, "title": title,16 "status": "pending", "blockedBy": blocked_by or []}17 path = self.dir / f"task_{new_id}.json"18 path.write_text(json.dumps(task, indent=2))19 return task20 21 def ready(self) -> list:22 tasks = self._load_all()23 done_ids = {t["id"] for t in tasks if t["status"] == "completed"}24 return [25 t for t in tasks26 if t["status"] == "pending"27 and all(dep in done_ids for dep in t.get("blockedBy", []))28 ]29 TaskManager instance points to a .tasks/ directory. mkdir(exist_ok=True) means the first call creates the directory automatically — no setup step required.Draw the task dependency graph for building a web app: 'setup project', 'create database schema' (depends on setup), 'build API' (depends on schema), 'build frontend' (depends on API), 'write tests' (depends on API + frontend). Which tasks can run in parallel?
Hint
After setup and schema, API is the only blocker. Frontend depends on API, but tests depend on both.
Create 5 tasks with dependencies, persist them as JSON files, and verify that get_ready() returns only unblocked tasks.
Hint
A task is ready when its status is 'pending' and all tasks in its blockedBy list are 'completed'.
Add cycle detection to the task graph. If a user creates tasks A→B→C→A, detect the cycle before the agent gets stuck in an infinite wait.
Hint
Use depth-first search with a 'visiting' state to detect back edges in the dependency graph.