Aller au contenu
beeraw
Coulisses ·

Le suivi de projet où c'est l'agent qui tient les tickets

Un outil de suivi que je ne remplis jamais à la main : l'agent ouvre les tickets, change les statuts et les clôt sans demander la permission, et moi je relis. Ce que ça change, et ce que l'outil doit garantir en échange.

Je travaille avec un agent de développement, et un agent travaille par sessions. Chacune repart de zéro : ce qui a été décidé la fois d’avant reste dans une conversation que personne ne rouvrira. Du coup je refais trois fois le même arbitrage, je redécouvre trois fois pourquoi telle piste avait été écartée, et la seule trace qui reste, c’est un message de commit d’une ligne.

Le réflexe, c’est de tenir un suivi de projet à côté. Sauf que le tenir, c’est du boulot, et ce boulot me retombe dessus pile au moment où je délègue le reste. Remplir un outil à la main pour raconter ce qu’une machine a fait, je sais d’avance que je ne tiendrai pas trois semaines (je me connais).

tsutsumbi prend donc le problème par l’autre bout : l’agent écrit, je relis.

Le renversement

Concrètement, l’agent ouvre les tickets, change les statuts, commente ses décisions, clôt et choisit la résolution. Moi je consulte, je recadre et j’archive, avec trois écrans, une recherche plein texte et un fil chronologique.

Et l’agent fait tout ça sans validation, sans demander la permission à personne. C’est ce qui surprend le plus quand je le raconte. Mais un agent qui doit faire valider chaque changement de statut n’écrira rien : demander lui coûte plus cher que se taire. Si je veux un journal complet, je ne dois rien mettre entre l’agent et le journal.

Évidemment, ça se paie ailleurs. Un outil où personne ne valide doit garantir deux choses en échange : que rien ne se réécrit en douce, et que tout se relit. La plupart des choix techniques viennent de là.

Le fichier qui déclenche tout

Déclarer un serveur MCP ne suffit pas : l’agent n’a aucune raison d’appeler un outil de suivi si personne ne le lui demande. La consigne est donc dans un CLAUDE.md posé à la racine du dossier qui contient mes dépôts, et tous les sous-dossiers en héritent.

CLAUDE.md, à la racine du dossier des dépôts (traduit de l'anglais) markdown
## Au démarrage d'une session

Appeler `resolve_project` avec le chemin absolu du répertoire de travail, dès
qu'il s'agit d'un enfant direct de ce dossier. Ne pas demander confirmation.
En dehors, ne rien faire tant que l'utilisateur ne le demande pas.

Comme ce fichier s’applique à tous les projets du dossier, il commence par une clause d’échappement : si les outils tsutsumbi ne sont pas là, l’agent ignore tout le fichier, sans rien signaler et sans essayer d’appeler quoi que ce soit. Sans elle, chaque session ouverte sur une machine où le serveur n’est pas déclaré commencerait par un appel qui échoue, puis par l’agent qui explique pourquoi ça a échoué (merci, mais non).

Ce qu'est un ticket

Laissé à lui-même, un agent ouvre des tickets qui décrivent son propre travail : « refactoriser le repository », « ajouter la migration », « corriger le test qui échoue ». Ce sont des étapes techniques, et relues trois mois plus tard, elles ne disent plus rien à personne. La description de l’outil create_ticket le dit comme ça (je traduis, le code est en anglais) :

Un ticket est une unité de travail que quelqu'un reconnaîtrait trois mois plus tard : « export CSV des relevés », « TVA fausse sur les avoirs partiels ». La façon dont le travail se fait n'est pas le travail.

La description de l'outil create_ticket, traduite de l'anglais

C’est la règle la plus dure à faire respecter, et on ne peut pas la mettre dans le code. Elle est dans le texte que l’agent lit au moment de choisir quel outil appeler. J’y reviens dans le billet suivant, parce que tout ce qui ressemble à de la discipline chez un agent se joue là.

Ce qu'on garantit quand personne ne valide

Première garantie : le fil ne se réécrit pas. Les commentaires et le journal d’audit sont fusionnés dans une seule chronologie datée. Un changement de statut apparaît à côté du commentaire qui l’explique, dans l’ordre où les deux sont arrivés.

Sur le papier, c’était simple. Sauf que l’horodatage est à la seconde, et qu’un agent enchaîne plusieurs appels dans la même seconde (lui ne prend pas le temps de souffler). Deux transitions de suite, todo → in_progress puis in_progress → done, se seraient affichées comme un seul moment. Celle-là, je ne l’avais pas vue venir. Le regroupement s’appuie donc sur un autre signal : si un champ apparaît deux fois, c’est qu’un second changement a commencé.

src/Timeline/TicketTimelineBuilder.php (commentaires traduits) php
// Même seconde, même acteur, et aucun champ vu deux fois.
if (null === $current || $key !== $currentKey || isset($seenFields[$event->getField()])) {
    // …un nouveau groupe commence
}

Deuxième garantie : un blocage ne peut pas être muet. Le statut blocked exige une note, écrite dans la même transaction que le changement de statut. Un ticket bloqué sans explication ne peut donc tout simplement pas exister. C’est d’ailleurs le seul cas où l’outil refuse un changement demandé par l’agent : un ticket bloqué en silence donne l’impression que ça avance, et je trouve ça pire qu’un ticket oublié.

Troisième garantie : rien ne disparaît. Fermer un ticket ne le retire pas de la recherche, et l’API renvoie les tickets clos par défaut. Seule l’interface les masque. Ce qui a été décidé il y a six mois répond souvent à la question du jour, alors autant pouvoir le retrouver.

Rouvrir un ticket

Il n’y a pas de bouton « rouvrir », ni d’outil reopen_ticket. Une résolution n’existe que sur un ticket clos : envoyer un autre statut que done efface la résolution, et c’est comme ça qu’on rouvre.

src/Domain/TicketWorkflow.php (commentaires traduits) php
// Sans résolution explicite : on garde l'actuelle tant que le statut ne
// bouge pas, on la lâche dès qu'il bouge. C'est ce qui fait de
// {"status": "todo"} sur un ticket clos une réouverture.
$resolution = $hasResolution
    ? $change->resolution
    : ($isTransition ? null : $ticket->getResolution());

Ça m’évite surtout un cas particulier. Chaque outil en plus, c’est une description de plus que l’agent doit lire au moment de décider, et une occasion de plus de se tromper d’appel. Du coup, dès qu’une action peut se ramener à un état plutôt qu’à un verbe, je ne lui crée pas d’outil.

Ce que l'humain garde

Trois écrans. Le tableau de bord range les projets par activité décroissante, avec leur nombre de tickets ouverts. La liste d’un projet se filtre par statut, type et résolution, et se cherche en plein texte. La fiche d’un ticket montre sa description, sa chronologie, et de quoi corriger ce que l’agent a écrit.

C’est peu, je sais. Mais ici je consulte, je recadre, j’archive, et tout le reste serait de la saisie, donc justement ce que je ne voulais plus faire. Pour résumer qui fait quoi :

  • l’agent ouvre, met à jour et clôt les tickets, commente ses décisions, choisit la résolution, et joint un fichier (exceptionnellement) ;
  • moi je relis, je recadre, j’archive un projet terminé, je corrige un ticket mal formulé et je révoque un jeton.

Ce que ça ne fait pas

tsutsumbi n’est pas fait pour une équipe : ni assignation, ni sprint, ni estimation, ni notification. La question à laquelle il répond, c’est « qu’est-ce qui a été décidé sur ce projet, et pourquoi ». Pour savoir qui fait quoi cette semaine, il faudra un autre outil.

Et il ne surveille pas l’agent. Le journal dit ce qui a changé et quand, il ne dit pas si c’était pertinent. Un agent qui ouvre un ticket mal formulé produit un ticket mal formulé, daté et signé, et c’est à moi de le recadrer. Sans validation, il reste la relecture.

Il faut aussi garder en tête qu’un journal tenu par une machine contient ce que la machine a compris, qui n’est pas toujours ce qui était vrai. Ce qu’il apporte, c’est la continuité. Le relire de temps en temps fait partie du dispositif.

Où ça en est

L’outil tourne et tient le suivi de mes propres projets, y compris le site que vous êtes en train de lire. Il est publié sous licence MIT. C’est un projet perso : je le publie parce que sa forme peut servir à quelqu’un d’autre, je ne cherche pas de co-mainteneurs.

Le code

github.com/beeraw/tsutsumbi : Symfony, MariaDB, un serveur MCP en Node, licence MIT. Le README documente l’installation, le déploiement et les écarts assumés par rapport à la spécification.

tsoutsoumbi, c’est un petit insecte de Mayotte. Insecte, bug, gestion de projet : le nom s’est imposé tout seul.

Racontez-moi votre projet

Dites-moi en quelques mots ce que vous voulez construire. Vous écrivez au développeur, pas à un formulaire anonyme.