Tous les articles

Notre boucle d'agent fait 239 lignes. Framework et runtime ne sont pas la même couche.

SOIT Team

Notre boucle d'agent fait 239 lignes. Framework et runtime ne sont pas la même couche.

L'expression plateforme d'agents a avalé au moins trois couches différentes : la bibliothèque avec laquelle on écrit un agent, le moteur qui l'exécute et le plan qui le gouverne. La plupart des discussions sur l'outillage des agents sont deux personnes qui répondent à des questions différentes. L'une dit avoir monté un agent en trois lignes ; l'autre veut savoir qui a autorisé l'appel parti hier à quinze heures. Les deux ont raison, et aucune ne parle de la même couche.

Nous n'allons pas juger les entrailles des autres, car être juste sur ce terrain supposerait de lire leur code d'assez près pour mériter l'opinion. Cet article fait donc deux autres choses : il décrit ce dont la couche framework répond habituellement, ce qui ne prête pas à débat, et il prend notre propre dépôt comme échantillon pour la couche runtime, où chaque affirmation vient avec un fichier et un numéro de ligne. L'échantillon est github.com/soit-ai/soit, Apache 2.0, au commit 3a57ae1.

Commençons par compter notre propre boucle d'agent. Le planificateur, l'exécuteur et le vérificateur totalisent 239 lignes. Le service qui enveloppe ces 239 lignes en compte 1 617, et sa liste d'imports trahit à quoi elles servent : un garde d'espace de travail, un registre d'approbations, un limiteur de débit, un service d'exécution d'outils porteur de baux et de clés d'idempotence, un écrivain de traces, une règle d'approbation, un résolveur d'outils. La boucle elle-même tient en un seul while. Une ligne de boucle, mille six cents autour, et presque aucune ne concerne la façon dont l'agent réfléchit.

La frontière entre les deux couches n'est pas un schéma, c'est un port. Chaque appel d'outil traverse une passerelle de politiques de 475 lignes avant d'atteindre un adaptateur, et c'est là que vivent les questions gênantes. Non pas si la logique de l'agent se lit joliment, mais si une nouvelle tentative ouvre deux fois le même ticket lorsque le système en aval n'honore pas une clé d'idempotence. C'est exactement pour cela que les appels à cette frontière sont au plus une fois.

On pourrait objecter, à juste titre, que nous avons simplement poussé le code de gouvernance en aval de notre boucle. La réponse est le deuxième modèle d'exécution du même dépôt : un moteur de workflows DAG de 6 269 lignes, structurellement sans rapport avec une boucle d'agent. Il atteint les outils par le même port d'outils et les modèles par le même port de modèles. Deux modèles d'exécution sans lien, un seul ensemble de garanties : voilà la définition opérationnelle d'une couche. La gouvernance est une propriété du port et non de la boucle, si bien que remplacer ce qui se trouve au-dessus n'enlève aucun des dix contrôles en dessous.

La mise en couches finit d'habitude écrite dans la documentation et pourrie dans le code ; celle-ci est donc confiée à un outil. Le premier contrat d'imports du dépôt interdit au noyau d'importer l'API, les modules, les adaptateurs, l'infrastructure ou le câblage. Quand le noyau a besoin de données produit ou infrastructure, il doit passer par des interfaces fournisseur enregistrées depuis la couche de câblage. C'est le coût de fonctionnement d'une frontière maintenue en vie, et il est payé par l'intégration continue plutôt que par la discipline.

La partie honnête : ce moteur de workflows recouvre bel et bien les frameworks d'orchestration, et prétendre le contraire serait absurde. Il a un compilateur, une résolution de variables, des exécuteurs de nœuds, une reprise et un ramasseur. L'affirmation exacte n'est pas que nous ne construisons aucune mécanique de framework, mais que nous n'en construisons que la part nécessaire, en la soumettant aux mêmes règles que n'importe quel autre appelant.

Que signifie alors, concrètement, ne pas être un framework concurrent ? Les outils écrits pour un framework peuvent être enregistrés via MCP, et dès lors chacun de leurs appels passe les mêmes contrôles. Les services peuvent être raccordés comme plugins HTTP. Le frontend n'a pas à changer, puisque l'interaction est un flux d'événements AG-UI. Ce qui manque compte davantage : il n'existe aucun point d'entrée qui prenne l'agent écrit par quelqu'un d'autre et l'héberge en entier. Votre boucle tourne toujours dans votre processus. Ce qui est gouverné, c'est la main qu'elle tend, l'appel d'outil, l'appel de modèle et la requête sortante, pas le raisonnement qui les précède.

Le framework décide comment un agent pense ; le runtime décide si l'instant où il tend la main compte. Ces deux préoccupations ne sont pas concurrentes, car elles n'agissent même pas sur la même échelle de temps : l'une porte sur les jours passés à écrire le code, l'autre sur chaque exécution ultérieure. La seule concurrence réelle porte sur l'attention, et le basculement de la première vers la seconde arrive le plus souvent trop tard, c'est-à-dire après le premier incident.