SYSTÈME D'ACCÈS INFORMATIQUE

Systèmes de Mémoire pour les Agents : Donner un Contexte Persistant à votre IA


Chaque agent IA a un problème de mémoire. Chaque appel au modèle est sans état. Le modèle ne se souvient pas de la dernière requête. Il ne se souvient pas du nom de l’utilisateur, de ses préférences ni de ses décisions précédentes. Sans mémoire externe, votre agent repart de zéro à chaque tour.

Ce guide couvre quatre stratégies de mémoire — de la plus simple à la plus puissante — et quand utiliser chacune. Tous les exemples utilisent TypeScript et le SDK Anthropic.

Pourquoi les Agents Ont Besoin de Mémoire

Considérez un agent de révision de code. Lors de la première exécution, l’utilisateur explique les conventions de son équipe : pas de types any, toujours gérer les erreurs explicitement, préférer le style fonctionnel. L’agent produit une bonne révision.

Lors de la deuxième exécution, l’utilisateur soumet un autre fichier. L’agent a tout oublié. Il manque les mêmes violations de convention qu’il avait signalées la veille.

C’est le problème central : les agents sont sans état, mais les agents utiles ont besoin de continuité.

La mémoire donne aux agents trois capacités :

  • Rappel — récupérer des faits de plus tôt dans une conversation ou d’une session précédente
  • Personnalisation — s’adapter aux préférences de l’utilisateur au fil du temps
  • Coordination — dans les systèmes multi-agents, partager l’état entre les agents

Les Quatre Types de Mémoire

Avant d’écrire du code, il est utile de nommer clairement les stratégies. La mémoire des agents se divise en quatre modèles :

TypeOù stockéPortéeIdéal pour
TamponEn contexte (tableau de messages)Session couranteConversations courtes
RésuméEn contexte (compressé)Session couranteConversations longues
SémantiqueExterne (base de données vectorielle)Inter-sessionsRappel de connaissances
ÉpisodiqueExterne (stockage clé-valeur)Inter-sessionsFaits utilisateur, préférences

Chaque stratégie présente un compromis différent entre simplicité, coût et capacité. Choisissez le minimum qui résout votre problème.

Stratégie 1 : Tampon de Conversation

La stratégie la plus simple consiste à transmettre l’historique complet de la conversation à chaque appel au modèle. Le modèle voit chaque message précédent et répond avec le contexte complet.

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface Message {
role: "user" | "assistant";
content: string;
}
// Le tampon contient l'historique complet de la conversation
const buffer: Message[] = [];
async function chat(userMessage: string): Promise<string> {
// Ajouter le nouveau message de l'utilisateur au tampon
buffer.push({ role: "user", content: userMessage });
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
// Transmettre le tampon complet à chaque appel
messages: buffer,
});
const assistantMessage =
response.content[0].type === "text" ? response.content[0].text : "";
// Ajouter la réponse de l'assistant au tampon
buffer.push({ role: "assistant", content: assistantMessage });
return assistantMessage;
}
// Exemple d'utilisation
await chat("Je m'appelle Alex et je préfère Python à TypeScript.");
await chat("Quel langage devrais-je utiliser pour mon prochain projet ?");
// Le modèle se souvient de la préférence exprimée dans le premier message

Quand l’utiliser : Conversations de moins de 20 tours. Chatbots simples. Prototypes.

La limite : Les fenêtres de contexte sont finies. Une session longue dépasse éventuellement la limite de tokens, et les messages les plus anciens doivent être supprimés. Le modèle perd le contexte initial silencieusement, ce qui entraîne un comportement incohérent.

Stratégie 2 : Résumé Progressif

Quand les conversations s’allongent, résumez les anciens tours au lieu de les supprimer. Maintenez un résumé compressé de ce qui s’est passé avant une fenêtre glissante de messages récents.

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface ConversationState {
summary: string; // Historique compressé des anciens tours
recentMessages: Array<{ role: "user" | "assistant"; content: string }>;
maxRecentTurns: number; // Conserver les N derniers tours en verbatim
}
const state: ConversationState = {
summary: "",
recentMessages: [],
maxRecentTurns: 6, // 3 tours utilisateur + 3 tours assistant
};
// Compresser les anciens tours dans le résumé progressif
async function compressSummary(
currentSummary: string,
messagesToCompress: Array<{ role: string; content: string }>
): Promise<string> {
const formatted = messagesToCompress
.map((m) => `${m.role.toUpperCase()}: ${m.content}`)
.join("\n");
const response = await client.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 512,
messages: [
{
role: "user",
content: `Mettez à jour le résumé de la conversation en ajoutant les nouveaux échanges ci-dessous.
Renvoyez uniquement le résumé mis à jour. Limitez-le à moins de 200 mots.
Résumé actuel :
${currentSummary || "(aucun)"}
Nouveaux échanges :
${formatted}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : currentSummary;
}
async function chat(userMessage: string): Promise<string> {
// Quand le tampon dépasse la limite, compresser la moitié la plus ancienne
if (state.recentMessages.length >= state.maxRecentTurns * 2) {
const toCompress = state.recentMessages.splice(0, state.maxRecentTurns);
state.summary = await compressSummary(state.summary, toCompress);
}
state.recentMessages.push({ role: "user", content: userMessage });
// Construire le tableau de messages : résumé système + tours récents verbatim
const messages = [
...(state.summary
? [
{
role: "user" as const,
content: `Contexte du début de cette conversation :\n${state.summary}`,
},
{
role: "assistant" as const,
content: "Compris. J'utiliserai ce contexte.",
},
]
: []),
...state.recentMessages,
];
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages,
});
const assistantMessage =
response.content[0].type === "text" ? response.content[0].text : "";
state.recentMessages.push({ role: "assistant", content: assistantMessage });
return assistantMessage;
}

L’appel de résumé utilise un modèle rapide et économique (Haiku). La conversation principale utilise le modèle performant (Sonnet). Cette séparation maintient les coûts bas tout en préservant la qualité.

Quand l’utiliser : Sessions de plus de 20 tours. Agents de support. Agents de tâches longues.

La limite : Les résumés perdent en détail. Les faits spécifiques exprimés tôt dans une conversation peuvent être compressés en déclarations vagues. Pour rappeler des faits précis, utilisez la mémoire épisodique.

Stratégie 3 : Mémoire Épisodique

La mémoire épisodique stocke des faits spécifiques sur un utilisateur ou une session dans un stockage clé-valeur. L’agent extrait des faits pendant une conversation et les récupère lors de sessions futures.

import Anthropic from "@anthropic-ai/sdk";
import fs from "fs/promises";
const client = new Anthropic();
// En production, remplacez ce stockage de fichiers par Redis ou une base de données
const MEMORY_PATH = "./memory.json";
interface EpisodicStore {
[userId: string]: Record<string, string>;
}
async function loadMemory(): Promise<EpisodicStore> {
try {
const data = await fs.readFile(MEMORY_PATH, "utf-8");
return JSON.parse(data);
} catch {
return {};
}
}
async function saveMemory(store: EpisodicStore): Promise<void> {
await fs.writeFile(MEMORY_PATH, JSON.stringify(store, null, 2));
}
// Extraire des faits structurés du dernier échange
async function extractFacts(
userMessage: string,
assistantReply: string
): Promise<Record<string, string>> {
const response = await client.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 256,
messages: [
{
role: "user",
content: `Extrayez les faits personnels, préférences ou décisions importantes de cet échange.
Renvoyez un objet JSON de paires clé-valeur. Renvoyez {} s'il n'y a rien qui vaille la peine d'être mémorisé.
Utilisateur : ${userMessage}
Assistant : ${assistantReply}`,
},
],
});
try {
const text = response.content[0].type === "text" ? response.content[0].text : "{}";
const match = text.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : {};
} catch {
return {};
}
}
// Formater les faits stockés comme une injection dans le prompt système
function formatMemory(facts: Record<string, string>): string {
const entries = Object.entries(facts);
if (entries.length === 0) return "";
return (
"Ce que vous savez sur cet utilisateur :\n" +
entries.map(([k, v]) => `- ${k} : ${v}`).join("\n")
);
}
async function chat(userId: string, userMessage: string): Promise<string> {
const store = await loadMemory();
const userFacts = store[userId] ?? {};
const systemPrompt = formatMemory(userFacts);
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: systemPrompt || undefined,
messages: [{ role: "user", content: userMessage }],
});
const assistantMessage =
response.content[0].type === "text" ? response.content[0].text : "";
// Extraire les faits et les persister pour les sessions futures
const newFacts = await extractFacts(userMessage, assistantMessage);
if (Object.keys(newFacts).length > 0) {
store[userId] = { ...userFacts, ...newFacts };
await saveMemory(store);
}
return assistantMessage;
}
// Session 1
await chat("user-123", "Je préfère des réponses concises. Pas de puces sauf si nécessaire.");
// Session 2 (un processus différent, quelques jours plus tard)
await chat("user-123", "Explique comment fonctionnent les poignées de main TCP.");
// L'agent se souvient de la préférence de formatage de la session 1

Quand l’utiliser : Agents orientés utilisateur s’exécutant sur plusieurs sessions. Personnalisation. Suivi des préférences.

La limite : Les faits s’accumulent au fil du temps. Des faits obsolètes peuvent produire un comportement incorrect. Ajoutez un mécanisme pour mettre à jour ou faire expirer les faits.

Stratégie 4 : Mémoire Sémantique (Recherche Vectorielle)

La mémoire épisodique stocke des faits discrets. La mémoire sémantique stocke des documents, du code ou des fragments de conversation indexés par signification. Quand l’agent a besoin d’informations, il recherche dans l’index à l’aide d’une requête.

C’est la base de la Génération Augmentée par Récupération (RAG).

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface Document {
id: string;
content: string;
embedding?: number[];
}
// Calculer la similarité cosinus entre deux vecteurs
function cosineSimilarity(a: number[], b: number[]): number {
const dot = a.reduce((sum, val, i) => sum + val * b[i], 0);
const magA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const magB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
return dot / (magA * magB);
}
class SemanticMemory {
private documents: Document[] = [];
// Indexer un document pour une récupération ultérieure
async add(id: string, content: string): Promise<void> {
const embedding = await embed(content); // Utiliser un modèle d'embedding dédié
this.documents.push({ id, content, embedding });
}
// Récupérer les k documents les plus pertinents pour une requête
async search(query: string, topK = 3): Promise<Document[]> {
const queryEmbedding = await embed(query);
return this.documents
.filter((doc) => doc.embedding)
.map((doc) => ({
doc,
score: cosineSimilarity(queryEmbedding, doc.embedding!),
}))
.sort((a, b) => b.score - a.score)
.slice(0, topK)
.map(({ doc }) => doc);
}
}
const memory = new SemanticMemory();
async function answerWithContext(question: string): Promise<string> {
// Récupérer les documents pertinents
const relevant = await memory.search(question);
const context = relevant.map((d) => d.content).join("\n\n---\n\n");
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: context
? `Répondez à la question en utilisant le contexte suivant :\n\n${context}`
: undefined,
messages: [{ role: "user", content: question }],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}
// Exemple : indexer une base de connaissances et l'interroger
await memory.add("doc-1", "L'équipe utilise React 18 avec le modèle app router.");
await memory.add("doc-2", "Toutes les routes API doivent valider les entrées avec des schémas Zod.");
await memory.add("doc-3", "Les requêtes de base de données utilisent Drizzle ORM. Éviter le SQL brut.");
const answer = await answerWithContext("Comment devrais-je écrire un nouveau endpoint API ?");
// L'agent récupère doc-2 et doc-3 et génère une réponse fondée

Pour une utilisation en production, remplacez le stockage en mémoire par une base de données vectorielle dédiée. Pgvector est l’option la plus simple si vous utilisez déjà PostgreSQL. Chroma et Qdrant sont de bonnes options autonomes.

Quand l’utiliser : Assistants de documentation. Agents de recherche de code. Questions-réponses sur des bases de connaissances.

Connecter la Mémoire à MCP

Si vous avez suivi le guide du serveur MCP, vous pouvez exposer la mémoire comme une ressource MCP. Cela rend votre couche de mémoire accessible à tout client compatible MCP.

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{ name: "memory-server", version: "1.0.0" },
{ capabilities: { tools: {}, resources: {} } }
);
// Outil : stocker un fait
server.setRequestHandler("tools/call", async (request) => {
if (request.params.name === "remember") {
const { key, value, userId } = request.params.arguments as {
key: string;
value: string;
userId: string;
};
await storeEpisodicFact(userId, key, value);
return { content: [{ type: "text", text: `Stocké : ${key} = ${value}` }] };
}
if (request.params.name === "recall") {
const { userId } = request.params.arguments as { userId: string };
const facts = await loadEpisodicFacts(userId);
return {
content: [{ type: "text", text: JSON.stringify(facts, null, 2) }],
};
}
});
const transport = new StdioServerTransport();
await server.connect(transport);

Tout agent qui parle MCP peut maintenant appeler remember et recall comme outils standard. La mémoire devient un service partagé plutôt qu’une implémentation par agent.

Choisir la Bonne Stratégie

Utilisez ce tableau de décision comme point de départ :

SituationStratégie
Session unique, moins de 20 toursTampon
Session unique, longue duréeRésumé
Multi-sessions, préférences utilisateurÉpisodique
Grande base de connaissances à interrogerSémantique
Tout ce qui précèdeCombiner : résumé pour la session + épisodique/sémantique entre sessions

Commencez avec le tampon. Ajoutez une couche de résumé quand les conversations s’allongent. Ajoutez la mémoire épisodique quand les utilisateurs reviennent sur plusieurs sessions. Ajoutez la mémoire sémantique quand vous avez un corpus de documents à récupérer.

N’ajoutez pas de complexité avant d’en avoir besoin. Un tampon de conversation est la bonne réponse pour la plupart des prototypes.

Conclusion

La mémoire n’est pas une seule chose — c’est un ensemble de stratégies qui abordent différents problèmes à différentes portées. Le tampon gère la session courante. Le résumé étend cette session. La mémoire épisodique relie les sessions. La mémoire sémantique ancre les réponses dans une base de connaissances.

Choisissez la couche qui résout le problème que vous avez aujourd’hui. Enveloppez-la derrière une interface propre pour pouvoir échanger les implémentations plus tard. Et si vos agents communiquent via MCP, exposez la mémoire comme un outil partagé — cela garde chaque agent simple tout en donnant au système dans son ensemble un cerveau persistant.


Articles Connexes