/always-on-agent-architecture — Construire des agents qui n'oublient jamais
Vous concevez l'architecture d'un agent IA toujours actif avec mémoire épisodique. Ce n'est pas un chatbot avec une longue fenêtre de contexte. C'est un système qui persiste son état entre les sessions, gère sa propre hiérarchie de mémoire, fonctionne comme un service, et maintient son identité sur des semaines et des mois. L'idée centrale : traiter le LLM comme un CPU qui opère sur une mémoire gérée, non pas comme une fonction sans état.
Points de décision
Arbre de sélection du framework mémoire
Q1 : Voulez-vous un runtime d'agent complet (serveur, API, outils) ?
├─ Oui → Utilisez Letta (le plus complet, prêt pour la production)
└─ Non, j'ai ma propre boucle d'agent
├─ Q2 : Avez-vous besoin d'un suivi temporel/relationnel ?
│ ├─ Oui → Utilisez Zep/Graphiti (meilleur graphe de connaissances temporel)
│ └─ Non → Aller à Q3
│ ├─ Q3 : Avez-vous besoin d'un hybride graphe + vecteur ?
│ │ ├─ Oui → Utilisez Mem0 (mode graphe)
│ │ └─ Non → Aller à Q4
│ │ ├─ Q4 : Utilisez-vous déjà LangGraph ?
│ │ │ ├─ Oui → Utilisez LangMem
│ │ │ └─ Non → Utilisez pgvector ou Chroma
└─ Vous voulez zéro dépendance ? → SQLite personnalisé + embeddings locaux
Déclencheurs d'éviction de la mémoire centrale
| Déclencheur | Seuil | Action |
|---|---|---|
| Dépassement de taille | Mémoire centrale > 4 Ko | Résumer le bloc le moins récent, déplacer le résumé vers l'archivage |
| Décroissance par âge | Données inutilisées > 30 jours | Marquer pour révision de compactage |
| Baisse de pertinence | Score d'accès < 0,3 | Déplacer vers la mémoire d'archivage avec balise de décroissance |
| Ordre de l'utilisateur | L'utilisateur dit "oublie X" | Suppression immédiate + pierre tombale d'archivage |
| Détection de conflit | Faits contradictoires stockés | Demander à l'agent de réconcilier ou questionner l'utilisateur |
Critères de sélection des bases de données vectorielles
Si latence_de_requête_requise < 10 ms ET taille_des_données > 100 millions de vecteurs :
→ Utilisez Qdrant (optimisé pour la vitesse)
Sinon si vous_utilisez_déjà_postgresql :
→ Utilisez pgvector (base de données unique, opérations plus simples)
Sinon si recherche_hybride_requise (mot-clé + sémantique) :
→ Utilisez Weaviate (meilleur hybride)
Sinon si zéro_opération_préféré :
→ Utilisez Pinecone (entièrement géré)
Sinon :
→ Utilisez Chroma (local d'abord, API simple)
Décision de routage des niveaux de mémoire
Entrée : Message utilisateur ou observation de l'agent
│
├─ Contient une mise à jour d'identité/préférence ?
│ └─ Oui → Mettre à jour la mémoire centrale, persister immédiatement
├─ Nécessite du contexte de conversation ?
│ └─ Oui → Rechercher dans la mémoire de rappel (historique des conversations)
├─ Nécessite des connaissances factuelles ?
│ └─ Oui → Rechercher dans la mémoire d'archivage (base vectorielle)
└─ Données externes nécessaires ?
└─ Oui → Utiliser les outils externes (API, fichiers, etc.)
Modes de défaillance
Cascade de corruption de mémoire
Symptômes : Dérive de la personnalité de l'agent, réponses contradictoires, conflits dans la mémoire centrale Cause racine : Écritures simultanées dans la mémoire centrale sans verrouillage, ou mises à jour partielles échouées Règle de détection : Si la taille de la mémoire centrale chute soudainement de >50 % ou contient du JSON/YAML mal formé Procédure de récupération :
- Arrêter l'agent immédiatement pour éviter toute corruption supplémentaire
- Restaurer la mémoire centrale à partir de la dernière bonne sauvegarde connue (moins d'1 heure)
- Rejouer le journal des conversations depuis la sauvegarde pour reconstruire les mises à jour perdues
- Implémenter des verrous d'écriture sur les mises à jour de la mémoire centrale avant le redémarrage
Dégradation de la recherche vectorielle
Symptômes : Résultats de recherche de plus en plus non pertinents, l'agent ne trouve pas les faits récemment stockés Cause racine : Dérive du modèle d'embedding, corruption de l'index, ou absence de compactage de la mémoire Règle de détection : Si la similarité cosinus moyenne des 3 premiers résultats < 0,7 pour des requêtes connues Procédure de récupération :
- Exécuter une vérification de cohérence des embeddings sur un échantillon aléatoire de 100 vecteurs
- Si >10 % présentent des embeddings anormaux, reconstruire l'intégralité de l'index
- Implémenter le verrouillage de la version du modèle d'embedding
- Ajouter une surveillance de la dérive des embeddings pour éviter la récurrence
Blocage de la couche de persistance
Symptômes : L'agent se bloque sur les opérations mémoire, timeouts de connexion à la base de données Cause racine : Lecture/écriture simultanées sur les mêmes blocs mémoire, pool de connexions insuffisant Règle de détection : Si une opération mémoire prend >30 s ou si la base de données montre des timeouts d'attente de verrou Procédure de récupération :
- Tuer les connexions bloquées pour libérer les verrous
- Implémenter une logique de réessai avec backoff exponentiel pour les opérations mémoire
- Ajouter un pool de connexions avec des limites de connexion maximales
- Réviser les niveaux d'isolation des transactions pour les mises à jour mémoire
Explosion de la fenêtre de contexte
Symptômes : Pic des coûts API, augmentation de la latence des réponses, erreurs de limite de tokens Cause racine : Gonflement de la mémoire centrale, récupération de trop nombreux fragments d'archivage par requête Règle de détection : Si le nombre moyen de tokens par requête > 80 % de la limite de contexte du modèle Procédure de récupération :
- Auditer la taille de la mémoire centrale – compresser ou archiver les blocs surdimensionnés
- Réduire le nombre de résultats de recherche d'archivage par défaut (10 → 5)
- Implémenter le comptage de tokens avant les appels LLM
- Ajouter des alertes de surveillance des coûts pour >1 $/conversation
Fuite de mémoire – Croissance illimitée
Symptômes : La taille de la base de données augmente linéairement, les performances de recherche se dégradent avec le temps Cause racine : Absence de compactage de la mémoire, insertion de faits en double, collecte des déchets manquante Règle de détection : Si la taille totale de la mémoire croît de >100 Mo/mois avec une utilisation normale Procédure de récupération :
- Exécuter une déduplication des faits dans la mémoire d'archivage (similarité cosinus > 0,92)
- Implémenter le résumé des conversations pour la mémoire de rappel de plus de 7 jours
- Ajouter un score de pertinence avec élagage automatique des mémoires à faible score
- Planifier des travaux de compactage hebdomadaires
Exemples concrets
Exemple : Construction d'un assistant de recherche personnel
Scénario : Concevoir l'architecture d'un agent qui aide à la recherche technique, se souvient de vos préférences et accumule des connaissances sur des mois.
Étape 1 – Conception des niveaux de mémoire
Mémoire centrale (2 Ko) :
- Nom de l'utilisateur : "Sarah"
- Domaines de recherche : ["apprentissage automatique", "systèmes distribués"]
- Sources d'articles préférées : ["arxiv", "bibliothèque numérique acm"]
- Style d'écriture : "détaillé avec exemples de code"
- Projet actuel : "optimisation de l'entraînement distribué"
Mémoire de rappel :
- Toutes les conversations dans PostgreSQL avec recherche en texte intégral
- Fenêtre de rétention de 30 jours, puis résumé
Mémoire d'archivage :
- Résumés d'articles, informations extraites, extraits de code
- pgvector sur PostgreSQL (déjà utilisé pour le rappel)
- nomic-embed-text pour l'embedding local (confidentialité + coût)
Étape 2 – Décision de sélection du framework En suivant l'arbre de décision :
- Besoin d'un runtime d'agent complet ? Non (construction personnalisée)
- Besoin de suivi temporel ? Non (les faits de recherche sont généralement intemporels)
- Besoin de graphe+vecteur ? Non (recherche sémantique simple suffisante)
- Déjà sur LangGraph ? Non
- → Décision : pgvector + PostgreSQL
Étape 3 – Implémentation de la boucle de l'agent
async def étape_de_recherche(requête_utilisateur: str):
# Charger la mémoire centrale
centrale = charger_mémoire_centrale() # Préférences utilisateur, projet actif
# Vérifier si la requête concerne le projet actuel
if "optimisation" in requête_utilisateur.lower():
# Rechercher dans l'archivage les connaissances spécifiques au projet
articles_pertinents = rechercher_archivage("optimisation de l'entraînement distribué")
contexte = f"Contexte du projet actuel : {articles_pertinents}"
else :
# Rechercher des connaissances générales du domaine
contexte = rechercher_archivage(requête_utilisateur)
# Construire le prompt avec la mémoire centrale + le contexte récupéré
prompt_système = f"""
Vous êtes l'assistant de recherche de Sarah.
Préférences de l'utilisateur : {centrale['préférences']}
Projet actuel : {centrale['projet_actuel']}
Contexte récupéré : {contexte}
"""
réponse = await llm.chat([
{"role": "system", "content": prompt_système},
{"role": "user", "content": requête_utilisateur}
])
# Persister l'interaction
sauvegarder_dans_rappel(requête_utilisateur, réponse)
return réponse
Ce qu'un novice aurait manqué :
- Stocker des articles bruts au lieu d'informations extraites dans l'archivage
- Ne pas implémenter la recherche dans les conversations (mémoire de rappel)
- Mettre trop de choses dans la mémoire centrale (liste de domaines de recherche avec 50 entrées)
- Aucune stratégie de compactage de la mémoire
Ce qu'un expert remarque :
- La mémoire centrale reste concentrée sur "l'identité de travail", pas sur la connaissance
- La mémoire d'archivage contient des faits organisés, pas des documents bruts
- Implémente la recherche avant la récupération (l'agent décide ce qui est pertinent)
- Planifie la croissance de la mémoire dès le premier jour
Points de contrôle qualité
Liste de validation du déploiement
- SLO de latence mémoire : La mémoire centrale se charge en <100 ms, la recherche d'archivage se termine en <500 ms
- Cohérence mémoire : La mémoire centrale survit au redémarrage de l'agent sans corruption (tester avec un arrêt volontaire)
- Pertinence de la recherche : Les 3 premiers résultats d'archivage ont une similarité cosinus >0,6 pour des requêtes connues
- Règles de dimensionnement mémoire : Mémoire centrale ≤4 Ko, rétention de rappel ≤30 jours, fragments d'archivage ≤1 Ko chacun
- Durabilité de la persistance : Toutes les mises à jour mémoire survivent au redémarrage de la base de données (conformité ACID vérifiée)
- Contrôles des coûts : Les opérations mémoire coûtent <0,01 $/conversation à 100 conversations/jour
- Planification du compactage : Le compactage mémoire s'exécute chaque semaine et réduit la taille totale d'au moins 10 %
- Cohérence de l'identité : La personnalité de l'agent reste stable sur 50+ sessions de conversation
- Récupération à froid : L'agent gère gracieusement un état mémoire vide (le flux d'intégration fonctionne)
- Vérification de la sauvegarde : La sauvegarde mémoire se restaure avec succès et préserve l'identité de l'agent
Limites – NE PAS utiliser
N'utilisez PAS ce skill pour :
- Choisir les données d'entraînement de l'agent → Utilisez
/always-on-agent-inputsà la place – ce skill couvre les données à fournir à l'agent, celui-ci couvre comment les stocker et les récupérer - Brainstormer des applications d'agents → Utilisez
/always-on-agent-applicationsà la place – ce skill couvre l'idéation des cas d'usage, celui-ci couvre l'implémentation technique - Sécurité et confidentialité de l'agent → Utilisez
/always-on-agent-safetyà la place – ce skill couvre la gouvernance des données, le consentement et la sécurité ; celui-ci suppose qu'ils sont déjà conçus - Patterns génériques d'agents → Utilisez
/agentic-patternsà la place – ce skill couvre les boucles ReAct, l'utilisation d'outils, la planification ; celui-ci couvre la couche de persistance sous-jacente - Tâches d'agent ponctuelles → Utilisez
/agent-creatorà la place – si l'agent n'a pas besoin de se souvenir entre les sessions, vous n'avez pas besoin d'une architecture toujours active - Conception de schémas de base de données → Ce skill suppose que vous comprenez les concepts de base de données ; utilisez des skills spécifiques aux bases de données pour l'optimisation des schémas
- Stratégies d'optimisation des coûts → Ce skill mentionne les considérations de coût mais n'approfondit pas l'optimisation ; déléguez aux skills spécifiques aux coûts