[!NOTE] Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.

AI L’architecture mémoire des agents en 2026 : Checkpoints, les stores vectoriels et la mémoire basée sur des fichiers

Deuxième partie de la série « Ingénierie de la pile Agentic »

Un reasoning loop ne survit qu’à une seule requête, à moins que son état ne soit stocké en dehors du worker. Sans mémoire d’agent, celui‑ci ne peut pas reprendre un plan interrompu, se remettre d’une panne, ni récupérer une préférence issue d’une session antérieure. Partie 1 Ce texte couvre le flux de contrôle. Il indique quel état chaque tour ultérieur nécessite et où cet état doit être stocké.

J’expliquerai en détail l’architecture mémoire du Agent d’analyse de marché, Je montrerai comment les stores de vecteurs chauds checkpoints, froids, ainsi que la mémoire de documents basée sur des fichiers collaborent pour alimenter des agents fonctionnant sur de longues périodes. Ensuite, j’aborderai les cas d’usage appropriés pour PostgreSQL, Redis, Qdrant, les stores clé-valeur et les fichiers Markdown classiques.

TL;DR : Séparez la mémoire en fonction du schéma d’accès. La mémoire chaude représente l’état au niveau des threads checkpoint, utilisé pour les pauses et les reprises. La mémoire froide stocke des données inter-sessions dans un système de type clé-valeur ou un stockage vectoriel. La mémoire documentaire conserve les connaissances propres à un projet au sein de fichiers consultables. Commencez par le problème que vous devez résoudre, puis choisissez le type de stockage adapté. Ne placez pas des données précises dans un système de recherche floue, et ne traitez pas un checkpoint comme un journal d’audit.


Qu’est-ce que la mémoire d’agent AI ?

AI mémoire d’agent correspond à la couche d’état qui permet à un agent de conserver l’avancement des tâches, de récupérer des connaissances antérieures et de mettre à jour ses informations au cours de différentes exécutions. En environnement de production, il ne s’agit pas d’une seule base de données vectorielle ; il s’agit plutôt d’un mélange de stockages checkpoints actifs, de stockages sémantiques ou structurés passifs, ainsi que d’une mémoire de documents lisible par les humains.

NécessiteMeilleure valeur par défautPourquoi ?
Pauser et reprendre une exécutionPostgreSQL checkpoint de stockageDes données d’application durables, consultables et faciles à manipuler
État transitoire à faible latenceStockage Redis checkpointReprise rapide et état à durée de vie courte, avec des compromis liés à la persistance
Rappel sémantique inter-sessionsRécupère les souvenirs en se basant sur leur signification, et non uniquement sur des clés exactes.
Faits structurés sur l’utilisateurPostgreSQL ou stockage clé-valeurLes mises à jour déterministes surpassent la recherche floue pour les préférences et les identifiants.
Conventions de projet et procédures éprouvéesFichiers Markdown ou JSONLisible par l’humain, permettant des diffs et facile à mettre à jour par les agents
Mémoire des relations entre multiples entitésGraphe de connaissancesUtile lorsque les relations priment sur les faits individuels.

Ne commencez pas par la mémoire, car ce terme donne une impression d’intelligence artificielle. Commencez plutôt par l’échec visible pour l’utilisateur : perte des progrès réalisés, oubli d’une préférence, répétition de recherches, ou incapacité à réutiliser une convention de projet.

Les échecs qui nécessitent une mémoire

Un agent sans état peut répondre à une question isolée, mais il oublie la requête dès la fin de l’appel. Cette conception échoue lorsque le produit nécessite l’un des comportements suivants :

Dans le Agent d’analyse de marché de Partie 1, La requête « Analyser NVDA » génère un plan, cinq tool calls, des données collectées ainsi qu’un projet de rapport. Lorsque l’utilisateur répond par « Cela a l’air bon, mais ajoutez une analyse des concurrents », un checkpoint store permet à l’agent de charger l’état du nœud précédent et d’ajouter l’étape relative aux concurrents. En l’absence d’état enregistré via des points de contrôle, il ne peut pas déterminer ce que signifie « Cela a l’air bon » et doit tout reprendre depuis le début.

La mémoire à long terme gère un cas différent. Si l’utilisateur revient une semaine plus tard et demande : « Mettez à jour mon analyse NVDA », l’agent peut devoir se remémorer la préférence pour des évaluations de risque conservatrices ainsi que l’intérêt porté aux actions de semi-conducteurs. Un stockage de mémoire basé sur des vecteurs permet de récupérer ces informations entre les sessions sans avoir à les demander à nouveau.

LangGraph effectue cette séparation en fonction du périmètre d’action. Chaque exécution de graphe s’effectue au sein d’un thread, ce qui correspond à une conversation ou une tâche donnée. L’état persistant au sein de ce thread constitue la mémoire à court terme. L’état partagé entre les threads représente quant à lui la mémoire à long terme. Le contexte actuel du modèle ainsi que les variables en cours de traitement forment la couche de mémoire de travail située au-dessus de ces deux types de stockage.

Taxonomie de la mémoire


Une taxonomie de la mémoire d’agent AI

Avant de passer à la mise en œuvre, il est utile de classer ce que les agents doivent mémoriser. CoALA framework <SUMERS_YAO_2023> est la taxonomie de référence qui s’appuie sur les principes de la science cognitive. J’y ai introduit le concept de ciblage de la mémoire dans mes travaux.</SUMERS_YAO_2023> context engineering publication; Voici sa décomposition en six catégories :

Type de mémoireChamp d’applicationDurée de vieExemplePattern de stockage
En fonctionnementÉtape actuelleMilisecondesTool call arguments, réponse LLM actuelle
À court termeThread actuelMinutes–heuresHistorique de conversation, progression du plan, données collectéesCheckpoint stocker
ÉpisodiqueInter-threadJours–moisLa semaine dernière, l’utilisateur a demandé des informations concernant les résultats financiers de NVDA.Stockage vectoriel / Stockage KV
SémantiqueInter-threadMois – permanent« L’utilisateur préfère des investissements conservateurs »Stockage vectoriel / Stockage KV
DocumentInter-threadJours – permanentNotes de projet, résumés de recherche, motifs identifiésStockage de fichiers (Markdown/JSON)
ProcéduralÀ l’échelle du systèmePermanenteLors de l’analyse des actions, il est indispensable de consulter systématiquement les documents déposés auprès de la SEC.Config / system prompt

Mémoire de travail est ce avec quoi le LLM raisonne activement en ce moment : les variables Python de la fonction en cours, le contenu de la fenêtre de contexte, ainsi que les arguments tool call pendant l’exécution. Il s’agit de la couche la plus rapide mais aussi la plus éphémère ; rien ne persiste au-delà de l’étape actuelle. La mémoire de travail est limitée par la fenêtre de contexte du modèle, ce qui en fait le véritable goulot d’étranglement. Tout ce que l’agent « sait » au moment de prendre une décision doit y trouver sa place, que ce soit provenant du stockage checkpoint, d’une requête vectorielle ou d’une lecture de fichier. Les autres niveaux existent pour fournir les informations appropriées à la mémoire de travail au bon moment.

La mémoire à court terme correspond aux données checkpoint que LangGraph écrit après chaque nœud. Les mémoires épisodique et sémantique persistent au sein des threads. La mémoire de document conserve les notes de projet, les résumés de recherche ainsi que les conventions apprises dans des fichiers accessibles tant aux humains qu’aux agents. Quant à la mémoire procédurale, elle est intégrée dans les instructions système et les définitions d’outils, sans être modifiée pour chaque utilisateur.

Pour la mise en œuvre, ces catégories se résument en trois niveaux. La mémoire à chaud stocke la session en cours. La mémoire à froid permet de récupérer des données entre différentes sessions. La mémoire de documents garantit que les connaissances accumulées sur un projet restent lisible et directement modifiables.

CoALA classe la mémoire de travail, épisodique, sémantique et procédurale. Le La mémoire à l’ère des agents AI : une étude de terrain Il met l’accent sur les stockeurs de vecteurs et les graphes de connaissances, tandis que LangGraph décrit checkpoints ainsi que son interface Store. Les connaissances propres aux projets, stockées sous forme de fichiers, restent en dehors de ces taxonomies, même si Claude Code, Cursor, Windsurf et Devin chargent tous des fichiers persistants de projet.

Ce même schéma de stockage se retrouve dans d’autres domaines. Voyager conserve les compétences de jeu réutilisables sous forme de bibliothèques de code, les équipes ECR3 ont itéré sur des documents procéduraux prompt, et la mémoire de flux de travail d’agent permet de générer des flux de travail web réutilisables à partir d’épisodes réussis. Les fichiers rendent ces connaissances inspectables et versionnables, sans avoir recours à un service embedding distinct.

La mémoire gérée par un agent diffère également d’une pipeline RAG fixe quant à l’entité responsable des écritures. C’est l’agent ou son harness qui décide de ce qui doit être stocké, mis à jour ou supprimé, avant de choisir le moment opportun pour le récupérer.

Le Article sur les agents génératifs (Park et al., 2023) ont démontré jusqu’où cela peut aller : des agents simulés ont été capables de stocker, de réfléchir sur et de récupérer leurs propres mémoires. Leur flux de mémoire classait les candidats en fonction de la proximité dans le temps, de l’importance et de la pertinence, une approche qui constitue encore aujourd’hui un point de référence utile pour la récupération de mémoires d’agent.


Mémoire à court terme de l’agent : le stockage checkpoint

Chaque fois qu’un nœud LangGraph est exécuté, le framework serialize l’état complet du graphe et l’écrit dans un checkpoint store. C’est cette base qui permet les fonctionnalités de pause/reprise, le débogage par « voyage dans le temps », ainsi que les workflows HITL.

Flux de mémoire chaude Checkpoint

Un checkpoint contient l’état du graphe nécessaire pour reprendre : le AgentState de Partie 1 (messages, identité, profil utilisateur, étapes du plan, données de recherche, mode d’exécution), ainsi que les métadonnées de LangGraph telles que le nœud qui les a générés et son ID checkpoint. Lors d’une interruption HITL ou d’un redémarrage de processus, le graphe charge la limite la plus récente enregistrée et reprend l’exécution depuis le nœud suivant. Il ne continue pas à partir d’une ligne Python arbitraire. Un checkpoint diffère également d’un journal d’événements ou d’un traceur à écriture seule ; Partie 5 isole explicitement ces surfaces d’observabilité runtime.

Comment fonctionne le checkpointing de LangGraph

de LangGraph BaseCheckpointSaver Il s’agit d’une interface simple : put() écrit un checkpoint, get_tuple() lit la dernière version d’un fil de discussion. list() retourne l’historique. Chaque checkpoint est identifié par un clé. (thread_id, checkpoint_ns, checkpoint_id), où thread_id identifie la conversation. checkpoint_ns gère l’espacement des noms de sous-graphes, et checkpoint_id Il s’agit d’une version unique.

La décision cruciale consiste à déterminer quel backend placer derrière ce mécanisme. PostgreSQL et Redis représentent deux choix courants en environnement de production.

PostgreSQL contre Redis

Redis contre PostgreSQL

DimensionPostgreSQL (langgraph-checkpoint-postgres)Redis (langgraph-checkpoint-redis)
Modèle de durabilitéLes transactions ACID, le WAL et la réplicationPersistance configurable en mode AOF ou RDB
Checkpoint historiqueHistorique persistant pour le suivi des états et le débogageLa durée de conservation dépend des paramètres de sauvegarde et d’éviction.
Contrainte principaleLatence d’écriture dans la base de données et croissance des tablesUtilisation de la RAM, éviction et configuration de la persistance
Adéquation opérationnelleLes équipes qui utilisent déjà des bases de données relationnellesLes équipes qui utilisent déjà Redis atteignent déjà un débit élevé.
Meilleure valeur par défaut pourReprise fiable du travail et débogage reproductibleÉtat de session réversible et sensible à la latence

Une base de données générique benchmarks ne permet pas de prédire les performances checkpoint. Il convient de mesurer la taille de l’état serialisé, la fréquence d’écriture, les paramètres de persistance ainsi que le niveau de concurrence de votre propre graphe.

PostgreSQL : le choix par défaut fiable

PostgreSQL constitue le choix par défaut plus sûr pour la plupart des équipes. Grâce aux Checkpoints capables de survivre aux pannes, on bénéficie d’une sémantique de transactions complète, et l’historique checkpoint permet de réaliser des débogages en « voyage dans le temps » de manière très simple.

Depuis checkpointer_setup.py:

from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver

async def create_postgres_checkpointer(connection_string: str) -> AsyncPostgresSaver:
    """Create a PostgreSQL-backed checkpoint store.

    PostgreSQL gives us ACID guarantees — if a checkpoint write succeeds,
    the state is durable even if the process crashes immediately after.
    """
    checkpointer = AsyncPostgresSaver.from_conn_string(connection_string)

    # Create the checkpoint tables if they don't exist.
    # This is idempotent — safe to call on every startup.
    await checkpointer.setup()

    return checkpointer

# Usage: wire into the graph compilation
checkpointer = await create_postgres_checkpointer(
    "postgresql://user:pass@localhost:5432/agent_memory"
)
graph = create_graph(checkpointer=checkpointer)

# Every invoke/stream call now persists state automatically
config = {"configurable": {"thread_id": "user-123-session-1"}}
result = await graph.ainvoke({"messages": [HumanMessage(content="Analyze NVDA")]}, config)

# Resume later — loads the latest checkpoint for this thread
result = await graph.ainvoke({"messages": [HumanMessage(content="approved")]}, config)

Le AsyncPostgresSaver utilise langgraph-checkpoint-postgres package, qui crée trois tables : checkpoints (l’état serialisé) checkpoint_blobs (données binaires de grande taille), et checkpoint_writes (Pending les écritures nécessaires à la récupération en cas de panne.) Le schéma prend en charge l’accès concurrentiel et utilise des verrous consultatifs afin d’éviter les conflits d’écriture.

Redis : lorsque la latence constitue le goulot d’étranglement

Lorsque des latences inférieures à un milliardième de seconde checkpoint sont cruciales (agents conversationnels en temps réel, boucles d’outil à haute fréquence), Redis constitue le choix préférable.

Depuis checkpointer_setup.py:

from langgraph.checkpoint.redis.aio import AsyncRedisSaver

async def create_redis_checkpointer(redis_url: str) -> AsyncRedisSaver:
    """Create a Redis-backed checkpoint store.

    Redis stores checkpoints in memory for sub-millisecond access.
    Trade-off: less durable than PostgreSQL unless AOF is enabled.
    """
    checkpointer = AsyncRedisSaver.from_conn_string(redis_url)

    # Initialize Redis data structures
    await checkpointer.setup()

    return checkpointer

# Usage: same graph API, different backend
checkpointer = await create_redis_checkpointer("redis://localhost:6379")
graph = create_graph(checkpointer=checkpointer)

Le AsyncRedisSaver de langgraph-checkpoint-redis Il stocke checkpoints sous forme de documents JSON identifiés par leur ID de thread. Le v0.1.0 refonte j’ai remplacé plusieurs opérations de recherche par une seule JSON.GET Appel, réduisant de manière significative la latence. Redis 8.0+ intègre par défaut RedisJSON et RediSearch — aucune module supplémentaire à installer.

Pour les déploiements soumis à des contraintes de mémoire, ShallowRedisSaver Il ne conserve que la dernière valeur de checkpoint par thread — sans historique, mais avec une consommation mémoire minimale. Utilisez cette approche lorsque vous avez besoin de fonctionnalités de pause/reprise sans avoir recours à un débogage permettant de revenir en arrière dans le temps.

Quand utiliser lequel

Utilisez PostgreSQL lorsque :

Utilisez Redis lorsque :

Autres options : langgraph-checkpoint-sqlite Fonctionne pour le développement local ainsi que les déploiements en mode processus unique. Pour les stacks natives d’AWS, langgraph-checkpoint-aws fournit un DynamoDBSaver grâce à un traitement intelligent du chargement : les petits fichiers checkpoints (<350 KB) restent stockés dans DynamoDB, tandis que les plus volumineux sont automatiquement transférés vers S3. Le modèle de tarification serverless ainsi que l’absence d’infrastructure à gérer en font une solution attractive pour les déploiements à charge variable.


Mémoire à long terme : mémorisation entre sessions

La mémoire à court terme gère la conversation en cours. Mais que se passe-t-il pour l’utilisateur qui reviendra la semaine prochaine ? La mémoire à long terme stocke des faits, des préférences ainsi que l’historique des interactions, ce qui permet à ces données de persister au-delà des différentes sessions.

LangGraph propose un Store interface permettant l’accès à la mémoire entre threads via son BaseStore classe. Chaque élément de mémoire est un (namespace, key) être associé à une valeur JSON ainsi qu’à un vecteur optionnel embedding. L’espace de noms indique généralement l’utilisateur ou l’organisation : ("user", "user-123", "preferences").

Flux de mémoire à long terme

Stockage vectoriel : récupération sémantique grâce à Qdrant

Lorsque l’agent doit récupérer des faits non structurés (« Qu’a dit l’utilisateur au sujet de son calendrier d’investissement ? »), la recherche vectorielle permet une récupération sémantique. Au lieu de rechercher des clés exactes, l’agent effectue des requêtes en se basant sur le sens.

Qdrant C’est une base de données vectorielle conçue spécifiquement en Rust, qui gère le stockage embedding, l’indexation (HNSW) ainsi que les recherches filtrées. J’ai abordé en détail HNSW et ses compromis dans mon classement des résultats de recherche. Qdrant propose également un serveur MCP qui agit comme une couche de mémoire sémantique — utile si votre agent framework prend en charge le Model Context Protocol.

Depuis memory_store.py:

from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
from langchain_anthropic import ChatAnthropic
import hashlib
import json

class UserMemoryStore:
    """Long-term memory backed by Qdrant vector search.

    Stores user facts as embedded vectors for semantic retrieval.
    Each fact is a short natural-language statement about the user.
    """

    def __init__(self, qdrant_url: str, collection_name: str = "user_memory"):
        self.client = QdrantClient(url=qdrant_url)
        self.collection_name = collection_name
        self._ensure_collection()

    def _ensure_collection(self):
        """Create the collection if it doesn't exist."""
        collections = [c.name for c in self.client.get_collections().collections]
        if self.collection_name not in collections:
            self.client.create_collection(
                collection_name=self.collection_name,
                vectors_config=VectorParams(
                    size=1536,  # text-embedding-3-small dimensions
                    distance=Distance.COSINE,
                ),
            )

    def store_fact(self, user_id: str, fact: str, embedding: list[float]):
        """Store a user fact with its embedding."""
        point_id = hashlib.md5(f"{user_id}:{fact}".encode()).hexdigest()
        self.client.upsert(
            collection_name=self.collection_name,
            points=[PointStruct(
                id=point_id,
                vector=embedding,
                payload={"user_id": user_id, "fact": fact},
            )],
        )

    def recall(self, user_id: str, query_embedding: list[float], top_k: int = 5):
        """Retrieve the most relevant facts for a user given a query."""
        results = self.client.query_points(
            collection_name=self.collection_name,
            query=query_embedding,
            query_filter={"must": [{"key": "user_id", "match": {"value": user_id}}]},
            limit=top_k,
        )
        return [hit.payload["fact"] for hit in results.points]

Le flux de travail est le suivant : (1) après chaque conversation, un LLM extrait les faits clés issus de l’interaction (« l’utilisateur présente une forte tolérance au risque », « l’utilisateur s’intéresse aux actions de semi-conducteurs »), (2) ces faits sont intégrés et stockés dans Qdrant, (3) au début de la conversation suivante, l’agent interroge Qdrant à l’aide du nouveau message de l’utilisateur afin de récupérer le contexte pertinent.

Évaluation des résultats de récupération : au-delà de la similarité cosinus

La similarité cosinus brute constitue un point de départ, mais les systèmes de mémoire en environnement de production exigent des méthodes de récupération plus sophistiquées. Article sur les agents génératifs (Park et al., 2023) ont introduit une fonction de scoring qui combine trois signaux :

Le score final de récupération est une somme pondérée : score = alpha * recency + beta * importance + gamma * relevance. Cela empêche que des faits récents et importants ne soient enterrés sous des données obsolètes mais sémantiquement similaires. Pour le Agent d’analyse de marché, J’accorde la plus grande importance à la pertinence (0,5), suivie de la proximité dans le temps (0,3) et de l’importance relative (0,2), car l’intention de la requête actuelle de l’utilisateur est primordiale. Il s’agit de poids de point de départ adaptés du papier sur les agents génératifs (où des poids égaux étaient utilisés) ; j’ai constaté que mettre l’accent sur la pertinence donnait de meilleurs résultats pour les requêtes d’analyse financière, mais ces valeurs reposent sur l’intuition plutôt que sur une optimisation empirique.

Alternatives à la recherche vectorielle

La recherche vectorielle est puissante, mais elle n’est pas toujours l’outil adapté. Voici les cas où il convient d’utiliser des alternatives :

ApprocheIdéal pourCoût opérationnel principal
Recherche vectorielle (Qdrant)Rappel sémantique des faits non structurésEmbedding ainsi que le cycle de vie de l’index
Stockage clé-valeur (Redis)Profils d’utilisateurs structurés et préférencesPolitique d’utilisation de la mémoire et de persistance
Stockage de documents (fichiers)Connaissances de projet et notes gérées par l’agentConcurrence, permissions et recherche
Recherche plein texte (PostgreSQL) index GIN)**Rappel des mots-clés à partir de l’historique de la conversationCroissance de l’index et optimisation des requêtes
Graphe de connaissances (Neo4j)Relations entre entités et requêtes à plusieurs sautsModélisation de graphes et autres systèmes de données
Hybride (vecteur + mot-clé)Rappelons que l’intention de la requête varieDeux chemins de scoring à ajuster et à évaluer

Les stockages clé-valeur s’avèrent particulièrement adaptés aux données structurées. Lorsque votre mémoire à long terme correspond à un profil d’utilisateur — tolérance au risque, horizon d’investissement, secteurs préférés — l’utilisation d’un hash Redis ou d’une colonne JSONB dans PostgreSQL est plus simple et plus rapide que embedding ainsi que que la recherche sur des vecteurs. Préférez la recherche vectorielle lorsque les données sont non structurées et que les requêtes de récupération varient en formulation.

Le stock intégré de LangGraph offre une interface clé-valeur basée sur des noms d’espace, complétée éventuellement par une recherche vectorielle. Le BaseStore API est simple : put(), get(), search(), et delete() avec un encadrement de nom d’espace hiérarchique. Trois implémentations sont disponibles :

Le index La configuration permet une recherche vectorielle sur les éléments stockés à l’aide d’un modèle embedding configurable. Pour de nombreux cas d’usage, ce stockage intégré est suffisant, sans nécessiter l’utilisation d’une base de données vectorielles dédiée.

from langgraph.store.memory import InMemoryStore

# Create a store with vector search enabled
store = InMemoryStore(
    index={
        "dims": 1536,
        "embed": my_embedding_function,  # e.g., OpenAI text-embedding-3-small
    }
)

# Store a user preference (namespace scopes to user)
await store.aput(
    namespace=("user", "user-123", "preferences"),
    key="risk-profile",
    value={"risk_tolerance": "high", "horizon": "long-term"},
)

# Semantic search across user's memories
results = await store.asearch(
    namespace=("user", "user-123"),
    query="What is their investment style?",
    limit=5,
)

Sélection d’une stratégie de mémoire à long terme

Commencez par une structure clé-valeur lorsque votre mémoire est structurée et bien définie (profils d’utilisateurs, paramètres, entités nommées). Ajoutez la recherche vectorielle lorsque vous avez besoin d’une récupération sémantique sur des données non structurées ou lorsque la formulation des requêtes varie de manière imprévisible.

Les graphes de connaissances s’avèrent particulièrement utiles lorsque les relations entre les entités jouent un rôle clé, par exemple : « Quelles entreprises que l’utilisateur a consultées sont des concurrents de NVDA ? » Le projet le plus intéressant dans ce domaine ces derniers temps est Graphiti (par Zep), qui construit un graph de connaissances conscient du temps permettant de suivre quand les faits étaient vrais, et non seulement ce qui était vrai. Chaque arête intègre des intervalles de validité, de sorte qu’une modification de la tolérance au risque de l’utilisateur rend caduque la valeur précédente au lieu de la remplacer silencieusement. Graphiti génère des rapports 94,8 % de précision sur le DMR benchmark, Et son modèle bitemporal permet de gérer le problème de la mémoire obsolète au niveau du layer de données.

Le problème réside sur le plan opérationnel. Gérer une base de données graphique n’est pas chose aisée, et pour la plupart des applications d’agents, la recherche vectorielle associée à un filtrage par métadonnées permet d’obtenir les mêmes résultats avec moins d’infrastructure.

Mémoire gérée frameworks telle que Mem0 et Letta (auparavant MemGPT), ce système gère à votre place l’extraction, la consolidation et la récupération pipeline. L’approche de Mem0 se distingue par le fait qu’un LLM extrait des mémoires candidates, un moteur de décision compare chaque nouvelle information aux entrées existantes dans le stockage vectoriel, et un résolveur décide s’il convient d’ajouter, de mettre à jour ou de supprimer ces données, ce qui permet de maintenir le stockage de mémoires cohérent et sans redondances. Letta, quant à elle, adopte une perspective inspirée des systèmes d’exploitation : les agents gèrent eux-mêmes leur fenêtre de contexte à l’aide d’outils de gestion de la mémoire, transférant de manière autonome les données entre la « mémoire principale » (dans le contexte) et la « mémoire d’archivage » (en dehors du contexte). Ces deux solutions méritent d’être évaluées si vous souhaitez accélérer le processus de mise en production sans avoir besoin d’un contrôle total sur la gestion de la mémoire pipeline.


Mémoire de document : le classeur de l’agent

Dans les taxonomies de mémoire mentionnées ci-dessus, la mémoire basée sur des fichiers bénéficie d’une adoption plus importante que d’une couverture exhaustive. Lors d’une évaluation LoCoMo menée par un fournisseur, Letta a fait son rapport 74,0 % pour son approche basée sur un système de fichiers. Il convient de maintenir ce résultat dans le cadre de son modèle, sous les conditions de benchmark et harness, mais l’avantage opérationnel est facile à observer : les développeurs peuvent lire, modifier et comparer les connaissances stockées directement.

Des fenêtres de contexte plus larges permettent également de lire des fichiers entiers de manière pratique pour certains documents de projet. La récupération par morceaux reste adaptée aux corpus volumineux, mais un fichier de conventions ou de transfert de petite taille peut souvent être chargé directement. Le choix dépend de la taille du document, de la précision de la récupération, du budget de contexte, ainsi que de la fréquence à laquelle il est nécessaire de revoir ou d’éditer cette mémoire.

Les bases de données vectorielles et les systèmes de stockage clé-valeur gèrent efficacement la récupération sémantique ainsi que les recherches structurées. Cependant, il existe une troisième catégorie de connaissances propres aux agents qui n’est pas prise en charge de manière optimale par ces solutions : le contexte de projet accumulé, c’est-à-dire les conventions, les notes de recherche et les décisions dont l’agent a besoin au fil des sessions, et qui bénéficient d’un format lisible par les humains ainsi que d’un contrôle de version.

Il s’agit de la mémoire de document : l’agent lit et écrit des fichiers structurés (Markdown, JSON, YAML) dans un répertoire prédéfini. Aucun embeddings, aucune base de données, aucune infrastructure. Seulement des fichiers sur le disque accessibles tant par l’agent que par le développeur. cat, grep, git diff, puis l’éditer manuellement.

Pourquoi des fichiers ?

Pour les flux de travail d’agents à longue durée de vie, le pattern le plus efficace que j’aie observé n’est pas une base de données vectorielle. Il s’agit plutôt d’un répertoire contenant des notes bien organisées. Pensez à ce qui se passe lorsque un agent de codage travaille sur un projet sur plusieurs semaines :

Ces faits présentent une structure trop rigide pour une recherche vectorielle (il est nécessaire d’obtenir un taux de rappel exact, et non une similarité floue), et leur nombre est trop élevé pour un stockage clé-valeur (ils forment des documents interconnectés, et non des faits isolés). De plus, il s’agit de faits que le développeur souhaite voir et modifier directement. Si l’agent apprend quelque chose de faux, il suffit d’ouvrir le fichier pour y apporter les corrections.

Voici donc comment Claude Code’s CLAUDE.md et .claude/ Travail sur les répertoires. L’agent lit au niveau du projet CLAUDE.md fichiers contenant des conventions et des instructions, ainsi que les écritures vers ~/.claude/MEMORY.md Pour le transfert de connaissances entre sessions. Ces fichiers sont en Markdown pur : vous les lisez, les modifiez, les committez dans Git, puis les partagez avec votre équipe. le curseur de .cursorrules et Le windsurfing .windsurfrules des fichiers texte simples que l’agent charge au démarrage afin de prendre connaissance du contexte du projet.

Mise en œuvre d’un stockage mémoire de fichiers

L’implémentation est délibérément simplifiée. L’agent dispose de quatre opérations : écrire un document, lire un document, lister les documents disponibles, et effectuer une recherche par mot-clé à l’intérieur des documents.

Depuis file_memory.py:

from pathlib import Path
import json
import fnmatch

class FileMemory:
    """Document memory backed by the local filesystem.

    Stores agent knowledge as human-readable files organized by topic.
    No embeddings, no database — just files that both the agent and
    the developer can read, edit, and version-control.
    """

    def __init__(self, base_dir: str | Path):
        self.base_dir = Path(base_dir)
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def write_doc(self, path: str, content: str, metadata: dict | None = None):
        """Write or overwrite a document at the given path.

        Paths are relative to base_dir. Directories are created automatically.
        Metadata (if provided) is stored as a JSON sidecar file.
        """
        full_path = self.base_dir / path
        full_path.parent.mkdir(parents=True, exist_ok=True)
        full_path.write_text(content, encoding="utf-8")

        if metadata:
            meta_path = full_path.with_suffix(full_path.suffix + ".meta")
            meta_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")

    def read_doc(self, path: str) -> str | None:
        """Read a document by path. Returns None if not found."""
        full_path = self.base_dir / path
        if full_path.exists():
            return full_path.read_text(encoding="utf-8")
        return None

    def list_docs(self, pattern: str = "**/*") -> list[str]:
        """List documents matching a glob pattern."""
        return [
            str(p.relative_to(self.base_dir))
            for p in self.base_dir.glob(pattern)
            if p.is_file() and not p.name.endswith(".meta")
        ]

    def search_docs(self, query: str, pattern: str = "**/*.md") -> list[dict]:
        """Search documents by keyword. Returns matching files with context.

        This is intentionally simple — grep-style keyword search.
        For semantic search, use a vector store instead.

        NOTE: This is a sketch for demonstration. A simple substring check
        won't scale beyond a few hundred documents. For production with 500+
        documents, use TF-IDF/BM25 scoring (e.g., rank_bm25) or a full-text
        search backend (PostgreSQL GIN index, Elasticsearch).
        """
        results = []
        for path in self.base_dir.glob(pattern):
            if not path.is_file() or path.name.endswith(".meta"):
                continue
            content = path.read_text(encoding="utf-8")
            if query.lower() in content.lower():
                # Return the paragraph containing the match for context
                for paragraph in content.split("\n\n"):
                    if query.lower() in paragraph.lower():
                        results.append({
                            "path": str(path.relative_to(self.base_dir)),
                            "match": paragraph.strip()[:500],
                        })
        return results

Structure des dossiers

La majeure partie de la valeur de la mémoire de document provient de la manière dont le répertoire est organisé. Voici la structure que j’utilise pour le Agent d’analyse de marché:

.agent-memory/
    README.md                  # What this directory is, for human readers
    user-profiles/
        user-123.md            # Preferences, history, risk profile
        user-456.md
    research/
        NVDA-2026-02.md        # Research notes from recent analysis
        TSLA-2026-01.md
    conventions/
        analysis-format.md     # How to structure analysis reports
        data-sources.md        # Preferred data sources and API patterns
    learnings/
        common-errors.md       # Mistakes the agent has learned to avoid
        tool-patterns.md       # Effective tool call sequences

Chaque fichier est au format Markdown. La finalité de chaque fichier est évidente à partir de son chemin. Vous pouvez git diff afficher tout le répertoire de mémoire afin de connaître ce que l’agent a appris au cours d’une session. git revert Un mauvais apprentissage peut survenir, ou bien il est possible de copier le répertoire vers un autre projet. Essayez de faire l’une de ces opérations avec une collection Qdrant.

Quand utiliser la mémoire de document, les vecteurs ou le format clé-valeur

Les trois backends mémoire répondent à des schémas d’accès distincts :

DimensionStockage vectorielStockage clé-valeurStockage de documents
Pattern de requête« Trouver des faits similaires à X »Récupérer la valeur associée à la clé”Lisez le document situé à l’adresse indiquée.”
Idéal pourRappel non structuré et variéRecherches structuréesContexte du projet, notes
Lisible par l’humainNon (embeddings)Partiellement (JSON)
DébogableScore de similarité élevéFacile (clés exactes)Trivial (ouvrir le fichier)
Version contrôlablePossibleOui (native Git)
Embedding infrastructureObligatoirePas nécessairePas nécessaire
Évolue jusqu’àDes millions de faitsDes millions de clésDes milliers de documents
Capacité de rechercheSimilitude sémantiqueCorrespondance exacteBasé sur des mots-clés ou des chemins

Utilisez la mémoire de document lorsque :

Utilisez des entrepôts vectoriels lorsque :

Utilisez des stores clé-valeur lorsque :

En pratique, les agents en environnement de production combinent fréquemment les trois approches. Le Agent d’analyse de marché il utilise PostgreSQL checkpoints pour la gestion de la mémoire à haute performance, Qdrant pour le rappel des faits utilisateurs sémantiques, ainsi qu’un stockage de documents basé sur des fichiers pour les conventions de projet et les notes de recherche.

Exemples du monde réel

Ce schéma est déjà largement répandu dans les assistants de codage AI :

Le point commun : tous ces systèmes stockent les connaissances de l’agent sous forme de fichiers texte lisibles par l’homme, avec des opérations de lecture/écriture explicites. Aucune embeddings. Aucune infrastructure vectorielle. C’est l’agent qui décide de ce qui doit être écrit, le développeur peut consulter et modifier tout le contenu, et l’ensemble du système tient dans un git diff.

Au-delà des assistants de codage

La mémoire de document n’est pas réservée aux agents de codage. Ce phénomène se retrouve dans des domaines d’agents très variés :

Le Atelier MemAgents au ICLR 2026 C’est un signe que la communauté de recherche rattrape ce que les praticiens ont déjà mis en place. La mémoire documentaire a clairement dépassé ses origines en tant qu’outil d’assistance à la programmation.

Les compétences utilisent des documents pour emballer des instructions procédurales. Norme des compétences d’agent stocke ces instructions dans SKILL.md Des fichiers contenant du frontmatter en YAML ainsi qu’un corps en Markdown. Cela ressemble à une mémoire de document au niveau du stockage, mais la fonction est différente : une compétence indique à l’agent comment effectuer un type de tâche, tandis que la mémoire enregistre les faits appris lors d’un projet ou d’une exécution précédente. Partie 3 Il prend en compte cette distinction du point de vue de l’outil.

MCP (Protocole de contexte du modèle) se dirige dans la même logique : les définitions d’outils sont des fichiers JSON Schema que tout agent peut découvrir et appeler. Le protocole dispose de 97 millions de téléchargements mensuels SDK Il est également pris en charge par OpenAI, Google, Microsoft et AWS. MCP n’est pas spécifique au codage. Ces mêmes serveurs permettent aux agents de se connecter aux bases de données, aux APIs internes ainsi qu’aux systèmes d’entreprise.

Tous deux indiquent le même schéma : des connaissances procédurales stockées sous forme de documents contrôlés par des schémas, comprenant des opérations de lecture/écriture explicites. MCP, désormais régies par le Agentic AI Fondation, c’est ce qui se rapproche le plus d’une norme d’interopérabilité dans l’écosystème des agents.

Échelle de la mémoire des documents en environnement de production

L’implémentation basée sur des fichiers présentée ci-dessus fonctionne bien pour les ordinateurs portables utilisés par un seul développeur ainsi que pour des déploiements à petite échelle. Une environnement de production multi-locataires accueillant des centaines d’utilisateurs et des milliers de documents exige une architecture complètement différente.

La limite de taille des fichiers en mode un seul nœud devient évidente : il est impossible d’effectuer une scalabilité horizontale des opérations I/O sur les fichiers, les écritures simultanées nécessitent un verrouillage, et la gestion des permissions entre différents locataires s’avère complexe. En environnement de production, il est indispensable d’utiliser un stockage de secours capable de gérer correctement la concurrence, les recherches et le modèle multi-locataires.

Trois approches courantes :

Approche A : hybride avec une couche de base de données légère

Conservez les fichiers destinés à l’élaboration (les développeurs modifient le Markdown localement), mais servez-les depuis une base de données à runtime. Lors du déploiement, synchronisez les fichiers avec les enregistrements de PostgreSQL. L’agent lit alors dans la base de données et non sur le disque. Cela vous offre :

Approche B : stockage d’objets + sidecar d’index vectoriel

Les documents sont stockés dans S3/GCS sous forme d’objets, et une collection Qdrant est utilisée pour indexer leurs embeddings. L’agent interroge Qdrant afin d’obtenir les identifiants des documents pertinents, puis récupère leur contenu depuis le stockage d’objets. Cette architecture permet une scalabilité horizontale et prend en charge la recherche sémantique, mais elle ajoute de la complexité : il faut gérer deux systèmes, assurer une embedding pipeline entre eux, et faire face à une cohérence éventuelle entre le stockage et l’index.

Approche C : stockage structuré de documents avec PostgreSQL (recommandée)

Stockez les documents sous forme de lignes JSONB dans PostgreSQL, en bénéficiant d’une recherche plein texte grâce à l’index GIN ainsi que, facultativement, de la recherche vectorielle embeddings (pgvector). Cela vous permet d’obtenir une recherche hybride (mots-clés + sémantique), des transactions ACID, et un seul système opérationnel.

Esquisse de l’Approche C :

from typing import Optional
import asyncpg

class ProductionDocumentMemory:
    """PostgreSQL-backed document memory with hybrid search.

    Schema:
        CREATE TABLE documents (
            id SERIAL PRIMARY KEY,
            tenant_id TEXT NOT NULL,
            path TEXT NOT NULL,
            content TEXT NOT NULL,
            metadata JSONB,
            embedding vector(1536),  -- pgvector extension
            ts_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
            created_at TIMESTAMPTZ DEFAULT NOW(),
            UNIQUE(tenant_id, path)
        );
        CREATE INDEX ON documents USING GIN(ts_vector);
        CREATE INDEX ON documents USING ivfflat(embedding vector_cosine_ops);
    """

    def __init__(self, pool: asyncpg.Pool):
        self.pool = pool

    async def write(
        self,
        tenant_id: str,
        path: str,
        content: str,
        metadata: Optional[dict] = None,
        embedding: Optional[list[float]] = None,
    ):
        """Write or update a document."""
        async with self.pool.acquire() as conn:
            await conn.execute(
                """
                INSERT INTO documents (tenant_id, path, content, metadata, embedding)
                VALUES ($1, $2, $3, $4, $5)
                ON CONFLICT (tenant_id, path) DO UPDATE
                SET content = EXCLUDED.content,
                    metadata = EXCLUDED.metadata,
                    embedding = EXCLUDED.embedding
                """,
                tenant_id, path, content, metadata, embedding,
            )

    async def search(
        self,
        tenant_id: str,
        query: str,
        embedding: Optional[list[float]] = None,
        limit: int = 5,
    ) -> list[dict]:
        """Hybrid search: full-text + optional vector similarity."""
        async with self.pool.acquire() as conn:
            if embedding:
                # Hybrid scoring: 0.6 * text relevance + 0.4 * vector similarity
                rows = await conn.fetch(
                    """
                    SELECT path, content, metadata,
                           (0.6 * ts_rank(ts_vector, plainto_tsquery('english', $2)) +
                            0.4 * (1 - (embedding <=> $3))) AS score
                    FROM documents
                    WHERE tenant_id = $1
                      AND ts_vector @@ plainto_tsquery('english', $2)
                    ORDER BY score DESC
                    LIMIT $4
                    """,
                    tenant_id, query, embedding, limit,
                )
            else:
                # Full-text search only
                rows = await conn.fetch(
                    """
                    SELECT path, content, metadata,
                           ts_rank(ts_vector, plainto_tsquery('english', $2)) AS score
                    FROM documents
                    WHERE tenant_id = $1
                      AND ts_vector @@ plainto_tsquery('english', $2)
                    ORDER BY score DESC
                    LIMIT $3
                    """,
                    tenant_id, query, limit,
                )
            return [dict(row) for row in rows]

Ce que vous obtenez :

Les fichiers s’avèrent très utiles pour les workflows impliquant un seul développeur. Dans un environnement de production multi-locataires, un stockage de documents structuré basé sur PostgreSQL offre généralement le bon équilibre entre simplicité, performances et maturité opérationnelle.


Assemblage : l’architecture complète

C’est ainsi que les trois niveaux de mémoire collaborent entre eux dans le Agent d’analyse de marché. Le diagramme illustre le flux complet, de la demande utilisateur à la réponse, en montrant que tous les niveaux de mémoire sont activés.

Architecture à mémoire complète

L’architecture dispose de trois chemins mémoire :

  1. Chemin critique (checkpoint store) : Chaque nœud de LangGraph écrit son état de graphe résumable dans le checkpoint store. Lorsque le graphe rencontre un interrupt_before node (comme le rapporteur dans Partie 1), Des pauses d’exécution surviennent. L’utilisateur peut fermer l’application, et lorsqu’il la réouvre, le graphe reprend son exécution à partir du checkpoint. Les journaux d’événements et les traces générées par les Runtime relèvent de considérations distinctes en environnement de production.

  2. Chemin froid (stockage à long terme) : Au début de chaque conversation, l’agent interroge le stockage à long terme afin d’obtenir le contexte pertinent de l’utilisateur. À la fin, il extrait et enregistre de nouvelles informations. Ce processus s’exécute de manière asynchrone — il ne doit en aucun cas bloquer le reasoning loop principal.

  3. Chemin du document (stockage de fichiers) : Lors du démarrage, l’agent charge les conventions de projet ainsi que les notes de recherche pertinentes depuis le stockage de fichiers. Pendant l’exécution, il enregistre sur disque de nouveaux résumés de recherche et les motifs appris. Contrairement à la voie « froide », les lectures de documents sont synchrones (elles influencent la tâche en cours), tandis que les écritures peuvent être différées.

La configuration des connexions dans LangGraph est assez simple : le checkpoint store ainsi que le long-term store sont transmis lors de la compilation du graphe, tandis que le document store est injecté en tant que dépendance. Le schéma local ci-dessous utilise InMemoryStore Ainsi, le fragment reste compact ; la topologie Docker de référence utilise Qdrant pour assumer la même fonction de rappel sémantique.

from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.store.memory import InMemoryStore

# Hot memory: PostgreSQL for durable checkpoints
checkpointer = await create_postgres_checkpointer(pg_connection_string)

# Cold memory: local sketch with vector search
# (The reference Docker topology uses Qdrant for persistent recall.)
memory_store = InMemoryStore(
    index={"dims": 1536, "embed": embedding_function}
)

# Document memory: file-based store for project knowledge
doc_memory = FileMemory(base_dir=".agent-memory")

# Checkpoint store and long-term store wired into the graph
graph = create_graph(
    checkpointer=checkpointer,
    store=memory_store,
)

# The store is accessible inside any node via the store parameter
def planner_node(state: AgentState, *, store: BaseStore) -> dict:
    """Plan with user context from long-term memory."""

    # Recall relevant user facts from vector store
    user_memories = store.search(
        namespace=("user", state.user_id),
        query=state.messages[-1].content,
        limit=5,
    )

    # Load project conventions from document memory
    conventions = doc_memory.read_doc("conventions/analysis-format.md")

    # Inject both into planning context
    memory_context = "\n".join(m.value["fact"] for m in user_memories)
    # ... rest of planning logic with personalized context and conventions

Le flux complet

Que se passe-t-il lorsque un utilisateur revenant envoie « Analyser TSLA » à la Agent d’analyse de marché:

  1. Charge mémoire des documents : Lors du démarrage, l’agent lit les conventions de projet stockées dans le système de gestion de documents : préférences de format d’analyse, sources de données privilégiées, schémas d’utilisation des outils. Ceux-ci définissent le comportement de base.

  2. Rappel en mémoire froide : Avant que le nœud routeur ne s’exécute, le graphe interroge le stockage à long terme afin d’y récupérer le message de l’utilisateur. Il extrait alors les informations suivantes : « L’utilisateur présente une forte tolérance au risque », « L’utilisateur préfère des analyses détaillées des concurrents », « L’utilisateur a déjà étudié NVDA et AMD ».

  3. Routeur + Planificateur : la classe de routeur classe cet élément comme DEEP_RESEARCHLe planificateur génère un plan de recherche en 5 étapes, personnalisé en fonction des préférences identifiées. Il comprend une étape d’analyse concurrentielle, car l’historique de l’utilisateur indique qu’il en a besoin. Ce plan respecte le format défini dans le document des conventions.

  4. Boucle d’exécution (mémoire chaude) : Chaque étape s’exécute selon le schéma ReAct Partie 1. Après chaque nœud (routeur, planificateur, chaque étape d’exécution), LangGraph écrit un checkpoint dans PostgreSQL. Si le processus plante après l’étape 3 sur 5, il faut le redémarrer pour reprendre à partir de l’étape 4.

  5. Interruption HITL : le graphe atteint reporter nœud doté de interrupt_before. Le projet de rapport se trouve dans le checkpoint. L’utilisateur le consulte quelques heures plus tard, et le graphe charge le checkpoint avant de poursuivre son exécution.

  6. Mises à jour de la mémoire : Une fois la conversation terminée : (a) un processus asynchrone extrait de nouvelles informations sur l’utilisateur (« l’utilisateur suit désormais TSLA », « l’utilisateur a approuvé le format du rapport ») et les stocke dans le stockage vectoriel à long terme, et (b) l’agent écrit un résumé des recherches dans le stockage de documents.research/TSLA-2026-02.md) à titre de référence future.

Le modèle à trois niveaux permet de séparer clairement les préoccupations fonctionnelles. Le stock checkpoint gère la durabilité et la reprise des opérations ; il s’agit d’infrastructures. Le stock à long terme est chargé de la personnalisation ; il correspond à la logique de produit. Enfin, le stock de documents conserve les connaissances accumulées sur le projet ; il constitue en quelque sorte le carnet de notes de l’agent.


Compromis et considérations

La mémoire ajoute de la valeur, mais elle entraîne également des coûts et une complexité accrues. Soyez honnête quant aux compromis à envisager :


Principaux enseignements

  1. La mémoire de l’agent se compose de plusieurs stockages présentant des schémas d’accès différents. Il convient de maintenir distincts les éléments checkpoints réutilisables, les faits structurés, le rappel sémantique ainsi que les documents de projet.
  2. Implémenter des fonctionnalités de pause et de reprise avant toute personnalisation. La perte des progrès d’une tâche constitue la première défaillance de mémoire rencontrée par un agent à exécution prolongée.
  3. Stocker les faits déterministes dans un espace de stockage structuré. Recourir à la recherche vectorielle lorsque la requête est floue ou que sa formulation varie.
  4. Utiliser des fichiers pour conserver les connaissances liées aux projets, qui doivent pouvoir être consultées, modifiées, versionnées ou comparées via des diff.
  5. Définir pour chaque type de mémoire des règles relatives à l’expiration, aux conflits et à la suppression. Une mémoire que le système ne peut pas corriger devient une dette technique.
  6. Limiter ce qui est renvoyé au modèle. La mémoire stockée n’a de valeur que si sa récupération permet d’intégrer les preuves appropriées dans le contexte actuel.

La couche suivante concerne les actions à entreprendre

Partie 3, AI Agent Tool Use en 2026, Il passe de l’état stocké à une action. Il examine la manière dont un agent découvre et invoque des outils, ainsi que le mécanisme par lequel les limites de ces outils renvoient des erreurs que le reasoning loop peut exploiter. Les parties 5 et 6 traitent du retour vers la mémoire depuis le point de vue opérationnel : le runtime restaure un checkpoint, tandis que le harness détermine ce qui doit être transmis lors du passage à la session de modèle suivante.

Références

Articles de recherche

Documentation de LangGraph

Checkpoint des backends

Bases de données vectorielles et outils de mémoire

Mémoire basée sur les documents et les fichiers

Mémoire frameworks

Benchmarks

Ateliers

Projet de démonstration


Le code complet de l’Agent Analyste de marché, y compris l’architecture mémoire décrite dans cette publication, se trouve sur GitHub si vous souhaitez suivre le texte en même temps._

Série : Conception de la pile Agentic