21 de julio de 2026

Cinco copias de un plugin: lo que un despliegue desordenado intentaba decirme

Cinco copias de un plugin: lo que un despliegue desordenado intentaba decirme

La semana pasada fui a buscar un pequeño fallo y encontré cinco copias del mismo software.

El software es hermes-memory-pgvector, un plugin de código abierto que mantengo. Proporciona a una flota de agentes de IA una memoria compartida y persistente sobre PostgreSQL y pgvector — de modo que lo que aprende un agente, los demás pueden recordarlo. Está publicado en PyPI. Puedes instalarlo con pip.

Y, sin embargo, en mi propio servidor se estaba ejecutando desde un checkout de git oculto dentro del árbol de código fuente de otro proyecto, a una rutina de limpieza de ser eliminado. También había una copia vendorizada en mi repositorio de infraestructura que no usaba nada, un directorio de compilación obsoleto, una caché de paquetes y un enlace simbólico hecho a mano creado minutos antes para evitar que todo el montaje se viniera abajo.

Ese es el tipo de configuración que heredas de ti mismo seis meses después y que no puedes justificar de inmediato.

Por qué pip install no era suficiente

Aquí está la parte que realmente explica el lío.

El framework anfitrión descubre los plugins de memoria mediante búsqueda en directorios. Mira dentro de su propia carpeta de plugins incluida, y dentro de una carpeta de plugins de usuario, en busca de un subdirectorio que contenga un __init__.py que mencione la clase correcta. Ese es todo el mecanismo de descubrimiento. Nunca inspecciona paquetes de Python instalados, y no existe registro de entry-point.

Así que pip install puso mi plugin en la máquina, correctamente, en exactamente el entorno adecuado — y el framework no podía verlo. Instalado perfectamente. Completamente invisible.

Ante eso, lo obvio es poner una copia donde el escáner mire. Copiar la carpeta al repositorio de despliegue. Colocarla junto al framework. Enlazarla con un symlink. Cada una de esas decisiones es razonable localmente, y juntas son la forma de acabar con cinco copias y un despliegue que nadie puede explicar.

La solución: dejar de pelearse con el escáner

La versión que publiqué esta semana añade un comando, que se ejecuta una vez después de instalar:

pip install hermes-memory-pgvector
hermes-pgvector install

Ese segundo comando escribe un pequeño shim en la carpeta que lee el escáner — unas pocas líneas cuya única tarea es importar el paquete real, instalado con pip. El escáner encuentra un directorio y queda satisfecho. Python resuelve la importación hacia la biblioteca correctamente instalada.

Esa única capa de indirection reduce cinco copias a una. Las actualizaciones pasan a ser un pip upgrade y un reinicio. Volver atrás significa fijar la versión anterior. Nada está vendorizado, nada está extraído del repositorio, nada se desvía, y las tareas rutinarias de mantenimiento en el repositorio de otra persona ya no pueden llevarse por delante mi capa de memoria.

Qué más salió de todo ello

Consolidar el despliegue significó volver a leer el código como es debido, lo que sacó a la luz varios errores reales que merecen ser nombrados:

Una búsqueda de subcadena que no lo era. Editar una memoria almacenada comparaba tu texto con la base de datos usando SQL LIKE, donde % es un comodín. Una memoria que contuviera "revenue grew 15% YoY" podía coincidir — y modificar — filas no relacionadas. Las barras invertidas tenían el problema opuesto: una ruta de archivo de Windows no coincidía con nada en absoluto, sin avisar.

Una cola que perdía trabajo mientras afirmaba terminarlo. El escritor en segundo plano aceptaba escrituras y luego, al apagarse, si casualmente estaba ocupado, se marchaba dejando atrás todo lo que seguía en cola sin una palabra en los registros.

Un error engañoso. Si apuntabas el plugin a un modelo de embeddings con el tamaño de salida incorrecto, obtenías un 404 de una ruta alternativa no relacionada, en lugar de "expected 768 dimensions, got 1024.".

Nada de esto era exótico. Todo ello eran cosas que revela una lectura cuidadosa y que una suite de pruebas que pasa por encima no detecta.

La lección que sigo reaprendiendo

Cuando un despliegue es un desastre, normalmente el desastre está diciendo algo verdadero sobre el diseño.

Las cinco copias no eran descuido. Cada una era una solución provisional racional. Eran un síntoma, y el problema de fondo era que mi paquete no podía ser descubierto de la forma en que estaba destinado a ser instalado. Arreglar las soluciones provisionales una por una habría producido un desastre más ordenado. Arreglar la brecha de descubrimiento hizo innecesarias las soluciones provisionales.

Si mantienes algo que la gente instala de una manera y despliega de otra, merece la pena cerrar esa brecha. Probablemente te esté intentando decir algo.


hermes-memory-pgvector tiene licencia BSD-3, y vive en GitHub y en PyPI.