Donnez une mémoire sémantique à Hermes Agent avec LanceDB : installation, configuration, benchmark


L’une des expériences les plus frustrantes avec un agent IA, c’est qu’il oublie. Vous lui dites lundi : « J’utilise les workspaces pnpm et je déploie avec wrangler. » Vendredi, dans une nouvelle session, il n’a aucune idée de ce dont vous parlez. Hermes Agent dispose bien d’une mémoire intégrée (MEMORY.md / USER.md sous ~/.hermes/memories/) et d’un rappel inter-sessions — mais au fond, il s’agit de correspondance lexicale. Dites « deploy » en disant « ship it », ou « pnpm » en parlant du « gestionnaire de paquets », et la mémoire n’est tout simplement pas retrouvée.

En août 2026, LanceDB a publié un plugin mémoire sémantique officiel pour Hermes Agent — hermes-agent-memory — qui transforme ce problème en simple problème d’ingénierie : les faits sont stockés sous forme de vecteurs dans une table LanceDB locale, et le rappel se fait par similarité sémantique plutôt que par mots-clés. Dans le benchmark LongMemEval du plugin, le rappel purement vectoriel obtient une précision de 0.661 / Recall@5 de 0.795, nettement devant la recherche de session FTS5 intégrée d’Hermes à 0.533 / 0.659.

Cet article vous guide pas à pas dans l’installation complète, explique comment fonctionnent les quatre outils mémoire, comment choisir entre les modes de recherche hybride, et lit attentivement les chiffres du benchmark.


1. Comprendre d’abord l’architecture mémoire d’Hermes

Avant d’installer quoi que ce soit, passez deux minutes à comprendre comment la couche mémoire d’Hermes est conçue — sinon vous risquez d’installer tout et de vous demander pourquoi rien ne fonctionne.

La mémoire d’Hermes repose sur une architecture de providers. agent/memory_provider.py définit une interface abstraite MemoryProvider, et agent/memory_manager.py l’orchestre (prefetch, sync, arrêt). Deux hooks comptent le plus :

  • on_pre_compress(messages) — extrait ce qui vaut la peine d’être conservé avant que la compression du contexte s’exécute ;
  • on_session_end(messages) — un dernier passage d’extraction à la fin d’une session.

La commande interactive hermes memory setup analyse les providers installés sous plugins/memory/, vous laisse en choisir un, et écrit memory.provider: <name> dans ~/.hermes/config.yaml (la ligne 277 de hermes_cli/memory_setup.py fait exactement cette écriture). En d’autres termes : le backend mémoire est un registre enfichable. D’usine, Hermes embarque huit providers : mem0, hindsight, honcho, supermemory, byterover, retaindb, holographic et openviking.

Le plugin LanceDB emprunte le même canal : il s’enregistre comme provider mémoire, s’installe dans ~/.hermes/plugins/lancedb/ via hermes plugins install, puis se sélectionne dans hermes memory setup.

Envie d’essayer sans toucher à votre configuration existante ? hermes profile create lancedb-demo crée un profil isolé ; ajoutez -p lancedb-demo à chaque commande ci-dessous, puis rm -rf ~/.hermes/profiles/lancedb-demo une fois terminé.

2. Installation : quatre étapes, environ cinq minutes

Étape 1 : installer le plugin

hermes plugins install lancedb/hermes-agent-memory

Cette commande clone en shallow https://github.com/lancedb/hermes-agent-memory.git dans ~/.hermes/plugins/lancedb/. Relancez la même commande plus tard pour récupérer les mises à jour.

Étape 2 : installer les dépendances d’exécution dans le Python d’Hermes

Hermes charge les plugins dans son propre interpréteur ; les dépendances doivent donc aller dans le venv d’Hermes — pas dans un virtualenv séparé :

# If you used the one-line installer:
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python3 lancedb openai pyyaml

Note : l’interpréteur d’Hermes est partagé entre tous les profils, donc cette étape n’a pas de drapeau -p et ne doit être exécutée qu’une seule fois. La configuration par défaut n’a besoin d’aucune pile ML locale — les embeddings passent par l’API OpenAI. Seule l’activation du reranker cross-encoder nécessite sentence-transformers (qui entraîne ~2 Go de torch).

Étape 3 : activer le provider

hermes memory setup
# pick "lancedb" in the interactive menu

Attendez-vous à une sortie de ce type, avec memory.provider: lancedb écrit dans ~/.hermes/config.yaml :

# ✓ LanceDB memory configured (embedding dim: 1536)
#  Start a new session to activate.

Les embeddings utilisent par défaut OpenAI text-embedding-3-small (1536 dimensions) ; une OPENAI_API_KEY doit donc être disponible.

Étape 4 : vérifier (ne sautez pas cette étape)

Le signalement « la mémoire ne fonctionne pas » le plus courant est tout simplement un provider inactif — si memory.provider n’est pas défini, Hermes retombe silencieusement sur ses notes intégrées et vous ne verrez jamais les outils lancedb_*. Vérifiez qu’il est bien actif :

hermes memory status          # look for: Provider: lancedb, installed ✓, available ✓
hermes plugins list           # should list "lancedb"
hermes chat -q "Hello"        # agent.log should contain "lancedb provider initialized"

Si memory status ne montre rien (ou le mauvais provider), relancez hermes memory setup et choisissez à nouveau lancedb.

3. Les quatre outils mémoire

Une fois actif, l’agent gagne quatre nouveaux outils :

Tool Purpose
lancedb_recall Rappel vectoriel (par défaut) ou hybride sur la mémoire de l’espace de travail ; renvoie les IDs, extraits, scores et IDs de tour de provenance
lancedb_remember Stocke un fait durable, typé preference / entity / event / case / pattern / general ; dédupliqué par hash du contenu
lancedb_read Récupère une mémoire par ID, éventuellement avec les tours de provenance dont elle a été extraite
lancedb_forget Suppression en deux étapes : action: preview liste les candidats par description, puis action: delete avec l’ID exact

Le bloc de system prompt du provider indique au modèle quand utiliser chaque outil : lancedb_remember uniquement lorsque l’utilisateur demande explicitement de se souvenir, et toujours preview avant toute suppression — pour que l’agent ne puisse pas effacer une mémoire importante par accident.

Au-delà des appels explicites, il existe un pipeline d’extraction automatique : dès qu’une session accumule assez de tours (par défaut min_turns: 3), un LLM auxiliaire extrait de la conversation les faits durables, à la fois avant la compression du contexte et en fin de session. Même si vous ne dites jamais « retiens ça », les informations utiles à long terme sont quand même persistées — c’est le mécanisme « plus vous utilisez l’agent, plus il devient intelligent ».

4. Modes de récupération : vector vs hybrid

Le rappel est le cœur de la mémoire sémantique, et le plugin vous offre deux niveaux de contrôle :

1. Mode de recherche (par appel) : vector (par défaut) ou hybrid (vector + plein texte BM25), à outrepasser à chaque appel via le paramètre mode de lancedb_recall.

2. Fusion hybride (configuration globale) : en mode hybrid, la façon dont les branches vectorielle et plein texte fusionnent est définie par plugins.lancedb.retrieval.reranker.type :

  • rrf (par défaut) — Reciprocal Rank Fusion, fusion à poids égaux basée sur les rangs ;
  • linear — combinaison linéaire pondérée ; reranker.weight (par défaut 0.7) favorise la branche vectorielle ;
  • cross-encoder — réordonne un pool suréchantillonné avec un modèle sentence-transformers local ; la meilleure qualité, le plus lent.

Exemple de configuration (~/.hermes/config.yaml — n’écrivez que les clés que vous voulez redéfinir) :

plugins:
  lancedb:
    retrieval:
      mode: hybrid          # vector (default) | hybrid
      top_k: 10
      reranker:
        type: linear        # rrf | linear | cross-encoder
        weight: 0.7

Un mode fts purement lexical existe aussi, mais les auteurs le déconseillent explicitement : une correspondance par mots-clés uniquement tend à remonter des lignes fortuites et hors sujet qui polluent le contexte de l’agent. Le rappel sémantique vit dans vector / hybrid.

5. Backends d’embedding : pas seulement OpenAI

Dans la configuration par défaut, le seul appel distant est l’API d’embedding — tout le reste est local. Pointez le client compatible OpenAI vers n’importe quel endpoint qui parle le même langage, et c’est réglé, sans modification de code. Quelques exemples pratiques :

# Non-OpenAI model via OpenRouter
plugins:
  lancedb:
    embedding:
      model: google/gemini-embedding-001
      base_url: https://openrouter.ai/api/v1
      api_key_env: OPENROUTER_API_KEY

# Fully local: Ollama
plugins:
  lancedb:
    embedding:
      model: nomic-embed-text
      base_url: http://localhost:11434/v1
      api_key_env: OLLAMA_API_KEY    # any value works for local Ollama

Changer de modèle d’embedding demande de la prudence : si la dimension du nouveau modèle ne correspond pas à la table existante, le plugin échoue bruyamment au lieu de renvoyer silencieusement rien. La solution consiste à supprimer ~/.hermes/lancedb/memories.lance/ et à laisser la prochaine session recréer la table (acceptable si les anciennes mémoires ne vous importent pas).

Le LLM auxiliaire utilisé pour l’extraction des faits peut aussi viser un modèle moins cher, via le routage auxiliaire propre à Hermes (routage des providers, fallback et épuisement du crédit gérés pour vous) :

auxiliary:
  lancedb_extraction:
    provider: openrouter
    model: google/gemini-3-flash

6. Lire le benchmark : le rappel sémantique est-il vraiment meilleur ?

Le dépôt du plugin embarque un banc d’essai QA de longues conversations LongMemEval-S (60 cas stratifiés, répondus par gpt-5.4, évalués par gpt-5.4-mini, top-k 5). Il compare les options de rappel dont dispose réellement un utilisateur d’Hermes :

Variant Accuracy Recall@5 MRR@5 Query p50
hermes-session-search (built-in FTS5/BM25 baseline) 0.533 0.659 0.639 0.002s
lancedb-vector (default) 0.661 0.795 0.682 0.207s
lancedb-hybrid-rrf 0.610 0.650 0.635 0.235s
lancedb-hybrid-linear 0.610 0.718 0.676 0.246s
lancedb-hybrid-cross-encoder 0.678 0.754 0.689 0.702s

À retenir :

  • Le rappel sémantique bat nettement la baseline lexicale : précision 0.661 contre 0.533, Recall@5 0.795 contre 0.659 — la paraphrase (deploy vs ship) est exactement là où BM25 devient aveugle, et la récupération vectorielle est bien plus robuste, à ~0.2s par requête.
  • Le RRF à poids égaux nuit en réalité (0.610 < 0.661) : des correspondances lexicales bruitées déplacent de bons résultats vectoriels. C’est pourquoi le défaut livré est vector, pas hybrid.
  • Si vous voulez du signal lexical, utilisez linear, pas rrf : la fusion pondérée ramène le Recall@5 à 0.718 pour un coût de latence minime.
  • Le cross-encoder domine en qualité (0.678 / 0.754) mais le p50 grimpe à ~0.7s et il exige torch — pour les configurations tolérantes à la latence et sensibles à la précision.

Les auteurs qualifient ces chiffres d’illustratifs : la précision absolue suit le modèle qui répond (ici gpt-5.4), mais l’ordre relatif des méthodes de récupération est stable. Le banc d’essai ne mesure d’ailleurs que le substrat de récupération (rappel textuel des tours d’origine), pas le cycle complet d’extraction des faits — en usage réel, une récupération orientée faits peut faire encore mieux.

7. Organisation du stockage et compaction automatique

Tout est local, aucun service externe :

Path Contents
~/.hermes/lancedb/memories.lance/ Dataset LanceDB (fragments, manifeste, index). Table unique memories ; la colonne kind sépare les lignes de type fait vs tour
~/.hermes/lancedb/.last_optimize_version Fichier sentinelle : table.version au dernier optimize() réussi
~/.cache/huggingface/ Cache du reranker cross-encoder ; présent uniquement quand reranker.type: cross-encoder est activé

Envie de fouiller le stockage directement, façon SQL :

uv run --project ~/.hermes/hermes-agent python -c "
import lancedb
db = lancedb.connect('~/.hermes/lancedb')
df = db.open_table('memories').to_pandas()
print(df[['kind', 'category', 'content']].head())
"

Les workloads d’agents sont dominés par des écritures mono-ligne, et chaque add/delete Lance est un commit — sans intervention, les petits fragments et fichiers de versions s’accumulent indéfiniment. La compaction automatique du plugin (activée par défaut) compare la version au fichier sentinelle et exécute table.optimize(cleanup_older_than=timedelta(days=7)) dans un thread démon dès que le delta dépasse optimize_every_commits (50 par défaut). Un verrou non bloquant garantit une seule optimisation à la fois et les écrivains ne sont jamais bloqués. Désactivez-la (maintenance.enabled: false) et le dataset grossit sans limite — généralement déconseillé.

8. Référence rapide de dépannage

  • hermes plugins list n’affiche pas lancedb : vérifiez que le symlink ~/.hermes/plugins/lancedb pointe bien vers le dépôt.
  • L’agent n’écrit que la mémoire intégrée, aucun outil lancedb_* : le provider n’est pas actif. Lancez hermes memory status — vous voulez voir Provider: lancedb avec available ✓ ; si c’est vide, relancez hermes memory setup.
  • Le rappel échoue avec une erreur d’authentification : les embeddings appellent l’API OpenAI — assurez-vous que OPENAI_API_KEY est définie (variable d’environnement ou ~/.hermes/.env).
  • Le répertoire .lance ne cesse de grossir : vérifiez que maintenance.enabled: true et que ~/.hermes/lancedb/.last_optimize_version progresse entre les sessions ; lancedb optimize starting dans agent.log signifie que la compaction tourne.
  • Vous avez changé embedding.model et le rappel ne renvoie rien : incohérence de dimension. Supprimez ~/.hermes/lancedb/memories.lance/ pour recréer la table.

En résumé

Le plugin LanceDB comble la lacune la plus importante de la mémoire à long terme de Hermes Agent : le rappel par le sens, pas par le mot-clé. Cinq minutes d’installation, aucun réglage sur les valeurs par défaut, toutes les données en local — en échange d’une précision supérieure d’environ 24 % (0.661 contre 0.533) et d’un bond du Recall@5 sur LongMemEval. Pour les travailleurs du savoir, cela signifie que « les choses que vous avez dites une fois reviennent, même quand vous demandez autrement ».

Pour aller plus loin :