Aller au contenu
beeraw
Coulisses ·

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

Un outil de suivi que personne ne remplit à la main : l'agent ouvre les tickets, change les statuts et les clôt sans demander la permission. Moi je relis. Ce que ce renversement change, et ce qu'il oblige à garantir.

Un agent de développement travaille par sessions. Chacune repart de zéro, et ce qui a été décidé la fois d’avant vit 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 retombe sur moi pile pendant que je délègue le reste. Un outil qu’on remplit à la main pour raconter ce qu’une machine a fait, ça ne tient pas trois semaines.

tsutsumbi prend le problème par l’autre bout : c’est l’agent qui écrit, et moi qui relis.

Le renversement

L'agent écrit
  • Ouvre les tickets
  • Change les statuts
  • Commente ses décisions
  • Clôt et choisit la résolution
L'humain lit
  • Consulte
  • Recadre
  • Archive
  • Trois écrans, la recherche plein texte, un fil chronologique

Sans validation, sans demander la permission. C’est ce qui surprend le plus quand je le décris, et c’est pourtant ce qui fait tenir le reste : un agent qui doit faire valider chaque changement de statut n’écrira rien, parce que demander coûte plus cher que se taire. Pour avoir un journal complet, il ne faut rien mettre entre l’agent et le journal.

Ça se paie ailleurs, forcément. Un outil où personne ne valide doit garantir deux choses en échange : que rien ne se réécrit en douce, et que tout se relise. C’est ce qui a dicté l’essentiel des choix.

Le fichier qui déclenche tout

Un serveur MCP déclaré ne suffit pas : l’agent n’a aucune raison d’appeler un outil de suivi si personne ne le lui demande. La consigne vit donc dans un CLAUDE.md posé à la racine du dossier qui contient les dépôts, et hérité par tous ses enfants.

CLAUDE.md, à la racine du dossier des dépôts 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.

Ce fichier a une portée large — tous les projets du dossier, sans exception — donc il s’ouvre sur 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.

Pourquoi cette clause n'est pas du zèle

Sans elle, chaque session ouverte sur une machine où le serveur n’est pas déclaré commence par un appel qui échoue, puis par l’explication de cet échec. Une consigne héritée partout doit savoir rester inerte quelque part.

Ce qu'est un ticket, et ce qu'il n'est pas

Un agent produit spontanément des tickets qui décrivent son propre travail : « refactoriser le repository », « ajouter la migration », « corriger le test qui échoue ». Ce sont des étapes, pas des unités fonctionnelles. Trois mois plus tard, ça ne raconte rien.

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

C’est la règle la plus dure à faire tenir, et elle n’est pas dans le code : elle est dans le texte que l’agent lit au moment de choisir quoi appeler. J’y reviens dans le billet suivant, parce que c’est là que se joue tout ce qui ressemble à de la discipline.

Ce qu'on garantit quand personne ne valide

Premier engagement : le fil ne se réécrit pas. Commentaires et 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.

Ça a l’air simple, et il y a un piège que je n’avais pas vu venir : l’horodatage est à la seconde, et un agent enchaîne plusieurs appels dans la même. Deux transitions de suite — todo → in_progress, puis in_progress → done — se seraient affichées comme un seul moment. Le regroupement s’appuie donc sur un autre signal : un champ vu deux fois, c’est qu’un second changement a commencé.

src/Timeline/TicketTimelineBuilder.php 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 engagement : un blocage ne peut pas être muet. Le statut blocked exige sa note, écrite dans la même transaction que le changement de statut. Un ticket bloqué sans explication ne peut donc pas exister — pas « ne devrait pas », ne peut pas.

L'état le plus coûteux

Un ticket bloqué en silence est pire qu’un ticket oublié : il donne l’impression que ça avance. C’est le seul cas où l’outil refuse un changement que l’agent demande.

Troisième engagement : rien ne disparaît. Fermer un ticket ne le retire pas de la recherche, et l’API renvoie les tickets clos par défaut. C’est l’interface qui les masque, pas les données — ce qui a été décidé il y a six mois est souvent la réponse à la question du jour.

Rouvrir n'est pas une action

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

src/Domain/TicketWorkflow.php 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());

L’intérêt, ce n’est pas d’économiser un bouton, c’est d’économiser un cas particulier. Un outil de plus, c’est une description de plus à lire au moment de décider, et une occasion de plus de se tromper d’appel. Dès qu’une action peut se ramener à un état plutôt qu’à un verbe, elle disparaît de la surface.

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, et c’est voulu : ici je consulte, je recadre, j’archive. Tout le reste serait de la saisie, c’est-à-dire exactement ce dont je voulais me débarrasser.

Ce que fait l'agent Ce que fait l'humain
Ouvre, met à jour, clôt Relit, recadre
Commente ses décisions Archive un projet terminé
Choisit la résolution Corrige un ticket mal formulé
Joint un fichier, exceptionnellement Révoque un jeton

Ce que ça ne fait pas

Ce n’est pas un outil d’équipe. Ni assignation, ni sprint, ni estimation, ni notification. La question qu’il traite, c’est « qu’est-ce qui a été décidé sur ce projet, et pourquoi », pas « qui fait quoi cette semaine ».

Et il ne surveille pas l’agent. Le journal dit ce qui a changé et quand, pas si c’était pertinent. Un agent qui ouvre un ticket mal formulé produit un ticket mal formulé, daté et signé — le recadrage reste un geste humain. C’est le prix de l’absence de validation, et il se paie en relecture.

Ce qu'il faut accepter avant de s'en servir

Un journal tenu par une machine contient ce que la machine a compris, pas ce qui était vrai. Il vaut par sa continuité, pas par son autorité. Le relire de temps en temps fait partie du dispositif, pas de la maintenance.

Où c'est arrivé

L’outil tourne, il tient le suivi de mes propres projets — y compris le site que vous lisez — et il est publié sous licence MIT. C’est un projet perso, publié parce que sa forme peut servir à quelqu’un d’autre, pas parce qu’il cherche des 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.

À retenir
  • Un journal que quelqu’un doit remplir à la main ne survit pas à la délégation. Si l’agent fait le travail, c’est l’agent qui tient la trace.
  • Retirer la validation est ce qui rend le journal complet. Demander la permission coûte plus cher que se taire : un agent qui doit faire valider n’écrit rien.
  • Ce qu’on retire en contrôle, on le rend en traçabilité. Rien ne se réécrit, tout est daté, et le blocage muet est refusé par le domaine.
  • Une action qui se ramène à un état n’a pas besoin d’exister. Rouvrir, c’est un statut qui quitte done, pas un verbe de plus.
  • Un fichier de consignes hérité partout doit savoir rester inerte. La clause d’échappement vaut mieux qu’un appel qui échoue au début de chaque session.