Chaque fois que je vois un diagramme d'architecture d'agent où la "mémoire" est une seule boîte pointant vers une base vectorielle, je sais que l'auteur n'a jamais eu à en faire fonctionner un au-delà de la démo. La mémoire dans un agent n'est pas un système unique. C'est une stack de cinq couches, chacune résolvant un problème différent, et vous choisissez les couches dont vous avez besoin selon ce que l'agent fait vraiment.
Je vais parcourir les cinq couches, ce que chacune fait bien ou mal, et la combinaison que je mettrais en production si vous me demandiez de monter un agent opérationnel cette semaine. Spoiler : Pinecone n'est presque jamais la bonne réponse quand vous faites déjà tourner Postgres.
Les cinq couches
Couche 1 : Scratchpad in-context
C'est le défaut. Le LLM garde ses pensées, actions et observations dans le prompt lui-même. Zéro infrastructure. Ça marche pour des démos et des tâches à un seul passage. Ça casse dès que votre boucle tourne 15 itérations ou que l'agent doit se souvenir de quoi que ce soit après la fin de la session. Si le pitch que vous regardez n'a que cette couche, c'est une démo.
Couche 2 : Cache
Redis avec un TTL de 24 à 72 heures, c'est la réponse habituelle ici. Cette couche garde l'état récent de session, les résultats récents d'appels d'outils, les préférences utilisateur qui n'ont pas encore leur place dans le stockage structuré. Le gain, c'est la vitesse. La perte, c'est qu'un cache clé-valeur n'est pas sémantique, donc vous ne pouvez pas lui demander "ai-je vu une requête similaire ?", seulement "quelle était la requête avec l'ID X ?". Si vous ne faites pas déjà tourner Redis, ne l'ajoutez pas pour ça. Une table Postgres unlogged avec une colonne expires_at couvre le même terrain sans nouveau service à opérer, et vous pourrez la remplacer le jour où les lectures de cache deviendront vraiment votre goulot d'étranglement.
Couche 3 : Stockage structuré
Postgres. Ennuyeux, éprouvé, et exactement ce qu'il faut pour les données qui ont des colonnes évidentes : ID d'incident, timestamp, version de l'agent, type de trigger, outils appelés, résultat, coût en tokens. C'est votre audit log, votre source d'analytics, et la chose que votre équipe d'astreinte lit vraiment quand ça part en vrille. Tout agent dont vous attendez qu'il passe un audit a besoin de cette couche, parce que c'est le seul enregistrement de ce que l'agent a vraiment fait.
CREATE TABLE agent_runs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_name TEXT NOT NULL,
agent_version TEXT NOT NULL,
trigger_type TEXT NOT NULL,
trigger_payload JSONB NOT NULL,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ended_at TIMESTAMPTZ,
status TEXT NOT NULL,
outcome JSONB,
tool_calls JSONB,
token_cost INTEGER,
escalated_to_human BOOLEAN DEFAULT FALSE,
confidence NUMERIC(3,2)
);
CREATE INDEX idx_agent_runs_agent_started
ON agent_runs (agent_name, started_at DESC);
CREATE INDEX idx_agent_runs_trigger
ON agent_runs USING GIN (trigger_payload);Couche 4 : Vector store
C'est la couche dont tout le monde parle et sur laquelle la plupart des équipes sur-investissent. Le rôle du vector store est le rappel sémantique : "a-t-on eu un incident qui ressemble à celui-ci ?" Vous embed un résumé des incidents passés, vous embed la situation actuelle, et vous tirez les top K cas les plus similaires dans le prompt. Bien fait, cela transforme un agent réactif en un agent qui apprend.
Mon opinion tranchée : si vous faites déjà tourner Postgres, utilisez pgvector. C'est une extension Postgres, pas un service séparé. Vous obtenez la recherche sémantique sans ajouter un autre système à opérer, une autre frontière d'auth, un autre endroit que votre astreinte doit connaître à 2 h du matin. Pinecone, Weaviate, Qdrant sont tous de bons produits. Ils sont surdimensionnés pour la plupart des équipes.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE agent_memory (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_name TEXT NOT NULL,
summary TEXT NOT NULL,
-- 384 dims matches all-MiniLM-L6-v2, which runs locally for free.
-- Bump this to your provider's dimension if you move to a hosted API.
embedding VECTOR(384) NOT NULL,
source_run_id UUID REFERENCES agent_runs(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_agent_memory_vector
ON agent_memory USING hnsw (embedding vector_cosine_ops);
-- Query: top 5 similar past incidents for a given embedding
SELECT id, summary, 1 - (embedding <=> $1) AS similarity
FROM agent_memory
WHERE agent_name = $2
ORDER BY embedding <=> $1
LIMIT 5;Couche 5 : Résumé
Celle-ci n'est pas du stockage. C'est de la compression. Quand une session dure longtemps, ou quand vous tirez 20 incidents passés du vector store et qu'ils feraient exploser votre budget de contexte, vous les passez à travers un LLM pour produire une version condensée. C'est la couche qui empêche vos coûts en tokens d'exploser sur des agents qui tournent longtemps. La plupart des decks d'introduction la sautent parce qu'elle paraît triviale, jusqu'à ce que vous voyiez la facture.
Comment les couches interagissent vraiment
Une exécution unique d'un agent DBA, de bout en bout :
- Le trigger se déclenche. L'agent se réveille avec un payload d'alerte.
- Avant d'appeler le planner LLM, interrogez le vector store pour des incidents passés similaires. Prenez le top 5.
- Passez ces 5 à travers la couche de résumé pour les compresser en quelques centaines de tokens.
- Passez la mémoire compressée plus le trigger plus le system prompt dans le planner. C'est l'état de départ du scratchpad.
- Boucle : planifier, appeler un outil, observer, répéter. Le scratchpad grossit.
- À la fin, écrivez le résultat structuré dans Postgres et un résumé sémantique dans pgvector. La prochaine exécution apprend de celle-ci.
Aucune couche seule ne pourrait faire tout ça. Le scratchpad vous donne l'état de session. Postgres vous donne l'audit et l'analytics. pgvector vous donne le "a-t-on déjà vu ça ?". Le résumé empêche le contexte d'exploser. Le cache comble le trou pour les données chaudes que vous ne voulez pas recalculer à chaque exécution.
La stack que j'enverrais vraiment en production
L'erreur que je vois le plus souvent
Les équipes traitent la mémoire comme une arrière-pensée. Ils construisent la boucle, la mettent en prod, puis visent la mémoire trois mois plus tard quand l'agent commence à faire la même erreur deux fois. À ce moment-là, le log structuré est un blob JSON dans S3 sans schéma, et vous ne pouvez pas reconstruire l'historique parce que vous ne l'avez jamais capturé correctement.
Concevez le schéma Postgres avant d'écrire le premier appel d'outil. Vous n'avez pas besoin de construire la couche vectorielle au jour un. Vous devez capturer chaque exécution avec assez de détails pour pouvoir remplir le vector store plus tard à partir des mêmes données.
L'agent qui se souvient est un produit différent de l'agent qui réagit. Choisissez-en un exprès.
Références
- pgvector : https://github.com/pgvector/pgvector
- Anthropic, Building Effective Agents (patrons de conception d'agents) : https://www.anthropic.com/engineering/building-effective-agents
- Article MemGPT (mémoire hiérarchique pour les LLM) : https://arxiv.org/abs/2310.08560