Les prompts sont des suggestions. Les hooks sont des arrêts impératifs.

Pendant des mois, j'ai traité Claude Code comme un développeur junior qui avait simplement besoin de règles de base claires. Mes instructions de projet étaient explicites : ne jamais faire de force-push, ne jamais supprimer de branches, ne jamais exécuter de commandes destructrices. La plupart des soirs, cela fonctionnait. L'agent écrivait des tests, refactorisait des fonctions et ne touchait pas à l'historique git. Puis, un rebase a mal tourné.

La fenêtre de contexte s'est remplie de sorties d'erreurs git. Les marqueurs de conflit, les messages de HEAD détaché et les avertissements de divergence de branche se sont accumulés, jeton par jeton. Enfouie sous ce bruit se trouvait mon instruction polie d'éviter le force-push. Pour le modèle, le texte le plus récent et le plus saillant dans le fil de discussion était le flux d'erreurs. L'attention statistique l'a emporté sur la politique. L'agent a exécuté une commande qui a effacé deux heures de changements locaux non validés. Ce n'était pas malveillant ; c'était une distraction. Cette distinction est importante. Un LLM ne transgresse pas les règles par malveillance. Il les transgresse parce qu'un motif plus bruyant dans la fenêtre de contexte l'emporte temporairement sur une instruction antérieure.

Cet incident a changé ma façon de concevoir la sécurité des agents. Un garde-fou qui fonctionne quatre-vingt-dix-neuf pour cent du temps est un risque. Si le mode de défaillance vous coûte du temps, de l'argent ou des données de production, vous ne pouvez pas le laisser à l'intérieur du prompt. Vous avez besoin d'une application de règles en dehors de la boucle de raisonnement du modèle.

Les hooks de Claude Code résolvent précisément ce problème. Ce sont de petits scripts qui interceptent les appels d'outils à trois moments précis : avant l'exécution d'un outil (PreToolUse), après la fin d'un outil (PostToolUse), et lorsque l'agent décide qu'il a terminé (Stop). Comme ils s'exécutent en tant que code externe, ils ne dépendent pas de la mémoire, de l'humeur ou de la pression contextuelle du modèle. Le modèle peut oublier toutes les instructions que vous lui avez données ; le hook dira toujours non.

Voici le dispositif que j'ai construit après cette soirée perdue.

Le hook de garde : intercepter avant les dégâts

Mon hook PreToolUse inspecte chaque commande Bash avant que le shell ne la touche. Je tiens une liste noire (denylist) stricte de motifs destructeurs. Si la chaîne de commande correspond à quelque chose de dangereux, le hook interrompt l'exécution et renvoie une erreur directement à l'agent.

Les motifs que je bloque sont simples et sans ambiguïté :

  • git push --force ou toute variante force-with-lease en laquelle je n'ai pas encore confiance
  • git reset --hard
  • rm -rf

Il ne s'agit pas de recherche sophistiquée en sécurité. C'est une ceinture de sécurité. Mais le détail critique est ce qui se passe après le blocage.

Je ne renvoie jamais un simple « Bloqué ». Un refus catégorique perd l'agent et peut l'enfermer dans une boucle où il tente des variations de la même commande destructrice. Au lieu de cela, le message d'erreur inclut une voie de sortie. Lorsqu'un hard reset est intercepté par le hook, il dit à l'agent : « Cette commande est bloquée pour protéger le travail non validé. Validez d'abord un point de contrôle (checkpoint), puis réévaluez. » Cette phrase supplémentaire change complètement le comportement de l'agent. Il passe d'une tentative de contrôle des dégâts à la création de sécurité. Le hook n'est pas seulement un mur ; c'est un contrôle de trafic.

J'ai également choisi une liste noire (denylist) plutôt qu'une liste blanche (allowlist) pour les commandes shell. Au début, j'ai envisagé de n'autoriser qu'un ensemble explicite de sous-commandes git sûres. Cela a échoué rapidement. Les agents sont créativement littéraux. Ils exécutent des commandes légitimes mais inattendues comme git stash push -m "wip" ou git branch --show-current pour vérifier l'état. Une liste blanche brise le flux de travail normal dès que le modèle invente une commande valide mais non répertoriée. Une liste noire courte et soigneusement sélectionnée de motifs véritablement destructeurs laisse de la liberté à l'agent tout en protégeant les limites.

Le hook de formatage : automatiser les tâches répétitives

J'avais l'habitude de gaspiller des jetons de prompt en disant à l'agent de « toujours exécuter le formateur après avoir modifié un fichier ». Il l'oubliait la moitié du temps. L'autre moitié, il s'arrêtait et demandait s'il devait formater, gaspillant un appel d'outil pour une décision qui n'avait qu'une seule bonne réponse.

Maintenant, je gère cela avec un hook PostToolUse. Après que l'agent a modifié un fichier, le hook vérifie l'extension du fichier. S'il s'agit de Python, il exécute Ruff. S'il s'agit de JavaScript ou TypeScript, il exécute Prettier. S'il s'agit de Go, il exécute gofmt. L'agent ne sait pas que le formateur existe. Il n'en a pas besoin.

Sortir cela du prompt a eu deux effets. Premièrement, le code est systématiquement propre sans ajouter de charge cognitive au modèle. Deuxièmement, mes instructions de projet sont devenues plus courtes. Chaque « toujours » et « ne jamais » que vous supprimez d'un prompt est un jeton que le modèle peut consacrer à la résolution réelle de problèmes. Le hook gère l'invariant ; le prompt gère l'intention.

La porte de qualité : redéfinir le concept de « terminé »

Le hook Stop s'exécute lorsque l'agent décide qu'il a terminé la tâche et tente de mettre fin à la session. Je ne le laisse pas faire. Au lieu de cela, le hook exécute la suite complète de tests. Si un test échoue, le hook bloque la commande d'arrêt et renvoie le résultat de l'échec à l'agent.

Cela change la définition de l'achèvement. « Terminé » n'est plus un sentiment ressenti par le modèle. C'est une étape de validation mesurable. L'agent ne peut terminer que lorsque le harnais confirme que le code fonctionne. En pratique, cela crée une boucle de rétroaction serrée. L'agent écrit du code, pense avoir fini, appuie sur le bouton d'arrêt et voit immédiatement une trace d'erreur pytest. Il s'autocorrige ensuite, corrige l'erreur d'importation ou l'assertion défaillante, et tente de s'arrêter à nouveau. J'ai vu des agents itérer trois ou quatre fois dans cette boucle sans intervention humaine. Le harnais impose la qualité ; le modèle fournit les correctifs.

Ce que cela enseigne sur l'ingénierie des agents

Construire des systèmes autonomes fiables nécessite un changement de mentalité. On passe de l'écriture de prompts de plus en plus longs à la construction de harnais de plus en plus serrés.

Utilisez les hooks pour l'application des règles et les prompts pour la politique. Si une règle doit s'appliquer cent pour cent du temps, elle doit figurer dans le code, pas en langage naturel. Les prompts excellent dans l'ambiguïté, le goût et l'architecture. Ils sont médiocres pour les invariants. Si une erreur vous coûte une après-midi de temps de récupération ou, pire, de la disponibilité en production, écrivez un hook.

Des prompts plus courts produisent de meilleurs résultats. Lorsque vous déplacez les règles mécaniques dans des scripts, le modèle a moins de choses à mémoriser et moins de risques de se contredire. La fenêtre de contexte de l'agent est une ressource rare. Ne la remplissez pas de rappels de formatage.

Enfin, acceptez que votre rôle évolue. À mesure que les agents gagnent en autonomie, le travail de l'humain passe de la génération de contenu à la conception de garde-fous. Vous construisez le harnais qui décide de ce que le modèle peut toucher, quand il peut terminer et comment il doit se comporter lorsque les choses tournent mal. C'est de l'ingénierie, pas du prompting.

La source qui a inspiré cette approche et des détails d'implémentation supplémentaires peuvent être trouvés ici.

Si vous construisez des systèmes avec des agents IA et souhaitez échanger avec d'autres praticiens, vous pouvez trouver la communauté d'apprentissage GyaanSetu ici.