J'ai confié mes relevés à un deep agent — et surtout, il s'en souvient
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 deepagents | Ce que ça fait | Ce que j’en disais déjà |
|---|---|---|
Planning (write_todos) | Un outil quasi no-op qui force l’agent à poser un plan avant d’agir | La boucle du harness, mais avec une todo-list explicite |
| Sous-agents | Délégation à contexte isolé ; seul le résultat final remonte | Mon article routeur : « appeler un agent = un appel d’outil » |
| Système de fichiers | Read/write/edit/search sur backends pluggables → mémoire + offload des gros outputs sur disque | Le pattern fichiers de Flue, le sandbox |
| System prompt | Le persona et les garde-fous | Mon 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-agent | Sa tâche | Ce qu’il fait des fichiers |
|---|---|---|
| extracteur | Sort la donnée structurée : abonnements, entrées/sorties, par catégorie | écrit memoire/extraction-<mois>.md |
| chat | Répond à n’importe quelle question ponctuelle sur tes finances | lit l’extraction + la mémoire |
| rapporteur | Génère le rapport synthétique : entrées, sorties, optimisations | lit 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 :
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.
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.