Accueil / Articles

Reconstruire un agent de style Claude Code avec LangChain Deep Agents

Comment fonctionne le harnais d’un agent de codage, du cycle principal aux outils, à la planification, à la mémoire, aux sous-agents et aux approbations en environnement isolé, reconstruit étape par étape à l’aide de DeepAgents.

Éditeur : REACTAPP.TOP16 min de lecture3592 mots

Sources: **Build Your Own Claude Code Using Langchin: A Deepdive Into LangChain’s Deep Agents**

Un agent de codage n’est pas un chatbot

Il est facile d’imaginer un agent de codage comme un chatbot plus intelligent, mais cette idée est fausse. Les capacités de Claude Code ne proviennent pas uniquement du modèle lui-même ; elles viennent plutôt du système d’agentification conçu autour de ce dernier. Ce système constitue le plan de contrôle : le mécanisme invisible qui fournit au modèle des outils pour lire des fichiers, un environnement pour exécuter du code, une mémoire de ses actions antérieures ainsi qu’une liste de contrôle pour rester sur la bonne voie. Sans ce système, un modèle linguistique n’est qu’un générateur de texte ; avec lui, le même modèle se comporte comme un ingénieur logiciel autonome et compétent.

L’objectif ici est d’examiner comment fonctionne un agent de type Claude Code, étape par étape, et de reconstruire chacune de ces étapes à l’aide de la bibliothèque deepagents de LangChain. À la fin, cette analyse devrait devenir claire, car vous aurez construit vous-même un tel agent.

La boucle au cœur du système

Une session commence par le message de l’utilisateur. Il peut s’agir d’une question anodine concernant le contenu d’un dossier ou d’une tâche importante comme la refonte du système de connexion et la mise à jour de ses tests. Le modèle lit ce message avant de choisir une action, et c’est à ce moment que le parcours possède plusieurs branches. Lorsqu’une action est nécessaire, il demande un outil, reçoit la réponse et recommence le processus ; ce cycle est identique pour une tâche à un seul étape et pour une tâche à cinquante étapes. Dès qu’il répond en prose ordinaire sans demander d’outil, la boucle se termine et l’utilisateur voit le résultat. L’absence de demande d’outil est précisément le signal indiquant que l’agent estime avoir terminé sa tâche.

La case « décide de l’étape suivante » combine trois phases décrites dans la recherche sur les agents : la collecte de contexte par lecture de fichiers, l’action par modification et exécution de commandes, ainsi que la vérification par exécution de tests. Elles ne fonctionnent pas en tant que modes séparés ; il s’agit simplement de différentes appels d’outils qui se succèdent au sein d’une même boucle.

Il existe un changement de mode délibéré, le mode planification. Généralement, l’agent planifie et agit en même temps. En mode planification, un point de contrôle se trouve avant la boucle : l’agent enquête et rédige une proposition sans rien modifier — les fichiers sont lus et les idées sont élaborées, mais les modifications et commandes attendent que l’utilisateur donne son accord, ce n’est qu’alors que la boucle normale s’exécute. Cela constitue une valve de sécurité pour les tâches volumineuses ou risquées. Ce n’est pas identique à la liste de tâches que l’agent conserve pour lui-même pour chaque mission, car le mode planification est une pause déclenchée par l’utilisateur et qui nécessite son approbation explicite.

Le diagramme omet toujours les mécanismes qui entourent le cycle : la compression lorsque la mémoire est pleine, les sous-agents qui exécutent leurs propres boucles privées, ainsi que la couche de permissions et de sandbox qui se situe entre « exécuter un outil » et le système réel. Aucun de ces éléments ne modifie le cycle ; ils l’entourent simplement.

Quatre capacités se trouvent à l’intérieur de ce système, et c’est elles qui maintiennent le cycle stable. La planification fait en sorte que l’agent réfléchisse d’abord. Les outils agissent comme ses mains. La gestion du contexte empêche les tâches longues de submerger sa mémoire. Les sous-agents prennent en charge des tâches secondaires dans leurs propres boucles privées afin que le thread principal reste organisé. Ils alimentent le cycle sans jamais le réécrire.

En dessous de tout cela se trouve la couche la plus importante. Lorsque l’agent souhaite interagir avec le monde réel en modifiant un fichier, en exécutant une commande ou en utilisant le réseau, l’action n’atteint pas directement la machine. Elle passe d’abord par des règles de permissions et un environnement isolé, et aucune autre mesure de sécurité intégrée n’a autant d’importance. Imaginez tenter de protéger un agent en ajoutant une phrase comme « veuillez ne pas supprimer quoi que ce soit d’important » à ses instructions. Il s’agit alors d’une note adhésive que le modèle peut ignorer, mal interpréter ou contourner. Une véritable protection repose en dehors du modèle : une règle de permissions qui rejette catégoriquement toute demande d’utilisation d’outil, ainsi qu’un environnement isolé du système d’exploitation qui bloque toute commande dangereuse, quel que soit le choix du modèle.

Le moteur nu

Avant de recourir à des bibliothèques, il est utile de voir le moteur tel qu’il est, sans rien ajouté. Il s’agit d’un cycle d’agent complet et fonctionnel.

def run_agent_loop(client, user_message, tools, tool_functions, max_turns=20):
    messages = [{"role": "user", "content": user_message}]
    for _ in range(max_turns):
        # 1. Ask the model what to do next.
        response = client.messages.create(
            model="claude-sonnet-4-6", max_tokens=1024, tools=tools, messages=messages
        )
        messages = [*messages, {"role": "assistant", "content": response.content}]

        # 2. Plain text and no tool request? The job is done.
        tool_uses = [b for b in response.content if b.type == "tool_use"]
        if not tool_uses:
            return "".join(b.text for b in response.content if b.type == "text")
        # 3. Run each requested tool and hand the results back. 4. Repeat.
        results = [
            {"type": "tool_result", "tool_use_id": call.id,
             "content": tool_functions[call.name](**call.input)}
            for call in tool_uses
        ]
        messages = [*messages, {"role": "user", "content": results}]
    raise RuntimeError(f"agent loop did not finish within {max_turns} turns")

Cela correspond à environ trente lignes, et le reste du design est constitué de cette boucle auxquelles sont ajoutés des éléments supplémentaires. Les outils plus puissants sont placés dans tool_functions. Un planificateur prend des notes dans messages. Un compacteur élimine les éléments de messages lorsque cette liste devient trop volumineuse. Un sous-agent n’est rien d’autre que la même fonction appelée à nouveau avec une liste messages nouvelle et vide. Une vérification des permissions a lieu entre le moment où le modèle demande un outil et la ligne qui l’exécute. La limite max_turns représente cette paranoïa inhérente à tout système réel, car un modèle bloqué dans une boucle consomme des ressources sans fin.

Chaque agent de codage possède ce cœur battant, la boucle d’agent qui transforme les instructions en actions, et elle est presque embarrassamment simple :

  1. Demander au modèle ce qu’il doit faire ensuite.
  2. Le modèle répond soit en texte brut, soit demande l’utilisation d’un outil.
  • Si une outil est demandé, le système exécute cet outil et renvoie le résultat au modèle.
  • Retournez à l’étape 1.
  • Lorsque le modèle répond en texte brut sans demander d’outil, la tâche est terminée ; arrêtez alors et affichez la réponse.
  • La lecture d’un fichier, l’exécution d’une commande et l’édition de code passent toutes par ce même cycle. Imaginez un entrepreneur à qui l’on donne une tâche et une liste de contrôle : il vérifie ce qu’il doit faire ensuite, l’exécute, examine le résultat, choisit à nouveau, et ne fait son rapport que lorsque la liste est vide. La boucle remplit alors le rôle du jugement de cet entrepreneur. Elle s’adapte également automatiquement, car une question simple peut nécessiter un seul passage, tandis qu’une refonte approfondie implique des dizaines d’appels d’outils, sans qu’il soit nécessaire de déterminer à l’avance le nombre d’étapes.

    Le construire avec des Deep Agents

    Il n’est pas nécessaire d’écrire la boucle while manuellement. La création d’un Deep Agent fournit automatiquement une boucle fonctionnelle. Commencez par installer la bibliothèque ainsi qu’un fournisseur de modèles.

    pip install deepagents langchain-anthropic
    

    Créez ensuite l’agent le plus simple possible.

    from deepagents import create_deep_agent
    
    def get_weather(city: str) -> str:
        """Get the weather for a given city."""
        return f"It's always sunny in {city}!"
    agent = create_deep_agent(
        model="anthropic:claude-sonnet-4-6",
        tools=[get_weather],
        system_prompt="You are a helpful assistant.",
    )
    result = agent.invoke(
        {"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}
    )
    

    Cette appel à create_deep_agent renvoie un agent dont le boucle est déjà intégrée, et .invoke() exécute exactement le cycle décrit ci-dessus. L’outil de gestion ne se soucie pas du modèle utilisé, qu’il s’agisse d’OpenAI, de Google ou d’Anthropic. Le boucle reste identique.

    Les outils sont les mains

    Tandis que le boucle est le moteur, les outils sont les mains. Sans eux, un modèle ne peut que parler. Avec de bons outils, il peut lire une base de code, la modifier et vérifier le résultat. Les véritables outils de programmation se répartissent en quatre familles :

    • Les outils de lecture examinent le contenu sans rien modifier : read_file ouvre un fichier, glob trouve des fichiers selon un motif de nom, comme tous les fichiers .py, et grep recherche à l’intérieur des fichiers un texte spécifique. Les débutants confondent souvent ces deux derniers, mais glob trouve les fichiers par nom tandis que grep les trouve par contenu.
  • Les outils d’édition modifient les fichiers en toute sécurité : write_file crée de nouveaux fichiers tandis que edit_file modifie une partie d’un fichier existant.
  • Un outil d’exécution lance des commandes de shell, identiques à celles tapées dans un terminal.
  • Un outil de délégation, task, assigne une sous-tâche à un agent auxiliaire.
  • Pourquoi ne pas permettre à l’agent d’utiliser cat pour lire et sed pour éditer ? C’est possible, mais les outils conçus spécifiquement ont des avantages sur deux points. D’abord, la gestion du budget de tokens : un outil de lecture dédié peut examiner la taille d’un fichier avant de le charger et la réduire si nécessaire, tandis que cat affiche tout le contenu d’un coup, ce qui peut dépasser la limite de la fenêtre de contexte. Ensuite, les permissions plus claires : une appel à l’outil bien connu edit_file indique précisément ce qui se passe à l’outil de gestion, lui permettant d’appliquer des règles de sécurité, alors qu’une commande de shell libre est une entité opaque et risquée.

    Il y a ici une particularité amusante. Les modèles ont appris à partir d’Internet public, rempli de réponses sur Stack Overflow utilisant cat et sed, de sorte que même les modèles avancés recourent par habitude au shell brut. L’invite système doit leur rappeler fermement d’utiliser leurs véritables outils et non les raccourcis du shell.

    Deep Agents fournit l’ensemble complet intégré : planification, outils de système de fichiers, exécution et délégation. Les outils personnalisés s’ajoutent en tant que fonctions décorées.

    import subprocess
    from langchain_core.tools import tool
    
    MAX_OUTPUT_CHARS = 20_000  # roughly 5k tokens
    @tool
    def run_tests(path: str = ".") -> str:
        """Run the project's pytest suite and return its output.
        Output is truncated to the last 20k characters (failures appear at the
        end). The run is killed after 5 minutes.
        """
        try:
            result = subprocess.run(["pytest", path], capture_output=True,
                                    text=True, check=False, timeout=300)
        except subprocess.TimeoutExpired:
            return "pytest timed out after 300s"
        output = result.stdout + result.stderr
        if len(output) > MAX_OUTPUT_CHARS:
            return ("[... output truncated to the last 20,000 characters ...]\n"
                    + output[-MAX_OUTPUT_CHARS:])
        return output
    agent = create_deep_agent(
        model="anthropic:claude-sonnet-4-6",
        tools=[run_tests],            # merged in alongside the built-ins
        system_prompt="You are a coding assistant. Always run tests after editing.",
    )
    

    Le décorateur @tool permet à une fonction ordinaire d’être appelée par le modèle. Sa documentation revêt une grande importance, car le modèle la considère comme le manuel indiquant quand et comment utiliser l’outil.

    Planifier avant d’agir

    Lorsqu’on soumet une demande complexe à un modèle simple, celui-ci fonctionne souvent de manière chaotique : des appels ici et là, sans plan global, parfois en boucle infinie. De bons agents de programmation évitent cela en élaborant un plan avant d’intervenir. Ils rédigent une liste de tâches structurée qui divise le travail en étapes claires, puis avancent pas à pas en marquant chaque élément comme en cours ou terminé. C’est un peu comme un cuisinier qui lit l’ensemble de la recette avant d’allumer le feu, afin que l’étape quatre ne soit pas inventée au moment où l’étape trois brûle.

    Il existe un astuce d’ingénierie ingénieuse derrière cela. Au cours d’une session longue, l’objectif initial se retrouve noyé sous des pages de résultats générés par les outils, et le modèle en perd la trace. Pour y remédier, le système insère à nouveau la liste des tâches en cours dans la conversation après les appels aux outils, un peu comme un collègue qui glisserait une liste de contrôle vers l’autre côté du bureau en guise de rappel.

    Dans Deep Agents, il s’agit de l’outil intégré write_todos. Aucune configuration n’est nécessaire, car l’instruction système par défaut apprend à l’agent à planifier avant d’agir et à maintenir la liste à jour tout au long du cycle.

    Gestion du contexte

    C’est ici que réside l’ingénierie la plus complexe. Chaque modèle dispose d’une fenêtre de contexte, c’est-à-dire d’un plafond fixe pour sa mémoire de travail. Quelques fichiers volumineux ainsi que quelques commandes suffisent à la remplir rapidement, et une fois dépassé ce seuil, l’agent commence à oublier des éléments, y compris les instructions initiales. Les agents de codage gèrent ce problème grâce à deux stratégies :

    1. Les fichiers servent de mémoire externe. Au lieu d’afficher un résultat de recherche très long dans la conversation, l’agent l’enregistre sur disque et ne conserve que le nom du fichier, puis l’ouvre à nouveau lorsqu’il a besoin des détails.
    2. Lorsque la conversation approche de sa limite, une compression est déclenchée. L’agent arrête la boucle, condense les échanges anciens, stocke les faits importants dans une mémoire à long terme et efface la mémoire à court terme surchargée.

    Pensez à l’écriture d’un rapport volumineux : personne ne peut garder tous les documents en mémoire. On prend des notes, on classe les documents et on conserve un résumé d’une page sur le bureau. Ces deux fonctionnalités sont intégrées dans Deep Agents, qui offre un système de fichiers virtuel pour gérer les résultats importants ainsi qu’un intermédiaire de résumé automatique, ce qui permet une gestion de la mémoire dès la création de l’agent.

    Sous-agents : diviser pour conquérir

    Certaines tâches sont trop volumineuses pour être traitées au cours d’une seule conversation. Rechercher dans une base de code immense peut signifier lire cinquante fichiers, et ces cinquante fichiers ne devraient pas engorger la mémoire du principal agent. La solution réside dans les sous-agents. Le principal agent lance un assistant, lui confie une tâche précise comme localiser l’endroit où se trouve la logique d’authentification, et cet assistant travaille dans sa propre fenêtre de contexte distincte. Une fois la tâche terminée, il renvoie un résumé concis. Un manager qui délègue des recherches à un employé junior et reçoit un résumé en une seule paragraphe maintient ainsi son travail organisé.

    Il existe également une mesure de sécurité : il est strictement interdit aux sous-agents de créer à leur tour des sous-agents. Sinon, un agent confus pourrait générer une chaîne infinie d’assistants imbriqués, entraînant un processus incontrôlable qui épuiserait le budget alloué aux API. Une limite stricte de profondeur permet de garantir la sécurité des délégations.

    Deep Agents comprend un outil task intégré permettant de créer des assistants. Ceux qui sont spécialisés, comme un moteur de recherche de code dédié, sont décrits à l’aide de dictionnaires simples.

    code_searcher = {
        "name": "code-searcher",
        "description": "Searches the codebase to find where specific logic lives. "
                       "Use this for any open-ended 'where is X?' question.",
        # ⚠️ NOTE: the key is `system_prompt`, NOT `prompt` (see correction below).
        "system_prompt": "You are an expert at navigating codebases. Use the grep "
                         "and glob tools to locate relevant files, then report a "
                         "concise summary of what you found and where. Do not make "
                         "any edits.",
    }
    
    agent = create_deep_agent(
        model="anthropic:claude-sonnet-4-6",
        tools=[run_tests],
        system_prompt="You are a coding assistant.",
        subagents=[code_searcher],
    )
    

    L’agent principal sait désormais qu’un spécialiste existe, ce qui permet de maintenir sa propre mémoire vierge pour le véritable travail de codage.

    Sécurité et approbation humaine

    À ce stade, l’agent peut modifier des fichiers et exécuter des commandes shell sur une machine réelle, ce qui mérite une réflexion. Un agent trop zélé pourrait supprimer des fichiers erronés ou exécuter des actions destructrices. Une grande puissance sans contrôles représente un véritable risque. Les agents de codage mettent en place des contrôles en deux niveaux :

    1. Les listes d’autorisation et les règles de refus permettent aux outils sûrs, tels que la lecture de fichiers, d’exécuter automatiquement, tandis que ceux qui sont destructeurs, comme la suppression de fichiers, nécessitent une approbation.
    2. Les demandes d’approbation, également appelées système humain intégré, font en sorte que l’agent s’arrête et demande une confirmation avant d’effectuer des actions risquées. L’utilisateur approuve, modifie ou rejette l’action, puis le cycle se poursuit.

    La règle fondamentale de la sécurité des agents est qu’une instruction ne peut pas constituer une barrière de sécurité. Demander à un modèle de ne pas supprimer quoi que ce soit d’important relève d’une requête courtoise et non d’un obstacle, car le modèle peut ignorer cette instruction en générant des hallucinations. L’application des règles doit se faire en dehors du modèle, dans le système de contrôle. Une règle d’autorisation peut refuser une appel à outil, et un environnement sandbox peut bloquer des commandes shell dangereuses au niveau du système d’exploitation, peu importe les tentatives du modèle de les exécuter. C’est pourquoi l’outil d’exécution nécessite un backend adéquat. La courtoisie n’est pas une forme de sécurité.

    Deep Agents place ses limites dans ce système de contrôle. L’accès au shell nécessite un backend, et le mécanisme d’interruption intégré à LangGraph pause l’exécution avant toute opération risquée.

    from deepagents import create_deep_agent
    # Import path for backends can vary by version - check the current
    # "Backends" page in the Deep Agents docs.
    from deepagents.backends import LocalShellBackend
    
    agent = create_deep_agent(
        model="anthropic:claude-sonnet-4-6",
        system_prompt="You are a coding assistant working inside this project.",
        backend=LocalShellBackend(),   # enables the `execute` shell tool
    )
    

    Si une outil protégé est demandé, la boucle s’arrête et le résultat contient un en-tête __interrupt__ qui décrit l’action en attente ainsi que les options proposées : approve, edit, reject ou respond. Une fois la décision prise, l’exécution reprend.

    from langgraph.types import Command
    
    result = agent.invoke({"messages": [...]}, config)
    result["__interrupt__"]   # the pending action + allowed decisions — show your user
    
    # You decide; the loop picks up exactly where it paused:
    agent.invoke(Command(resume={"decisions": [{"type": "approve"}]}), config)
    # ... or {"type": "reject"} — the tool is never run, and the model is told so.
    

    Les limites réelles se trouvent dans le système de contrôle, et non seulement dans les instructions fournies.

    Mémoire entre sessions

    Définitivement, un agent perd tout dès que la conversation se termine. Un assistant véritablement utile devrait se souvenir des conventions de codage d’un projet, de ce que l’utilisateur préfère et de là où il s’était arrêté la veille. Deux mécanismes permettent d’y parvenir. Le pointage de sauvegarde stocke l’état précis de l’agent, afin qu’une tâche longue puisse survivre à une interruption et reprendre au même endroit. La mémoire à long terme conserve des informations qui dépassent la durée d’une seule conversation, comme par exemple le fait qu’un projet doit être indenté avec quatre espaces.

    Puisque Deep Agents fonctionne sur LangGraph, la persistance est assurée en intégrant un mécanisme de pointage de sauvegarde.

    from deepagents import create_deep_agent
    from langgraph.checkpoint.memory import InMemorySaver
    
    agent = create_deep_agent(
        model="anthropic:claude-sonnet-4-6",
        tools=[run_tests],
        system_prompt="You are a coding assistant.",
        checkpointer=InMemorySaver(),   # remembers state within a session
    )
    # A "thread_id" ties messages together into one ongoing conversation.
    config = {"configurable": {"thread_id": "project-alpha"}}
    agent.invoke(
        {"messages": [{"role": "user", "content": "Start refactoring the auth module."}]},
        config=config,
    )
    # Later, same thread_id - the agent remembers the earlier turn:
    agent.invoke(
        {"messages": [{"role": "user", "content": "Now update the tests too."}]},
        config=config,
    )
    

    InMemorySaver n’est actif que pendant la durée de vie du programme. Pour une persistance qui survive aux redémarrages, remplacez-le par un point de contrôle géré par une base de données.

    Tout dans un seul agent

    Lorsqu’ils sont assemblés ensemble, ces éléments forment un seul agent : une boucle principale ainsi que des outils personnalisés, un mécanisme de planification, un système de gestion du contexte, un sous-agent d’aide, une interface shell et un état enregistré, soit sept composants au total.

    from deepagents import create_deep_agent
    from deepagents.backends import LocalShellBackend
    from langchain_core.tools import tool
    from langgraph.checkpoint.memory import InMemorySaver
    
    # --- A custom tool (Part 2) — token-budgeted, see Part 2 for the full body ---
    @tool
    def run_tests(path: str = ".") -> str:
        """Run the project's pytest suite and return its (truncated) output."""
        import subprocess
        try:
            result = subprocess.run(["pytest", path], capture_output=True,
                                    text=True, check=False, timeout=300)
        except subprocess.TimeoutExpired:
            return "pytest timed out after 300s"
        output = result.stdout + result.stderr
        return output if len(output) <= 20_000 else "[... truncated ...]\n" + output[-20_000:]
    
    # --- A specialized subagent (Part 5) — note the `system_prompt` key ---
    code_searcher = {
        "name": "code-searcher",
        "description": "Finds where specific logic lives in the codebase. "
                       "Use for open-ended 'where is X?' questions.",
        "system_prompt": "You navigate codebases using grep and glob, then report a "
                         "concise summary of what you found. You never make edits.",
    }
    
    # --- A system prompt that teaches good behavior (Parts 2 & 3) ---
    SYSTEM_PROMPT = """You are a careful coding assistant.
    Workflow:
    1. Plan the task as a to-do list before doing anything.
    2. Use your built-in read, grep, and glob tools to explore - never the raw shell
       equivalents like cat or grep.
    3. Make focused edits.
    4. ALWAYS run the tests after editing, and fix anything that breaks.
    5. Delegate broad codebase searches to the code-searcher subagent.
    """
    
    # --- Assemble the agent (Parts 1, 4, 6, 7 handled by the harness) ---
    agent = create_deep_agent(
        model="anthropic:claude-sonnet-4-6",                  # Part 1: the loop, model-agnostic
        tools=[run_tests],                                     # Part 2: custom hands
        system_prompt=SYSTEM_PROMPT,                           # Parts 2 & 3: behavior + planning
        subagents=[code_searcher],                             # Part 5: delegation
        backend=LocalShellBackend(root_dir=".", virtual_mode=False),  # Part 6: shell access
        interrupt_on={"execute": True,                         # Part 6: the brakes —
                      "write_file": True, "edit_file": True},  #   approval before anything destructive
        checkpointer=InMemorySaver(),                          # Part 7: memory across turns
    )
    # Part 4 (context management) and built-in planning come on automatically.
    
    config = {"configurable": {"thread_id": "my-project"}}
    result = agent.invoke(
        {"messages": [{"role": "user", "content": "The login tests are failing. Fix them."}]},
        config=config,
    )
    print(result["messages"][-1].content)
    

    Moins d’une centaine de lignes suffisent à créer un agent de codage capable de planifier, rechercher, modifier du code, exécuter des tests, confier des tâches à d’autres agents, rester dans un environnement isolé et conserver son contexte. Son architecture correspond à celle de Claude Code : une boucle simple entourée d’outils, de mécanismes de planification, de gestion de la mémoire, de délégation ainsi que de freins situés en dehors du modèle.