Quatre architectures d’agents IA en treize jours : graphe ou collègue ?
Du graphe à blocs façon n8n au collègue à qui on parle, chaque version du projet a été abandonnée pour une raison écrite dans l’historique. La réponse retenue combine un collègue pour l’utilisateur, du code versionné pour la logique et une instance réglée dans l’admin.

Un agent IA, c’est quoi au juste ? Un graphe de blocs qu’on relie à la souris, façon n8n, avec un déclencheur, des étapes et des conditions ? Ou un collègue à qui on parle, qui lit ses mails, comprend ce qu’on attend de lui et demande avant d’envoyer ?
Le projet étudié ici a répondu à cette question quatre fois entre le 11 et le 23 octobre, avec quatre dépôts et quatre architectures différentes. La troisième a commencé par supprimer la deuxième : un commit de ménage à +550 / −52 633 lignes sur 466 fichiers. Voici ce que chaque version cherchait à faire, pourquoi elle a été abandonnée, ce qui a survécu, et ce que Claude Code a changé à la façon de prototyper.
1. Quatre dépôts en treize jours
Pour suivre, voici la chronologie, reconstituée à partir de l’historique git et des documents de reprise tenus dans chaque projet :
| Version | Dates | Ce qu’est un agent |
|---|---|---|
| Première | 11–13 octobre | Un membre d’une équipe qui se répartit des tâches |
| Deuxième | 14–15 octobre | Un graphe JSON de blocs, construit par l’admin |
| Troisième | 17–22 octobre | Un employé qu’on recrute et à qui on parle |
| Quatrième | 22–23 octobre | Un modèle écrit dans le code, instancié dans l’admin |
Pour comparer, on suivra le même agent dans les trois dernières versions : celui qui trie les e-mails d’un service client.
2. Première version : une équipe d’agents qui se répartit le travail
Au départ, il y avait un studio d’agents : on y dépose un projet, et une équipe d’agents le comprend, le décompose, pose ses questions à l’humain, estime, réalise, teste et recette. Back-end Symfony avec Symfony AI, front React, et un cadre de règles numérotées que les agents doivent respecter. Le premier commit pose près de 88 000 lignes ; la même journée du 11 octobre, le catalogue passe de 28 intégrations MCP officielles à 237 intégrations en quatre « vagues ».
Les deux jours suivants, il a été simplifié autour d’un tableau de bord d’activité et d’un orchestrateur qui répartit les tâches entre agents spécialisés.
Le document de reprise note aussi l’analyse d’un projet « qui tournait depuis des heures avec trop d’allers-retours », et la réorientation qui a suivi vers des résultats rapides et concrets. Une équipe d’agents qui discutent entre eux, c’est séduisant à regarder, mais chaque échange coûte des jetons et du temps.
Surtout, ce studio n’était pas fait pour être ouvert à d’autres utilisateurs. Son README est explicite : « Le studio n’a pas d’authentification humaine. » Le 14 octobre au matin, la décision est écrite noir sur blanc : le studio reste un banc d’essai interne, et pour un usage extérieur on part sur un nouveau projet propre, qui ne garde que la vision et les enseignements du banc d’essai. Aucun code, compte, secret ni donnée ne passe d’un projet à l’autre.
Cette règle a l’air excessive. Elle oblige pourtant à reformuler ce qu’on a appris au lieu de traîner du code par habitude.
3. Deuxième version : le graphe à blocs et la Forge
La demande fondatrice de la deuxième version tenait en une phrase : donner à des entreprises utilisatrices un tableau de bord sur lequel elles voient leurs agents. Un agent y exécute un flux métier défini, un déclencheur puis une suite d’actions claires, « comme n8n ». Seul l’admin construit les flux ; l’utilisateur voit et ajuste quelques réglages.
Concrètement, un agent est une définition JSON validée par une classe FlowDefinition de 642 lignes, exécutée par un FlowEngine de 833 lignes. Voici, raccourci, l’agent de tri du support tel qu’il figurait dans la galerie d’exemples :
'trigger' => ['type' => 'email', 'uses' => 'boite',
'query' => 'is:unread -category:promotions -category:social'],
'steps' => [
['key' => 'tri', 'type' => 'ai', 'uses' => 'cerveau',
'prompt' => "E-mail de {{trigger.from}}\nObjet : {{trigger.subject}}\n\n{{trigger.text}}\n\nClasse la demande…",
'schema' => ['categorie' => 'text', 'urgent' => 'boolean', 'resume' => 'text', 'reponse' => 'text']],
['key' => 'urgence', 'type' => 'action', 'action' => 'slack.post_message',
'when' => ['left' => '{{steps.tri.output.urgent}}', 'op' => 'eq', 'right' => true], …],
['key' => 'validation', 'type' => 'approval', 'timeoutHours' => 24,
'message' => 'J\'envoie cette réponse à {{trigger.from}} ?', …],
['key' => 'reponse', 'type' => 'action', 'action' => 'gmail.send', …],
],
Tout y est explicite et vérifiable : chaque gabarit {{steps.cle.output.champ}} est validé à l’enregistrement, chaque étape a un marqueur (réussie, en attente, en échec), on peut rejouer une exécution à blanc. Sur le papier, c’est le système idéal pour une entreprise qui veut de la traçabilité.
La Forge : une IA qui écrit du PHP.
Restait le problème du catalogue : aucun catalogue de blocs ne couvre tous les services d’une entreprise. D’où la Forge, un agent réservé à l’admin qui écrit lui-même un nouveau bloc, en PHP, à partir d’une description. Le contrat imposé tient dans son prompt système :
- Une seule classe : « final class GeneratedAction implements DescribedAction »,
avec le constructeur « public function __construct(private GeneratedApi $api) {} ».
- Interdit : commandes système (exec, shell_exec, system, passthru, proc_open, popen,
guillemets inversés).
Le code généré passe ensuite par un CodeGuard (analyse des jetons PHP, refus des commandes système), puis s’exécute dans un processus PHP séparé, environnement vidé, avec open_basedir, 128 Mo et 30 secondes au plus. Un processus par appel, soit 50 à 150 ms, négligeable devant les appels réseau. L’erreur exacte revient au modèle, qui corrige : 5 soumissions par conversation, 2 conversations au plus. Elle a ensuite gagné un coffre pour garder une session entre deux blocs, et un navigateur piloté pour en tirer depuis un site sans API.
Techniquement, ça marchait. Le 14 octobre au soir : 89 tests backend verts sur SQLite et PostgreSQL, PHPStan au niveau maximal sans erreur.
Pourquoi elle a été abandonnée.
Les raisons sont écrites dans l’historique, et elles viennent de l’intérieur du projet. Le 14 au soir, à l’essai de l’outil par son propre concepteur : « Je ne comprends rien à la création d’agents actuelle. » On refait le constructeur en blocs visuels. Le lendemain, nouvelle remarque : interface trop complexe, variables, entrées, sorties.
Chaque correction ajoutait de la complexité au lieu d’en retirer. Pour des « si oui / si non », il a fallu des étapes branch. Pour traiter une liste, des loop. Pour faire passer une donnée d’un bloc à l’autre, des extraOutputs. Pour saisir les variables, un champ à pastilles glissables. Le format décrit au modèle qui construisait les flux faisait à lui seul plus de vingt lignes de règles serrées.
Et un échec est révélateur. Un bloc blog.recent_posts écrit par la Forge fonctionnait : 5 articles en appel réel. Mais l’essai par défaut se faisait dans un bac à sable sans exemple, où chaque requête recevait {}. Résultat affiché : « Aucun article trouvé ». Le code était bon, le banc d’essai mentait. Plus il y a de couches entre l’intention et l’exécution, plus il y a d’endroits où ce genre de mensonge peut se cacher.
Le graphe était devenu un langage de programmation, avec son propre IDE, que personne n’avait envie d’apprendre. Pas même son concepteur.
4. Troisième version : un collègue à qui on parle
Le 17 octobre, la plateforme d’employés IA reprend le code de la deuxième version (621 fichiers, 81 188 lignes) et change de promesse. Elle est écrite en tête de la vision : on recrute un employé IA dans un catalogue de métiers, il fait son intégration en conversation, se branche aux outils, travaille sous le regard de son manager. Et surtout :
On ne voit jamais un bloc, du code ou un secret.
La décision est consignée le même jour : l’utilisateur crée ses agents en conversation, le graphe reste un outil de l’admin. Une mission n’est plus un graphe, c’est une consigne en langage courant. Le même tri des e-mails devient ceci, dans le catalogue des métiers :
{
"id": "tri-mails",
"title": "Trier les mails et préparer les réponses",
"brief": "Pour chaque nouveau mail : identifie l'expéditeur et l'objet, classe-le (urgent, à répondre, à lire, sans intérêt) […]. Si une réponse est attendue, prépare un brouillon de réponse dans le ton de la fiche. Si c'est urgent, préviens ton manager avec un compte rendu court.",
"triggers": [{ "type": "watch", "watch": "new_mail" }]
}
Ce qui remplace les blocs approval, c’est l’autonomie, appliquée à chaque appel d’outil selon sa classe : lire toujours ; ranger, faire un brouillon, agir, supprimer selon quatre niveaux (faire, faire et le dire, demander, jamais). Une action « demander » met le travail en attente, la décision du manager le fait reprendre.
Le ménage du 18 octobre.
Le lendemain, commit 323bbbf, « Ménage : suppression de l’ancien système d’agents à blocs ». Partent le moteur de flux, la Forge et les blocs PHP générés, le constructeur et la galerie, l’agent conversationnel, le catalogue de services et le client MCP, l’OAuth Google direct, le coffre de connexions, les projets et les espaces. Avec des migrations qui suppriment les anciennes tables. 466 fichiers, 52 633 lignes retirées, 550 ajoutées.
Garder un moteur de flux à côté des employés aurait voulu dire maintenir deux réponses contradictoires à la même question.
Ce que le « collègue » coûte.
Le modèle conversationnel a ses propres pièges, et la production les a montrés vite. Une mission « À chaque nouveau mail » remontait toute la boîte de réception : 30 étapes, 2 millions de jetons, 0,60 $ pour un seul mail. Là où le graphe imposait un chemin, l’employé l’invente, et parfois il se promène.
Il a fallu remettre des rails : le mail reçu ouvert en entier avant le travail, les outils de parcours retirés pendant ce travail, 12 étapes au plus pour une mission déclenchée, 600 000 jetons au plus par travail, le même outil bloqué après 5 appels d’affilée, un appel identique déjà réussi jamais refait. Puis un test qui lance chaque mission du catalogue avec le vrai modèle : 95 missions pour 1,59 $, environ 0,017 $ par mission.
Autre mesure : la description des outils d’un employé pesait 197 000 caractères par étape. Avec 10 outils par service au lieu de 24 et des descriptions raccourcies, elle est descendue à 85 000.
5. Quatrième version : modèles dans le code, instances dans l’admin
Le 22 octobre au soir démarre un socle pour agents IA. Changement de pile (TypeScript, Mastra pour la boucle d’agent et les workflows, AI SDK pour les modèles, PostgreSQL) et changement de cible : ce n’est plus un produit en libre-service, c’est un socle qu’on clone pour chaque déploiement, dans lequel on écrit les agents de cette organisation. Sa règle d’or est dans le deuxième commit :
Le code définit des modèles, l’administration crée les instances.
| Dans le code (le « comment ») | Dans l’admin (le « qui, avec quoi ») |
|---|---|
| Consignes, outils, déclencheurs | L’instance, son nom, actif ou non |
| Besoins : « une boîte à surveiller » | La connexion choisie pour chaque besoin |
| Réglages possibles (schéma zod) | Leurs valeurs, l’IA, l’autonomie |
Rien de propre à un compte (clé, jeton, adresse) ne va dans le code. Et le tri des e-mails redevient du code, mais du code lisible, versionné et testé :
export const triMails = defineAgent({
id: 'tri-mails',
name: 'Tri des e-mails clients',
instructions: `Tu reçois un e-mail de client (complet : pas besoin de relire la boîte).
1. Si c'est une publicité, une notification automatique ou un spam : ne fais rien et réponds « ignoré ».
2. Sinon, cherche la commande s'il donne un numéro, regarde s'il a déjà un ticket ouvert. […]`,
needs: { boite: need(mailConnector, 'Boîte du service client'), boutique: need(shopifyConnector, 'La boutique') },
tools: [mailSearch, mailRead, shopifyOrders, ...ticketTools],
triggers: [mailInbox({ id: 'sav-mails', input: (m) => `Nouvel e-mail de ${m.from}…` })],
limits: { maxSteps: 8 },
});
On retrouve les leçons des versions précédentes, compactées. La première ligne des consignes vient directement de l’incident des 2 millions de jetons : l’e-mail arrive complet, inutile de relire la boîte. maxSteps: 8 est le garde-fou. Chaque outil déclare sa nature (read, write, act, delete) et l’autonomie par défaut en découle : écrire est automatique, agir et supprimer demandent un accord.
Le graphe revient, mais en code.
Le socle distingue deux formes, et la règle tient en deux lignes de sa documentation. Agent : quand la suite dépend de ce qu’il lit (conversation, tri). Workflow : quand les étapes sont connues d’avance. Et : « Préférer le workflow dès que le processus est stable : moins cher, prévisible, testable étape par étape. »
C’est, au fond, la réconciliation des versions deux et trois. Le graphe n’avait pas tort ; il était au mauvais endroit. Un processus stable mérite des étapes fixes, mais écrites dans le code, avec des tests qui tournent sans IA ni réseau, plutôt que dessinées sur un canevas.
La Forge, elle, ne revient pas. Parmi les pistes du socle figure une « création assistée » (décrire un besoin dans l’admin, recevoir une proposition d’instance validée par un humain) avec cette précision : pas de code généré. Une capacité nouvelle est du code relu et testé dans le catalogue.
6. Ce qui a survécu
Quand je relis les quatre dépôts, ce qui a traversé les versions n’est presque jamais l’idée centrale. Ce sont les briques de plomberie et les garde-fous :
- Le client MCP et l’OAuth. Supprimés par le ménage du 18, ils reviennent trois jours plus tard dans la plateforme d’employés, « repris de l’ancien code (commit
323bbbf) », pour brancher les serveurs MCP officiels en direct (PKCE, jetons scellés et renouvelés). Git a servi d’entrepôt : rien n’était perdu. - Le coffre. Secrets chiffrés au repos, jamais renvoyés ni journalisés, jamais montrés à l’IA : présent dans les quatre versions, sous des noms différents.
- L’accord humain par nature d’action. D’abord un bloc
approval, puis une autonomie par classe d’outil, enfin unkinddéclaré sur chaque outil. Le socle refuse explicitement de laisser un LLM juger seul d’un accord : c’est la nature déclarée de l’outil qui décide. - Les garde-fous anti-boucle et le budget, nés de l’incident des 2 millions de jetons.
- Le navigateur piloté et la protection du réseau interne : requêtes vers l’internet public seulement, redirections vérifiées.
À l’inverse, toutes les représentations de « ce qu’est un agent » ont été jetées. La plomberie répond à des contraintes qui ne changent pas (un jeton expire, un secret ne doit pas fuiter) ; la représentation répondait à une question encore ouverte.
7. Le rôle de Claude Code
Quatre architectures en treize jours n’auraient pas été envisageables sans Claude Code. Le premier commit de la deuxième version, le jour même de la demande fondatrice, pose 50 708 lignes : organisations, droits, centre de connexion, coffre, moteur de flux, interface. Le premier soir de la troisième version, une équipe d’employés IA complète (inscription, intégration en conversation, fiche de poste, travail autonome) arrive en un commit de 10 366 lignes, moins de deux heures après la reprise du code.
Mais la vitesse brute n’est pas ce qui a le plus servi le projet. Trois pratiques ont compté davantage :
- Le document de reprise. L’historique de la deuxième version commence par cette phrase : tout ce qui a été dit et fait, « pour qu’une nouvelle session Claude Code reprenne avec l’historique et la compréhension complète du projet ». Demandes, décisions datées, limites connues : c’est grâce à lui que cet article cite les raisons exactes de chaque abandon, et pas des souvenirs reconstruits.
- La qualité exigée malgré la vitesse. PHPStan au niveau maximal, couches contrôlées par deptrac, tests sur SQLite et PostgreSQL, et deux revues de code indépendantes sur la deuxième version (8 puis 4 défauts trouvés, tous corrigés et testés). Sans ce filet, jeter 52 633 lignes aurait été jeter 52 633 lignes de dette.
- Un socle écrit pour être lu par l’IA. La documentation d’architecture du socle s’ouvre en s’adressant directement à Claude, ou à tout agent de code, chargé de construire une application à partir de lui. Chaque déploiement a son dépôt et ses consignes pour Claude. La quatrième version n’est pas seulement construite avec Claude Code : elle est conçue pour que Claude Code y écrive les agents de chaque déploiement.
Ce dernier point est peut-être la vraie réponse à la Forge. La Forge visait une IA qui écrive des blocs PHP à la volée, en production, dans un bac à sable. Le projet a fini avec une IA qui écrit des agents en TypeScript, au moment du développement, dans un dépôt versionné, avec des tests et une revue. Même idée, mais au bon moment du cycle de vie.
Le revers est réel : quand produire du code ne coûte presque plus rien, on peut construire très loin dans une mauvaise direction. Le constructeur à blocs a été refait deux fois en deux jours avant que soit posée la seule question qui comptait : est-ce que quelqu’un a envie de s’en servir ?
8. Alors, graphe ou collègue ?
Ni l’un ni l’autre, et les deux. Pour l’utilisateur final, la réponse est le collègue : il ne doit jamais voir un bloc, une variable ou un secret. Pour la logique, la réponse dépend du processus : s’il dépend de ce que l’agent lit, c’est un agent avec des consignes, des outils et des limites ; s’il est stable, c’est un workflow à étapes fixes, moins cher et testable. Dans les deux cas, cette logique vit dans le code, pas dans un canevas ni dans du code généré à l’exécution.
Et ce qui varie d’une organisation à l’autre (quelle boîte, quel seuil, quel ton, qui valide) n’est ni du code ni une conversation : c’est une instance, réglée dans une administration.
Les leçons
- Séparez la représentation de la plomberie. Les quatre manières de décrire un agent ont été jetées ; MCP, OAuth, coffre et garde-fous ont tous survécu.
- Essayez votre outil vous-même, tôt. « Je ne comprends rien à la création d’agents actuelle » a été le meilleur retour utilisateur du projet, et il venait de son auteur.
- Un graphe assez riche devient un langage de programmation. Embranchements, boucles, variables : à ce stade, autant écrire du vrai code, avec de vrais tests.
- Un agent libre a besoin de rails. Le passage du graphe au collègue a coûté un incident à 2 millions de jetons, réglé par des limites d’étapes et de budget.
- Cloisonnez les prototypes. « Aucun code ne passe d’un projet à l’autre » oblige à transmettre des enseignements plutôt que des habitudes. Git garde le reste au besoin.
- Avec un agent de code, la documentation de reprise vaut autant que le code. C’est elle qui garde les raisons des décisions quand le code a disparu.