Un agent IA sans mémoire durable est un stagiaire brillant amnésique. Il livre un correctif propre lundi, oublie la base de code d'ici mardi, et réintroduit la même régression mercredi. Chez CodeCourier, nous avons continué à heurter ce mur - jusqu'à ce que nous cessions de traiter la mémoire comme une fonctionnalité et commencions à la traiter comme le substrat central. Cet article est l'immersion technique sur le fonctionnement de notre couche Contexts : la stratégie d'embedding, le pipeline de récupération hybride, le modèle de portée, le cadre d'évaluation, et les leçons surprenantes que nous avons recueillies en chemin.
J'écris cela de bâtisseur à bâtisseur. Si vous concevez une mémoire d'agent IA pour un agent de code, un agent de support, ou un copilote interne, les mêmes questions architecturales vous prendront en embuscade. Nous allons les nommer, et montrer les réponses que nous avons livrées en production.
1. Qu'est-ce que le contexte durable pour un agent IA ?
Le contexte d'agent durable est la connaissance persistante, récupérable, et délimitée qui survit à travers les exécutions de l'agent et est injectée sélectivement dans la fenêtre de contexte d'un modèle au moment de l'inférence. Contrairement au buffer de conversation (qui meurt à la fin de la session) et contrairement au fine-tuning (qui fige la connaissance dans les poids), le contexte durable est un état chaud, éditable, et adressable qu'un agent peut lire, citer, et mettre à jour sans réentraînement.
Chez CodeCourier, un Context est l'unité de cette mémoire durable. Concrètement, c'est un lot versionné de fragments - paragraphes, échantillons de code, ADR, runbooks, résumés d'anciennes Issue Sessions, politiques de sécurité - adressables par ID, délimités à une organisation, un dépôt, une persona, ou une session, et indexés pour la récupération hybride (lexicale plus sémantique, avec un reranker). Quand une nouvelle Issue Session démarre à l'intérieur d'une sandbox, les bons Contexts sont sélectionnés, classés, compressés, et épinglés au prompt automatiquement.
Le raccourci que nous utilisons en interne : les poids sont pour les compétences, le contexte est pour les faits. Si la connaissance change plus vite que votre prochaine exécution d'entraînement, elle appartient à la couche de contexte.
2. Pourquoi le RAG naïf s'effondre pour les agents d'ingénierie
La recette naïve - tout découper, l'embedder, stocker les vecteurs, faire une recherche cosinus top-K, ajouter au prompt - fonctionne bien pour un chatbot de documents marketing. Elle s'effondre rapidement pour un agent d'ingénierie. Cinq modes d'échec que nous avons mesurés sur le terrain :
- Dérive sémantique sur les requêtes proches du code. Les embeddings denses classent « authentication » et authorization comme des quasi-jumeaux. L'agent récupère la mauvaise politique et écrit avec assurance un middleware cassé. Sur un ensemble de référence, la récupération cosinus naïve a obtenu recall@10 = 0,61 sur des requêtes d'authentification ambiguës ; notre pipeline hybride l'a porté à 0,87.
- Les frontières de découpage déchiquettent la sémantique. Un morceau de 512 tokens tranche à travers un corps de fonction, laissant l'agent avec la seconde moitié d'une transaction SQL et aucune idée de ce qui était annulé.
- Le top-K est opaque. Quand un agent hallucine, vous avez besoin d'un post-mortem. La recherche vectorielle pure ne laisse aucune piste d'audit au-delà de « ces huit vecteurs étaient les plus proches » - ce qui est infalsifiable et irréparable.
- Aucune sécurité de portée. Les embeddings d'un locataire fuitent dans la récupération d'un autre locataire parce que l'index est global. Le RSSI l'apprend. Vous passez une mauvaise semaine.
- Pourriture de fraîcheur. L'embedding d'un ADR vieux de six mois est tout aussi « proche » que celui écrit hier qui l'a remplacé. Sans signaux de fraîcheur, l'agent cite le cadavre.
Le RAG naïf est une démo. Une véritable architecture RAG pour agents a besoin de récupération hybride, de portée structurée, de fraîcheur, de citations, et d'une boucle d'évaluation qui attrape les régressions avant qu'elles ne soient livrées.
3. Notre architecture en un coup d'œil
Imaginez trois voies verticales. À gauche, une voie d'ingestion ingère les documents sources - markdown, code, transcriptions, sorties d'agent précédentes - et émet des fragments normalisés. Au milieu, une voie d'indexation écrit ces fragments dans un index inversé BM25 et un index vectoriel dense en parallèle, avec des ID de fragment partagés. À droite, une voie de récupération sert les requêtes d'agent : elle déploie des recherches lexicales et denses, fusionne les candidats, les reclasse avec un cross-encoder, et retourne un petit lot citable qui tient dans le budget du prompt.
De bout en bout, le pipeline s'exécute en sept étapes ordonnées :
- Normaliser - supprimer le bruit, canoniser les espaces, détecter la langue, attacher les métadonnées source (dépôt, chemin, auteur, horodatage).
- Découper - découpage conscient du code avec chevauchement ; nous utilisons les frontières AST pour le code, les frontières de titre pour la prose.
- Embedder - générer des vecteurs denses par fragment ; produire aussi une signature lexicale creuse.
- Indexer - écrire dans le magasin dense (HNSW) et le shard BM25, indexé par ID de fragment, avec des étiquettes de portée.
- Récupérer - pour chaque requête, exécuter BM25 top-100 et dense top-100 en parallèle.
- Fusionner et reclasser - Reciprocal Rank Fusion fusionne, puis un reranker cross-encoder note le top-50.
- Composer - appliquer les filtres de portée, les boosts de fraîcheur, le graphe de citations, les épingles manuelles ; compresser et épingler au prompt de l'agent.
Chaque étape est observable indépendamment. Chaque fragment qui finit dans un prompt est traçable jusqu'au document source, à la version du découpeur, à la version du modèle d'embedding, et au score de reclassement. Cette auditabilité est ce qui rend le système débogable en production - et c'est ce qui manque aux implémentations RAG naïves dès le premier jour.
4. Stratégie d'embedding - modèle, découpage, dédoublonnage
Trois décisions dominent la qualité des embeddings : quel modèle, comment découper, et comment dédoublonner.
Choix du modèle
Nous utilisons un embedding généraliste de 1024 dimensions pour la prose et un embedding spécialisé code pour les fichiers source, stockés dans des espaces de noms séparés. Mélanger les modalités dans un seul espace de noms a dégradé le recall d'environ 9 points sur notre évaluation interne ; l'embedding de code rapproche le contenu en forme de fonction dans son propre espace, tandis que le modèle de prose gère mieux les ADR et les politiques. Nous ré-embeddons à chaque mise à niveau de modèle et gardons la génération précédente active pendant une fenêtre fantôme de 14 jours.
Découpage conscient du code
Les fenêtres génériques de 512 tokens déchiquettent les fonctions. Nous découpons le code le long des frontières AST - fonction, classe, instruction de niveau supérieur - et attachons un en-tête portant le chemin du fichier et le symbole englobant. La prose se découpe selon la hiérarchie des titres. Les deux modes gardent un chevauchement de 64 tokens pour préserver la sémantique transfrontalière. La taille moyenne de fragment s'établit à 320 tokens ; le p95 à 780.
Dédoublonnage et fragments canoniques
Les corpus d'ingénierie sont pleins de quasi-doublons : le même échantillon de code collé dans trois runbooks, le même paragraphe reflété d'un wiki dans un README. Nous calculons une signature SimHash par fragment et effondrons les quasi-doublons (distance de Hamming ≤ 3) en un seul fragment canonique avec plusieurs pointeurs source. Cela a réduit notre index de 34% sur un locataire représentatif et amélioré la précision du reranker parce que le cross-encoder ne gaspille plus de capacité sur des jumeaux.
5. Récupération hybride - BM25 + dense + reranker
La récupération hybride est le mur porteur d'une véritable base de connaissances d'agent. Les embeddings denses attrapent la paraphrase ; BM25 attrape les identifiants exacts - noms de fonctions, codes d'erreur, colonnes de tables. Aucun des deux seul ne suffit.
Notre appel de récupération, en pseudo-schéma :
POST /contexts/retrieve
{
"query": "why does the rate limiter drop requests on burst?",
"scope": {
"org_id": "org_7Hk2",
"repo_id": "repo_courier-api",
"persona_id": "persona_backend-sre",
"session_id": "iss_2026-03-19-A91"
},
"budget_tokens": 3200,
"k_lexical": 100,
"k_dense": 100,
"rerank_top": 50,
"freshness_halflife_days": 90,
"min_score": 0.42
}
200 OK
{
"fragments": [
{
"id": "frag_8f21",
"doc_id": "adr_rate-limit-v4",
"version": 4,
"score": 0.913,
"tokens": 412,
"source": "runbooks/rate-limit.md#burst",
"updated_at": "2026-02-11T08:14:00Z"
}
],
"trace_id": "trc_8c1d"
}
Chiffres auxquels nous nous tenons en production :
- recall@10 = 0,87 sur l'ensemble de référence d'ingénierie (1 200 requêtes étiquetées), contre 0,61 avec cosinus seul.
- MRR@10 = 0,74 après reclassement cross-encoder.
- Latence de récupération p50 = 88 ms, p95 = 240 ms pour un budget ≤ 4k tokens, reclassement inclus.
- Taux d'hallucination sur la réponse finale de l'agent (jugé par un LLM-juge avec vérifications ponctuelles humaines) est tombé de 14,1% à 3,2% après le déploiement du reranker.
Le reranker est un petit cross-encoder. Il note 50 candidats et nous gardons ce qui tient dans le budget de tokens après compression. La compression est bête à dessein - nous supprimons les en-têtes standards, gardons le chemin de titre, et ne paraphrasons jamais le corps. Les agents citent du texte exact ou ne citent rien.
6. Portée et permissions - dépôt, organisation, persona, session
La sécurité de portée n'est pas négociable. Un seul index d'embeddings qui sert plusieurs locataires est un incident d'exfiltration de données qui n'attend qu'une invitation de calendrier. Nous attachons des étiquettes de portée à chaque fragment au moment de l'écriture et les appliquons au moment de la requête, non par post-filtrage mais par partitionnement d'index au moment de la requête.
Quatre portées, évaluées de bas en haut :
- Session - fragments éphémères produits pendant l'Issue Session actuelle, visibles seulement à l'intérieur de cette session.
- Persona - fragments liés à une Persona spécifique (par ex. SRE backend, relecteur d'accessibilité frontend) ; ils biaisent la récupération vers le domaine de la persona.
- Dépôt - connaissance liée au code, délimitée à un dépôt et héritant de la politique d'accès de ce dépôt.
- Organisation - connaissance transversale aux dépôts comme la politique de sécurité et la voix de marque, contrôlée par l'appartenance à l'organisation.
Au moment de la récupération, l'ensemble d'ACL du principal demandeur est intersecté avec l'ensemble de portée de chaque fragment candidat. Un fragment sans chevauchement n'est même pas noté. Nous traitons la portée comme une propriété de justesse, pas comme un indicateur de fonctionnalité, et nous la testons comme nous testons le code d'authentification. La discipline est documentée dans notre vue d'ensemble de la sécurité.
7. Classement et fraîcheur - récence, citations, épingles
La pertinence est une fonction de similarité, de récence, d'autorité, et d'intention de l'opérateur. Le classeur final est un mélange pondéré :
score(f) = w_r * rerank(f, q)
+ w_f * exp(-age_days(f) / halflife)
+ w_c * citation_pagerank(f)
+ w_p * is_pinned(f, scope)
Les poids sont ajustés par persona. Les Contexts d'un relecteur de sécurité penchent vers l'autorité (graphe de citations et épingles) ; un agent frontend corrigeant un ticket ouvert penche vers la fraîcheur. Le graphe de citations est construit hors ligne : quand un fragment renvoie vers ou est cité par d'autres fragments, il accumule une autorité de type PageRank. Les épingles manuelles gagnent toujours - un tech lead peut épingler un fragment en haut d'une portée, et le système obéit sans discussion.
La récence utilise une décroissance à demi-vie, pas un seuil dur. Un ADR vieux de deux ans peut encore émerger si rien de plus frais n'existe ; un ADR d'une semaine sur le même sujet dominera simplement. La demi-vie est configurable ; le défaut est de 90 jours pour le contenu d'ingénierie et 365 jours pour la politique.
8. Cadre d'évaluation - comment nous mesurons la qualité de récupération
La qualité de récupération est le seul chiffre qui compte, et c'est le plus facile de se tromper soi-même. Notre cadre d'évaluation repose sur trois couches, chacune plus coûteuse et plus honnête que la précédente.
- Ensembles de référence. 1 200 requêtes étiquetées avec des fragments idéaux curés par des humains. Exécuté à chaque changement du pipeline. Recall@k, MRR@k, nDCG@k, et ventilations par portée.
- Relectures de régression. Des requêtes de production réelles (anonymisées et consenties) rejouées contre le pipeline candidat ; nous comparons les fragments récupérés et signalons toute requête dont le top-3 change au-delà d'un seuil de chevauchement de tokens.
- Évaluations d'agent de bout en bout. Exécutions d'agent complètes dans des sandboxes isolées contre des tâches notées ; nous jugeons les sorties finales avec un LLM-juge et des vérifications ponctuelles humaines. C'est la seule couche qui mesure ce que nous livrons réellement.
Chaque PR qui touche le chemin de récupération doit battre le pipeline précédent sur l'ensemble de référence ou atterrir avec une dérogation écrite. La plupart des équipes d'ingénierie sous-investissent ici. Nous avons livré des versions qui avaient l'air superbes en démo et ont perdu trois points de recall en production. Nous ne les livrons plus.
9. Décisions de conception surprenantes
Écrire moins, lier davantage
Les clients ont essayé de déverser des exports Notion complets et des archives Slack entières dans Contexts. Le recall a baissé. Le reranker s'est noyé dans les quasi-doublons et l'agent a perdu le fil. Nous avons reconstruit l'interface pour imposer des fragments minimaux - la plus petite unité qui capture une décision - et ajouté une résolution de liens à la demande. La longueur médiane des fragments est passée de 1 400 tokens à 320. Le taux d'hallucination a chuté avec elle.
La structure bat la prose
Les fragments rédigés comme des listes de décisions à puces surpassent la même information rendue sous forme de prose fluide. Notre hypothèse : l'agent traite la structure comme un échafaudage bon marché à analyser et consacre plus d'attention au contenu. Nous intégrons cela dans les directives de rédaction dans nos guides.
Les embeddings sont un départage, pas un signal principal
Contre-intuitif pour quiconque a grandi avec du RAG vectoriel pur, mais BM25 + le filtrage de portée fait la majeure partie du travail lourd. L'embedding dense règle les égalités et sauve les requêtes de paraphrase. Si nous devions n'en livrer qu'un sans l'autre, nous garderions BM25.
Les contextes négatifs fonctionnent
Nous avons ajouté des fragments négatifs - anti-patterns explicites et pièges connus - et vu une baisse mesurable des régressions répétées. Sur une classe de bugs d'un client (des nouvelles tentatives silencieuses masquant des erreurs 5xx), les incidents répétés ont chuté de 41% après que le fragment d'anti-pattern a été épinglé à la portée du dépôt.
Propriétaires ou pourriture
Chaque fragment a un propriétaire humain nommé. Quand le système signale un fragment comme obsolète - souvent référencé mais non édité depuis six mois - le propriétaire reçoit une relance dans son workflow. Sans propriétaires, l'index dégénère en documentation zombie ; avec des propriétaires, il se compose.
10. RAG naïf contre Contexts - comparaison
| Dimension | RAG naïf | CodeCourier Contexts |
|---|---|---|
| Récupération | Cosinus dense uniquement | BM25 + dense + reclassement cross-encoder |
| recall@10 (ensemble de référence) | 0,61 | 0,87 |
| Latence p95 | ~120 ms (sans reclassement) | ~240 ms (avec reclassement) |
| Taux d'hallucination (réponse finale) | 14,1% | 3,2% |
| Sécurité de portée | Post-filtre (fuyant) | Partitionnement d'index + intersection d'ACL |
| Fraîcheur | Aucune | Décroissance à demi-vie + épingles manuelles |
| Auditabilité | ID de vecteur uniquement | ID de fragment + version + source + score de reclassement |
| Discipline de rédaction | Déverser et espérer | Fragments minimaux + propriétaires + versions |
Les gains se composent. Un pipeline de récupération plus rapide et plus précis rend le prompt plus petit, ce qui abaisse le coût d'inférence et la latence de queue, ce qui permet d'exécuter plus d'étapes par Issue Session, ce qui produit de meilleures sorties d'agent, qui se réinjectent dans les Contexts comme fragments canoniques.
11. Questions ouvertes et prochaines étapes
Nous ne sommes pas terminés. Trois fils sont activement en cours. Les Contexts inter-équipes - permettre à une équipe plateforme de publier un Context auquel les équipes en aval s'abonnent sans forker une copie - est la fonctionnalité la plus demandée dans la file et la plus difficile à réussir sans briser les garanties de portée. Le reclassement conditionné par persona utilise la Persona active comme fonctionnalité de reclassement supplémentaire, si bien que la même requête retourne des fragments différents pour un SRE et un relecteur d'accessibilité. La récupération consciente du workflow lie les Contexts au workflow builder pour qu'une étape qui modifie du code Stripe tire automatiquement le runbook des paiements sans que personne n'ait besoin de le câbler.
Si vous voulez un regard plus approfondi sur la plateforme elle-même, la page d'accueil CodeCourier est la visite la plus rapide, et nous documentons des patterns pratiques dans le blog d'ingénierie. Si vous construisez votre propre couche de mémoire d'agent et voulez comparer les notes, contactez-nous via contact.
12. FAQ
Qu'est-ce que la mémoire d'agent IA ?
La mémoire d'agent IA est la connaissance persistante et récupérable qu'un agent utilise à travers les sessions. Elle est distincte du buffer de conversation (qui est éphémère) et des poids du modèle (qui sont statiques). La mémoire d'agent pratique combine le contexte durable (faits, décisions, code), la récupération (BM25 + dense + rerank), et un modèle de portée qui contrôle qui voit quoi.
Pourquoi ne pas simplement fine-tuner le modèle sur notre base de code ?
Le fine-tuning cuit la connaissance dans les poids, ce qui est le mauvais endroit pour des faits qui changent chaque semaine. C'est aussi coûteux, lent à itérer, difficile à auditer, et impossible à délimiter par locataire. Le contexte durable gagne sur le coût, la fraîcheur, et la gouvernance. Utilisez le fine-tuning pour les compétences (style, format, comportement), pas pour les faits.
Pourquoi la récupération hybride est-elle meilleure que la recherche vectorielle pure ?
Les embeddings denses excellent en paraphrase mais manquent les identifiants exacts ; BM25 excelle sur les identifiants mais manque la paraphrase. Les fusionner avec Reciprocal Rank Fusion et un reranker cross-encoder a fait passer notre recall@10 de 0,61 à 0,87 sur un ensemble de référence d'ingénierie étiqueté.
Quelle devrait être la taille d'un fragment de contexte ?
Plus petit que vous ne le pensez. Notre médiane est de 320 tokens, le p95 est de 780. Les petits fragments se reclassent plus précisément, se composent mieux, et laissent plus de budget pour le raisonnement propre de l'agent.
Comment empêchez-vous les locataires de fuiter les uns vers les autres ?
La portée est appliquée au moment de la requête via le partitionnement d'index et l'intersection d'ACL - pas par post-filtrage. Les fragments sans chevauchement de portée avec le principal demandeur ne sont jamais notés. Nous testons la portée comme nous testons le code d'authentification.
Comment mesurez-vous la qualité de récupération ?
Trois couches : un ensemble de référence de 1 200 requêtes avec recall, MRR, et nDCG ; des relectures de requêtes de production anonymisées avec détection de diff ; et des exécutions d'agent de bout en bout notées par un LLM-juge avec des vérifications ponctuelles humaines. Chaque PR touchant le chemin de récupération doit battre le pipeline précédent ou atterrir avec une dérogation explicite.
Les embeddings ou BM25 comptent-ils davantage ?
BM25 porte plus de poids dans notre système que la plupart des équipes ne s'y attendent. Si nous devions n'en livrer qu'un, ce serait BM25 + la portée. L'embedding dense justifie sa place sur la paraphrase et le départage code-contre-prose.
Qu'est-ce qu'un contexte négatif, et pourquoi aide-t-il ?
Un contexte négatif est un fragment qui code un anti-pattern ou un piège connu. Épinglé à une portée, il dit à l'agent ce qu'il ne faut pas faire. Nous avons mesuré des baisses de 30 à 40% des régressions répétées sur des classes de bugs spécifiques après avoir épinglé le bon fragment d'anti-pattern.
La couche de contexte est la partie d'une plateforme d'agent qui transforme l'ingéniosité ponctuelle en intelligence institutionnelle qui se compose. Construisez-la comme de l'infrastructure - versionnée, délimitée, avec des propriétaires, et petite - et vos agents cessent d'être des stagiaires amnésiques.