Chez Glorics, nos agents ne se contentent pas d'exécuter des tâches — ils se souviennent. Ils accumulent des observations, apprennent du feedback client, et affinent leur comportement au fil des semaines et des mois d'opération. Mais stocker des souvenirs, c'est la partie facile. Retrouver le bon souvenir au bon moment — c'est là que l'ingénierie devient sérieuse.
Cet article vous emmène à l'intérieur d'Agentics, la couche d'intelligence mémorielle de la plateforme Glorics. Vous allez voir comment 12 agents autonomes maintiennent, recherchent et croisent leurs souvenirs grâce à une architecture hybride qui combine la précision des mots-clés avec la compréhension sémantique. Voici les briques fondamentales :
Avant de parler de recherche, il faut comprendre où les souvenirs sont stockés. Chaque agent Glorics possède une soul (âme) — un document markdown structuré qui contient tout ce que l'agent sait. Pas un blob d'embeddings opaque, pas un key-value store. Un document lisible, éditable, auditable par un humain.
La soul est organisée en trois niveaux hiérarchiques, du plus général au plus spécifique :
Chaque soul de niveau projet contient six sections : Identité, Brand Voice, Contexte Métier, Règles, Ce que j'ai appris (auto-observations + notes humaines), et Résumé de performance. L'agent accumule les observations automatiquement — quand un contenu est noté, quand une position SERP change, quand l'humain ajoute une note comme « se concentrer sur le contenu implants ».
La soul est du markdown brut dans un textarea. Pas d'éditeur WYSIWYG, pas d'assistant formulaire. C'est un choix de design délibéré — la même approche que Claude Projects, les fichiers Cursor rules, et tous les outils IA modernes. Un humain peut la lire, la modifier, l'auditer. La mémoire de l'agent n'est jamais une boîte noire.
Le problème : jusqu'ici, la soul entière était injectée dans le prompt de l'agent à chaque exécution. Toutes les observations, toutes les notes, tout — sans distinction de pertinence. Le copywriter qui écrit sur les implants dentaires recevait les observations sur l'orthodontie, le blanchiment et la parodontie. Le signal était noyé dans le bruit.
C'est pour ça qu'on a construit le Soul Search System — la couche de récupération hybride qui se superpose à la soul. La soul reste la source de vérité. Soul Search la rend cherchable.
La recherche par mots-clés est la méthode de récupération la plus directe. On cherche des correspondances exactes entre les termes de la requête et le contenu stocké. C'est rapide, c'est précis sur les identifiants, et c'est le socle de tout système de recherche.
Chez Glorics, on utilise BM25 via l'extension FTS5 de SQLite — le même algorithme qui propulse Elasticsearch et Lucene, mais embarqué dans un seul fichier sans aucun serveur. BM25 ne se contente pas de trouver des correspondances — il classe les résultats en pondérant la fréquence des termes et la longueur des documents :
score(D, Q) = Σ IDF(qi) × [ f(qi, D) × (k1 + 1) ] / [ f(qi, D) + k1 × (1 - b + b × |D|/avgdl) ]
Où :
f(qi, D) = fréquence du terme qi dans le document D
|D| = longueur du document
avgdl = longueur moyenne des documents
k1, b = paramètres de tuning (typiquement k1=1.2, b=0.75)
TF (Term Frequency) → plus un terme apparaît, plus le document est pertinent
IDF (Inverse Document Frequency) → terme plus rare = signal plus fort Dans Agentics, chaque chunk de soul est indexé dans une table virtuelle FTS5. Quand le copywriter s'apprête à écrire un article sur « prix implant dentaire », BM25 retrouve chaque observation contenant ces mots exacts :
-- Chunks de soul indexés pour recherche BM25 dans Agentics
CREATE VIRTUAL TABLE soul_chunks_fts USING fts5(
text,
content='soul_chunks',
content_rowid='chunk_id',
tokenize='porter unicode61' -- stemming + support unicode
);
-- Rechercher les observations du copywriter sur les scores implants
SELECT sc.chunk_id, sc.agent_key, sc.text, rank
FROM soul_chunks_fts
JOIN soul_chunks sc ON soul_chunks_fts.rowid = sc.chunk_id
WHERE soul_chunks_fts MATCH 'implant score SEO'
ORDER BY rank; La recherche sémantique ne cherche pas des mots identiques — elle cherche des significations similaires. Le texte est converti en embeddings : des vecteurs de nombres qui capturent le sens sémantique. Deux phrases qui veulent dire la même chose produisent des vecteurs similaires, même si elles ne partagent aucun mot.
Chez Glorics, on génère les embeddings localement sur notre propre serveur avec sentence-transformers — aucune API tierce, aucune donnée qui quitte l'infrastructure, zéro coût au token. Notre serveur (128 Go de RAM, 32 cores) encaisse ça sans broncher :
from sentence_transformers import SentenceTransformer
# Chargé une seule fois au démarrage du serveur — 420 Mo, ~8ms par embedding
model = SentenceTransformer('all-mpnet-base-v2')
# La magie : sens proche → vecteurs proches
v1 = model.encode("article implant score SEO 91")
v2 = model.encode("contenu prothèse bonne performance")
v3 = model.encode("meilleurs restaurants à Paris")
# v1 et v2 sont PROCHES (sens similaire)
# v1 et v3 sont ÉLOIGNÉS (sujets sans rapport)
print(f"Dimensions : {len(v1)}") # 768 Glorics tourne exclusivement sur Claude d'Anthropic pour le raisonnement (Haiku, Sonnet, Opus). Plutôt que d'ajouter une dépendance API supplémentaire pour les embeddings, on fait tourner all-mpnet-base-v2 en local. Zéro latence réseau, zéro coût au token, zéro fuite de données. Tout le pipeline d'embedding reste sur notre infrastructure.
Les embeddings sont stockés dans sqlite-vec — une extension de recherche vectorielle pour SQLite. Même fichier, même base, aucun serveur vectoriel à gérer. L'index sémantique de l'agent cohabite avec son index BM25 :
Ni la recherche par mots-clés ni la recherche sémantique ne suffisent seules. La recherche mots-clés trouve « SEO score 91 » parfaitement mais rate « le contenu a bien performé ». La recherche sémantique comprend le sens mais ne matche pas fiablement les scores, dates et noms spécifiques. La réponse : exécuter les deux en parallèle et fusionner les résultats.
Agentics utilise la fusion pondérée par scores — chaque résultat reçoit un score combiné qui favorise la compréhension sémantique (70%) tout en préservant la précision des mots-clés (30%) :
score_final = 0.7 × score_sémantique + 0.3 × score_mots_clés
Observation | Séman. | BM25 | Score fusionné
-------------------------------|--------|-------|----------------------------
"implant moy. 91 vs ortho 72" | 0.92 | 0.85 | 0.7×0.92 + 0.3×0.85 = 0.899 ← meilleur
"prothèse perf. bonne" | 0.88 | 0.00 | 0.7×0.88 + 0.3×0.00 = 0.616
"données ortho T1" | 0.31 | 0.00 | 0.7×0.31 + 0.3×0.00 = 0.217 ← sous le seuil
"tendances prix implant" | 0.75 | 0.72 | 0.7×0.75 + 0.3×0.72 = 0.741
Seuil minimum : 0.35 — tout ce qui est en dessous est éliminé
-- Les résultats présents dans LES DEUX recherches sont naturellement favorisés L'alternative est la Reciprocal Rank Fusion (RRF), qui utilise la position plutôt que les scores bruts :
score_RRF(d) = Σ 1 / (k + rang(d)) (k = 60 standard)
Observation | Rang séman. | Rang mots-clés | Score RRF
-------------------------------|:-----------:|:--------------:|:---------:
"implant moy. 91 vs ortho 72" | #1 | #2 | 1/61 + 1/62 = 0.0325
"client préfère le narratif" | #2 | #5 | 1/62 + 1/65 = 0.0315
"featured snippet prix" | #4 | #1 | 1/64 + 1/61 = 0.0320 Agentics utilise la fusion pondérée parce qu'elle préserve l'amplitude du match. Un match sémantique quasi-parfait (0.92) se distingue clairement d'un match médiocre (0.31). RRF aplatit cette distinction — rang #1 vs #4 ne dit rien sur combien meilleur est le premier. Pour la récupération de mémoire agent, où la qualité du match impacte directement le raisonnement, préserver l'amplitude des scores est essentiel.
Tout ce qui précède converge dans un seul système : la couche Soul Search. Un fichier SQLite par projet, à côté du backend, contenant l'index hybride sur toutes les souls des agents de ce projet.
-- Chunks de soul : sections markdown découpées en blocs cherchables
CREATE TABLE soul_chunks (
chunk_id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_key TEXT NOT NULL, -- 'copywriter', 'serp_scout', 'angela'...
section TEXT NOT NULL, -- 'auto_observations', 'human_notes', 'archive'
text TEXT NOT NULL,
tokens INTEGER NOT NULL,
source TEXT DEFAULT 'soul_md',
created_at TEXT NOT NULL,
embedding_hash TEXT -- SHA256 : évite le re-embedding si inchangé
);
-- Recherche mots-clés BM25 (via FTS5)
CREATE VIRTUAL TABLE soul_chunks_fts USING fts5(
text, content='soul_chunks', content_rowid='chunk_id',
tokenize='porter unicode61'
);
-- Recherche vectorielle (similarité cosinus, embeddings locaux)
CREATE VIRTUAL TABLE soul_chunks_vec USING vec0(
chunk_id INTEGER PRIMARY KEY,
embedding FLOAT[768] -- all-mpnet-base-v2 = 768 dimensions
); Contexte du run : "Écriture article : Guide implant dentaire 2026. Mots-clés : implant dentaire, prix implant"
Étape 1 — Embedding du contexte (modèle local, ~8ms)
Étape 2 — Recherche parallèle (multiplicateur ×4)
BM25 search → 20 candidats (Score = 1/(1+rang))
Vector search → 20 candidats (Score = 1 - distance_cosinus)
Étape 3 — Fusion pondérée
score = 0.7 × score_sémantique + 0.3 × score_mots_clés
(chunk absent d'une recherche → score = 0 de ce côté)
Étape 4 — Filtrage
• Seuil minimum : 0.35
• Tri décroissant par score fusionné
• Cap à 5 résultats
Résultats :
[copywriter] "Articles implant moy. 91 vs ortho 72" score : 0.87
[human_note] "Dr. Martin veut des cas cliniques" score : 0.72
[serp_scout] "'implant dentaire prix' — featured snippet" score : 0.68
[copywriter] "Note 5/5 sur 'Guide implant 2026'" score : 0.61
[signal_analyst] "Concurrent a publié 3 articles sur les prix" score : 0.45 Si Agentics a besoin de 5 résultats finaux, chaque recherche retourne 20 candidats. Ce surplus donne à la fusion plus de matière pour identifier les résultats croisés — les observations qui apparaissent dans les deux recherches remontent naturellement, parce qu'elles scorent sur les deux dimensions.
# Rang BM25 → normalisé 0–1
def normalize_bm25(rank):
return 1 / (1 + rank)
# rank=0 (meilleur) → score=1.0
# rank=1 → score=0.5
# rank=9 → score=0.1
# Distance cosinus → similarité
def cosine_to_similarity(distance):
return 1 - distance
# distance=0.0 (identique) → score=1.0
# distance=0.2 → score=0.8
# distance=0.5 → score=0.5 C'est ce qui distingue Agentics des systèmes RAG génériques. La soul est découpée en sections fixes (Identité, Brand Voice, Contexte Métier, Règles) et sections dynamiques (observations, notes, archive). Les sections fixes sont toujours injectées intégralement — elles sont courtes et toujours pertinentes. Les sections dynamiques passent par soul_search() :
get_smart_soul_context(project_id, agent_key="copywriter", run_context="écrire article implant")
1. Injecter INTÉGRALEMENT : Identité + Brand Voice + Contexte Métier + Règles
→ Toujours pertinent, toujours court (~500 tokens total)
2. Appeler soul_search(run_context) pour les observations
→ Retourne les 5 observations les plus pertinentes cross-agent
→ Remplace l'ancien mode « tout balancer d'un coup »
3. Injecter INTÉGRALEMENT : Résumé de performance
→ Court, toujours utile (~60 tokens)
Résultat : contexte précis (~800 tokens) au lieu du dump complet (~2500 tokens)
Même valeur informationnelle, 70% de bruit en moins. Pour les agents stratégiques comme Angela (l'orchestratrice) et le Briefing Writer, Agentics va plus loin. Ces agents reçoivent soul_search et soul_get comme outils appelables — ils décident activement quand et quoi chercher, plutôt que de recevoir passivement des résultats pré-filtrés :
# Angela décide d'explorer un pattern cross-agent
SOUL_TOOLS = [
{
"name": "soul_search",
"description": "Chercher dans la mémoire collective de tous les agents du projet.",
"input_schema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"agent": { "type": "string", "description": "Limiter à un agent (optionnel)" }
},
"required": ["query"]
}
},
{
"name": "soul_get",
"description": "Récupérer le texte complet d'une observation spécifique par ID.",
"input_schema": {
"type": "object",
"properties": {
"chunk_id": { "type": "integer" }
},
"required": ["chunk_id"]
}
}
] Ce pattern en deux étapes garde la fenêtre de contexte lean. La recherche retourne des previews courts (200 caractères). L'agent ne charge le texte complet que pour ce qu'il utilise réellement. C'est la façon dont un humain utilise un index de livre — on scanne la table des matières d'abord, puis on ouvre la bonne page.
C'est ici qu'Agentics fait quelque chose qu'aucun autre framework ne propose. Parce que les observations des 12 agents sont indexées dans le même fichier SQLite par projet, n'importe quel agent peut chercher dans la mémoire collective de tous les agents.
Exemple concret : Angela prend ses décisions stratégiques hebdomadaires. Elle appelle soul_search("cannibalisation mots-clés implant") sur tous les agents. Reviennent des résultats du SERP Scout (données de positions d'il y a 3 semaines), du Signal Analyst (analyse de tendances concurrentielles), du Copywriter (quels sujets d'articles se chevauchaient), et une note humaine (le client veut consolider). Quatre agents, des semaines d'historique, une seule requête. Aucun agent seul n'avait la vision complète — mais ensemble, via l'index, le pattern est clair.
C'est rendu possible par un seul choix architectural : un index SQLite par projet, pas par agent. Chaque observation de chaque agent est indexée dans le même fichier. La colonne agent_key permet de filtrer sur un seul agent si nécessaire, mais le défaut est la recherche à l'échelle du projet. L'index ne se soucie pas du nombre d'agents — ajoutez un 13ème, un 25ème agent, et leurs observations sont automatiquement indexées et cherchables.
Un dernier élément critique. Quand les agents accumulent plus de 15 observations, une étape de compression intelligente résume les plus anciennes en quelques bullet points. Ça garde le document soul lisible. Mais les observations brutes — avec leurs scores, dates et détails spécifiques — sont archivées dans soul_observations_archive avant que la compression n'ait lieu. L'archive est indexée dans l'index hybride SQLite. Donc même des observations compressées il y a 3 mois restent cherchables via soul_search().
La soul affiche le résumé. L'archive préserve les détails. L'index rend tout cherchable. Rien n'est jamais perdu.
Agentics, c'est ce qui se passe quand on prend la récupération de mémoire au sérieux pour des agents IA en production. Pas du RAG greffé sur un chatbot — un système conçu spécifiquement pour que 12 agents autonomes accumulent, compriment, cherchent et croisent des mois d'intelligence opérationnelle.
Le Soul System fournit la structure — trois niveaux hiérarchiques (Identité, Global, Projet), six sections par agent, auto-apprentissage depuis l'exécution des pipelines, compression intelligente qui préserve les données clés, et du markdown éditable par l'humain qui n'est jamais une boîte noire.
La recherche hybride fournit la précision — BM25 pour les scores, dates et identifiants exacts ; embeddings sémantiques locaux pour le sens et les synonymes ; fusion pondérée 70/30 qui favorise la compréhension tout en préservant la précision littérale. Le tout en local, ~15-20ms par recherche, zéro coût API.
L'intelligence cross-agent fournit la rupture — un index par projet signifie que n'importe quel agent peut chercher dans l'historique des 12 agents. Des patterns qu'aucun agent seul ne pourrait détecter émergent quand l'orchestratrice corrèle des semaines de données SERP, de performance de contenu, de mouvements concurrentiels et de feedback humain en une seule requête.
L'archive d'observations garantit que rien n'est jamais perdu — les compressions gardent la soul lisible, mais les observations brutes sont préservées et indexées. Un agent peut retrouver un data point spécifique d'il y a 6 mois, bien après sa compression hors de la soul visible.
C'est ça, « des agents qui se souviennent ». Pas un slogan marketing — un système de mémoire cherchable, croisé, auditable, avec récupération hybride, tournant sur une infrastructure dédiée sans aucune dépendance tierce.
Nous concevons et déployons des équipes d'agents autonomes avec des systèmes de mémoire de niveau production pour le SEO et le marketing digital.