Data Engineering & Data Science — Atelier interne

La mémoire des agents LLM
Implémentation avec LangGraph & DeepAgents

Partie 2/2 — Pratique : code, démo live, notebook
Durée visée : ~50 min + questions  ·  Support : docs/code/agent_memory_langgraph_deepagents.ipynb
Septembre 2026

English version

Rappel — Session 1

La grille de lecture qu'on va instrumenter

CoALA (Session 1) Working memory Long-term memory LangGraph (Session 2) Checkpointer + thread_id Store namespacé recherche sémantique

Court terme Working memory

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
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)
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.

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)

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.
Récap

LangGraph memory — tableau de référence

ConceptMécanismePortéeBackend typique
Court termeCheckpointer + thread_idune conversationInMemorySaver (dev), PostgresSaver (prod)
Long termeStore (put/search) + namespacecross-thread, par utilisateur/orgInMemoryStore (dev), PostgresStore (prod)
Mémoire-outillangmem.create_manage_memory_tool / create_search_memory_toolles deuxenveloppe un Store
Consolidationcreate_memory_manager / ReflectionExecutorlong termeenveloppe un Store
Transition

DeepAgents : au-dessus de LangGraph, pas à côté

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

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

BackendPersistanceÉquivalent CoALA / LangGraph
StateBackend (défaut)éphémère, dans l'état du thread courantworking memory / checkpointer
StoreBackendpersisté via un Store LangGraphlong-term memory / Store
FilesystemBackenddisque local réel (dev)
CompositeBackendrouteur par préfixe de chemincombine les deux ci-dessus
Agent DeepAgents CompositeBackend (routeur par préfixe) / (défaut) /memories/* StateBackend éphémère, scope thread StoreBackend Store LangGraph (persisté)

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

  1. Invoquer l'agent avec thread_id="A", lui faire écrire /memories/notes.md.
  2. Invoquer avec thread_id="B" (même store) : demander de read_file("/memories/notes.md")ça fonctionne.
  3. 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

Passage en production

Ce qui change entre le prototype et la prod

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

  1. Similarité cosinus « from scratch » (numpy) — pas de framework, juste l'intuition.
  2. Score de récupération façon Generative Agents (recency + importance + relevance) sur des souvenirs jouets.
  3. LangGraph — checkpointer, threads isolés, time travel.
  4. LangGraph — Store, namespaces, recherche sémantique, mémoire pilotée par l'agent (langmem).
  5. DeepAgents — sous-agent de recherche, filesystem virtuel, persistance cross-session via StoreBackend.
Pour l'équipe

Exercices proposés (après la session)

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

TermeDéfinition
BackendDans 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.
OffloadingDé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.