Dale a Hermes Agent una memoria semántica con LanceDB: instala, configura y evalúa


Una de las experiencias más frustrantes con un agente de IA es que olvida. El lunes le dices: «uso pnpm workspaces y despliego con wrangler». El viernes, en una sesión nueva, no tiene ni idea de lo que le hablas. Hermes Agent sí tiene memoria integrada (MEMORY.md / USER.md en ~/.hermes/memories/) y recall entre sesiones, pero en el fondo es coincidencia léxica. Di «deploy» como «ship it», o «pnpm» como «the package manager», y la memoria simplemente no aparece.

En agosto de 2026, LanceDB publicó un plugin oficial de memoria semántica para Hermes Agent — hermes-agent-memory — que convierte esto en un problema puramente de ingeniería: los hechos se guardan como vectores en una tabla local de LanceDB y el recall se basa en la similitud semántica en lugar de las palabras clave. En el benchmark LongMemEval del plugin, el recall puramente vectorial consigue una precisión de 0.661 y un Recall@5 de 0.795, claramente por delante de la búsqueda de sesión FTS5 integrada de Hermes, con 0.533 / 0.659.

Esta publicación te guía por la instalación completa, explica cómo funcionan las cuatro herramientas de memoria, cómo elegir entre los modos de recuperación híbrida y analiza con cuidado las cifras del benchmark.


1. Primero, entiende la arquitectura de memoria de Hermes

Antes de instalar nada, dedica dos minutos a entender cómo está diseñada la capa de memoria de Hermes; de lo contrario, puedes instalarlo todo y preguntarte por qué no funciona nada.

La memoria de Hermes es una arquitectura de providers. agent/memory_provider.py define una interfaz abstracta MemoryProvider, y agent/memory_manager.py la orquesta (prefetch, sync, shutdown). Dos hooks importan sobre todo:

  • on_pre_compress(messages) — extrae lo que merece la pena conservar antes de que se ejecute la compresión de contexto;
  • on_session_end(messages) — una última pasada de extracción al final de la sesión.

El comando interactivo hermes memory setup escanea los providers instalados en plugins/memory/, te deja elegir uno y escribe memory.provider: <name> en ~/.hermes/config.yaml (la línea 277 de hermes_cli/memory_setup.py es exactamente esa escritura). En otras palabras: el backend de memoria es un registro de providers conectables. De serie, Hermes trae ocho providers: mem0, hindsight, honcho, supermemory, byterover, retaindb, holographic y openviking.

El plugin de LanceDB usa el mismo canal: se registra como memory provider, se instala en ~/.hermes/plugins/lancedb/ mediante hermes plugins install y luego se selecciona en hermes memory setup.

¿Quieres probarlo sin tocar tu configuración actual? hermes profile create lancedb-demo crea un perfil aislado; añade -p lancedb-demo a cada comando de abajo y ejecuta rm -rf ~/.hermes/profiles/lancedb-demo cuando termines.

2. Instalación: cuatro pasos, unos cinco minutos

Paso 1: instala el plugin

hermes plugins install lancedb/hermes-agent-memory

Esto hace un shallow clone de https://github.com/lancedb/hermes-agent-memory.git en ~/.hermes/plugins/lancedb/. Vuelve a ejecutar el mismo comando más adelante para actualizarlo.

Paso 2: instala las dependencias de ejecución en el Python propio de Hermes

Hermes carga los plugins dentro de su propio intérprete, así que las dependencias deben ir al venv de Hermes, no a un virtualenv aparte:

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

Nota: el intérprete de Hermes es compartido por todos los perfiles, así que este paso no lleva la bandera -p y solo hay que ejecutarlo una vez. La configuración por defecto no necesita ningún stack de ML local: los embeddings pasan por la API de OpenAI. Solo si activas el reranker cross-encoder necesitarás sentence-transformers (que arrastra unos 2GB de torch).

Paso 3: activa el provider

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

Espera una salida como esta, y memory.provider: lancedb escrito en ~/.hermes/config.yaml:

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

Por defecto, los embeddings usan OpenAI text-embedding-3-small (1536 dimensiones), así que debe haber una OPENAI_API_KEY disponible.

Paso 4: verifica (no te lo saltes)

El informe de «la memoria no funciona» más común es, simplemente, que el provider no está activo: si memory.provider no está definido, Hermes recurre silenciosamente a sus notas integradas y nunca verás las herramientas lancedb_*. Confirma que está activado:

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 no muestra nada (o muestra el provider equivocado), vuelve a ejecutar hermes memory setup y elige lancedb de nuevo.

3. Las cuatro herramientas de memoria

Una vez activo, el agente gana cuatro herramientas nuevas:

Herramienta Propósito
lancedb_recall Recall vectorial (por defecto) o híbrido sobre la memoria del workspace; devuelve IDs, fragmentos, puntuaciones e IDs de turno de procedencia
lancedb_remember Guarda un hecho duradero, con tipo preference / entity / event / case / pattern / general; deduplicado por hash de contenido
lancedb_read Recupera una memoria por ID, opcionalmente con los turnos de procedencia de los que se extrajo
lancedb_forget Borrado en dos pasos: action: preview lista los candidatos por descripción y luego action: delete con el ID exacto

El bloque de system prompt del provider indica al modelo cuándo usar cada herramienta: lancedb_remember solo cuando el usuario pide explícitamente recordar algo, y siempre preview antes de cualquier borrado, para que el agente no pueda eliminar una memoria importante por accidente.

Más allá de las llamadas explícitas, hay un pipeline de extracción automática: cuando una sesión acumula suficientes turnos (por defecto min_turns: 3), un LLM auxiliar extrae los hechos duraderos de la conversación, tanto antes de la compresión de contexto como al final de la sesión. Aunque nunca digas «recuerda esto», la información útil a largo plazo se sigue guardando: ese es el mecanismo de «cuanto más lo usas, más listo se vuelve el agente».

4. Modos de recuperación: vector vs híbrido

El recall es el corazón de la memoria semántica, y el plugin te da dos niveles de control:

1. Modo de búsqueda (por llamada): vector (por defecto) o hybrid (vector + texto completo BM25); se sobreescribe en cada llamada mediante el parámetro mode de lancedb_recall.

2. Fusión híbrida (configuración global): en modo hybrid, cómo se combinan la parte vectorial y la de texto completo lo define plugins.lancedb.retrieval.reranker.type:

  • rrf (por defecto) — Reciprocal Rank Fusion, fusión por rango con pesos iguales;
  • linear — combinación lineal ponderada; reranker.weight (por defecto 0.7) favorece la parte vectorial;
  • cross-encoder — reordena un pool sobremuestreado con un modelo local de sentence-transformers; máxima calidad, más lento.

Ejemplo de configuración (~/.hermes/config.yaml; escribe solo las claves que quieras sobreescribir):

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

También existe un modo puramente léxico fts, pero los autores desaconsejan explícitamente usarlo: la coincidencia solo por palabras clave tiende a sacar filas irrelevantes y casuales que contaminan el contexto del agente. El recall semántico vive en vector / hybrid.

5. Backends de embeddings: no solo OpenAI

En la configuración por defecto, la única llamada remota es la API de embeddings; todo lo demás es local. Apunta el cliente compatible con OpenAI a cualquier endpoint que hable el mismo formato y listo, sin cambios de código. Algunos ejemplos prácticos:

# 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

Cambiar de modelo de embeddings requiere cuidado: si la dimensión del nuevo modelo no coincide con la tabla existente, el plugin falla de forma ruidosa en lugar de devolver silenciosamente nada. La solución es borrar ~/.hermes/lancedb/memories.lance/ y dejar que la siguiente sesión vuelva a crear la tabla (perfecto si no te importan las memorias antiguas).

El LLM auxiliar que se usa para extraer hechos también puede apuntar a un modelo más barato, a través del enrutamiento auxiliar propio de Hermes (el enrutamiento de providers, el fallback y el agotamiento de crédito los gestiona por ti):

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

6. Cómo leer el benchmark: ¿es realmente mejor el recall semántico?

El repositorio del plugin trae un banco de pruebas de QA de conversaciones largas LongMemEval-S (60 casos estratificados, respondidos por gpt-5.4 y evaluados por gpt-5.4-mini, top-k 5). Compara las opciones de recall que tiene realmente un usuario de Hermes:

Variante Accuracy Recall@5 MRR@5 Query p50
hermes-session-search (baseline FTS5/BM25 integrado) 0.533 0.659 0.639 0.002s
lancedb-vector (por defecto) 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

Conclusiones destacables:

  • El recall semántico gana claramente a la línea base léxica: 0.661 frente a 0.533 de precisión y 0.795 frente a 0.659 de Recall@5; la paráfrasis (deploy vs ship) es justo donde BM25 se queda ciego, y la recuperación vectorial es mucho más robusta, con ~0.2s por consulta.
  • El RRF con pesos iguales en realidad perjudica (0.610 < 0.661): los aciertos léxicos ruidosos desplazan a los buenos resultados vectoriales. Por eso el valor por defecto que se distribuye es vector, no hybrid.
  • Si quieres señal léxica, usa linear, no RRF: la fusión ponderada recupera el Recall@5 hasta 0.718 con un coste de latencia mínimo.
  • El cross-encoder lidera en calidad (0.678 / 0.754), pero el p50 sube a ~0.7s y necesita torch: para entornos tolerantes a la latencia y sensibles a la precisión.

Los autores las etiquetan como ilustrativas: la precisión absoluta depende del modelo que responde (aquí gpt-5.4), pero el orden relativo de los métodos de recuperación es estable. Además, el banco de pruebas solo mide el sustrato de recuperación (recall literal de los turnos originales), no el ciclo completo de extracción de hechos: en uso real, la recuperación basada en hechos puede funcionar incluso mejor.

7. Estructura de almacenamiento y compactación automática

Todo es local, sin servicios externos:

Ruta Contenido
~/.hermes/lancedb/memories.lance/ LanceDB dataset (fragments, manifest, indexes). Una única tabla memories; la columna kind separa las filas de hechos y de turnos
~/.hermes/lancedb/.last_optimize_version Archivo centinela: table.version en el último optimize() con éxito
~/.cache/huggingface/ Caché del reranker cross-encoder; solo presente cuando reranker.type: cross-encoder está activado

¿Quieres inspeccionar el almacén directamente, estilo 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())
"

Las cargas de trabajo de los agentes están dominadas por escrituras de una sola fila, y cada add/delete de Lance es un commit: sin intervención, los fragmentos diminutos y los archivos de versión se acumulan para siempre. La compactación automática del plugin (activada por defecto) compara la versión con el archivo centinela y ejecuta table.optimize(cleanup_older_than=timedelta(days=7)) en un hilo daemon cuando el delta supera optimize_every_commits (por defecto 50). Un lock no bloqueante garantiza un solo optimize a la vez y nunca bloquea a los escritores. Si la desactivas (maintenance.enabled: false), el dataset crece sin límite; por lo general, no se recomienda.

8. Referencia rápida de solución de problemas

  • hermes plugins list no muestra lancedb: comprueba que el symlink ~/.hermes/plugins/lancedb apunta al repositorio.
  • El agente solo escribe en la memoria integrada, sin herramientas lancedb_*: el provider no está activo. Ejecuta hermes memory status; quieres ver Provider: lancedb con available ✓; si está en blanco, vuelve a ejecutar hermes memory setup.
  • El recall falla con un error de autenticación: los embeddings llaman a la API de OpenAI; asegúrate de que OPENAI_API_KEY está definida (en el entorno o en ~/.hermes/.env).
  • El directorio .lance no deja de crecer: confirma que maintenance.enabled: true y que ~/.hermes/lancedb/.last_optimize_version avanza entre sesiones; si en agent.log aparece lancedb optimize starting, la compactación está en marcha.
  • Cambiaste embedding.model y el recall no devuelve nada: hay un desajuste de dimensiones. Borra ~/.hermes/lancedb/memories.lance/ para recrear la tabla.

Recapitulación

El plugin de LanceDB cubre la laguna más importante de la memoria a largo plazo de Hermes Agent: recuperar por significado, no por palabras clave. Cinco minutos de instalación, cero ajustes sobre los valores por defecto y todos los datos en local, a cambio de una precisión ~24% mejor (0.661 frente a 0.533) y un gran salto en Recall@5 en LongMemEval. Para quienes trabajan con conocimiento, significa que «lo que le contaste una vez vuelve a aparecer aunque lo preguntes de otra forma».

Lecturas relacionadas: