Sans boucle, pas d'agent
Tout le secret de Claude Code tient en une ligne : while stop_reason == "tool_use"
Que fait concrètement un agent ?
Quand vous lancez Claude Code dans votre terminal et lui demandez de « regrouper tous les commentaires TODO dans une liste », vous le voyez : décider de lancer grep, lire quelques fichiers avec cat, puis produire du Markdown. Le modèle n'exécute pas le code lui-même — il se contente de demander son exécution. Ce qui lui donne l'apparence d'un vrai acteur, c'est la trentaine de lignes de code d'assemblage qui l'entourent.
Cette leçon démonte ce code d'assemblage pour l'examiner. On appelle cela l'agent loop (boucle d'agent), et sa structure est la suivante :
while response.stop_reason == "tool_use": response = LLM(messages, tools) # 1. ask the model execute_tools(response.tool_calls) # 2. run what it asked for messages.append(tool_results) # 3. feed results back
C'est tout. Rien de plus. Le Claude Code de production empile par-dessus des permissions, des hooks, des sous-agents, l'isolation par worktree et la compression mémoire — mais le noyau reste ces quatre lignes.
Intuition clé : à chaque réponse, le modèle dit soit « je veux appeler un outil », soit « j'ai terminé ». La boucle continue tant qu'il est dans le premier état ; elle s'arrête dès qu'il entre dans le second. Le critère est le champ stop_reason de la réponse.
Voir messages[] grandir pas à pas
La boucle ci-dessous simule la tâche suivante : « Quels fichiers se trouvent dans le répertoire courant ? Et lis package.json. » Cliquez sur Step : chaque appui avance d'une action dans la boucle. À gauche, la vue conversation lisible par un humain ; à droite, le vrai tableau messages[] transmis au modèle — observez comment il s'allonge.
Les résultats d'outils doivent réintégrer l'historique
L'erreur la plus fréquente chez les débutants est de traiter « exécuter un outil » comme un effet de bord — une fois exécuté, affaire classée. Mais le modèle ne voit que messages[] lors du prochain cycle d'inférence : ce qui n'y figure pas n'a tout simplement pas existé à ses yeux. Oublier l'étape append suffit à casser toute la boucle.
Les erreurs ne sont pas toutes identiques. Deux variantes courantes :
- Oublier l'append : le modèle ne verra pas le résultat et redemandera le même outil — vous obtenez une boucle infinie.
- Append sans
tool_use_id: l'API Anthropic retourne directement une erreurtool_result must have tool_use_id, et la boucle plante à l'appel API.
Même tâche : « Combien de fichiers Python y a-t-il dans ce projet ? » Les deux versions tournent en parallèle — observez la différence de résultat.
Décoder stop_reason
Chaque réponse du modèle contient un stop_reason. C'est ce champ qui décide si la boucle continue :
tool_use— le modèle veut appeler un outil, la boucle continue.end_turn— le modèle a terminé, on sort de la boucle.max_tokens— la génération a atteint la limite de tokens et a été tronquée, on sort de la boucle (à traiter comme une exception, en avertissant l'utilisateur que la sortie est incomplète).stop_sequence— une séquence d'arrêt personnalisée a été rencontrée, on sort de la boucle.
Écrire à tort « continuer tant qu'il n'y a pas tool_use », ou ignorer max_tokens en le traitant comme un end_turn normal, sont des bugs classiques.
Tester localement
Clonez shareAI-lab/learn-claude-code en local :
git clone https://github.com/shareAI-lab/learn-claude-code cd learn-claude-code pip install -r requirements.txt cp .env.example .env # fill in ANTHROPIC_API_KEY python agents/s01_agent_loop.py
Donnez-lui ensuite une vraie tâche : Compte combien de fichiers .py il y a dans ce dépôt. Vous le verrez émettre un ls -R, lire la sortie, envoyer un find . -name "*.py" | wc -l, puis vous donner la réponse. Tout cela, c'est la boucle en action.