Leçon 08 · Concurrence

Laisser les tâches tourner sans bloquer l'agent

« Fire and forget — l'agent ne se bloque pas pendant que la commande tourne. »

⏱ ~10 min · 📝 3 widgets interactifs · 🧑‍💻 Basé sur shareAI-lab · s08_background_tasks.py

La douleur des appels synchrones

L'outil bash de S02 est synchrone : subprocess.run(..., timeout=120). Une commande comme npm install prend 90 secondes — pendant ce temps, toute la boucle de l'agent est bloquée. L'utilisateur regarde le terminal sans savoir si ça plante ou si ça tourne.

La solution de s08 : donner à l'agent un outil background_run. Il retourne immédiatement un task_id, la commande tourne dans un thread séparé. L'agent continue sa boucle, fait autre chose ; quand la tâche de fond se termine, elle pousse le résultat dans une notification queue.

def run(self, command: str) -> str:
    task_id = str(uuid.uuid4())[:8]
    self.tasks[task_id] = {"status":"running", ...}
    thread = threading.Thread(target=self._execute, args=(task_id, command), daemon=True)
    thread.start()
    return f"Background task {task_id} started"   # retour immédiat

Comment le résultat revient-il à l'agent ?

La clé : une queue thread-safe. Le thread de fond y pousse le résultat à la fin ; le thread principal draine la queue avant chaque appel LLM et injecte les notifications comme message utilisateur.

def agent_loop(messages):
    while True:
        # Drain bg notifications before each LLM call
        notifs = BG.drain_notifications()
        if notifs:
            messages.append({
                "role": "user",
                "content": f"<background-results>{notif_text}</background-results>",
            })
        response = client.messages.create(...)
        ...

Ainsi, si l'agent spawne une tâche de fond au tour N, et qu'elle se termine pendant le tour N+3, le prochain appel LLM l'inclura automatiquement. Le modèle voit un bloc <background-results> et comprend : « ah, cette tâche est finie, je continue. »

Démonstration de la chronologie

Le widget ci-dessous vous permet de simuler : le thread principal tick une fois par seconde (rythme de la boucle de l'agent) ; vous pouvez spawner des tâches de fond à tout moment. Observez comment les deux fils se rejoignent au « point de drain ».

Quelles commandes méritent d'être en arrière-plan ?

Toutes les commandes ne devraient pas être en arrière-plan. Deux critères :

  1. Durée : en dessous de quelques secondes, une exécution synchrone est plus simple — inutile de gérer une queue.
  2. Dépendance immédiate au résultat : si l'étape suivante nécessite immédiatement ce résultat (par exemple cat file.txt suivi d'un grep), mettre en arrière-plan n'a aucun sens — vous allez quand même attendre.
Interactif

Widget 1 · Timeline · thread principal + 2 threads de fond

Cliquez sur Spawn pour donner du travail à un thread de fond. Le thread principal vérifie la notification queue à chaque tick. Observez les trois swim lanes s'entrelacer.

🧠 Main (agent loop)
⚙ Background thread A
(idle)
⚙ Background thread B
(idle)
queue: []
Interactif

Widget 2 · Fire & Forget · 8 commandes, lesquelles méritent l'arrière-plan ?

Pour chaque commande, choisissez foreground (exécution synchrone) ou background (spawn asynchrone). Pensez aux deux dimensions : durée + dépendance immédiate au résultat.

Correct : 0 / 8
Interactif

Widget 3 · Drain Timing · à quel tour le résultat est-il visible ?

Règle clé : le thread principal draine la queue avant chaque appel LLM. Pour 5 scénarios, répondez à quel tour le résultat d'une tâche de fond entre dans le champ de vision de l'agent.

Correct : 0 / 5