Plusieurs agents, une seule architecture : ce qui se passe quand deux d'entre eux modifient le même système

Trois agents, trois pull requests, trois opinions raisonnables sur l'endroit où doit vivre la logique de retry.

L'un met le retry dans le client HTTP. L'un enveloppe le handler. L'un ajoute une queue et la vide. Lisez n'importe laquelle des trois toute seule et vous l'approuveriez. Lisez-les dans le même après-midi et vous remarquez que le système retente maintenant à trois endroits, avec trois politiques de backoff différentes, et que personne n'a décidé ça.

C'est la forme du problème dès que plus d'un agent travaille sur une base de code en même temps. Chaque agent est localement correct. L'incohérence est globale, et elle ne devient visible que pour la personne qui relit en dernier.

Pourquoi le fichier de règles n'arbitre pas ça

La réponse standard, c'est un fichier de règles : CLAUDE.md, AGENTS.md, .cursor/rules. Nous avons écrit pourquoi ces fichiers se périment, et la péremption est ici le plus petit des problèmes. Le plus gros, c'est qu'un fichier de règles ne peut pas arbitrer.

C'est de la prose. Deux agents à qui on donne le même paragraphe en produiront deux lectures différentes, toutes les deux défendables, et il n'y a aucun point où ces lectures se rencontrent. Il vit par dépôt, donc une règle sur une frontière de service est dans un dépôt pendant que le service de l'autre côté de la frontière est dans un autre. Et il n'a pas d'état : il ne peut pas savoir qu'un autre agent a proposé quelque chose il y a quarante minutes, parce que c'est un fichier, et que les fichiers ne savent rien.

Ce qu'il faut pour arbitrer, ce n'est pas de la meilleure prose. C'est une chose partagée que les deux agents lisent et écrivent, qui peut contenir une décision, et qui peut remarquer un désaccord.

Ce qu'un modèle vous donne et qu'un document ne donne pas

Un modèle d'architecture, ce sont des éléments et des relations que vous pouvez interroger. Des systèmes, des containers, des components, les arêtes entre eux, et accrochées à ces arêtes les choses qui font qu'un design est un design : la décision qui a rendu une frontière délibérée, le propriétaire à prévenir, le contrat sur lequel un consommateur s'appuie.

Trois conséquences en découlent, et chacune est un mécanisme plutôt qu'une intention.

Tous les agents peuvent lire les mêmes octets. L'action generate-context écrit un archyl.txt à partir du modèle, en markdown par défaut, éventuellement committé automatiquement dans le dépôt. Neuf agents qui lisent un seul fichier généré, ce n'est pas la même situation que neuf agents qui paraphrasent chacun un document en prose. Ce n'est pas malin. C'est juste partagé.

Le désaccord peut être attrapé à l'entrée. L'action conformance-check exécute les règles d'architecture sur les fichiers modifiés par une pull request, annote les violations en ligne, et fait échouer la vérification à la sévérité que vous choisissez. fail-on accepte error, warning ou none. Si "les retries appartiennent au client" est une règle plutôt qu'une phrase, les deux agents qui les ont mis ailleurs l'apprennent en CI plutôt qu'en revue.

Une décision a un endroit où vivre. Les ADR s'attachent aux éléments C4 qu'ils contraignent. La raison d'être de la queue est sur la queue, pas dans un thread Slack de mars que jamais aucun agent n'a vu.

Ce qu'on avait faux

C'est ici que ça a cessé d'être un billet de blog sur une jolie idée.

Les agents ne modifient pas le modèle directement. Ils ouvrent une Change Request : une proposition, relue et mergée par une personne. Quand une Change Request est créée, archyl enregistre la version du modèle sur laquelle elle a été construite. Quand elle merge, la version s'incrémente. C'est exactement la machinerie qu'on voudrait pour ce problème précis.

Nous n'avions jamais relié les deux.

La version de base était écrite à la création et relue nulle part. Ce qui voulait dire que cette séquence fonctionnait, silencieusement et complètement :

  1. L'agent A et l'agent B lisent tous les deux le modèle. Tous les deux voient la version 7.
  2. A ouvre une Change Request. B ouvre une Change Request. Les deux sont basées sur la version 7.
  3. La Change Request de A merge. Le modèle est maintenant en version 8.
  4. La Change Request de B merge. Elle avait été écrite contre un modèle qui n'existe plus.

Aucun avertissement, aucun conflit, aucune note dans l'historique. Le second jeu de modifications se pose par-dessus le premier, et si les deux se contredisent, la contradiction est désormais l'architecture documentée. C'est un merge dont on a retiré la détection de conflits, et c'est exactement l'échec que toute l'histoire du "plusieurs agents" est censée empêcher.

Donc on l'a corrigé. Merger une Change Request dont la version de base ne correspond plus au projet échoue maintenant avec un 409 Conflict et un message qui nomme les deux versions :

architecture request is based on version 7 but the model is now at version 9;
rebase the request and merge again

Ce n'est pourtant pas la comparaison qui rend la chose sûre. Deux merges arrivant au même instant passeraient tous les deux une comparaison et continueraient tous les deux. Ce qui ferme ce trou, c'est de rendre l'incrément de version lui-même conditionnel : le merge fait avancer le modèle seulement si le modèle est encore sur la version contre laquelle la Change Request a été construite. S'il a déjà bougé, le merge ne trouve rien à faire avancer, tout est annulé, et pas une seule modification n'est appliquée. La comparaison en amont n'existe que pour que le message d'erreur puisse vous dire de combien vous êtes en retard.

409 plutôt que 400 compte plus qu'il n'y paraît. Un agent qui retente sur un 400 boucle indéfiniment, parce qu'une requête malformée reste malformée. 409 dit l'inverse : ce que vous avez envoyé était bon et a cessé d'être applicable. Récupérez le modèle courant et réessayez.

Ce que ça ne fait toujours pas

Quatre limites, toutes vérifiables.

Aucun agent ne merge quoi que ce soit. Il n'existe aucun tool MCP qui merge une Change Request. Les agents proposent ; une personne relit et merge. C'est une frontière délibérée et nous ne prévoyons pas de la retirer, mais ça veut dire que la boucle n'est pas entièrement automatique et que vous ne devriez pas concevoir comme si elle l'était.

La détection de conflits est grossière. La version est par projet, pas par élément. Deux agents qui touchent des coins réellement sans rapport du même projet entreront quand même en collision sur la version. C'est le sens sûr dans lequel se tromper, et c'est quand même une erreur.

La récupération de contexte est lexicale. find_relevant_context note les éléments par recouvrement de mots sur les noms, les descriptions, les tags et les chemins. Il n'y a pas d'embedding et pas d'expansion par synonymes, donc une tâche à propos de "checkout" ne fera pas remonter un component appelé OrderProcessor. L'avantage est réel (déterministe, aucun coût en tokens, aucun code envoyé où que ce soit), mais c'est de la correspondance, pas de la compréhension.

Les règles ne s'écrivent pas toutes seules. Tout ce qui précède suppose que quelqu'un a exprimé "les retries appartiennent au client" sous forme de règle de conformité. Un jeu de règles vide n'attrape rien, quel que soit le nombre d'agents qui tournent.

Quoi en faire cette semaine

Vous n'avez rien à acheter pour savoir où vous en êtes.

Prenez la dernière semaine où votre équipe a mergé plus d'une pull request écrite par un agent. Lisez-les ensemble plutôt qu'à la suite. Demandez-vous si deux d'entre elles ont pris la même décision différemment, puis demandez-vous ce qui, dans votre installation actuelle, vous l'aurait dit.

Si la réponse est "le relecteur l'a remarqué", ça marche jusqu'au jour où le relecteur en lit neuf.


Les Change Requests, les règles de conformité et le modèle C4 font partie d'archyl. Les GitHub Actions et les agent skills sont open source. À lire aussi : pourquoi vos agents ont un fichier de règles et pas un modèle, comment fonctionnent les Change Requests, et comment le modèle est tenu honnête.