How to Build an AI Agent
How to Build an AI Agent
s12

Worktree + Isolation des tâches

Collaboration

Isolate by Directory

694 LOC16 outilsComposable worktree lifecycle + event stream over a shared task board
Each works in its own directory; tasks manage goals, worktrees manage directories, bound by ID

s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > [ s12 ]

"Chacun travaille dans son propre répertoire, pas d'interférence" -- les tâches gèrent les objectifs, les worktrees gèrent les répertoires, liés par ID.

Problème

À s11, les agents peuventclaimer et compléter les tâches de manière autonome. Mais chaque tâche s'exécute dans un répertoire partagé. Deux agents refactorant différents modules en même temps vont entrer en collision : l'agent A modifie config.py, l'agent B modifie config.py, les changements non stagés se mélangent, et aucun ne peut revenir en arrière proprement.

Le tableau de tâches suit quoi faire mais n'a pas d'opinion sur où le faire. La solution : donner à chaque tâche son propre répertoire git worktree. Les tâches gèrent les objectifs, les worktrees gèrent le contexte d'exécution. Les lier par ID de tâche.

Solution

Plan de contrôle (.tasks/)             Plan d'exécution (.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)

Machines à états:
  Tâche:     pending -> in_progress -> completed
  Worktree:  absent  -> active      -> removed | kept

Comment Ça Marche

  1. Créer une tâche. Persister l'objectif d'abord.
TASKS.create("Implement auth refactor")
# -> .tasks/task_1.json  status=pending  worktree=""
  1. Créer un worktree et le lier à la tâche. Passer task_id fait avancer automatiquement la tâche vers in_progress.
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"

Le lien écrit l'état des deux côtés :

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)
  1. Exécuter des commandes dans le worktree. cwd pointe vers le répertoire isolé.
subprocess.run(command, shell=True, cwd=worktree_path,
               capture_output=True, text=True, timeout=300)
  1. Clore. Deux choix :
    • worktree_keep(name) -- préserver le répertoire pour plus tard.
    • worktree_remove(name, complete_task=True) -- supprimer le répertoire, compléter la tâche liée, émettre un événement. Un seul appel gère le démontage + la complétion.
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", ...)
  1. Flux d'événements. Chaque étape du cycle de vie émet vers .worktrees/events.jsonl :
{
  "event": "worktree.remove.after",
  "task": { "id": 1, "status": "completed" },
  "worktree": { "name": "auth-refactor", "status": "removed" },
  "ts": 1730000000
}

Événements émis : worktree.create.before/after/failed, worktree.remove.before/after/failed, worktree.keep, task.completed.

Après un crash, l'état se reconstruit depuis .tasks/ + .worktrees/index.json sur disque. La mémoire de conversation est volatile ; l'état de fichier est durable.

Qu'est-ce qui a Changé par Rapport à s11

ComposantAvant (s11)Après (s12)
CoordinationTableau de tâches (owner/statut)Tableau de tâches + lien worktree explicite
Portée d'exécutionRépertoire partagéRépertoire isolé par tâche
RécupérabilitéStatut de tâche seulementStatut de tâche + index worktree
DémontageComplétion de tâcheComplétion de tâche + keep/remove explicite
Visibilité cycleImplicite dans les logsÉvénements explicites dans .worktrees/events.jsonl

Essayer

python agents/s12_worktree_task_isolation.py
  1. Créer des tâches pour le backend auth et la page de login frontend, puis lister les tâches.
  2. Créer un worktree "auth-refactor" pour la tâche 1, puis lier la tâche 2 à un nouveau worktree "ui-login".
  3. Exécuter "git status --short" dans le worktree "auth-refactor".
  4. Garder le worktree "ui-login", puis lister les worktrees et inspecter les événements.
  5. Supprimer le worktree "auth-refactor" avec complete_task=true, puis lister les tâches/worktrees/événements.