← Notes

J'ai confié mes relevés à un deep agent — et surtout, il s'en souvient

· ia · stack · build · 11 min · EN

Ma série sur les agents a suivi un arc simple : le harness qui transforme un LLM en exécutant, puis l’aiguilleur qui fait travailler deux agents, puis Flue qui emballe le tout en framework — côté Astro, en TypeScript.

Cette fois, c’est LangChain qui sort son framework : deepagents. Même idée, autre écosystème (Python, LangGraph). Et pour le tester, pas un jouet — un vrai besoin perso : un compagnon qui lit mes relevés bancaires (en PDF, sur ma machine) et me dit où part l’argent, dans une vraie fenêtre de chat.

deepagents en une table

deepagents se décrit comme un « batteries-included agent harness » posé sur LangGraph. Le truc marrant, c’est que ses quatre piliers sont exactement les briques que je décris depuis trois mois — mais nommées et livrées d’office :

Pilier deepagentsCe que ça faitCe que j’en disais déjà
Planning (write_todos)Un outil quasi no-op qui force l’agent à poser un plan avant d’agirLa boucle du harness, mais avec une todo-list explicite
Sous-agentsDélégation à contexte isolé ; seul le résultat final remonteMon article routeur : « appeler un agent = un appel d’outil »
Système de fichiersRead/write/edit/search sur backends pluggables → mémoire + offload des gros outputs sur disqueLe pattern fichiers de Flue, le sandbox
System promptLe persona et les garde-fousMon agent.md / SKILL.md

Pourquoi la banque justifie les quatre piliers

Ma règle, répétée dans l’article routeur : l’architecture est une réponse à une complexité réelle, pas un trophée. Un bon use case, c’est celui où les quatre piliers ont chacun une vraie raison d’exister. La gestion bancaire les coche tous — et surtout le dernier, celui que je sous-estimais.

Parce que gérer un budget, ce n’est pas une analyse jetable. C’est longitudinal : le mois prochain compte par rapport à ce mois-ci. Et c’est précisément ce que le système de fichiers apporte — la mémoire. C’est lui qui transforme une analyse one-shot en compagnon qui se souvient. Voilà la vraie découverte : le pilier qui fait la différence n’est ni le plan ni les sous-agents, c’est le disque.

1. Le plan — l’agent pose sa todo avant d’exécuter

Deux types de demandes arrivent dans le chat. Une question ponctuelle (« combien en resto ce mois ? ») : pas besoin de plan, le dispatcher délègue direct. Une demande multi-étapes (« génère le rapport de juin ») : là, l’agent écrit d’abord son plan via write_todos :

todos:
  ☐ extraire le relevé depuis releves/2026-06.pdf (en local)
  ☐ sortir la donnée structurée          → sous-agent extracteur
  ☐ écrire l'extraction dans memoire/extraction-2026-06.md
  ☐ générer le rapport + optimisations    → sous-agent rapporteur
  ☐ mettre à jour memoire/budget.md

write_todos n’exécute rien. C’est un no-op qui force la décomposition : sur une tâche à plusieurs étapes, l’agent qui a écrit son plan dérive beaucoup moins que celui qui improvise.

2. Trois agents, un dispatcher — et ils collaborent par les fichiers

Le main deep agent ne répond pas lui-même : il classe ta demande et délègue au bon spécialiste. C’est le pattern de mon article routeur, sauf qu’ici c’est le framework qui déclenche task(...) — le style B, la délégation autonome. Trois spécialistes, trois rôles nets :

Sous-agentSa tâcheCe qu’il fait des fichiers
extracteurSort la donnée structurée : abonnements, entrées/sorties, par catégorieécrit memoire/extraction-<mois>.md
chatRépond à n’importe quelle question ponctuelle sur tes financeslit l’extraction + la mémoire
rapporteurGénère le rapport synthétique : entrées, sorties, optimisationslit l’extraction, écrit memoire/rapport-<mois>.md

Le point clé est dans la dernière colonne : les trois ne se parlent pas directement. L’extracteur écrit un fichier, le rapporteur et le chat le relisent. Leur canal de collaboration, c’est le système de fichiers — exactement l’atelier partagé que deepagents met au centre. (On y revient section 4 : le même disque sert de mémoire entre les mois et d’atelier entre les agents.)

En code, ça tient en un appel — et je pointe deepagents vers un modèle Claude, parce que le framework est agnostique du modèle :

from deepagents import create_deep_agent

subagents = [
    {
        "name": "extracteur",
        "description": "Sort la donnée structurée d'un relevé : abonnements, entrées/sorties, par catégorie.",
        "prompt": "Lis le texte du relevé. Rends un JSON { entrees, sorties, "
                  "categories, abonnements[] }. Aucun montant inventé. "
                  "Écris le résultat dans memoire/extraction-<mois>.md.",
    },
    {
        "name": "chat",
        "description": "Répond à n'importe quelle question ponctuelle sur mes finances.",
        "prompt": "Réponds en t'appuyant sur memoire/. Chiffre, cite le mois. "
                  "Donnée manquante → tu le dis, tu n'inventes pas.",
    },
    {
        "name": "rapporteur",
        "description": "Génère un rapport synthétique du mois avec des optimisations.",
        "prompt": "À partir de memoire/extraction-<mois>.md, écris un rapport : "
                  "1) entrées vs sorties, 2) top postes, 3) évolution vs mois "
                  "précédent, 4) trois optimisations concrètes et chiffrées. "
                  "Pas de moralisation.",
    },
]

agent = create_deep_agent(
    model="anthropic:claude-opus-4-8",   # agnostique : Claude, open-weight ou local
    system_prompt=CONSEILLER_BUDGET,
    subagents=subagents,
    tools=[extraire_releve],             # extraction PDF locale — défini juste en dessous
)

Remarque ce que je n’écris pas : aucune boucle d’aiguillage à la main. Le dispatcher, c’est le main agent lui-même ; le code se contente de déclarer les trois spécialistes et de lui donner le tool d’extraction PDF. Il décide qui bosse.

3. Les relevés arrivent en PDF — extraits en local

Ma banque ne me donne pas un CSV propre : elle me donne un PDF par mois. C’est le format réel, et c’est aussi le plus sensible. Donc l’extraction se fait sur ma machine, hors-ligne, avant que quoi que ce soit n’atteigne le modèle. deepagents accepte des tools custom : j’en écris un qui lit le PDF en local.

import pdfplumber  # extraction 100 % locale, aucune requête réseau

def extraire_releve(chemin: str) -> str:
    """Lit un relevé PDF sur le disque et rend son texte. Rien ne sort de la machine."""
    with pdfplumber.open(chemin) as pdf:
        return "\n".join(page.extract_text() or "" for page in pdf.pages)

L’agent appelle extraire_releve("releves/2026-06.pdf") quand son plan l’exige. Le PDF ne quitte jamais le dossier ; seul le texte extrait entre dans le raisonnement. C’est le premier maillon d’une boucle 100 % locale — j’y reviens plus bas.

4. Le système de fichiers — la mémoire, la vraie vedette

Voici la pièce qui change tout. deepagents donne à l’agent des outils fichiers (ls, read_file, write_file, edit_file) sur un backend pluggable. Je m’en sers pour trois rôles distincts :

finances/
├─ releves/
│  ├─ 2026-05.pdf              ← relevé mensuel brut (le PDF de la banque)
│  └─ 2026-06.pdf              ← données lourdes, offloadées (hors contexte)
└─ memoire/
   ├─ extraction-2026-06.md    ← donnée structurée, écrite par l'extracteur
   ├─ budget.md                ← mémoire persistante : mon budget de référence
   └─ rapport-2026-06.md       ← le rapport du mois, écrit par le rapporteur
  • releves/*.pdf : les données lourdes vivent sur le disque, pas dans le contexte. L’agent extrait ce dont il a besoin, quand il en a besoin.
  • memoire/extraction-*.md : écrite par l’extracteur, relue par le chat et le rapporteur. C’est l’atelier partagé entre les trois agents — leur seul canal de communication.
  • memoire/budget.md : le mois prochain, l’agent relit ce fichier et compare. C’est ça, se souvenir — la mémoire entre les mois.
  • memoire/rapport-*.md : le livrable, versionné mois après mois.

Et comme on parle de données bancaires, le point de sécurité n’est pas négociable. deepagents propose des permissions déclaratives par chemin — l’écho direct de la « sécurité par périmètre » de mon article routeur, sauf qu’ici le périmètre porte sur des fichiers, pas sur des tools :

from deepagents.backends import FilesystemBackend

backend = FilesystemBackend(
    root_dir="./finances",
    permissions={
        "releves/**": "read",         # il lit les relevés, il n'y touche pas
        "memoire/**": "read-write",   # il tient sa mémoire à jour
    },
)

5. Le persona — factuel, il ne moralise pas

Le system_prompt tient le rôle de mon agent.md. Ici, une posture nette :

CONSEILLER_BUDGET
Tu es un conseiller budget factuel.
- Chiffre tout. Aucun montant inventé : si tu n'as pas la donnée, tu le dis.
- Tu ne moralises pas. « 340 € de resto » est un fait, pas un reproche.
- L'essentiel d'abord : où part l'argent, puis ce qui est coupable.
- Compare toujours au mois précédent via memoire/budget.md.

6. L’UI — assistant-ui, parce que deepagents = LangGraph

Un compagnon, ça se parle. Pas un agent.invoke() dans un terminal — une vraie fenêtre de chat. Et là, un heureux hasard technique : deepagents tourne sur LangGraph, et assistant-ui — la librairie React de chat IA — a justement un runtime LangGraph de première classe. Les deux se branchent sans colle.

Tu sers ton deep agent avec langgraph dev (serveur local), et côté front, useLangGraphRuntime fait le pont :

import { AssistantRuntimeProvider, Thread } from "@assistant-ui/react";
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
import { Client } from "@langchain/langgraph-sdk";

// serveur LangGraph LOCAL : `langgraph dev` sert le deep agent sur localhost
const client = new Client({ apiUrl: "http://localhost:2024" });

export function BudgetChat({ threadId }: { threadId: string }) {
  const runtime = useLangGraphRuntime({
    stream: async (messages, { command }) =>
      client.runs.stream(threadId, "agent", { input: { messages }, command }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Thread />   {/* fil de conversation + composer, prêts à l'emploi */}
    </AssistantRuntimeProvider>
  );
}

Le bonus, c’est les interrupts. deepagents sait demander une validation humaine avant une action sensible ; le runtime LangGraph d’assistant-ui gère ces interrupts nativement. Concrètement : avant de réécrire memoire/budget.md, l’agent s’arrête, l’UI affiche « je mets à jour ton budget de référence ? », tu valides. Le human-in-the-loop est livré par le branchement, pas codé à la main.

La carte complète

Quatre étages, un seul sens de lecture : la vue parle au harness, qui découpe le travail entre trois agents, qui collaborent par les documents — et la réponse remonte dans le chat.

VUE assistant-ui · chat local useLangGraphRuntime · stream + interrupts HARNESS · deep agent (LangGraph) write_todos · dispatch · la boucle LE DÉCOUPAGE — 3 agents, 1 dispatcher extracteur abonnements · in/out → écrit extraction.md écrit chat toute question lit la mémoire lit rapporteur rapport + optims → écrit rapport.md lit · écrit DOCUMENTS · 100% local releves/2026-06.pdf relevé brut (hors contexte) memoire/extraction.md atelier partagé entre agents memoire/budget.md mémoire persistante memoire/rapport.md livrable du mois réponse / rapport streamé
De la vue au disque : l'UI parle au harness, qui découpe le travail entre trois agents, qui collaborent par les documents.

Honnête : où ça en est

Trois réserves, dans l’esprit de ce que je disais sur Flue.

C’est du Python/LangGraph pour l’agent, et du React pour l’UI — pas mon terrain Astro zéro-JS. Ce compagnon est donc un petit outil à part, pas un truc que je boulonne sur ce portfolio statique. Il existe deepagentsjs si tu veux tout garder en TS, mais l’écosystème mûr (traces LangSmith, backends) reste côté Python. Choisis ton versant en connaissance de cause.

La boucle peut rester 100 % locale, et c’est tout l’intérêt. PDF extrait sur ta machine (pdfplumber, hors-ligne) + modèle open-weight ou local (deepagents est agnostique) + serveur LangGraph auto-hébergé (langgraph dev) + UI servie en local : la donnée bancaire ne quitte jamais ta machine. Les permissions filesystem sont un garde-fou de plus, pas une bénédiction.

Et comme pour Flue : je l’ai câblé pour voir, pas encore mis en prod. Prends ça comme une cartographie, pas comme un retour de combat.

Ce que ça m’apprend

Toute ma série d’agents, jusqu’ici, était sans mémoire. Le harness, le routeur, Flue : à chaque fois, un contexte vierge, une tâche, une réponse, puis l’oubli. deepagents ajoute la pièce que je n’avais jamais vraiment exploitée — le disque comme mémoire — et c’est elle qui fait la bascule.

Un agent qui analyse ton mois, c’est un outil. Un agent qui se souvient de ton mois dernier pour juger celui-ci, à qui tu parles dans une vraie UI, et dont les relevés ne quittent jamais ta machine — ça, c’est un petit produit. La différence tient dans un dossier memoire/, deux fichiers markdown et un runtime qui se branche tout seul. Le plan et les sous-agents, je savais déjà les faire ; c’est la mémoire qui manquait pour que l’agent arrête de repartir de zéro à chaque fois — comme moi devant mes relevés, tous les mois, avant lui.