Seis semanas después de que v0.4.2 hiciera el plugin instalable con pip, ya está aquí hermes-memory-pgvector v0.5.0. Es la primera versión de la línea 0.x con un cambio realmente incompatible, y la razón es un nombre que no debería haber tomado en primer lugar.
La colisión
El paquete de importación se llamaba pgvector. Ese nombre pertenece a pgvector-python. Instala ambos en el mismo virtualenv y el que se haya instalado el último gana el nombre de nivel superior — momento en el que el shim de descubrimiento de este plugin podía importar por completo el módulo equivocado.
El modo de fallo es lo que hace que merezca un salto de versión mayor o similar: el cargador trata una importación incorrecta como "plugin ausente" y recurre a la memoria integrada en una sola línea de log. No se rompe nada. La flota simplemente deja de compartir memoria en silencio, y te enteras semanas después, cuando la recuperación vuelve vacía.
Así que el paquete de importación ahora es hermes_pgvector. Tres cosas que no cambiaron, porque son espacios de nombres separados y solo uno de ellos se movió:
- la distribución — sigue siendo
hermes-memory-pgvector - la CLI — sigue siendo
hermes-pgvector - el proveedor hermes — sigue siendo
pgvector(memory.provider: pgvector)
Actualización
v0.5.0 declara el punto de entrada hermes_agent.memory_providers, así que en un host lo bastante nuevo como para resolver plugins de esa forma, una simple instalación con pip ya es suficiente y el shim deja de ser necesario. Un directorio shim sigue teniendo prioridad sobre el punto de entrada, que es exactamente por lo que hay que deshacerse de uno obsoleto:
pip install -U hermes-memory-pgvector
# preferido: elimina el shim, deja que el punto de entrada lo resuelva
hermes-pgvector install --remove
# host más antiguo que solo escanea directorios de plugins:
hermes-pgvector install --force
hermes memory status # se espera: Provider: pgvector; Status: available
Comprueba tus trabajos programados antes de marcharte. Cualquier invocación python -m pgvector ... ahora es hermes-pgvector ..., y falla en silencio — la recarga nocturna simplemente se detiene, y las filas escritas durante una caída del embedding siguen sin poder buscarse:
sudo grep -rl 'python -m pgvector' /etc/systemd/system/ /etc/cron.d/
La parte que no esperaba
Una revisión de todo el código base de los 24 archivos rastreados encontró 28 incidencias confirmadas. Las que dolieron no fueron casos límite — fueron contratos de configuración que se contradijeron por completo dentro de este repositorio, cada uno invirtiendo en silencio un control mientras parecía estar configurado correctamente:
allowed_themesse declaraba como cadena y se consumía como lista. Una allow-list de cadena se iteraba carácter por carácter, así que cada tema fallaba la comprobación de pertenencia y toda la flota se enroutaba adefault. La gobernanza de identidad parecía configurada mientras hacía exactamente lo contrario.- Los interruptores booleanos ignoraban
false.embed_on_write,sync_turns,hybrid_searchybulk_sync_on_initse declaran por el esquema como las cadenas"true"/"false", y luego se leen con simple veracidad.bool("false")esTrue. - Los timeouts de embedding nunca se pasaban desde la configuración. Cada llamador usaba un 10s codificado. Frente a un endpoint que respondía en 6–17s, una gran parte de las escrituras agotaban el tiempo y quedaban con embeddings NULL — y los reintentos no podían ayudar, porque cada intento estaba limitado por debajo de la latencia que necesitaba. Ahora se divide por ruta:
embed_timeout(10s, hilo del agente) yembed_write_timeout(30s, escritor en segundo plano). replace()actualizaba cero filas. Un UPDATE masivo chocaba conUNIQUE(agent_identity, target, content)y lanzabaUniqueViolation, así que un replace que coincidía con dos o más entradas no hacía nada mientras el estado de la herramienta integrada avanzaba.- Cada turno de conversación se escribía dos veces.
sync_turnyon_session_endcapturaban ambos los mismos turnos, yconversationsno tiene restricción única. - Una base de datos caída permanecía en silencio. Los fallos del worker se registraban en
debugy_healthynunca se volvía a comprobar, así que un reinicio de Postgres descartaba cada escritura durable de la sesión sin ninguna señal.
Esa es la historia honesta de esta versión: el cambio de nombre es el titular, y los errores de configuración son la razón por la que el cambio de nombre merecía hacerse ahora y no en 1.0.
El bucketing de identidad ahora se aplica en ambos lados
whatsapp-dm, el nuevo external-group y _bench eran solo sumideros del lado de escritura. La normalización de identidad eliminaba PII de la identidad, pero los cuerpos de los mensajes seguían viviendo en content y nada los filtraba al leer — así que cualquier tema podía llevar contenido de DM a su contexto mediante scope='all'. Ahora scope='all' excluye esos sumideros y nombrar uno explícitamente se rechaza. Un agente que sí es un bucket conserva acceso completo a sus propias filas, y la recuperación ordinaria entre temas no cambia.
Un matiz que merece leerse con atención: la puerta se excluye por nombre de bucket, y el bucketing ocurre en tiempo de escritura. Las filas nunca se reescriben retroactivamente — eso es deliberado, ya que de otro modo las filas históricas dejarían de poder recuperarse. Las filas escritas antes de que su clave fuera bucketed conservan la identidad en bruto. hermes-pgvector remap --old <raw> --new whatsapp-dm previsualiza el cambio; añade --execute para realizarlo de verdad.
Validación
100 tests en modo skip, 145 contra Postgres 16 en vivo con pgvector 0.8.6 y las cuatro migraciones aplicadas. No hay cambios de esquema ni migraciones nuevas en esta versión.
Actualización — v0.5.1
v0.5.1 llegó una hora después, y es la única versión de esta línea sobre la que no deberías dejar pasar el tiempo: un solo memory remove borraba cada entrada espejada de un tema, no una.
La operación remove de la herramienta integrada lleva su destino en old_text y deja content vacío, y el host la reenvía a través de metadatos en lugar de contenido. _worker estaba pasando old_text=item.content, que por tanto siempre era "" — así que store.remove construía content LIKE '%%', y eso coincide con todas las filas. Un remove borraba todo el espejo para ese par (agent_identity, target). El almacén integrado nunca se vio afectado; solo el espejo pgvector. Comprobé la implantación de referencia en busca de daños y no encontré ninguno — el historial de cada tema es continuo, lo que en su mayoría te indica lo raros que son los removes en la práctica.
Está corregido en dos capas, porque una sola no basta para un borrado silencioso de alcance total: _worker ahora lee extra["old_text"] y rechaza un remove sin un destino utilizable, y store.remove() rechaza de forma directa un patrón vacío, así que el borrado destructivo es inalcanzable por omisión desde cualquier llamador. remove() además ahora borra como mucho una fila, igual que el integrado.
La misma versión corrigió el problema de contenido vacío que estaba detrás. Nada rechazaba un add/replace vacío, así que podía existir una fila que embed() nunca puede procesar — EmbeddingError("empty input") es incondicional para texto vacío. La recarga nocturna lo reintentaba entonces en cada ejecución, manteniendo failed por encima de cero y haciendo inalcanzable remaining == 0, lo que destruye la única señal que un operador realmente vigila: una fila atascada permanentemente se vuelve indistinguible de un fallo genuino nuevo. Las escrituras vacías ahora se omiten (remove excluido), y las filas no embebibles se excluyen del barrido y se informan aparte como unembeddable.
Dos cosas menores que merecen nombrarse, porque ambas eran invisibles. trim() de Postgres recorta solo espacios, así que una fila que contuviera solo un salto de línea o una tabulación seguía colándose y seguía fallando para siempre — ahora cada sitio usa content ~ '\S'. Y esos predicados eran simples f-strings, donde \S es una secuencia de escape inválida: cuatro DeprecationWarning hoy, SyntaxWarning en 3.12+, y bajo -W error el módulo falla al importar directamente — lo que haría que el cargador de hermes-agent recayera en silencio a la memoria integrada.
Validación: 118 tests en modo skip, 164 contra Postgres 16 en vivo con pgvector 0.8.6, las cuatro migraciones aplicadas. Dos pasadas de revisión — la primera encontró el error de pérdida de datos, la segunda encontró la secuencia de escape rota en esa corrección.
Consíguelo
pip install hermes-memory-pgvector
Fuente, notas de actualización y referencia completa de configuración en GitHub:
