Module 4 — Mémoire de travail et mémoire longue
Un agent qui tourne dix minutes accumule dix pensées, dix appels d'outils et dix observations souvent longues. À ce rythme, la fenêtre de contexte du modèle sature en une poignée d'étapes, et la fiabilité s'écroule avant même que la limite d'itérations soit atteinte. Ce module traite ce que l'agent garde en tête, ce qu'il archive, et — plus important qu'on ne croit — ce qu'il oublie.
Deux mémoires, deux rôles
La mémoire de travail est la fenêtre de contexte du modèle. Elle contient la consigne système, la question de l'utilisateur, la trace ReAct depuis le début et l'observation qui vient d'arriver. Elle est perdue à la fin de l'exécution. Son rôle est de porter la décision immédiate : « au vu de ce que je sais maintenant, que fais-je à l'étape suivante ? »
La mémoire longue vit en dehors de l'agent. C'est une base — souvent vectorielle — où l'on range les faits vérifiés, les notes précédentes, les préférences de l'utilisateur. Elle est consultée par un outil comme les autres, et son rôle est de rappeler des choses acquises hors de l'exécution en cours.
Confondre les deux est une erreur fréquente. Charger toute la base vectorielle dans le contexte au démarrage sature le modèle ; à l'inverse, mettre chaque observation en base ralentit l'exécution sans profit, parce qu'elle sera déjà dans le contexte quand elle sera utile.
Le contexte se remplit vite
Sur le fil rouge, une seule page web pesée par lire_url fait environ 6 000 caractères, soit 1 800 jetons. Cinq pages, plus les pensées et les schémas d'outils, dépassent facilement 15 000 jetons. À partir de 20 000 jetons, on observe deux dérives : le modèle oublie la consigne initiale — il boucle sans se rappeler la condition d'arrêt — et il répète des recherches déjà faites parce que ses propres pensées passées sont noyées.
La solution la plus simple, souvent la meilleure, est le résumé glissant. Au-delà d'un seuil, les k premières étapes de la trace sont compressées par un appel au modèle en un paragraphe. Le format de la trace après compression :
[Contexte compresse]
Trois recherches faites sur les concurrents A, B, C ; deux pages lues ;
faits accumules : A a un Kanban depuis mars 2025 avec integration Slack,
B annonce Kanban en Q4 sans date precise. Reste a verifier C et a
chercher un concurrent D mentionne dans la page A.
[Etape 6 - pensee] ...
[Etape 6 - action] ...
Le résumé n'est pas anodin : mal fait, il perd des faits, ce que le module 8 nomme la dérive de tâche. On garde donc les faits sourcés et les décisions prises intégralement, on n'admet la compression que pour les observations volumineuses.
Résumé glissant en pratique
def compresser_si_besoin(messages, seuil=15000):
if compter_jetons(messages) < seuil:
return messages
a_compresser = messages[1:-4] # on garde systeme + 4 dernieres etapes
fixes = [messages[0]] + messages[-4:]
resume = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": (
"Resume la trace d'un agent en preservant chaque fait "
"avec sa source et chaque decision prise. Pas de commentaire."
)},
*a_compresser,
],
temperature=0,
).choices[0].message.content
return [
fixes[0],
{"role": "system", "content": f"[Contexte compresse]\n{resume}"},
*fixes[1:],
]
Trois principes de conception guident cet appel. On utilise un modèle moins cher pour compresser, parce qu'il fait un travail simple. On garde les quatre dernières étapes intactes, parce que c'est là que se joue la décision suivante. Et on emploie une température nulle, parce que la reformulation ne doit rien inventer.
Mémoire longue par plongements
Pour la mémoire longue, on utilise le patron du cours 18 : indexer des faits par plongement, retrouver par similarité. Sur l'agent de veille, on l'utilise pour se rappeler les enquêtes précédentes : « il y a deux mois, ce concurrent avait annoncé telle chose ». C'est un outil supplémentaire, appelé quand la question suggère une continuité :
def se_souvenir(requete: str, k: int = 3) -> list[dict]:
embed = client.embeddings.create(
input=requete, model="text-embedding-3-small"
).data[0].embedding
resultats = base_vecteurs.query(embed, k=k)
return [{"date": r.date, "note": r.contenu} for r in resultats]
En fin d'exécution, l'agent range ses faits vérifiés dans la même base. La note finale devient ainsi un point d'ancrage pour les enquêtes futures.
Ce qu'un agent doit oublier
L'oubli est un choix de conception. Il faut oublier les observations intermédiaires dès qu'un fait en a été extrait — garder la source, jeter le texte. Il faut oublier les pensées écartées — un modèle qui hésite entre trois recherches puis en choisit une n'a pas besoin de se relire hésiter. Il faut aussi oublier les échecs d'outil — un outil qui a répondu « erreur 500 » à l'étape 3 encombre la trace, la note à jour suffit.
À l'inverse, il faut refuser d'oublier les décisions prises — sinon l'agent reprend un chemin déjà écarté. Le module 8 documente cet échec sous le nom d'oscillation.
En résumé
- La mémoire de travail vit dans le contexte du modèle ; la mémoire longue vit dans une base et se consulte par un outil.
- Au-delà de 15 000 à 20 000 jetons, la boucle dérive : oublie de la consigne, répétitions, pensées noyées.
- Un résumé glissant au modèle moins cher, à température nulle, en préservant les faits et les décisions, tient le contexte.
- Oublier est un choix : observations intermédiaires oui, décisions prises non.
Module suivant : la planification — quand rédiger un plan explicite plutôt que d'improviser à chaque étape, et à quel prix.