Mémoire des agents IA : nous avons confié le tri à Jev

Le premier article de cette série installait une mémoire commune pour nos agents IA avec Cognee. À l’usage, le plugin qui reliait les agents à cette mémoire ajoutait des souvenirs à chaque message, sans trier. Nous avons confié ce tri à Jev, le modèle de décision de TypeSafe. Cet article raconte ces essais et présente memsift, le plugin open source que nous en avons tiré.

Thomas Sarazin
13 min de lecture

Pourquoi la mémoire de nos agents IA ajoutait des souvenirs à chaque message

Depuis l’installation décrite dans l’article sur Cognee, un agent qui ouvre un de nos projets peut retrouver les décisions prises et leurs raisons. Nous pouvons ainsi lui confier des tâches qui demandaient auparavant un long rappel du contexte. Claude Code et Codex consultent le même serveur, qui contient nos façons de travailler et l’historique de nos projets. Après un mois d’usage, nous souhaitions conserver cette mémoire commune, mais les ajouts du plugin officiel de Cognee aux conversations demandaient à être revus.

Le problème venait de la façon dont ce plugin interrogeait Cognee. Dans la version utilisée, il envoyait les échanges de la session au serveur, puis lançait une recherche à chaque message pour ajouter au contexte les passages les plus proches de la demande. Il ne vérifiait pourtant ni leur utilité pour le travail en cours ni leur présence dans la conversation. L’agent pouvait donc recevoir un rappel qu’il venait de lire quelques messages plus haut. Si cette fonctionnalité peut correspondre à certains usages, comme ceux nécessaires à l'agentique autonome, elle n'était pas adaptée à nos pratiques internes, plus proches de celles du développement classique.

En effet, sur plusieurs centaines de messages, nous avons mesuré environ 5 700 caractères ajoutés à chaque prompt, après six secondes d’attente. Même une réaction à la réponse de l’agent déclenchait le traitement prévu pour une question sur un choix passé, alors que la plupart des passages figuraient déjà dans ce que le harness de Claude Code ou Codex transmettait au modèle.

Le coût de ces rappels inutiles se prolongeait au-delà de la recherche. Tant que l’outil conservait un passage dans le contexte, l’agent devait le traiter aux tours suivants, après les secondes d’attente de l’appel initial. Le serveur et les appels à la mémoire coûtaient quelques centimes par jour, auxquels s’ajoutait le coût de ce texte dans les entrées du modèle, selon sa tarification et son cache. Avec un gros modèle ou plusieurs agents recevant les mêmes passages, nous pouvions payer davantage pour ce surplus que pour la recherche elle-même. Si ces coûts peuvent sembler négligeables, il n'en reste pas moins qu'ils ne nous semblaient pas scalables à l'échelle d'une organisation de taille moyenne.

À ce coût s’ajoutait une préoccupation sur la qualité des réponses. Dans l’étude Context Rot, les chercheurs de Chroma ont évalué dix-huit modèles et observé des baisses de performance à mesure que le contexte s’allongeait. Leurs expériences sur les distracteurs montraient aussi qu’un passage proche du sujet, mais ne répondant pas à la question, suffisait à dégrader les résultats. En triant les rappels avant de les transmettre, nous voulions éviter de laisser ce travail à l’agent pendant qu’il devait s’occuper du projet.

Ce qu’une mémoire d’agent IA doit garder

Ce tri commence à l’écriture de la mémoire. Au fil des sessions, les échanges avec un agent produisent des milliers de lignes, dont une partie sert à reprendre un projet, par exemple le choix d’un prestataire de paiement et sa raison, ou une version de langage imposée par l’hébergeur. Ces informations tiennent en quelques dizaines de lignes. Avant de les faire sélectionner par Jev, il fallait définir ce qui méritait de rejoindre la mémoire.

Ce qui mérite d’être retenu

Notre premier critère consistait à garder ce que le code ne disait pas. Nous l’avons abandonné au cours des tests, car beaucoup de choix se lisent dans le dépôt, y compris le prestataire de paiement retenu ou l’absence d’un framework. Encore faut-il que l’agent sache où regarder, sans refaire une recherche menée lors d’une session précédente. Nous avons donc retenu un critère lié à ce temps de recherche ou de raisonnement, en gardant l’information lorsqu’elle évitera de reprendre le même travail si le sujet revient.

Avec ce critère, nous conservons un choix technique et sa raison, une contrainte qui risque de poser problème à la prochaine session, ou un incident accompagné de sa correction. Les réactions et les tâches ponctuelles restent dans la conversation. Quant aux détails d'UI, ils figurent dans le design system, tandis que les habitudes communes aux projets vont dans le CLAUDE.md. Ces habitudes n’ont pas besoin de passer par la mémoire, puisque l’agent charge les fichiers de consignes en entier.

Cette sélection suppose d’accepter quelques pertes, dont la raison d’un choix inscrit dans le dépôt. Exiger de la conserver à chaque fois nous obligerait à justifier nos décisions systématiquement, sans que cela soit toujours nécessaire. Nous gardons les explications présentes dans les échanges, sans imposer ce travail supplémentaire, et l’agent peut consulter le dépôt pour vérifier l’implémentation.

À quels messages répondre par un rappel

Une fois ces informations conservées, il reste à choisir les occasions de les rappeler. La plupart des messages adressés à un agent ne le demandent pas, puisqu’un remerciement, une demande de commit ou une consigne contenant les éléments nécessaires se traitent avec la conversation en cours. Nous avons réparti les messages en onze catégories pour distinguer ces cas. Cinq ne déclenchent aucune recherche, tandis que les six autres conduisent à examiner si une note ou un passage de la mémoire répond à la demande.

Onze-genres-de-messages.txt
le message
     │
     ▼
Jev, de quel genre est-il ?
     │
     ├─ une réaction          ┐
     ├─ une action sur le     │
     │  travail terminé       │
     ├─ une consigne complète ├► rien
     ├─ de la culture         │
     │  générale              │
     ├─ une question sur la   │
     │  conversation          ┘
     │
     ├─ une nouvelle décision ┐
     ├─ un choix passé        │
     ├─ une reprise du        │
     │  travail               ├► notes,
     ├─ un problème qui       │  puis
     │  revient               │  Cognee
     ├─ une tâche sur         │
     │  l'existant            │
     └─ un ancien échange     ┘

Où chercher d’abord

Pour ces six catégories, le plugin cherche d’abord dans les notes locales, écrites sur la machine au fil des sessions et accessibles en quelques millisecondes. S’il y trouve une réponse, il la transmet à l’agent et arrête la recherche. Sinon, il consulte Cognee, à condition que le sujet corresponde aux informations à mémoriser. Après un dernier tri, l’agent reçoit les passages retenus, avec des indications sur les choix passés et les endroits où regarder. Il doit toutefois vérifier les informations critiques dans le code avant d’agir, car le projet a pu changer depuis l’écriture d’une note.

Jev, le modèle de décision de TypeSafe, à l’essai sur le tri de la mémoire

TypeSafe a présenté Jev le 15 septembre avec plusieurs démonstrations, dont un agent qui jouait à Doom en prenant des décisions en une fraction de seconde. Le tri de la mémoire se prêtait à un essai sur notre propre travail, avec des décisions précises à lui soumettre et une fréquence d’appels qui donnait de l’importance à son coût et à sa vitesse. Il offrait aussi un moyen de vérifier les résultats, en relisant les échanges pour voir si nous aurions gardé les mêmes informations ou déclenché les mêmes recherches.

Jev reçoit un contexte et des questions accompagnées de réponses possibles. À partir de ces éléments, il renvoie pour chaque question un choix, une probabilité ou un score, sans rédiger de texte. Avec un tarif affiché de 0,042 dollar par million de tokens en entrée pour Jev 1.13 et une sortie gratuite, une décision sur quelques milliers de tokens coûte une fraction de centime. Nous pouvions donc soumettre chaque message au tri sans que le coût de ces appels devienne un obstacle.

Dans les premières questions, nous demandions à Jev si l’agent manquait d’une information que la mémoire pouvait lui fournir. Lors de l'un de nos essais, l’utilisateur évoquait une base de données installée plusieurs semaines auparavant, alors que la conversation récente expliquait le fonctionnement général de ce type de base. Jev arrêtait la recherche, bien que l’agent ait besoin des détails de notre installation. Plusieurs reformulations n’ont pas suffi à corriger l’erreur, car il continuait de confondre une conversation qui parle du sujet avec une conversation qui contient la réponse.

Nous avons alors changé la tâche en demandant à Jev de classer le message selon son genre, puis le sujet et l’échange selon les catégories utiles à chaque étape. Ses réponses se sont améliorées avec ces questions portant sur le texte qu’il recevait. Dans le cas de la base de données, il lui suffisait de reconnaître une demande liée à une installation passée pour déclencher la recherche. La décision ne dépendait donc plus d’une déduction sur ce qu’un autre agent savait ou ignorait.

Le contenu fourni à Jev comptait aussi dans l’évaluation des souvenirs. Avec les cinq passages renvoyés par Cognee dans un même contexte, il attribuait aux passages utiles des scores entre 0,40 et 0,67, en dessous de notre seuil. En lui présentant les passages un par un, chacun face au message, nous avons obtenu des scores entre 0,88 et 0,92 pour les bons, tandis que les mauvais ne dépassaient pas 0,63 dans cet essai. Ce résultat rejoint la documentation de TypeSafe, qui indique que la précision de Jev varie avec la taille du contexte et baisse au-delà de ses limites par défaut. Nous avons donc conservé l’évaluation séparée et les questions portant sur un seul élément, avec la possibilité de réutiliser cette façon de procéder pour d’autres tris, à condition d’en vérifier les résultats sur leurs propres exemples.

memsift, un plugin de mémoire open source pour Claude Code et Codex

Pour utiliser ces tris dans nos sessions, nous avons développé memsift à partir du plugin publié par les auteurs de Cognee. Nous en avons conservé l’historique et la licence Apache-2.0, tout en réécrivant l’essentiel du fonctionnement. Cette version fonctionne avec Claude Code et Codex, sur un serveur Cognee existant. Nous la publions sans affiliation avec Cognee.

agent-memory · ai-agents · claude-code

memsift

Une mémoire pour Claude Code et Codex qui ne donne à l’agent que ce qui lui manque. Un modèle de décision trie chaque message avant tout rappel depuis Cognee.

npx skills add nualt/memsift

Comment memsift décide quoi rendre à l’agent

Le-trajet-dun-message.txt
le message
 │
 ▼
Jev, 2 s max
quel genre de message ?
 ├─ rien à chercher
 │  (la plupart) ──────► rien
 ▼
Jev, chaque note jugée seule
elle répond au message ?
 ├─ oui ─────────► la note
 ▼
Jev
un sujet à garder ?
(choix, contrainte, problème)
 ├─ non ─────────────► rien
 ▼
Cognee, 12 s max
ses passages les plus proches
 ▼
Jev, 2 s max, un par un
ils répondent au message ?
 └─ oui ────► quelques lignes
              (souvent aucune)

Le plugin arrête le parcours dès qu’une étape permet de répondre ou d’écarter la recherche. Chaque appel de tri à Jev coûte une fraction de centime et, pour la plupart des messages, le parcours se termine sans ajout de mémoire. À la fin de l’échange, le plugin sollicite Jev pour choisir ce qui mérite d’être gardé, selon le même critère du travail à ne pas refaire. Il ajoute les échanges retenus aux notes locales du projet, qu’un modèle de langage révise ensuite en tâche de fond, avant l’envoi vers un dataset réservé à memsift dans Cognee.

Cette révision demande une précaution, car le modèle pourrait modifier des passages auxquels il n’a aucune raison de toucher en réécrivant le fichier entier. Pour limiter ce risque, nous lui demandons de désigner les lignes à remplacer ou à retirer. Le code applique ces changements et conserve les autres lignes mot pour mot.

Ce que ça change à l’usage

Pour comparer le texte ajouté, l’attente et les rappels retrouvés, nous avons fait passer une conversation de quatre-vingts messages dans le plugin d’origine puis dans memsift, avec un serveur Cognee réel dans les deux cas. Un autre modèle avait préparé les messages et les réponses attendues avant le test, sans accès au code de memsift. Ce choix visait à éviter de sélectionner les exemples en fonction de notre implémentation, tout en soumettant les deux plugins à la même conversation.

Les-memes-messages-deux-plugins.txt
80 messages, même conversation
                 origine   memsift
collé            80 / 80    13 / 80
caractères      204 297     11 352
attente           3,0 s      0,8 s
collé pour rien  64 / 64     6 / 64
rappels trouvés   9 / 16     7 / 16

Le plugin d’origine ajoute un passage aux quatre-vingts messages, y compris aux soixante-quatre qui ne demandaient aucun rappel. Avec memsift, treize messages sur quatre-vingts reçoivent un ajout de mémoire, dont six sans besoin de rappel, ce qui ramène le volume total de 204 297 à 11 352 caractères. Les résultats sur les rappels utiles sont toutefois en baisse. Sur les seize attendus, memsift en retrouve sept, contre neuf avec le plugin d’origine.

La réduction du texte inutile et de l’attente s’accompagne donc de deux rappels utiles manqués en plus. Ces rappels manqués viennent de la recherche vers le serveur, que memsift a sollicitée neuf fois pendant ce test sans en obtenir de passage utile, et c’est l’étape que l’usage permettra d’ajuster.

À l’usage, memsift s’installe sans dépendance et met la mémoire en pause lorsqu’un service tombe en panne. L’agent signale alors l’interruption, ce qui permet de poursuivre le travail en sachant que les rappels sont indisponibles. Avant les envois aux services, le plugin applique un masquage des secrets. Vous trouverez les détails d’installation et de fonctionnement dans la documentation sur Nualt et le code sur GitHub.

Chez Nualt, un outil se complète quand il ne suffit pas

Un outil qui ne répond pas à notre besoin ne nous arrête pas, et nous ne le remplaçons pas non plus au premier défaut. Le plugin de Cognee alimentait et interrogeait la mémoire sans trier, alors nous avons précisé ce que nous attendions d’elle, revu les questions posées au modèle et réécrit le plugin jusqu’à obtenir un fonctionnement utilisable sur nos projets. Nous l’avons ensuite publié avec sa documentation, comme nous l’avions fait pour la brique d’authentification de Medusa lorsqu’elle manquait à Medusa. Cognee reste sur notre serveur, et memsift tourne depuis sur nos projets.

Nous appliquons la même règle aux projets de nos clients. Quand une brique manque ou qu’un outil se comporte mal, nous construisons ce qui est nécessaire plutôt que d’imposer un contournement, et le client en reste propriétaire, du code aux accès, et jusqu’à sa mémoire lorsqu’il en utilise une. Avec memsift, il garde ses notes et son serveur Cognee, tandis que le tri et la révision passent par des services externes que nous documentons, pour qu’il choisisse en connaissance de cause à qui il confie ses données.

Mémoire des agents IA, vos questions

Les réponses aux questions qu’on nous pose le plus souvent sur ce sujet.

memsift remplace-t-il Cognee ?

Non. Cognee reste le serveur de mémoire, tandis que memsift sélectionne les informations à y envoyer et les passages à transmettre à l’agent.

Faut-il un serveur Cognee ?

Oui. memsift se branche sur un serveur Cognee existant, chez vous ou chez un hébergeur.

Fait-il doublon avec la mémoire de Claude Code ?

Pas en soi. La mémoire de Claude Code reste propre à un outil et à une machine, tandis que memsift donne la même mémoire à Claude Code et à Codex, sur tous les projets.

Combien coûte le tri ?

Sur une journée de tests d’environ deux cents messages, nous avons mesuré quatre centimes pour le tri. Le montant dépend du volume de texte soumis à Jev et ne représente pas le coût complet de l’installation.

Quelles données quittent la machine ?

Le plugin envoie le message et la fin de la conversation à Jev, les notes du projet au modèle chargé de les réviser, puis les notes nettoyées à Cognee. Avant l’envoi, il applique un masquage des secrets et n’inclut aucune sortie d’outil telle quelle.

Ça marche avec Cursor ?

Pas encore dans la version présentée ici. L’intégration dépend de la possibilité d’ajouter du contexte à l’agent depuis un hook de prompt, comme le fait memsift dans Claude Code et Codex.

Que se passe-t-il si Jev ne répond plus ?

Le plugin met la mémoire en pause et l’agent signale l’interruption. Vous pouvez alors poursuivre le travail sans les rappels.

Racontez-nous ce que vous voulez construire.