→ Checkpointer LangGraph + thread_id : l'état d'une conversation, snapshotté à chaque étape.
Long terme Long-term memory
→ Store LangGraph : clé/valeur namespacé, cherchable par embeddings, indépendant du thread.
DeepAgents ajoute deux choses par-dessus ces deux primitives : un filesystem virtuel (offloading de contexte) et des sous-agents (isolation de contexte) — pas un troisième système de persistance.
Setup
Environnement — versions à épingler
# requirements.txt (voir aussi docs/code/requirements.txt)
langgraph>=1.2,<2.0
langchain>=1.4,<2.0
langmem>=0.0.30
deepagents>=0.7,<0.8
python>=3.11,<4.0
Ces trois packages évoluent vite (DeepAgents est passé de 0.2 à 0.7 en 2026, avec des changements de comportement — ex. le todo-list n'est plus activé par défaut depuis 0.7). Vérifiez la doc/PyPI au moment de l'exécution, ne recopiez pas ce deck dans un an sans revérifier.
LangGraph — rappel express
State, Graph, compile
from langgraph.graph import StateGraph, MessagesState, START
def call_model(state: MessagesState):
response = my_llm.invoke(state["messages"])
return {"messages": [response]}
graph = StateGraph(MessagesState)
graph.add_node("call_model", call_model)
graph.add_edge(START, "call_model")
app = graph.compile() # <- pas encore de mémoire ici
Sans checkpointer, chaque invoke() repart de zéro : aucune working memory ne survit entre deux appels.
Working memory
Le checkpointer : mémoire courte terme, par thread
from langgraph.checkpoint.memory import InMemorySaver
# (canonique depuis les versions récentes ; "MemorySaver" reste un alias)
checkpointer = InMemorySaver() # dev — remplacer par PostgresSaver en prod
app = graph.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "user-42-session-1"}}
app.invoke({"messages": [("user", "Bonjour, je suis Alex")]}, config=config)
app.invoke({"messages": [("user", "Quel est mon prénom ?")]}, config=config)
# -> se souvient, car même thread_id
Un thread_id différent = une conversation totalement isolée.
En prod : PostgresSaver / AsyncPostgresSaver — scalable, résiste aux redémarrages.
Bonus du checkpointing
Time travel, human-in-the-loop, tolérance aux pannes
state_now = app.get_state(config) # dernier checkpoint
state_then = app.get_state({ # un checkpoint antérieur précis
**config,
"configurable": {**config["configurable"], "checkpoint_id": some_id},
})
app.update_state(config, {"messages": [...]}) # écrit un nouveau checkpoint
# ("fork" — l'historique n'est pas détruit)
Le graphe peut se mettre en pause (interrupt) à un nœud, persister l'état, attendre un humain, puis reprendre exactement où il en était.
Rejouer depuis un ancien checkpoint ré-exécute les nœuds (nouveaux appels LLM/outils) — ce n'est pas juste un replay de sortie mise en cache.
Crash / redémarrage : on reprend le thread depuis son dernier checkpoint durable.
Garder la working memory sous contrôle
Trimming & résumé glissant
Le checkpointer résout la persistance, pas le budget de tokens : un historique qui grossit finit par saturer le contexte.
trim_messages (langchain_core.messages) : coupe une copie des messages à un budget de tokens/messages avant l'appel LLM — ne modifie pas l'état persisté.
Pattern plus robuste : un résumé glissant des tours anciens (injecté comme message système) + les k derniers messages verbatim. Fidélité totale sur le récent, gist compressé sur le reste.
C'est l'équivalent fonctionnel exact de la « compaction » vue en session 1 (vocabulaire Anthropic / Claude Code).
Long-term memory
Le Store : clé/valeur namespacé + recherche sémantique
from langgraph.store.memory import InMemoryStore
import uuid
store = InMemoryStore(index={"embed": embed_fn, "dims": 1536})
user_id = "user-42"
namespace = (user_id, "memories") # scoping par utilisateur/organisation
store.put(namespace, str(uuid.uuid4()), {"fact": "Préfère des réponses concises, code d'abord"})
store.put(namespace, str(uuid.uuid4()), {"fact": "Équipe data engineering, utilise Airflow"})
hits = store.search(namespace, query="comment formater mes réponses pour cet utilisateur ?")
# -> classés par similarité d'embedding, strictement dans ce namespace
Les namespaces sont de simples tuples — on peut les imbriquer arbitrairement : (org_id, user_id, "preferences").
Injection dans un nœud
Le Store, injecté automatiquement
from langgraph.store.base import BaseStore
def personalize_node(state, *, store: BaseStore):
memories = store.search(
(state["user_id"], "memories"),
query=state["messages"][-1].content,
)
# -> on injecte `memories` dans le prompt/message système
# avant l'appel LLM
...
Le système d'injection de dépendances de LangGraph fournit le store au nœud via l'annotation de type — sans que ce soit un paramètre visible du graphe.
Comme pour le checkpointer, ça ne fonctionne que si le graphe a été compilé avec un store : app = graph.compile(checkpointer=checkpointer, store=store) — oublier store= est une source fréquente d'erreur runtime déroutante.
Mémoire pilotée par l'agent
langmem : la mémoire comme outil
from langmem import create_manage_memory_tool, create_search_memory_tool
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(
"anthropic:claude-haiku-4-5-20251001",
tools=[
create_manage_memory_tool(namespace=("memories",)),
create_search_memory_tool(namespace=("memories",)),
],
store=store,
)
# -> l'agent décide LUI-MÊME, en cours de conversation,
# quand écrire ou chercher en mémoire
C'est le même principe que les core_memory_* / archival_memory_* de MemGPT (session 1) — mais construit sur le Store LangGraph plutôt que sur un framework dédié.
create_react_agent transmet automatiquement les kwargs store/checkpointer à son .compile() interne — si vous construisez un graphe personnalisé avec StateGraph à la place (comme dans les slides précédentes), ce passthrough n'est PAS automatique et vous devez appeler vous-même .compile(store=..., checkpointer=...).
Consolidation
Traitement en arrière-plan (ne pas tout faire dans le hot path)
create_memory_manager : extrait/consolide les faits notables d'une conversation (mémoire sémantique), sans bloquer le tour en cours.
create_memory_store_manager + ReflectionExecutor : exécute ça de façon asynchrone/différée plutôt qu'après chaque message — évite le travail redondant en rafale, les décisions sur un contexte incomplet, et le gaspillage de tokens.
Même distinction que « observation vs. reflection » (Generative Agents) ou « écriture paginée vs. consolidation archivale » (MemGPT) — trois frameworks, une seule idée sous-jacente.
deepagents (GitHub langchain-ai/deepagents) : « the batteries-included agent harness », explicitement inspiré de Claude Code.
Construit sur create_agent de LangChain (donc sur LangGraph) : mêmes briques, mais avec filesystem, sous-agents, gestion de contexte et skills fournis par défaut.
Trois principes affichés par le projet : opinionated (bons défauts pour du travail long/multi-étapes), extensible (pas besoin de forker), model-agnostic.
Primitive 1
Planification : le todo-list
from deepagents import create_deep_agent
from deepagents.middleware import TodoListMiddleware
agent = create_deep_agent(
model="anthropic:claude-sonnet-5",
middleware=[TodoListMiddleware()], # opt-in depuis deepagents 0.7 (n'est plus fourni par défaut)
)
L'agent écrit et met à jour explicitement une checklist structurée de sous-tâches (avec statut par tâche), plutôt que de garder le plan implicitement dans son raisonnement — calqué directement sur le comportement de Claude Code.
Changement de comportement notable en 2026 : si vous suivez un tutoriel antérieur à la 0.7, il suppose ce middleware actif par défaut — ce n'est plus le cas — les évals internes de LangChain ont montré que le prompt et l'outil todo par défaut n'amélioraient pas significativement les résultats, d'où ce changement.
Primitive 2
Sous-agents : la quarantaine de contexte
research_subagent = {
"name": "web-researcher",
"description": "Mène une recherche web approfondie sur une question précise "
"et retourne un résumé condensé.",
"system_prompt": "Tu es un assistant de recherche focalisé. Investigue "
"en profondeur, puis rapporte uniquement tes conclusions.",
"tools": [web_search_tool],
}
agent = create_deep_agent(
model="anthropic:claude-sonnet-5",
subagents=[research_subagent], # + un sous-agent "general-purpose" est toujours dispo
)
Le sous-agent tourne dans sa propre fenêtre de contexte ; seul son rapport final condensé revient dans le contexte de l'agent parent — aucun bruit d'appels d'outils intermédiaires ne s'y déverse.
Primitive 3
Filesystem virtuel : l'offloading de contexte
Outils ls, read_file, write_file, edit_file, glob, grep — une surface de fichiers manipulée par appels d'outils, qui ne touche pas le disque réel par défaut.
Un gros résultat d'outil (recherche web volumineuse, fichier long, résultat de requête) est écrit dans un fichier virtuel plutôt que déversé dans l'historique de messages — l'agent ne relit ensuite que la portion utile.
C'est le mécanisme concret du « retrieval juste-à-temps » et du « note-taking structuré » vus en session 1 — plus une méthode qu'un simple utilitaire.
Où vivent ces fichiers ?
Les backends : le point de jonction avec LangGraph
Backend
Persistance
Équivalent CoALA / LangGraph
StateBackend (défaut)
éphémère, dans l'état du thread courant
working memory / checkpointer
StoreBackend
persisté via un Store LangGraph
long-term memory / Store
FilesystemBackend
disque local réel (dev)
—
CompositeBackend
routeur par préfixe de chemin
combine les deux ci-dessus
Si le backend d'une route ne supporte pas une opération (ex. delete sur une route en lecture seule), DeepAgents remonte soit une erreur, soit masque simplement cet outil au modèle — vérifiez lequel, par backend, avant de supposer que delete fonctionne toujours.
C'est le point le plus important de tout le talk : le filesystem de DeepAgents n'est pas un 3ᵉ mécanisme de persistance — c'est une façade sur le Store LangGraph.
Code
Router : ce qui doit survivre, et ce qui ne doit pas
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
backend = lambda rt: CompositeBackend(
default=StateBackend(rt), # brouillon éphémère, scope = thread
routes={"/memories/": StoreBackend(rt)}, # tout sous /memories/ survit entre sessions
)
agent = create_deep_agent(model="anthropic:claude-sonnet-5", backend=backend, store=store)
agent.invoke({"messages": [(
"user",
"Recherche le modèle mémoire de LangGraph et écris tes conclusions "
"dans /memories/langgraph_notes.md",
)]})
Démo (notebook)
Vérifier la persistance cross-session
Invoquer l'agent avec thread_id="A", lui faire écrire /memories/notes.md.
Invoquer avec thread_id="B" (même store) : demander de read_file("/memories/notes.md") → ça fonctionne.
Refaire l'essai avec un fichier hors/memories/ (donc sur StateBackend) → il a disparu dans le thread B.
Ce contraste, exécuté en live sur le notebook, est la meilleure façon de faire « voir » la différence court terme / long terme à l'équipe.
Bonus
Skills : le chargement à la demande
SkillsMiddleware (étendu en DeepAgents 0.6) : des comportements réutilisables, chargés à la demande.
Seule une métadonnée (nom + description courte) reste toujours dans le prompt système ; le contenu complet n'est chargé que si l'agent juge la skill pertinente pour la tâche en cours.
C'est une technique de gestion de contexte à part entière : on ne paie le coût en tokens d'une capacité que si elle est réellement utilisée ce tour-ci.
Passage en production
Ce qui change entre le prototype et la prod
Backends persistants : PostgresSaver / PostgresStore plutôt que les variantes InMemory* — nécessaires dès qu'on redémarre un process ou qu'on scale horizontalement.
Observabilité : tracer les appels de sous-agents (LangSmith ou équivalent) pour vérifier ce qui ne revient pas dans le contexte parent — sinon l'isolation de contexte devient une boîte noire à débugger.
Sécurité : le FilesystemBackend a un mode « virtuel » qui bloque la traversée de chemin (../, ~/) — ne jamais exposer l'accès disque réel sans ce garde-fou dans un contexte serveur.
Guide de décision
Quoi choisir, pour quel besoin
1
Une tâche longue, mono-session (recherche approfondie, gros job de code/données) → DeepAgents : sous-agents + filesystem virtuel (backend par défaut).
2
Personnalisation multi-session (préférences utilisateur, faits qui doivent survivre) → LangGraph Store namespacé, avec ou sans langmem.
3
Les deux à la fois → DeepAgents avec un CompositeBackend routant une partie de l'espace fichiers vers un StoreBackend — elles se composent, elles ne s'excluent pas.
Passer au notebook
Plan du notebook d'accompagnement
docs/code/agent_memory_langgraph_deepagents.ipynb
Similarité cosinus « from scratch » (numpy) — pas de framework, juste l'intuition.
Score de récupération façon Generative Agents (recency + importance + relevance) sur des souvenirs jouets.
LangGraph — checkpointer, threads isolés, time travel.
DeepAgents — sous-agent de recherche, filesystem virtuel, persistance cross-session via StoreBackend.
Pour l'équipe
Exercices proposés (après la session)
Remplacer InMemoryStore par un PostgresStore sur une base locale, et vérifier que la mémoire survit à un redémarrage du kernel.
Ajouter un second sous-agent DeepAgents spécialisé (ex. « analyste SQL ») et observer, via les traces, ce qui revient — ou pas — dans le contexte parent.
Sur un cas réel de l'équipe (ex. un agent de reporting), identifier quel type de mémoire (court terme / long terme / les deux) résoudrait le problème actuel.
Récap final
Une seule carte mentale à garder
Working ↔ long-term (CoALA) devient checkpointer/thread ↔ Store/namespace (LangGraph), et DeepAgents ajoute par-dessus l'isolation (sous-agents) et l'offloading (filesystem virtuel) — les 4 techniques de context engineering d'Anthropic, avec un nom de classe Python pour chacune.
Glossaire (spécifique à DeepAgents)
Termes non déjà présents dans le glossaire de la Partie 1
Terme
Définition
Backend
Dans DeepAgents, le système qui détermine où vivent réellement les fichiers virtuels (état éphémère, Store persistant, disque...) ; plus généralement, la partie d'un système qui gère stockage/traitement, invisible depuis l'interface utilisateur.
Offloading
Déplacer une information hors de la fenêtre de contexte active (ex. vers un fichier) pour libérer du budget de tokens, quitte à la relire plus tard si besoin.
Sub-agent (quarantaine)
Un agent auxiliaire qui tourne dans sa propre fenêtre de contexte isolée ; seul son résultat final "traverse" vers l'agent parent — le bruit intermédiaire ne pollue pas le contexte parent (d'où "quarantaine").
Vocabulaire complet (Framework, Harness, Opinionated, Token, Embedding, RAG, Checkpointer, Store, Grounding, Context engineering...) : voir le glossaire de la Partie 1.
Questions ?
Notebook, decks et bibliographie : docs/ de ce site.