# s12: Worktree + Task Isolation `s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > [ s12 ]` > *"Each works in its own directory, no interference"* -- tasks manage goals, worktrees manage directories, bound by ID. ## Problem By s11, agents can claim and complete tasks autonomously. But every task runs in one shared directory. Two agents refactoring different modules at the same time will collide: agent A edits `config.py`, agent B edits `config.py`, unstaged changes mix, and neither can roll back cleanly. The task board tracks *what to do* but has no opinion about *where to do it*. The fix: give each task its own git worktree directory. Tasks manage goals, worktrees manage execution context. Bind them by task ID. ## Solution ``` Control plane (.tasks/) Execution plane (.worktrees/) +------------------+ +------------------------+ | task_1.json | | auth-refactor/ | | status: in_progress <------> branch: wt/auth-refactor | worktree: "auth-refactor" | task_id: 1 | +------------------+ +------------------------+ | task_2.json | | ui-login/ | | status: pending <------> branch: wt/ui-login | worktree: "ui-login" | task_id: 2 | +------------------+ +------------------------+ | index.json (worktree registry) events.jsonl (lifecycle log) State machines: Task: pending -> in_progress -> completed Worktree: absent -> active -> removed | kept ``` ## How It Works 1. **Create a task.** Persist the goal first. ```python TASKS.create("Implement auth refactor") # -> .tasks/task_1.json status=pending worktree="" ``` 2. **Create a worktree and bind to the task.** Passing `task_id` auto-advances the task to `in_progress`. ```python WORKTREES.create("auth-refactor", task_id=1) # -> git worktree add -b wt/auth-refactor .worktrees/auth-refactor HEAD # -> index.json gets new entry, task_1.json gets worktree="auth-refactor" ``` The binding writes state to both sides: ```python def bind_worktree(self, task_id, worktree): task = self._load(task_id) task["worktree"] = worktree if task["status"] == "pending": task["status"] = "in_progress" self._save(task) ``` 3. **Run commands in the worktree.** `cwd` points to the isolated directory. ```python subprocess.run(command, shell=True, cwd=worktree_path, capture_output=True, text=True, timeout=300) ``` 4. **Close out.** Two choices: - `worktree_keep(name)` -- preserve the directory for later. - `worktree_remove(name, complete_task=True)` -- remove directory, complete the bound task, emit event. One call handles teardown + completion. ```python def remove(self, name, force=False, complete_task=False): self._run_git(["worktree", "remove", wt["path"]]) if complete_task and wt.get("task_id") is not None: self.tasks.update(wt["task_id"], status="completed") self.tasks.unbind_worktree(wt["task_id"]) self.events.emit("task.completed", ...) ``` 5. **Event stream.** Every lifecycle step emits to `.worktrees/events.jsonl`: ```json { "event": "worktree.remove.after", "task": {"id": 1, "status": "completed"}, "worktree": {"name": "auth-refactor", "status": "removed"}, "ts": 1730000000 } ``` Events emitted: `worktree.create.before/after/failed`, `worktree.remove.before/after/failed`, `worktree.keep`, `task.completed`. After a crash, state reconstructs from `.tasks/` + `.worktrees/index.json` on disk. Conversation memory is volatile; file state is durable. ## What Changed From s11 | Component | Before (s11) | After (s12) | |--------------------|----------------------------|----------------------------------------------| | Coordination | Task board (owner/status) | Task board + explicit worktree binding | | Execution scope | Shared directory | Task-scoped isolated directory | | Recoverability | Task status only | Task status + worktree index | | Teardown | Task completion | Task completion + explicit keep/remove | | Lifecycle visibility | Implicit in logs | Explicit events in `.worktrees/events.jsonl` | ## Try It ```sh cd learn-claude-code python agents/s12_worktree_task_isolation.py ``` 1. `Create tasks for backend auth and frontend login page, then list tasks.` 2. `Create worktree "auth-refactor" for task 1, then bind task 2 to a new worktree "ui-login".` 3. `Run "git status --short" in worktree "auth-refactor".` 4. `Keep worktree "ui-login", then list worktrees and inspect events.` 5. `Remove worktree "auth-refactor" with complete_task=true, then list tasks/worktrees/events.`