← Tous les articles

J'ai arrêté d'écrire du code. J'écris des tickets que les agents construisent seuls.

Mon travail consiste maintenant à rédiger des tickets qu'une équipe d'agents peut construire seule. Chaque agent prend une slice, la construit et la prouve de bout en bout, sans que personne n'intervienne. Voici les règles qu'un ticket doit suivre pour que ça marche, et ce que les relecteurs ont trouvé que je n'aurais pas vu.

Sujets : Claude Code · IA · Processus d'Ingénierie · Outils de Développement

Sur cette page

TL;DR : Ces dernières semaines, je n’ai presque pas écrit de code sur le produit que je construis. Mon travail consiste maintenant à rédiger des tickets qu’une équipe d’agents peut construire seule, en parallèle, sans que personne n’intervienne. Ça ne marche que si un ticket règle chaque décision et dit à quoi ressemble « terminé » sous une forme qu’un agent peut prouver de bout en bout. Cet article présente les règles auxquelles je suis arrivé et une vraie session où elles ont payé.

Ces dernières semaines, je n’ai presque pas écrit de code.

Les agents ne sont pas devenus assez bons pour deviner ce que je veux. J’ai arrêté de leur demander de deviner, et mon travail est devenu le ticket. Une équipe d’agents prend les tickets, les construit en parallèle, prouve chacun d’eux et ouvre les merge requests. Personne n’intervient en cours de route.

Le même accord qu’avec mon CTO

Quand j’écrivais le code moi-même, mon CTO et mon CEO ne me demandaient jamais comment je l’avais construit. Ils voulaient voir que ça faisait ce que le ticket demandait. Le comment, c’était mon affaire.

J’ai le même accord avec les agents aujourd’hui, sauf que je suis de l’autre côté. J’écris les tickets et je prends les décisions de conception du système. Les agents s’occupent du reste : le code, les tests, la merge request.

Chaque merge request arrive aussi avec une courte vidéo. On y voit un agent dérouler le parcours utilisateur du ticket, étape par étape, dans le vrai produit. Je la regarde comme mon CTO regardait mes démos. Si le parcours marche à l’écran et que les tests prouvent chaque ligne de succès, je n’ai pas besoin de lire chaque ligne de code pour lui faire confiance.

Cet accord ne tient que si le ticket est clair. S’il est vague, personne n’est là pour lever le doute. L’agent comble le vide avec quelque chose de plausible. Le code plausible passe la démo et casse un mois plus tard, quand plus personne ne se souvient pourquoi il a été écrit ainsi.

Voici ce qu’un ticket doit porter pour que ça n’arrive pas.

Un ticket est un contrat, pas un pense-bête

Le ticket que j’écrivais avant était une note pour moi ou pour un collègue : « Ajouter une expiration aux liens d’invitation, voir le fil Slack. » Ça marchait parce que le lecteur partageait mon contexte.

Un agent n’en partage rien, et personne n’est là pour répondre à ses questions. Donc un ticket que j’appelle ready doit régler chaque décision qui lui revient. L’agent planifie le code et ne décide rien d’autre. Le ticket ne porte que ce que l’agent ne peut pas retrouver facilement dans le dépôt :

  • le problème, qui l’a, et pourquoi maintenant
  • l’appétit : combien de jours ça vaut
  • les décisions déjà prises
  • ce qui sera vrai pour l’utilisateur une fois livré
  • ce qui est exclu
  • ce qui reste ouvert

Tout point laissé ouvert signifie que le ticket n’est pas prêt. L’état « prêt » se calcule à partir des questions ouvertes, je ne le choisis pas. Un runner refuse un epic tant que chaque slice qu’il contient n’est pas prête.

Trois règles pour les lignes de succès

L’essentiel du travail porte sur les lignes « Done when ». Trois règles les tiennent.

Règle 1 : nommer qui le voit. Chaque ligne commence par la personne qui observe le résultat. « Un client ne peut pas utiliser un lien d’invitation après son expiration », et non « un lien d’invitation expiré ne peut pas être utilisé ». Ça paraît pointilleux, jusqu’à ce qu’on remarque que l’acteur est le rôle que l’agent joue dans le test. Sans acteur nommé, l’agent teste ce qui est le plus facile à atteindre, en général une fonction.

Règle 2 : le test de formulation. Pour chaque ligne, je me demande : cette formulation changerait-elle si l’implémentation changeait ? Si oui, la ligne n’a pas sa place dans le ticket.

  • « Le propriétaire du compte voit qui a accepté une invitation sans ouvrir chacune d’elles. » Passe.
  • « L’endpoint de liste renvoie un accepted_count. » Échoue.

La seconde prend une décision de conception et la fait passer pour une exigence. L’agent construit alors exactement ça, même quand une meilleure conception existe. Les routes, les sélecteurs, les chemins de fichiers et les noms de frameworks de test échouent tous de la même façon. Les noms du domaine restent (« invitation » ou « membre », « application web » ou « mobile ») parce que ce sont des faits du produit.

Règle 3 : la preuve fait partie du ticket. Chaque ticket se termine par la même phrase :

Every Done when line is proven by an integration test that walks its journey step through the product’s real entry point. The test asserts the line’s observable result as written. Unit tests cover pure logic only. No test mocks the thing it proves.

Elle est dans chaque ticket mot pour mot, parce qu’un ticket publié doit tenir seul. Le parcours à côté a un chemin nominal, plus les chemins d’échec qu’un utilisateur peut vraiment rencontrer : refusé, expiré, vide, hors ligne.

J’écris aussi les corps dans une langue contrôlée, à peu près du Simplified Technical English : un terme par concept, un fait par phrase, un sujet nommé. Ça se lit de façon un peu raide. Ça se lit aussi d’une seule façon, et c’est le but.

Le processus : deux portes et une vérification du graphe

J’ai regroupé tout ça dans une skill Claude Code appelée writing-tickets. Le déroulé :

  1. Classer. Un ticket, un epic avec des slices, ou plusieurs epics. Plus une liste de termes, pour qu’« invitation » veuille dire une seule chose du début à la fin.
  2. Porte 1. La forme et les titres, puis elle s’arrête jusqu’à ce que je tape oui. C’est mon point de contrôle le moins cher, avant qu’aucun corps n’existe.
  3. Corps, écrits selon les règles ci-dessus.
  4. Vérification du graphe. Un script lit les brouillons et vérifie les dépendances, les vagues et l’état « prêt ». Les slices d’une même vague tournent en parallèle, donc chaque paire d’une vague doit montrer que les deux sont vraiment indépendantes. Le script affiche quelque chose comme ok: 15 slices, 5 waves, ou une ligne d’échec par problème.
  5. Relecture. Cinq relecteurs indépendants, chacun avec un angle (ci-dessous), sur deux tours.
  6. Porte 2. Les brouillons finaux, puis un second oui tapé, puis la publication dans le tracker.

Cinq relecteurs, un angle chacun

Un relecteur à qui on demande de « tout vérifier » vérifie les évidences. Donc chaque relecteur reçoit une seule question :

  1. Fuite d’implémentation. Une ligne décrit-elle le comment au lieu du quoi ?
  2. Intégrité des slices. Chaque slice peut-elle être livrée seule, et le graphe de dépendances est-il honnête ?
  3. Complétude et découpage. Manque-t-il quelque chose, ou une slice en cache-t-elle deux ?
  4. Recouvrement avec le code. Vérifié contre le vrai dépôt : est-ce que ça existe déjà, ou est-ce que ça entre en collision avec quelque chose qui existe ?
  5. Adversarial. Un modèle d’une autre famille attaque l’ensemble. Les relecteurs d’une même famille ont tendance à partager les mêmes angles morts, donc celui-ci est volontairement un outsider.

Une vraie session

Le produit a un epic sur la protection des données client au repos. Les agents ont livré les deux premières vagues, sept slices. Les notes de relecture sont revenues avec 54 constats.

Voici à quoi ressemblait la session depuis ma chaise :

  • L’agent a dédupliqué et trié les 54 constats : 27 demandaient un ticket, 6 demandaient une décision de ma part, et le reste était déjà couvert ou ne demandait rien.
  • J’ai pris les 6 décisions.
  • Il a proposé 15 tickets. J’ai dit que c’était trop. Il les a regroupés par thème en 4 slices.
  • Je voulais ces slices terminées avant la vague suivante prévue, donc elles sont entrées comme une nouvelle vague 3.
  • Les relecteurs ont tourné. Porte 2. Publié.

L’epic a fini à 5 vagues et 15 slices, dont 14 prêtes à être prises et construites par des agents sans surveillance. Ma part, c’était les décisions et beaucoup de « non ».

Les relecteurs ont trouvé des choses que j’aurais livrées :

  • Une dépendance cachée. Une slice reposait discrètement sur une autre slice de la même vague. En parallèle, elles seraient entrées en collision.
  • Une contradiction avec une décision verrouillée. Une slice disait « supprimer les tokens stockés ». L’epic avait une décision verrouillée : aucune slice ne migre ni ne remplit de données a posteriori. La reformuler en « arrêter de stocker les tokens » a réglé le conflit, et a aussi retiré la seule étape irréversible de tout le plan.
  • Un test qui prouvait la mauvaise chose. Une ligne sur un webhook était prouvée en vérifiant qu’un statut avait changé. Ça prouve un statut, pas une livraison.
  • Une preuve de test instable qui pouvait rater. « Le lancer 200 fois » n’aide pas quand l’échec se trouve sur une frontière temporelle que 200 exécutions rapides ne franchiront peut-être jamais.
  • Un objectif sans référence. « 10 % plus rapide » que quoi ? C’est maintenant fixé sur un commit précis, une graine fixe et la médiane de cinq exécutions.

Ce sont des erreurs ordinaires. Elles passent parce que la personne qui écrit le ticket sait déjà ce qu’il veut dire, donc elle lit ce qu’elle voulait dire au lieu de ce qu’elle a écrit.

Ce que j’ai mal fait

Dans cette même session, mon agent a signalé qu’un token d’accès avait fuité et que je devais le renouveler. Ce n’était pas le cas. Une analyse des transcriptions de session n’a trouvé aucune fuite. Le constat était faux.

La règle que j’en ai tirée : vérifier avant de dire à un humain d’agir. Une fausse alerte qui reste dans la boucle de l’agent coûte une minute. Un humain qui renouvelle ses identifiants dans la panique à cause d’elle perd un après-midi.

Ce que ça coûte

Ce n’est pas gratuit, et je ne veux pas le survendre.

  • C’est plus lent au départ. Écrire des lignes centrées sur un acteur et sans implémentation demande une vraie réflexion. Un ticket d’une ligne est plus rapide à écrire. Il n’est juste pas plus rapide à terminer.
  • Les tours de relecture coûtent des tokens et du temps réel. Cinq relecteurs sur deux tours, c’est une vraie dépense. Pour une correction de bug d’une ligne, je ne lance pas tout.
  • Les frontières des slices sont provisoires. L’agent qui prend une slice peut la redécouper une fois qu’il voit le code, et le processus doit le permettre.
  • Il faut toujours connaître le domaine. Les six décisions de cette session étaient les miennes. Aucun relecteur n’aurait pu les prendre.

Où est passé le goulot d’étranglement

Je pensais que la contrainte pour livrer, c’était l’écriture du code. Les agents en ont retiré l’essentiel. Ce qui reste, c’est la partie qui a toujours été difficile : savoir ce que « terminé » veut dire, assez précisément pour qu’une équipe d’agents sans contexte puisse le construire et le vérifier seule.

C’est ça, le travail maintenant : les tickets et la conception du système. Les agents s’occupent du reste. Certains jours, ça ressemble plus à rédiger des contrats qu’à faire de l’ingénierie, et je ne sais pas encore ce que j’en pense. Pour l’instant, c’est là que mon temps sert le plus.