Accueil / Articles

Qui devrait posséder le cycle d’agent : les outils en temps de exécution contre les frameworks composés

Un cadre de décision pour la création d’agents IA qui repose sur une seule question : à qui appartient le cycle d’exécution, en comparant les outils Node harness avec LangGraph, CrewAI et LangChain.

Éditeur : REACTAPP.TOP13 min de lecture2927 mots

Sources: Who Owns the Agent Loop? Node.js Harnesses vs. Python Frameworks (LangGraph, CrewAI, LangChain)

D’au loin, les outils permettant de créer des agents d’intelligence artificielle semblent interchangeables. Chacun fournit un modèle, un moyen d’appeler des outils, ainsi que des mécanismes qui maintiennent le modèle en fonctionnement jusqu’à l’achèvement d’une tâche, et avec n’importe lequel d’eux on peut mettre en place un agent opérationnel en une après-midi. Les différences entre eux restent cachées tant que l’on se limite à la démonstration et que l’on n’entre pas en phase de production ; c’est alors que ces différences s’avèrent fondamentales : qui possède le cycle qui fait fonctionner l’agent ? Une famille d’outils vous fournit un environnement de exécution et se tient à l’écart ; l’autre vous fournit des abstractions et vous demande de les assembler vous-même. Cet article trace clairement la frontière entre ces deux philosophies, puis vous propose un moyen de déterminer à quel camp appartient un projet donné. L’objectif n’est pas de désigner un gagnant, mais de rendre explicites les compromis à faire.

D’un côté se trouvent les frameworks de type harnais : des environnements d’exécution qui gèrent entièrement le cycle consistant à envoyer une requête, exécuter des outils, afficher les résultats et le répéter. Vous n’écrivez pas ce cycle ; vous le configurez et l’étendez. De l’autre côté se trouvent des frameworks tels que LangGraph, CrewAI et LangChain, qui vous fournissent des éléments de construction explicites tels que des graphes, des équipes et des chaînes, tout en exigeant que vous mettiez vous-même en place le flux de contrôle. Bien que cela soit présenté comme une opposition entre Node et Python, le langage n’est qu’un effet secondaire. La véritable variable est la mesure dans laquelle vous souhaitez prendre en main les mécanismes sous-jacents.

Deux définitions résistantes à la pression

Déterminez clairement les deux termes avant de vous en servir. Un harness est un environnement d’exécution qui prend en charge la boucle de l’agent. Il effectue ce cycle à votre place : il envoie la requête, rassemble les outils demandés par le modèle, les exécute, renvoie les résultats et répète l’opération jusqu’à ce que le modèle cesse de demander quoi que ce soit. Il gère également les problèmes complexes qui y sont associés, tels que la gestion du contexte et de la mémoire, la compression de l’historique à mesure que le budget de tokens s’épuise, les tentatives répétées et la persistance des sessions. Vous le dirigez grâce à des paramètres de configuration — comme le prompt système, le choix du modèle, les outils autorisés et les limites — ainsi qu’à des points d’ancrage et de prolongement, sans jamais avoir à écrire manuellement la boucle. Un harness de ce domaine, Pi, un environnement d’exécution basé sur Node et TypeScript, résume cette philosophie en insistant sur les primitives plutôt que sur les fonctionnalités : vous adaptez l’environnement d’exécution à votre flux de travail plutôt que de forcer votre flux de travail pour qu’il s’y adapte.

Un framework, ou SDK, est plutôt un ensemble d’abstractions explicites que vous composez dans du code. LangGraph propose un graphe de nœuds reliés par des arêtes. CrewAI propose des équipes d’agents jouant des rôles. LangChain propose des chaînes. Dans chacun d’eux, vous êtes l’auteur du flux de contrôle, et la bibliothèque ne fournit que les composants qui vous permettent de les relier. La distinction vraiment importante est la suivante : dans un harness, le boucle est cachée et appartient au système d’exécution, tandis que dans un framework, le boucle, la structure du graphe ou l’organisation des équipes est visible et vous appartient.

C’est un spectre plutôt qu’un interrupteur. À une extrémité, vous dirigez le système d’exécution grâce à des configurations et des points d’ancrage, tandis qu’il gère le cycle ; à l’autre extrémité, vous créez chaque nœud, chaque arête, chaque équipe ou chaque chaîne, et le boucle est littéralement votre propre code.

HARNESS    you → [config + hooks] → ╔═══ runtime owns the loop ═══╗ → result
                                    ║ prompt → tools → results → ↺║
FRAMEWORK  you author every node / edge / crew / chain → the loop IS your code

Gardez cet axe en tête, car tout ce qui suit dépend de l’emplacement d’une outil sur celui-ci.

L’approche du harnais : confier la boucle au temps de exécution

Pi est un harnais minimal, natif de Node, organisé en une pile stratifiée, et vous n’utilisez que la couche dont vous avez besoin. En bas se trouve une abstraction de fournisseur couvrant de nombreux fournisseurs de modèles ; au milieu se trouvent la boucle d’agent et l’affectation des outils ; par-dessus se trouve un temps de exécution complet avec persistance des sessions ; et en surface se trouve une interface utilisateur terminale. Pour la plupart des ingénieurs, c’est la couche du milieu, la boucle, qui est intéressante. Lancer un agent signifie décrire ce qu’il doit être puis transférer le contrôle. Un petit agent de recherche construit sur l’API de session ressemble à ceci.

import { getModel } from "@mariozechner/pi-ai";
import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";

const { session } = await createAgentSession({
  model: getModel("anthropic", "claude-sonnet-4-6"),
  cwd: process.cwd(),
  sessionManager: SessionManager.open("./session.jsonl"),
  tools: ["read", "grep", "bash", "search_docs"], // built-ins + your custom tools, by name
  customTools: [searchDocsTool],                   // your typed tools
});

// You steer the runtime with config, not loop code:
session.agent.state.systemPrompt = "You are a research assistant. Always cite sources.";

// A single call runs the entire prompt → tools → results → repeat cycle:
await session.prompt("Summarize the latest on agent frameworks and cite sources.");

Remarquez ce qui est absent. Il n’y a pas de boucle while, aucune branche indiquant « si le modèle a renvoyé des appels d’outils, exécutez-les puis appelez à nouveau le modèle », et pas de mécanisme de comptage des tokens pour déterminer quand l’historique doit être compressé. Le temps d’exécution gère tout cela. Tout ce que vous avez fourni, c’est un modèle, les outils qu’il peut utiliser et une instruction système, suivis d’une seule appel à la méthode correspondante. Un outil personnalisé est également un objet simple et structuré : un nom, une description destinée au modèle, un schéma pour ses paramètres et une fonction d’exécution.

const searchDocsTool = {
  name: "search_docs",
  description: "Search the internal docs and return matching passages.",
  parameters: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
  async execute(_id: string, params: { query: string }, signal?: AbortSignal) {
    const passages = await search(params.query, signal);
    return { content: [{ type: "text", text: passages }], details: {} };
  },
};

Jusqu’ici, cela ressemble à une simple commodité. La complexité apparaît dès que l’on souhaite modifier le comportement de la boucle, car on ne peut pas réécrire une boucle qui n’appartient pas à soi. On doit plutôt y accéder par le biais d’événements et d’hooks. S’abonner aux événements permet d’observer son exécution et d’intervenir, par exemple en comptant les tours effectués et en arrêtant le processus une fois un plafond atteint.

let turns = 0;
const MAX_TURNS = 20;

const unsubscribe = session.agent.subscribe((event) => {
  if (event.type === "turn_end" && ++turns >= MAX_TURNS) {
    session.agent.abort(); // pull the cord on a loop you didn't write
  }
});

La même approche s’applique aux plafonds de coût : on accumule le coût par message au fur et à mesure de leur arrivée et on interrompt l’exécution dès que ce coût dépasse un certain seuil. Le système continue d’exécuter la boucle pendant que l’on observe son déroulement et que l’on décide du moment d’y mettre fin. Les hooks vont plus loin, en permettant d’intercepter une décision avant qu’elle ne prenne effet, de manière invisible pour le modèle. Un hook avant appel d’outil peut examiner une demande en attente et la refuser.

session.agent.beforeToolCall = async (ctx) => {
  if (ctx.toolCall.name === "bash" && isDangerous(ctx.args.command)) {
    return { block: true, reason: "blocked by policy" };
  }
  return undefined; // allow
};

L’approche par framework : composez vous-même le flux de contrôle

L’écosystème Python a choisi la voie opposée. Au lieu d’un moteur de exécution qui contrôle la boucle, il fournit des abstractions que vous assemblez vous-même, parmi lesquelles trois dominent.

LangChain : composer une chaîne

LangChain est le plus ancien et le plus complet des trois. Il a débuté en tant que moyen de relier les appels aux modèles de langage grand public, puis est devenu une plateforme étendue : plus d’un millier d’intégrations, une interface qui ne se soucie pas du modèle qui se trouve derrière elle, ainsi qu’un produit associé pour l’observabilité. Pour les agents, il offre un cycle d’appel d’outils de type ReAct, et son style de composition relie chaque composant au suivant. Sa grande force réside dans sa portée : s’il existe un modèle, un stockage de vecteurs ou un outil externe, il est probable qu’il existe aussi une intégration LangChain pour celui-ci. Sa faiblesse, c’est le prix payé pour cette généralité. Des couches profondes d’abstraction peuvent transformer le débogage en une véritable quête, et comme il est par défaut sans état, la durabilité doit être ajoutée en supplément plutôt que d’être inhérente. LangChain convient aux workflows standard, principalement linéaires et monopilotes, où la largeur des intégrations est ce que l’on valorise le plus.

LangGraph : modélisation d’une machine à états

LangGraph propose une hypothèse claire et tranchée : les systèmes d’agents sont des machines à états, et il convient de gérer chaque transition. On modélise l’agent sous forme d’un graphe dont les nœuds représentent les étapes et les arêtes représentent les transitions, le tout fonctionnant à l’aide d’un objet d’état typé.

from langgraph.graph import StateGraph, MessagesState, START, END

def call_model(state: MessagesState):
    return {"messages": [llm.invoke(state["messages"])]}
graph = StateGraph(MessagesState)
graph.add_node(call_model)
graph.add_edge(START, "call_model")
graph.add_edge("call_model", END)
app = graph.compile()
app.invoke({"messages": [{"role": "user", "content": "hi"}]})

Là où un framework cache la boucle, LangGraph en fait l’élément que vous créez vous-même. La prise de décision conditionnelle est définie explicitement.

graph.add_conditional_edges("call_model", should_continue, {"tools": "tools", "end": END})

Ce fonction continue ou arrête est précisément la branche que le framework exécute pour vous derrière une simple appel de prompt. Voici la vôtre : visible, testable et versionnable. La récompense de cette plus grande verbosité réside dans la fonctionnalité phare de LangGraph, à savoir le sauvegardage natif à chaque transition. Comme l’état est enregistré sur disque au fur et à mesure que le graphe progresse, vous bénéficiez d’une exécution qui survit aux redémarrages, à la récupération après une panne, de la possibilité de remonter dans le temps pour examiner des états antérieurs, ainsi que de pauses permettant d’attendre une intervention humaine au cours d’une exécution. Les inconvénients sont une courbe d’apprentissage raide et un design délibérément peu adapté au déploiement serverless. LangGraph convient aux workflows à long terme, cycliques, résistants aux pannes et nécessitant un état persistant.

CrewAI : assembler une équipe

CrewAI estime que la plupart des problèmes multi-agents se décomposent facilement en équipes de spécialistes. Chaque agent est défini par un rôle, un objectif et une histoire personnelle, puis on les rassemble en équipe pour leur confier des tâches.

from crewai import Agent, Task, Crew

researcher = Agent(role="Researcher", goal="Find key insights on {topic}",
                   backstory="A meticulous analyst.")
writer = Agent(role="Writer", goal="Summarize the research clearly",
               backstory="A concise technical writer.")

research = Task(description="Research {topic}.", expected_output="3 insights.", agent=researcher)
summary = Task(description="Write a summary.", expected_output="A short summary.", agent=writer)

Crew(agents=[researcher, writer], tasks=[research, summary]).kickoff(inputs={"topic": "agent frameworks"})

Le modèle mental est intuitif et le chemin vers un pipeline multi-agents fonctionnel est court, c’est pourquoi il est largement adopté par les entreprises. D’un autre côté, l’abstraction basée sur les rôles et les objectifs est pratique tant que tout se passe bien, mais devient alors une boîte noire ; la délégation entre agents peut créer des boucles ; et il faut mettre en place ses propres mécanismes de contrôle pour éviter des coûts incontrôlés. CrewAI convient aux systèmes multi-agents basés sur des rôles et au prototypage rapide. Ce qui relie ces trois éléments, c’est que c’est vous qui écrivez le flux de contrôle. Cela représente à la fois un coût — plus de code, plus de décisions, plus à maintenir — et un avantage, puisque vous pouvez voir, tester et modifier chaque transition. L’influence de Python dans le domaine des données et de l’apprentissage automatique constitue ici un véritable atout, car les agents fonctionnent à côté des bibliothèques et des notebooks que vous utilisez déjà.

Les critères selon lesquels le choix est réellement fait

Ces approches, alignées côte à côte, se distinguent selon un petit nombre d’axes, et quelques-unes d’entre elles représentent la majeure partie de l’effort en pratique.

La propriété du boucle d’exécution constitue l’axe principal ; tout le reste en dérive. Un framework conserve cette boucle pour lui-même, tandis qu’un autre vous la remet entre les mains ; LangGraph correspond à l’approche où vous écrivez tout, Pi à celle où le système d’exécution écrit tout, et les autres se situent entre ces deux extrêmes.

La durabilité constitue l’avantage décisif de LangGraph. Le fait de conserver un point de contrôle à chaque transition vous permet de récupérer rapidement en cas de panne et de rejouer les événements sans effort supplémentaire. La persistance des sessions offerte par certains outils ainsi que les types de mémoire fournis par CrewAI sont utiles, mais aucun d’eux ne permet de rejouer de manière fiable l’état du système. Lorsqu’un processus est interrompu au milieu d’une exécution, un graphe enregistré à des points de contrôle reprend là où il s’était arrêté, tandis qu’un journal de session ne permet généralement que de reconstituer a posteriori ce qui s’est produit.

L’orchestration de plusieurs agents est intégrée nativement dans CrewAI grâce aux rôles, et dans LangGraph via les sous-graphe. D’autres outils y parviennent par d’autres moyens, que ce soit grâce à des sous-agents, des extensions ou à une logique de coordination que vous fournissez.

L’écosystème linguistique environnant a tendance à décider discrètement. Choisir Python place l’agent dans l’orbite du travail avec des données et de l’apprentissage automatique, à côté de vos dataframes, de votre code d’entraînement et de vos notebooks. Choisir TypeScript l’oriente vers le web et les environnements edge, à côté de vos interfaces API, de vos types front-end partagés et de tous les endroits où vous déployez des fonctions serverless. Cette attraction gravitationnelle l’emporte souvent sur n’importe quelle caractéristique indiquée dans une fiche comparative, et il est avantageux d’être honnête quant au monde dans lequel votre équipe évolue déjà.

Une dernière précision pour garder la comparaison honnête : le schéma « Node avec outil d’orchestration » et « Python avec framework » est une tendance, pas une règle absolue. Il existe des environnements d’exécution de type outil d’orchestration écrits en Python, ainsi que des bibliothèques d’orchestration de type graphe écrites en TypeScript. Prenez votre décision en fonction du paradigme et de vos contraintes réelles, jamais en fonction du langage indiqué sur le paquet.

Une tâche, vue de chaque côté

Imaginez une tâche simple et ordinaire : un agent qui récupère quelque chose à l’aide d’un outil puis en produit un résumé. Ce que chaque paradigme vous fait affronter dès le départ, c’est l’aspect révélateur. Avec ce framework, le travail consiste presque entièrement en configuration. Une fois qu’un outil de recherche et une instruction système ont été définis, vous lancez une seule requête ; le moteur d’exécution prend alors le relais, en appelant le modèle, en vérifiant qu’il souhaite effectuer une recherche, en l’exécutant, en renvoyant les résultats, en permettant au modèle de composer le résumé, puis en se terminant automatiquement. Ce framework vous demande en réalité une seule chose : quelles capacités cet agent doit-il posséder ?

Au sein d’un cadre comme LangGraph, le travail consiste presque entièrement en la définition de la structure. Vous spécifiez l’objet d’état, un nœud qui appelle le modèle, un nœud qui exécute l’outil, ainsi qu’une arête conditionnelle qui choisit entre retourner au modèle ou terminer. Tant que ce graphe n’a pas été entièrement dessiné, rien n’est exécuté du tout. LangGraph vous demande quelque chose de différent : quel est précisément le flux de contrôle ?

L’idée clé à retenir est que aucune des deux versions n’est difficile ; la tâche reste simple dans les deux cas. Ce qui les différencie n’est pas l’effort requis, mais ce que vous devez décider à l’avance et ce qui reste inspectable ou récupérable par la suite. Un outil d’assistance fait disparaître cette première question et vous permet de disposer rapidement d’un agent fonctionnel. Un framework, quant à lui, refuse de la faire disparaître et vous compense en vous offrant un flux que vous pouvez suivre étape par étape lorsque le système se comporte mal à un moment inopportun. Ce compromis devient de plus en plus important à mesure que le système s’agrandit. Lorsque vous développez un assistant de codage ou un agent intégré à une application, c’est la question des capacités qui importe, et l’outil d’assistance constitue alors un soulagement. Lorsque vous concevez un flux de travail qui doit fonctionner pendant longtemps, résister aux pannes et passer des audits, c’est la question du flux de contrôle qui prime, et chaque ligne supplémentaire du graphe explicite s’avère utile.

Adapter l’outil au système que vous devez posséder

Les heuristiques découlent directement de tout ce qui a été mentionné ci-dessus. Choisissez un harnais lorsque la vitesse d’un agent opérationnel est une priorité, lorsque les options intégrées au runtime vous conviennent déjà, lorsque la cible est un agent de codage ou d’outils pour développeurs, ou lorsque vous placez un agent au sein d’une application TypeScript et souhaitez la sécurité des types ainsi qu’une proximité avec votre interface utilisateur. Choisissez LangGraph lorsque la perte de progrès est inacceptable, ce qui correspond à des workflows de longue durée ou cycliques qui doivent survivre aux pannes, nécessitent un contrôle précis sur l’état et chaque transition, ou doivent être suspendus en attendant une intervention humaine. Choisissez CrewAI lorsque le problème se décompose réellement en un ensemble de rôles spécialisés et que vous souhaitez mettre en place rapidement une chaîne multi-agents. Choisissez LangChain lorsque l’ampleur des intégrations disponibles pour un flux conventionnel à un seul agent appelant des outils l’emporte sur tous les autres facteurs, en particulier au sein d’une base de code Python existante. Et encore une fois

Avec sensibilité, les frontières sont floues : il s’agit de choisir le paradigme et les contraintes, et non de déterminer quel langage est imprimé sur l’emballage.

La boucle était la question depuis le début

Le débat n’a jamais vraiment porté sur Node contre Python ou un package contre son concurrent. Il s’agissait plutôt de savoir si c’était le runtime qui devait gérer la boucle d’agent ou bien vous-même. Un harness renonce à la liberté de réécrire le flux en échange d’un agent fonctionnel rapidement, ainsi que de la possibilité de modifier son comportement via des configurations, des événements et des hooks, tout cela appliqué à une boucle que vous n’avez jamais écrite. Un framework échange la vitesse contre des commandes explicites, une durabilité et de l’orchestration, tout en vous faisant supporter les coûts permanents liés à l’écriture et à la maintenance du flux. Pour la plupart des équipes, une approche raisonnable consiste à utiliser un harness pour les prototypes, la programmation d’agents et tout élément intégré dans un produit, où la rapidité est le critère principal ; passer à LangGraph une fois que l’état durable devient une exigence stricte, et recourir à CrewAI lorsque le travail nécessite réellement une équipe de spécialistes. Comme ces outils continuent d’absorber les meilleures idées des uns et des autres, la compétence qui perdure n’est pas la maîtrise d’un seul d’entre eux.

API, mais il faut juger en regardant l’agent qui se trouve devant soi pour savoir qui devrait être responsable de son boucle.

Lectures complémentaires