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





