Comment confier des tâches à un agent IA : une fonctionnalité à la fois, liste des fonctionnalités et critères de fin
On confie les tâches à un agent IA une par une : une seule fonctionnalité est en cours à la fois, elle a un comportement observable et une commande de vérification exécutable, et tout ce qui est remarqué en chemin est noté dans le backlog plutôt que corrigé. L'état de toutes les fonctionnalités est conservé dans une liste lisible par machine (par exemple feature_list.json), et seul un script de vérification peut la passer à « terminé », pas l'agent lui-même.
Pourquoi l'agent s'attaque à tout à la fois
Vous demandez à l'agent de réaliser une inscription. Deux heures plus tard, le diff compte douze fichiers modifiés et huit cents nouvelles lignes : modèle d'utilisateur, vérification de l'e-mail, service de messagerie branché, hachage des mots de passe, middleware refactorisé et tests déplacés. Tout est fortement lié, et aucun scénario ne passe de bout en bout. La session suivante ouvre ce code et ne sait pas par où commencer.
La cause n'est pas un « mauvais modèle ». Un humain a le sentiment de « ça suffit pour aujourd'hui » et une fatigue ordinaire : ce sont eux qui limitent l'ampleur du travail. L'agent n'a ni l'un ni l'autre. Ajouter « et je vais aussi corriger la gestion des erreurs » lui coûte quelques dizaines de tokens, mais le coût pour le projet est réel : l'attention se disperse, le couplage augmente, et rien n'est réellement terminé.
Il y a aussi une arithmétique simple. Soit C la capacité utile du contexte, et k le nombre de tâches que l'agent porte en même temps. Chaque tâche reçoit environ C/k de ressource de raisonnement. Quand cette valeur passe sous ce qui est nécessaire pour finir une seule tâche, aucune ne se termine. C'est pourquoi la bonne réponse à la question de la façon de confier des tâches à un agent IA ne commence pas par la formulation du prompt, mais par une contrainte : une seule tâche en cours.
La règle d'une fonctionnalité (WIP=1)
WIP, pour work in progress, désigne le nombre de tâches en cours. Pour un agent, on le fixe à un : à tout moment, une seule fonctionnalité est active, et la suivante ne commence qu'après que la courante a passé la vérification. On écrit la règle dans le fichier d'instructions du projet (AGENTS.md ou CLAUDE.md), pour que l'agent la voie à chaque session :
## Règles de travail
- À tout moment, il y a EXACTEMENT UNE fonctionnalité dans l'état `active`.
- Commencer la fonctionnalité suivante seulement quand la courante est passée à `passing`.
- Il est interdit de « profiter de l'occasion » pour refactoriser, renommer, réorganiser
les dossiers ou corriger des bogues sans rapport. Si un problème est trouvé, notez-le dans
`BACKLOG.md` et continuez.
- Toute modification hors du périmètre de la fonctionnalité active est une violation,
même si elle est utile.
Le dernier point compte plus qu'il n'y paraît. L'agent juge sincèrement ses « au passage » utiles, et c'est souvent vrai. Mais une correction utile hors périmètre mélange tout de même deux tâches dans un même diff, et l'on ne peut plus les annuler ni les vérifier séparément.
BACKLOG.md : où ranger les « au passage »
On ne peut pas simplement interdire l'envie de « corriger au passage » : il lui faut une issue légitime. Cette issue est le fichier BACKLOG.md : l'agent y note le problème en une ou deux lignes et revient à la fonctionnalité en cours. L'information n'est pas perdue, et le périmètre ne s'élargit pas. Sans backlog, la règle d'une fonctionnalité finira par être contournée, car « laisser le bogue de côté en silence » paraît à l'agent pire que la violation.
# BACKLOG.md
- [ ] middleware/errors.py : format d'erreur différent dans /auth et /orders
- [ ] tests/ : les fixtures d'utilisateur sont dupliquées dans trois fichiers
- [ ] la vérification de l'e-mail à l'inscription est-elle nécessaire ? — à demander
Critères de fin : « terminé » doit être une commande
La seconde moitié du problème : l'agent décide lui-même que la tâche est terminée. « Le code a l'air correct », « la fonction existe », « il n'y a pas d'erreur de compilation » : ce sont des impressions, pas des faits. Le critère de fin d'une tâche pour un agent doit être une commande exécutable au résultat sans ambiguïté : elle passe ou elle ne passe pas.
F01 : inscription de l'utilisateur
verification: pytest tests/e2e/test_auth.py::test_register_flow
state: active
La différence entre un critère faible et un critère strict apparaît clairement sur des exemples :
| Critère faible | Critère strict |
|---|---|
| Le code compile | pytest tests/e2e/test_cart.py -q passe |
| Pas d'erreur de syntaxe | curl sur /cart renvoie le code 200 |
La fonction addToCart existe | Après l'ajout d'un produit, GET /cart l'affiche avec le bon total |
| Ça a l'air correct | npx playwright test cart.spec.ts passe |
Une bonne commande de vérification est déterministe (même code, même résultat), se lance sans intervention humaine et contrôle le comportement de l'extérieur, tel que le verra l'utilisateur ou le client de l'API. Si vous n'arrivez pas à écrire une telle commande, c'est le signe que la tâche est mal formulée.
Découpage : quelle taille doit avoir une fonctionnalité
La question qui revient après « une fonctionnalité à la fois » est : que compte-t-on comme une fonctionnalité ? Une tâche trop large ne se termine pas en une session, une tâche trop étroite se noie dans les frais de lancement, de contexte et de vérification.
- Trop large : « implémenter le panier ». C'est une semaine de travail et une dizaine de scénarios.
- Juste ce qu'il faut : « l'utilisateur peut ajouter un produit au panier et le voir dedans ». Un seul comportement observable, un seul scénario de vérification.
- Trop étroit : « créer le champ quantity dans le modèle CartItem ». C'est une étape d'implémentation, pas un comportement ; la vérifier séparément n'a pas de sens.
Test pratique de taille : une fonctionnalité = une session d'agent = un commit significatif = un scénario de bout en bout (E2E). Si la fonctionnalité ne tient pas dans une session, découpez davantage. Si l'on ne peut pas écrire de scénario distinct pour elle, elle est trop petite : fusionnez-la avec sa voisine.
Exemple de découpage de la tâche « panier »
Une exigence large se décompose en unités atomiques, chacune avec un seul comportement et sa propre commande de vérification :
- Ajouter un produit au panier et le voir dans la liste.
- Modifier la quantité d'un produit : le total se recalcule.
- Supprimer un produit du panier.
- Le panier d'un visiteur est conservé après la connexion au compte.
- Impossible d'ajouter un produit indisponible : une erreur claire est affichée.
Contrôle final du découpage : aucune unité n'oblige à toucher « au passage » une autre. Si le point 4 exige de réécrire le point 1, l'ordre ou les limites ont été mal choisis.
Liste des fonctionnalités : feature_list.json
La règle d'une fonctionnalité maintient l'ordre dans une session. Mais qui se souvient, d'une session à l'autre, de ce qui est terminé ? Une note dans le chat du type « auth et catalogue faits, panier presque prêt, mais il y a des bogues » n'apporte rien à la session suivante : que signifie « presque », quels tests passent, qu'est-ce qui bloque le paiement ? On ne le sait pas. Chaque nouvelle session ressemble à un nouveau conducteur de la même voiture : si les notes n'existaient que dans la tête de l'ancien, le nouveau ne sait rien.
C'est pourquoi on sort l'état dans un fichier lisible par machine. Dans le cours, il s'agit de .agent/feature_list.json :
[
{
"id": "F01",
"behavior": "L'utilisateur s'inscrit avec son e-mail et un mot de passe, puis il est connecté",
"verification": "pytest tests/e2e/test_auth.py::test_register_flow",
"state": "passing",
"evidence": "a3f9c12 : 1 passed in 2.31s"
},
{
"id": "F02",
"behavior": "L'utilisateur ajoute un produit au panier et le voit dans la liste",
"verification": "pytest tests/e2e/test_cart.py::test_add_item -q",
"state": "active"
},
{
"id": "F03",
"behavior": "Paiement de la commande par carte",
"verification": "pytest tests/e2e/test_payment.py -q",
"state": "blocked",
"blocker": "pas de clés de test pour la passerelle de paiement"
}
]
Chaque entrée a trois champs obligatoires : la description du comportement (observable de l'extérieur), la commande de vérification (exécutable sans humain) et l'état. Un quatrième champ facultatif, evidence, contient le hash du commit et la sortie de la vérification : la preuve que le passage à passing a bien eu lieu.
Pourquoi du JSON et non une liste Markdown ? Ce fichier est lu non seulement par l'agent mais aussi par des scripts : le planificateur choisit la fonctionnalité suivante en not_started, le vérificateur prend la commande de contrôle, le rapport de session se construit automatiquement, et la répartition des états donne des indicateurs de santé du projet. La liste des fonctionnalités est l'épine dorsale du harnais, pas un aide-mémoire.
Quatre états et transitions autorisées
| Transition | Quand |
|---|---|
not_started → active | La fonctionnalité est prise en charge ; aucune autre n'est active |
active → passing | Le script de vérification se termine avec le code 0 |
active → blocked | Un blocage externe ; la cause est notée dans blocker |
blocked → active | Le blocage est levé |
Il n'existe pas d'autres transitions. En particulier, il n'y a pas de chemin vers passing qui contourne la vérification, ni d'état « presque terminé ».
Verrou passing : l'agent peut demander, mais pas déclarer
Règle centrale de tout ce système : l'agent ne modifie pas le champ state à la main. Il peut lancer la vérification, mais ne peut pas déclarer le résultat lui-même : c'est le script qui le fait. La différence entre « l'agent a rendu compte » et « le script a confirmé » est la différence entre un texte et un fait. Ces règles aussi sont écrites dans le fichier d'instructions :
## Règles de la liste des fonctionnalités
- Fichier : `.agent/feature_list.json`. C'est la seule source de vérité sur le périmètre.
- Exactement une fonctionnalité dans l'état `active`.
- Tu NE modifies PAS le champ `state` à la main. Il est changé par `scripts/verify.sh`.
- Pour passer une fonctionnalité à `passing`, lance `./scripts/verify.sh F02` et obtiens le code 0.
- Si une fonctionnalité est bloquée, passe-la à `blocked` et indique obligatoirement `blocker`.
- Les nouvelles fonctionnalités ne s'ajoutent qu'en `not_started` et seulement avec une commande de vérification.
Le script lui-même vérifie à deux niveaux : d'abord l'exécution globale du projet (la fonctionnalité ne doit rien casser d'autre), puis la commande propre à la fonctionnalité. Un succès local avec une régression globale n'est pas un succès.
#!/usr/bin/env bash
# Le seul composant autorisé à mettre passing.
set -uo pipefail
FID="$1"
FILE=".agent/feature_list.json"
CMD=$(jq -r --arg id "$FID" '.[] | select(.id==$id) | .verification' "$FILE")
if [ -z "$CMD" ] || [ "$CMD" = "null" ]; then
echo "FAIL: pas de commande de vérification pour $FID"; exit 2
fi
echo "== contrôle global =="
make check || { echo "FAIL: contrôle global rouge, $FID ne peut pas être passing"; exit 1; }
echo "== contrôle de la fonctionnalité : $CMD =="
if OUT=$(eval "$CMD" 2>&1); then
SHA=$(git rev-parse --short HEAD)
LAST=$(printf '%s\n' "$OUT" | tail -n 1)
TMP=$(mktemp)
jq --arg id "$FID" --arg ev "$SHA: $LAST" \
'map(if .id==$id then .state="passing" | .evidence=$ev else . end)' \
"$FILE" > "$TMP" && mv "$TMP" "$FILE"
echo "PASS: $FID"
else
printf '%s\n' "$OUT"
echo "FAIL: $FID"; exit 1
fi
La règle « ne modifie pas state » écrite en texte reste une demande. Pour en faire une contrainte, on peut verrouiller l'écriture mécaniquement : par un contrôle en CI qui compare evidence à l'historique des commits, ou par des droits d'accès restreints de l'agent dans votre outil. Le principe est le même que partout dans le harnais : une règle importante doit être soutenue par un mécanisme.
Comment mesurer que l'agent mène les tâches à terme
Une mesure séduisante est le volume de code par session. En pratique, elle agit à l'envers : un gros diff signifie souvent que l'agent s'est dispersé sur plusieurs directions. Comptez les fonctionnalités à l'état passing, pas les lignes.
Une métrique pratique du cours est le VCR (Verified Completion Rate) : le nombre de tâches vérifiées divisé par le nombre de tâches activées. La règle est simple : tant que le VCR est inférieur à un, on ne peut pas activer de nouvelles tâches. C'est une pression en retour que le harnais exerce sur l'agent, et un moyen de rendre le travail inachevé visible par un chiffre, et non par une impression.
Erreurs typiques dans la formulation des tâches
- Tâche sans commande de vérification. « Fais l'inscription » sans scénario : l'agent décide lui-même où s'arrêter, et il décide mal.
- Plusieurs fonctionnalités dans une seule demande. « Fais le panier et le paiement » : ce sont deux tâches actives, et aucune ne sera menée à terme.
- État uniquement dans le chat. La progression notée dans la conversation disparaît à la fin de la session. Elle doit vivre dans un fichier.
- Auto-rapport au lieu de vérification. « L'agent a dit que les tests passent » n'est pas la même chose que le code de retour du script.
- Pas de backlog. Interdire les « au passage » sans endroit où noter ce qu'on remarque : l'agent contourne l'interdiction.
Quand une fonctionnalité à la fois ne suffit plus
Le schéma « une fonctionnalité, une session, une vérification » fonctionne bien pour un agent dans un seul cycle. Quand les tâches deviennent nombreuses et que des branches indépendantes apparaissent, on l'étend à un système de plusieurs cycles : un nœud de planification, des nœuds de réalisation, un nœud de vérification et des arêtes entre eux, parallèles, conditionnelles, avec retour à la réalisation si la vérification échoue. Mais là aussi, chaque nœud de réalisation suit la même règle : une tâche, un critère de fin exécutable, un état hors de la tête de l'agent. Le graphe ne remplace pas la discipline d'une fonctionnalité, il s'appuie dessus.
Un avertissement issu de la même analyse : une boucle optimise honnêtement la métrique qu'on lui donne. Si le critère de fin est mal choisi, par exemple « le ticket est fermé » au lieu de « le problème est résolu », l'agent apprendra à remplir le critère, et non à résoudre la tâche. La formulation du comportement et de la commande de vérification mérite donc plus de temps qu'il n'y paraît.
En résumé
Pour aller à l'essentiel, on confie les tâches à un agent IA une par une, avec un comportement observable et une commande de vérification exécutable, un backlog pour tout ce qui est remarqué en chemin, et une liste des fonctionnalités dans un fichier où le passage à « terminé » est fait par un script, et non par l'agent. Le frein appartient au harnais : l'agent ne le mettra pas de lui-même. La règle WIP=1, feature_list.json et verify.sh sont présentés en pratique dans le cours de harness engineering, sur un projet de bout en bout.
Questions fréquentes
Peut-on donner plusieurs tâches à la fois à l'agent si elles sont petites ?
On peut les ranger dans la liste des fonctionnalités, mais une seule doit être en cours à la fois. L'agent prend la suivante seulement après que la courante a passé la vérification : ainsi chaque tâche est menée à son terme.
Que faire si l'on ne peut pas écrire de vérification automatique pour une tâche ?
Cela signifie en général que la tâche est formulée comme une action et non comme un comportement. Reformulez-la à travers ce que verra l'utilisateur ou le client de l'API ; pour la partie visuelle, des tests e2e dans le navigateur conviennent.
Qui écrit feature_list.json : l'humain ou l'agent ?
Il est pratique de dresser la liste initiale ensemble : l'agent propose un découpage, l'humain le corrige. Ensuite, l'agent peut ajouter de nouvelles fonctionnalités, mais seulement en état not_started et avec une commande de vérification.
En quoi feature_list.json diffère-t-il des tickets d'un tracker ?
Le tracker est conçu pour les humains, la liste des fonctionnalités pour une lecture par machine : chaque entrée a une commande exécutable et un état strict que seul le script modifie. On peut les synchroniser, mais l'agent a besoin d'un fichier dans le dépôt.
La règle d'une fonctionnalité ne ralentit-elle pas le travail ?
Les sessions deviennent plus courtes, mais la part des tâches menées à terme augmente. Le temps qui allait à démêler du code à moitié fini, mêlant trois fonctionnalités, est économisé.
Ce sujet est traité en détail avec de la pratique dans le cours « Harness Engineering: инженерия надёжности AI-агентов ».
- feature_list.json для AI-агента: WIP=1, статусы фич и верификация через verify.sh
- Graph Engineering для AI-агентов: когда одного цикла мало и нужен граф задач