Mémoire des agents : types et coûts de stockage
Un tableau compare les mémoires sémantique, épisodique et procédurale, puis détaille leur coût de stockage et de ré-embedding sur un VPS.
Les trois types de mémoire des agents
Les types de mémoire des agents se répartissent en trois catégories, qui ont chacune un impact différent sur le matériel que vous payez : la mémoire sémantique contient les faits, la mémoire épisodique contient ce qui s’est passé et la mémoire procédurale contient la manière d’effectuer une tâche. Le tableau ci-dessous définit chaque type avec un exemple côté serveur. La suite traite de l’aspect généralement passé sous silence : le coût de stockage de chaque type et le coût de sa reconstruction.
The data behind this chart
[
{
"label": "Semantic",
"what_it_holds": "Facts the agent should treat as currently true",
"server_example": "The database listens on 10.8.0.4:5432 and the nightly dump runs at 03:15 UTC"
},
{
"label": "Episodic",
"what_it_holds": "A record of one past event or session",
"server_example": "On 2026-08-11 the deploy failed because the disk was full, and rotating logs fixed it"
},
{
"label": "Procedural",
"what_it_holds": "How to carry out a task, as steps the agent can run",
"server_example": "The restore runbook: stop the service, load the dump, run migrations, start the service"
}
]Les noms viennent de la psychologie humaine, mais cette analogie reste approximative. Cette distinction est utile pour une raison pratique : ces trois types ont des tailles et des modes de réparation différents. Les regrouper dans un seul vector store dégrade donc chacun d’eux.
La mémoire sémantique est limitée et vous voudrez la modifier manuellement
Quelques centaines de faits sur vos propres serveurs représentent quelques dizaines de kilooctets de texte. Le stockage n’est pas le problème. La correction des données l’est. Une information incorrecte dans la mémoire sémantique fausse toutes les réponses que l’agent fournit ensuite. Le stockage doit donc permettre de retrouver un fait par son nom, de le modifier et de vérifier que l’ancienne valeur a bien disparu.
Cela oriente vers un stockage indexé par clé : une table Postgres avec une clé primaire ou un répertoire de petits fichiers Markdown versionnés dans git. Dans les deux cas, vous pouvez exécuter une requête, afficher la valeur et la modifier directement. La recherche par similarité ne le permet pas, car elle récupère les éléments par ressemblance et non par clé. « Modifier le port de la base de données » devient alors « trouver chaque bloc qui mentionne le port de la base de données », sans pouvoir prouver que tous les blocs ont été trouvés. Conservez les faits avec leur clé. Vous pouvez aussi les indexer sous forme d’embeddings si vous voulez une recherche plus souple, mais considérez la copie indexée par clé comme la source de vérité.
Les informations obsolètes ne se signalent pas d’elles-mêmes. Le port change, mais la ligne reste inchangée. L’agent continue donc de répondre avec un numéro qui était correct en juin. Une politique d’obsolescence et d’élagage pour la mémoire de l’agent constitue l’autre volet de cette page. Sa conception coûte beaucoup moins cher tant que la table reste petite.
Pourquoi la mémoire épisodique augmente sans limite
La mémoire épisodique est un journal, et les journaux grossissent. Chaque session, chaque appel d’outil et chaque commande échouée peut devenir un épisode. Un agent qui écrit une ligne par tour en écrira bien plus en un mois que personne n’en lira jamais. Le disque n’est pas le seul coût : chaque épisode indexé rejoint aussi l’index que la recherche doit parcourir.
Définissez la règle de rétention le jour où vous créez la table, tant que la suppression reste simple. Deux questions permettent de trancher la plupart des cas. Premièrement, que faut-il réellement écrire : un résumé de session est généralement utile, tandis que la sortie complète d’un ls -la ne l’est généralement pas. Deuxièmement, combien de temps chaque classe d’épisode doit-elle être conservée : par exemple, 30 jours pour les épisodes bruts et un an pour les résumés de session.
Ajoutez à chaque ligne d’épisode un horodatage created_at et une colonne source. Sans created_at, vous ne pouvez pas supprimer les entrées selon leur ancienneté. Sans source, vous ne pouvez pas supprimer tout ce qui provient d’une même source défaillante. C’est exactement ce qu’il faut faire le jour où une page web ou un ticket s’avère avoir injecté des instructions dans la mémoire.
DELETE FROM episodes WHERE created_at < now() - interval '30 days';Exécutez cette opération avec un timer systemd, puis vérifiez que le nombre de lignes et la taille de la table évoluent réellement. Une politique de rétention que personne n’exécute n’est qu’un commentaire.
psql -d agentmem -c "SELECT count(*) FROM episodes;"
psql -d agentmem -c "SELECT pg_size_pretty(pg_total_relation_size('episodes'));"La mémoire procédurale doit être stockée dans un dépôt
La mémoire procédurale décrit la façon dont l’agent exécute une tâche : un script shell, un fichier de skill ou un runbook avec des étapes numérotées. C’est du code. Le code doit donc être stocké là où se trouve le code : dans un dépôt git avec des revues, un historique des versions et un diff lisible.
Si vous stockez un runbook sous forme de chunks intégrés, vous récupérez une copie approximative. La recherche renvoie les chunks qui obtiennent les meilleurs scores. L’agent peut donc exécuter l’étape 2 et l’étape 5 alors que l’étape 3 n’a jamais été trouvée. Rien n’indique non plus quelle version de la procédure a été exécutée. Dans git, git log répond aux deux questions. Le coût de stockage est presque nul. C’est une autre raison de ne pas payer le prix des bases vectorielles pour ce type de contenu.
Ce qu’un embedding coûte réellement sur disque
The data behind this chart
[
{
"label": "bge-small-en-v1.5 (384 dims)",
"bytes_per_vector": 1544,
"mib_per_100k_rows": 147.2
},
{
"label": "bge-base-en-v1.5 (768 dims)",
"bytes_per_vector": 3080,
"mib_per_100k_rows": 293.7
},
{
"label": "bge-large-en-v1.5 (1024 dims)",
"bytes_per_vector": 4104,
"mib_per_100k_rows": 391.4
},
{
"label": "text-embedding-3-small (1536 dims)",
"bytes_per_vector": 6152,
"mib_per_100k_rows": 586.7
},
{
"label": "text-embedding-3-large (3072 dims)",
"bytes_per_vector": 12296,
"mib_per_100k_rows": 1172.6
}
]pgvector stocke un vector à raison de 4 octets par dimension, auxquels s’ajoute un en-tête de 8 octets. Ce calcul est donc fixe et vous pouvez l’anticiper avant tout chargement. Un vecteur de 384 dimensions occupe 1544 octets. 100,000 chunks représentent donc 147.2 MiB de vecteurs. Le même corpus avec des embeddings de 3,072 dimensions occupe 1172.6 MiB, à raison de 12296 octets par ligne. Le texte est identique, mais l’espace requis est presque huit fois supérieur.
Cela ne concerne que la colonne de vecteurs. Le texte des chunks, la clé primaire, le surcoût des lignes et l’index s’y ajoutent. L’index est l’élément souvent oublié. HNSW (hierarchical navigable small world, l’index de graphe construit par pgvector) conserve sa propre copie des vecteurs auxquels il est relié. Un stockage indexé dépasse donc largement le double des valeurs ci-dessus. Mesurez votre propre cas au lieu d’estimer.
SELECT pg_size_pretty(pg_total_relation_size('memories')) AS total,
pg_size_pretty(pg_relation_size('memories_embedding_idx')) AS idx;La RAM détermine la rapidité perçue des recherches, car le parcours du graphe n’est rapide que tant que celui-ci reste en mémoire. Lorsque l’index dépasse la quantité de mémoire que Postgres peut lui allouer, les recherches commencent à lire sur le disque et la latence augmente. La construction a sa propre limite, maintenance_work_mem. Lorsque le graphe la dépasse, le processus de construction le signale et ralentit :
NOTICE: hnsw graph no longer fits into maintenance_work_mem after 61440 tuples
HINT: Increase maintenance_work_mem to speed up builds.Deux paramètres permettent de réduire l’espace requis par le même corpus. Choisissez un modèle plus petit : 384 dimensions coûtent un quart de 1,536 dimensions et, pour retrouver vos propres notes, la différence de précision est souvent suffisamment faible pour être acceptable. Vous pouvez aussi utiliser la précision binaire : le type halfvec utilise 2 octets par dimension, avec le même en-tête de 8 octets. Cela réduit presque de moitié la colonne et son index.
Une limite doit être connue avant de choisir un modèle. En août 2026, une colonne vector peut être indexée jusqu’à 2,000 dimensions. Un embedding de 3,072 dimensions est donc accepté par la colonne, mais refusé par l’index :
ERROR: column cannot have more than 2000 dimensions for hnsw indexhalfvec permet d’indexer jusqu’à 4,000 dimensions. La solution habituelle consiste donc à indexer le cast :
CREATE INDEX ON memories USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);Quand pgvector est préférable à un service de mémoire distinct
Si Postgres est déjà installé sur le serveur, les vecteurs tiennent dans un seul paquet et une seule instruction SQL.
psql -V
sudo apt install postgresql-16-pgvectorCREATE EXTENSION vector;Le nombre dans le nom du paquet correspond à la version majeure de Postgres : 16 sur Ubuntu 24.04. Consultez donc psql -V avant de saisir la commande.
Conserver la mémoire dans cette base de données permet d’avoir une seule sauvegarde pour la mémoire et les données de l’application au même moment, un seul pool de connexions et des transactions : un fait et la ligne qui le décrit sont validés ensemble ou échouent ensemble. Un service distinct ne peut pas le garantir.
Passez à un service de mémoire dédié dans l’un des cas suivants. La charge de recherche concurrence celle de l’application et nécessite sa propre machine. Plusieurs agents sur plusieurs hôtes partagent une même mémoire. Ou vous voulez la logique d’extraction et de déduplication fournie par un produit complet, ce qui justifie un serveur de mémoire Mem0 auto-hébergé. Pour un seul agent et un corpus de quelques millions de segments au maximum, pgvector sur le serveur que vous utilisez déjà demande moins d’administration et présente moins de risques de panne. Le choix du moteur lui-même, ainsi que la mémoire vive nécessaire à chaque moteur, est présenté dans exécuter une base de données vectorielle sur un VPS.
Quel est le coût d’une ré-émission quand vous changez de modèle
Les vecteurs produits par deux modèles différents ne sont pas comparables. Vous ne pouvez donc pas générer les embeddings des nouvelles mémoires avec un nouveau modèle tout en conservant les anciennes lignes. Une table mixte renvoie des résultats incohérents, car une distance calculée entre deux systèmes de coordonnées différents n’a aucune signification. Changer de modèle signifie générer à nouveau les embeddings de tout le corpus.
La facture comporte quatre éléments : les tokens (frais d’API, ou temps CPU et GPU sur votre propre machine), le temps réel d’exécution, l’espace disque nécessaire aux deux colonnes pendant le backfill et la reconstruction de l’index à la fin. La procédure sûre consiste à ajouter une nouvelle colonne, à la remplir par lots, à basculer les requêtes, puis à supprimer l’ancienne colonne et son index.
Mesurez le débit sur votre propre matériel au lieu de vous fier à une valeur publiée. La génération d’embeddings uniquement sur CPU, sur un petit VPS, est beaucoup plus lente qu’avec le même modèle sur un GPU. Chronométrez un chunk représentatif, puis extrapolez en fonction de la taille du corpus.
ollama pull nomic-embed-text
time curl -s http://localhost:11434/api/embed \
-d '{"model": "nomic-embed-text", "input": "one representative chunk of your corpus"}' > /dev/nullUn modèle d’embeddings local stocke également ses poids sur le même disque que le store de mémoires. l’emplacement où Ollama stocke les modèles téléchargés explique où cet espace est utilisé.
Une condition rend tout cela possible : conserver le texte source à côté de chaque vecteur. Un store qui ne contient que des vecteurs ne peut pas être ré-émis, car il ne reste plus rien à fournir au nouveau modèle. Si vous ne pouvez pas répondre à la question « quel texte a produit cette ligne ? », votre procédure de migration consiste à reconstruire entièrement les données depuis l’emplacement d’origine du texte.
Ce qu’il faut surveiller une fois la mémoire en service
Le coût n’est pas le seul élément qui évolue à mesure que le store se remplit. Les informations anciennes deviennent obsolètes et les anciens épisodes écartent les résultats utiles : le problème de pruning se pose à nouveau. Un store de mémoire est également une entrée inscriptible pour le comportement futur de l’agent. Tout ce qui peut y écrire peut donc orienter l’agent par la suite. Si du texte provenant de pages web ou de tickets est enregistré dans la mémoire, consultez comment fonctionne l’empoisonnement de la mémoire d’un agent avant d’élargir les sources autorisées à y écrire. La taille de la récupération détermine aussi la consommation de tokens à chaque requête. C’est là que commence la maîtrise prévisible du coût d’exécution d’un agent.
FAQ
Ai-je besoin d’une base de données vectorielle pour la mémoire d’un agent ?
Pas pour les faits. La mémoire sémantique est réduite et vous devez pouvoir la corriger par son nom. Une table indexée par clé ou un répertoire de fichiers Markdown versionné dans git convient donc mieux, car vous pouvez afficher une valeur et la modifier. Les embeddings justifient leur coût lorsque vous devez rechercher par similarité sémantique dans un corpus trop volumineux pour être parcouru manuellement. Il s’agit généralement de la mémoire épisodique et des documents. Si vous utilisez déjà Postgres, CREATE EXTENSION vector couvre ce besoin sans ajouter un autre service à administrer.
Quelle quantité d’espace disque un magasin de mémoire d’agent utilise-t-il ?
La taille des vecteurs est prévisible : 4 octets par dimension, plus un en-tête de 8 octets dans pgvector. Avec 768 dimensions, cela représente 293.7 MiB pour 100,000 lignes, et avec 384 dimensions, 147.2 MiB. Il faut ensuite ajouter le texte des chunks, la surcharge des lignes et un index HNSW qui conserve sa propre copie des vecteurs. Prévoyez donc au moins le double de la taille des vecteurs et mesurez la valeur réelle avec pg_total_relation_size.
Où la mémoire procédurale doit-elle être stockée ?
Dans un dépôt git, sous forme de scripts ou de fichiers de skills que l’agent exécute directement. Une procédure doit être rappelée exactement et disposer d’un historique des versions. La recherche par similarité ne fournit ni l’un ni l’autre. Un runbook découpé en chunks renvoie les éléments obtenant les meilleurs scores. L’étape 2 et l’étape 5 peuvent donc être renvoyées alors que l’étape 3 manque, sans qu’aucun enregistrement n’indique quelle version a été exécutée.
Quel est le coût du changement de modèle d’embedding ?
Il faut ré-embedder l’ensemble du corpus, car les vecteurs produits par des modèles différents ne peuvent pas être comparés entre eux. Prévoyez le coût en tokens ou le temps GPU, l’espace disque nécessaire pour conserver simultanément les anciennes et les nouvelles colonnes, ainsi que la reconstruction de l’index. Ajoutez la nouvelle colonne, alimentez-la par lots, basculez la requête, puis supprimez l’ancienne colonne. Tout cela suppose d’avoir conservé le texte source à côté de chaque vecteur.