Une exploration de la mémoire d'un agent à partir des premiers principes : des listes Python aux fichiers Markdown, en passant par la recherche vectorielle, les hybrides graphe-vecteur, et enfin, une solution open-source propre pour tout cela.

Un LLM est apatride par conception. Chaque appel API repart de zéro. La « mémoire » que vous ressentez en discutant avec ChatGPT est une illusion créée par le renvoi de l'intégralité de l'historique de la conversation à chaque requête.
Cette astuce fonctionne pour les discussions informelles. Elle s'effondre dès que vous essayez de construire un véritable agent.
Voici 7 modes de défaillance qui apparaissent dès que vous sautez la mémoire :
- Amnésie contextuelle : l'agent demande des informations que vous lui avez déjà fournies
- Zéro personnalisation : chaque interaction semble générique
- Échec des tâches multi-étapes : l'état intermédiaire se perd silencieusement en cours de tâche
- Erreurs répétées : aucun rappel épisodique signifie les mêmes erreurs, pour toujours
- Aucune accumulation de connaissances : chaque session part de zéro
- Hallucination due aux lacunes : lorsque le contexte déborde, le modèle invente
- Effondrement de l'identité : pas de continuité, pas de confiance
La réponse évidente est « d'y mettre plus de contexte ». C'est pourquoi les fenêtres de 128 000 et 200 000 jetons semblent devoir tout résoudre.
Ce n'est pas le cas.
La précision chute de plus de 30 % lorsque des informations pertinentes se situent au milieu d'un long contexte. C'est l'effet bien documenté de « perte au milieu ».
Le contexte est un budget partagé : les invites système, les documents récupérés, l'historique des conversations et la sortie se battent tous pour les mêmes jetons.
Même à 100 000 jetons, l'absence de persistance, de priorisation et de saillance rend la longueur brute du contexte insuffisante.

La mémoire ne consiste pas à entasser plus de texte dans l'invite. Il s'agit de structurer ce dont l'agent se souvient afin qu'il puisse trouver ce qui est important.
Le cadre des sciences cognitives qui aide vraiment
La formulation de Lilian Weng en 2023 est devenue le cadre par défaut :
Agent = LLM + Mémoire + Planification + Utilisation d'outils.
Les quatre piliers d'égale importance.
Sa taxonomie emprunte aux sciences cognitives, où la mémoire humaine se divise en trois systèmes :
- La mémoire sensorielle capture les entrées perceptuelles brutes et les conserve pendant une fraction de seconde. Seules les parties auxquelles vous prêtez attention sont transmises.
- La mémoire de travail est l'endroit où se déroule la pensée active. Elle contient environ 7±2 éléments à la fois (découverte de Miller en 1956). Perdez votre concentration, et le contenu disparaît.
- La mémoire à long terme est un stockage durable sans limite de capacité pratique. La récupération est le goulot d'étranglement : vous pouvez stocker des millions de choses et échouer à vous souvenir de celle dont vous avez besoin.
Chacune correspond directement à un composant dans les architectures d'agents modernes :

La mémoire à long terme se divise encore :
- Épisodique : événements passés spécifiques (« mardi, le cluster PostgreSQL est tombé en panne »)
- Sémantique : faits et concepts (« PostgreSQL est une base de données relationnelle »)
- Procédurale : compétences et flux de travail (« lorsqu'un utilisateur demande un remboursement, vérifiez d'abord la date d'achat »)
Le pont entre l'épisodique et le sémantique est la consolidation de la mémoire : des événements spécifiques répétés se distillent en connaissances générales. Un agent qui remarque que « les utilisateurs préfèrent systématiquement les résumés exécutifs » à travers des dizaines d'interactions devrait transformer cela en une règle réutilisable. Sans consolidation, votre agent rejoue des événements individuels plutôt que d'apprendre d'eux.

L'agent minimal, et ce qui casse en premier
Enlevez les cadres, et un agent est une boucle : percevoir, penser, agir.
1class Agent:2 """Agent AI minimal : percevoir, penser, agir"""3 def __init__(self):4 self.client = anthropic.Anthropic()5 self.model = "claude-sonnet-4-20250514"67 def run(self, user_input: str) -> str:8 response = self.client.messages.create(9 model=self.model,10 max_tokens=1024,11 messages=[{"role": "user", "content": user_input}],12 )13 return response.content[0].text
Dites-lui « J'ai 4 pommes », puis demandez « J'en ai mangé une, combien en reste-t-il ? » et il n'a aucune idée de quelles pommes vous parlez. Chaque appel existe en isolation.
Couche 1 : La liste Python
La première correction que tout le monde utilise :
1class Agent:2 def __init__(self):3 self.client = anthropic.Anthropic()4 self.messages = [] # Toute la "mémoire" est une liste56 def chat(self, user_input: str) -> str:7 self.messages.append({"role": "user", "content": user_input})8 response = self.client.messages.create(9 model="claude-sonnet-4-20250514",10 max_tokens=1024,11 messages=self.messages, # L'historique complet est envoyé à chaque fois12 )13 reply = response.content[0].text14 self.messages.append({"role": "assistant", "content": reply})15 return reply
Les tours multiples fonctionnent maintenant. La question des pommes obtient une réponse correcte car la conversation complète est renvoyée à chaque appel.
Deux problèmes apparaissent rapidement :
- La liste croît sans limite. Au tour 200 environ, vous atteignez le plafond du contexte et les messages les plus anciens sont silencieusement supprimés. Le nom de l'utilisateur du tour 1 disparaît bien avant la blague jetable d'hier. Aucune priorisation, juste un ordre chronologique strict.
- Tout vit dans la RAM. Dès que le processus Python se termine, votre agent n'a aucune idée de qui vous êtes.
Couche 2 : Les fichiers Markdown pour la persistance
L'étape suivante consiste à écrire la mémoire sur le disque. Markdown est un choix naturel : lisible par l'homme, compatible Git, et l'agent peut le relire comme texte brut. Claude Code utilise exactement ce modèle avec les fichiers CLAUDE.md et MEMORY.md.
1class MarkdownMemoryAgent:2 def __init__(self):3 self.client = anthropic.Anthropic()4 self.history_file = Path("memory/conversation_history.md")5 self.facts_file = Path("memory/known_facts.md")67 def save_to_disk(self, role: str, content: str) -> None:8 with open(self.history_file, "a") as f:9 f.write(f"### {role} à {datetime.now().isoformat()}\n{content}\n\n")1011 def load_history(self) -> str:12 if self.history_file.exists():13 return self.history_file.read_text()14 return ""1516 def chat(self, user_input: str) -> str:17 self.save_to_disk("user", user_input)18 history = self.load_history()19 response = self.client.messages.create(20 model="claude-sonnet-4-20250514",21 max_tokens=1024,22 system=f"Conversation précédente :\n{history}",23 messages=[{"role": "user", "content": user_input}],24 )25 reply = response.content[0].text26 self.save_to_disk("assistant", reply)27 return reply
La persistance est résolue. Redémarrez le script, et la conversation est toujours sur le disque. Vous pouvez également maintenir un fichier de faits séparé que l'agent extrait au fil du temps :
1- Le nom de l'utilisateur est Sarah2- Sarah gère l'équipe backend chez Acme Corp3- Acme Corp est une entreprise SaaS B2B4- En train de migrer la base de données de production vers une nouvelle région AWS
Vous pouvez ouvrir le fichier dans n'importe quel éditeur, voir exactement ce que l'agent sait, et le corriger à la main. Vraiment utile pour le prototypage.
Avec 4 faits, cela fonctionne parfaitement. Chargez l'intégralité du fichier dans le contexte et le LLM répond à toute question sur Sarah, son entreprise ou son secteur.
Maintenant, avancez de trois mois. Votre agent a 2 000 faits extraits et 200 journaux de conversation. Cela représente plus de 500 000 jetons de markdown sur le disque, et votre fenêtre de contexte est de 128 000.
Vous ne pouvez plus tout charger. Vous devez récupérer sélectivement uniquement les faits pertinents pour la requête en cours. Avec des fichiers plats, votre seule option est la recherche par mot-clé :
1# L'utilisateur demande : "Quel est le statut de notre migration cloud ?"2grep("migration cloud", facts_file)3# Retourne : []4# Le fait sur le disque dit "migration de la base de production vers une nouvelle région AWS."5# Les mots "migration cloud" n'apparaissent nulle part.67# L'utilisateur demande : "Quelle équipe s'occupe du travail sur la base de données ?"8grep("équipe base de données", facts_file)9# Retourne : []10# Un fait dit que Sarah "gère l'équipe backend." Un autre dit que l'équipe11# "migre la base de production." Mais aucune ligne ne contient12# à la fois "base de données" et "équipe" ensemble.
À petite échelle, les fichiers Markdown fonctionnent. À échelle réelle, ils imposent une recherche par mot-clé, et les mots-clés ne peuvent pas gérer les synonymes, les paraphrases ou les connexions entre les faits.
L'information est sur le disque. Mais vous ne pouvez pas tout charger, et la recherche par mot-clé est trop fragile pour trouver les bonnes pièces.
Si vous avez utilisé OpenClaw, vous avez vu cela se produire. Il stocke la mémoire sous forme de fichiers de point de contrôle Markdown, et après des semaines d'utilisation quotidienne, les faits plus anciens disparaissent discrètement à mesure que le contexte s'accumule et se compacte. Le stockage est là. La récupération ne l'est pas.
Le stockage sans récupération intelligente est une bibliothèque sans catalogue.
Couche 3 : La recherche vectorielle et le mur qu'elle heurte
Ajoutez des embeddings. Découpez votre markdown, intégrez les morceaux, recherchez par similarité cosinus. Maintenant, « base de données » correspond à « PostgreSQL » car leurs vecteurs vivent proches dans l'espace d'embedding. Le problème des synonymes disparaît.
Puis vous heurtez un nouveau mur. Considérez ces trois faits dans votre base vectorielle :
1- "Alice est la responsable technique du projet Atlas"2- "Le projet Atlas utilise PostgreSQL comme magasin de données principal"3- "Le cluster PostgreSQL a subi une panne mardi"
L'utilisateur demande : « Le projet d'Alice a-t-il été affecté par la panne de mardi ? »
La requête mentionne Alice et la panne de mardi, donc la recherche vectorielle classe les premier et troisième faits en haut. Mais le pont critique, « Le projet Atlas utilise PostgreSQL », ne mentionne ni Alice ni mardi. C'est la pièce de liaison, et c'est celle qui ne remontera pas.
Chaque fait est un point isolé dans l'espace d'embedding. Le tissu conjonctif qui les relie est invisible pour les vecteurs.

Ce n'est pas un cas marginal. C'est la forme normale des questions du monde réel. La connaissance métier est intrinsèquement relationnelle : les personnes appartiennent à des équipes, les équipes possèdent des projets, les projets dépendent de systèmes, les systèmes ont des incidents. Toute question qui traverse deux sauts ou plus dépasse ce que la récupération vectorielle plate peut répondre.
La matrice des capacités
Chaque couche corrige la douleur précédente mais en révèle une plus profonde :

Vous avez besoin de persistance, de compréhension sémantique et de raisonnement relationnel dans une seule couche de mémoire.
Construire cela vous-même signifie assembler une base vectorielle, une base de graphes, un magasin relationnel, un extracteur d'entités, un pipeline de déduplication et un système de pondération des arêtes. Cela représente des semaines de travail d'infrastructure avant d'écrire une seule ligne de logique d'agent.
J'utilise une solution qui comble cette lacune proprement. Elle est entièrement open-source, gère les trois paradigmes de stockage sous un même toit, et vous pouvez la faire fonctionner en quelques minutes. Parlons de Cognee.
Cognee : trois magasins, un moteur, quatre appels
Cognee est un moteur de connaissance open-source conçu pour la mémoire des agents. Il combine la recherche vectorielle avec des graphes de connaissances et une couche de provenance relationnelle en un seul système.
L'ensemble de la surface API est de quatre appels asynchrones :
1import cognee23await cognee.add("Votre document ici") # Ingérer n'importe quoi4await cognee.cognify() # Construire le graphe de connaissances + embeddings5await cognee.memify() # Auto-améliorer la mémoire6await cognee.search("Votre requête") # Récupérer avec raisonnement
Derrière ces quatre appels se trouve une architecture à trois magasins.

Pourquoi trois magasins et pas un ?
Chaque magasin capture une dimension de la connaissance que les autres ne peuvent pas :
- Magasin relationnel → provenance : d'où viennent les données, quand elles ont été ingérées, qui y a accès
- Magasin vectoriel → sémantique : ce que le contenu signifie, à quoi il ressemble
- Magasin de graphes → relations : comment les entités sont connectées, ce qui cause quoi, qui relève de qui
Aplatir l'un d'entre eux et vous perdez des informations qui comptent pour la précision de la récupération.
La pile par défaut est SQLite + LanceDB + Kuzu, entièrement intégrée et basée sur des fichiers. pip install cognee plus une clé API LLM et vous êtes opérationnel.
Pas de Docker, pas de services externes.
Pour la production, remplacez SQLite par Postgres, LanceDB par Qdrant/Pinecone/pgvector, et Kuzu par Neo4j/FalkorDB/Neptune.
Même API à quatre appels dans les deux cas.
Que fait réellement cognify ?
cognee.cognify() exécute un pipeline en plusieurs étapes qui convertit le texte brut en connaissances structurées et interconnectées :
- Classification des documents par type et domaine
- Vérification des permissions pour le contrôle d'accès multi-locataire
- Extraction de morceaux qui respecte la structure des paragraphes (pas des découpes de taille fixe)
- Extraction d'entités et de relations via LLM, avec déduplication automatique via le hachage du contenu
- Génération de résumés pour une récupération efficace
- Double indexation dans le magasin vectoriel (embeddings) et le magasin de graphes (arêtes)
L'étape de déduplication est plus importante qu'il n'y paraît. Si la même entité apparaît dans 50 documents, Cognee la fusionne en un seul nœud de graphe avec 50 arêtes entrantes. Votre agent ne voit plus « Alice » comme 50 inconnues différentes. Et le pipeline est incrémental par défaut : seuls les fichiers nouveaux ou mis à jour sont retraités.

Chaque nœud du graphe a un embedding correspondant. Cette double représentation est l'astuce centrale : entrez par les vecteurs (trouvez du contenu sémantiquement similaire) et sortez par le graphe (suivez les relations vers les entités connectées), ou l'inverse. C'est ce qui permet aux requêtes multi-sauts de fonctionner sans sacrifier la recherche sémantique.
Memify : une mémoire qui apprend
memify() est ce qui distingue Cognee de tous les outils « ingérer et rechercher ». Il exécute une passe d'optimisation inspirée du RL sur le graphe :
- Renforcement des chemins utiles qui ont conduit à une bonne récupération
- Élagage des nœuds obsolètes qui n'ont pas été touchés
- Auto-ajustement des poids des arêtes basé sur l'utilisation réelle
- Ajout de faits dérivés en identifiant les relations implicites
Le graphe d'un agent de support client renforce naturellement les chemins à travers les documents produits et les politiques de remboursement tout en laissant les arêtes RH rarement interrogées se dégrader. Le graphe développe son propre sens de la pertinence au fil du temps.

Quatorze modes de récupération
Cognee propose 14 modes de recherche. Ceux auxquels vous aurez réellement recours :

Construire un véritable agent avec la mémoire Cognee
Voici le modèle complet pour intégrer Cognee dans la boucle percevoir-penser-agir :
1import cognee2from cognee import SearchType34class CogneeMemoryAgent:5 """Agent avec mémoire persistante hybride graphe-vecteur."""67 def __init__(self, session_id: str = "default"):8 self.llm_client = OpenAI()9 self.session_id = session_id1011 async def ingest(self, text: str, dataset: str = "main"):12 await cognee.add(text, dataset)13 await cognee.cognify([dataset])1415 async def recall(self, query: str) -> str:16 results = await cognee.search(17 query_text=query,18 query_type=SearchType.GRAPH_COMPLETION,19 session_id=self.session_id,20 )21 return results[0] if results else ""2223 async def chat(self, user_input: str) -> str:24 context = await self.recall(user_input)25 messages = [26 {"role": "system", "content": "Vous êtes utile. Utilisez le contexte mémoire."},27 {"role": "system", "content": f"Contexte mémoire :\n{context}"},28 {"role": "user", "content": user_input},29 ]30 response = self.llm_client.chat.completions.create(31 model="gpt-4o-mini", messages=messages32 )33 reply = response.choices[0].message.content34 await cognee.add(35 f"Utilisateur : {user_input}\nAssistant : {reply}",36 "conversations"37 )38 await cognee.cognify(["conversations"])39 return reply
Le cycle mémoire : ingérer, extraire, stocker, récupérer, répondre, stocker à nouveau. Chaque tour enrichit le graphe de connaissances, et le traitement incrémental signifie que vous ne payez que pour indexer le nouveau contenu.
La mémoire de session gère automatiquement la résolution des pronoms :
1await cognee.search(query_text="Où vit Alice ?", session_id="conv_1")2await cognee.search(query_text="Que fait-elle comme travail ?", session_id="conv_1")3# "elle" est résolu en Alice à partir du contexte de session
La multi-location est intégrée au niveau du graphe avec des permissions par ensemble de données (lecture, écriture, suppression, partage). Pas de séparation par espace de noms, une isolation réelle au niveau du graphe.
La voie pratique à suivre
Si vous construisez un agent aujourd'hui, la vraie question de départ est : « De quoi mon agent a-t-il besoin de se souvenir, et à quel genre de questions répondra-t-il ? »
Si vos requêtes n'ont besoin que d'une recherche de similarité (« trouver des conversations similaires à celle-ci »), la mémoire vectorielle seule fonctionne. Dès que les requêtes traversent les frontières des entités (« Le projet d'Alice a-t-il été affecté par la panne de mardi ? »), vous avez besoin d'un parcours de graphe.
Vous pouvez connecter vous-même des magasins vectoriels, de graphes et relationnels séparés. Les équipes qui suivent cette voie brûlent généralement des semaines sur l'infrastructure pour une couche mémoire qui n'apprend toujours pas de sa propre utilisation.
Cognee réduit cela à quatre appels API. Les valeurs par défaut intégrées vous permettent de démarrer en quelques minutes. Les backends interchangeables (Postgres, Qdrant, Neo4j) vous amènent en production sans modifier le code de votre agent.
L'intelligence nécessite de la structure, pas seulement du stockage. Les trois paradigmes de stockage (relationnel, vectoriel, graphe) ne sont pas des options concurrentes. Ce sont des couches complémentaires du même système de mémoire. Les traiter comme telles est ce qui transforme un simple wrapper LLM apatride en quelque chose qui apprend vraiment.
Quelle est la prochaine chose que vous voudriez que votre agent se souvienne demain et qu'il a oubliée aujourd'hui ? Commencez par là.
👉 Découvrez Cognee sur GitHub →, donnez-lui une étoile, et essayez de l'intégrer dans votre prochain agent.
Quatre appels asynchrones, un pip install, et vous êtes opérationnel.
C'est tout !
Si vous avez aimé lire cet article :
Retrouvez-moi → @akshay_pachaar ✔️
Chaque jour, je partage des tutoriels et des idées sur l'IA, le Machine Learning et les meilleures pratiques de codage « vibe ».





