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
- Ouvre les tickets
- Change les statuts
- Commente ses décisions
- Clôt et choisit la résolution
- 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.
## 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.
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.
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é.
// 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.
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.
// 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.
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.
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.
- 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.