La semaine dernière, j’ai cherché un petit bug et j’ai trouvé cinq copies du même logiciel.
Le logiciel est hermes-memory-pgvector, un plugin open source que je maintiens. Il fournit à une flotte d’agents IA une mémoire partagée et durable sur PostgreSQL et pgvector — ainsi, ce qu’un agent apprend, les autres peuvent s’en souvenir. Il est publié sur PyPI. Vous pouvez l’installer avec pip.
Et pourtant, sur mon propre serveur, il s’exécutait depuis un checkout git caché à l’intérieur du code source d’un autre projet, à une routine de nettoyage près d’être supprimé. Il y avait aussi une copie intégrée dans mon dépôt d’infrastructure que rien n’utilisait, un répertoire de build obsolète, un cache de packages et un lien symbolique bricolé quelques minutes plus tôt pour empêcher tout l’ensemble de s’écrouler.
C’est le genre de configuration qu’on hérite de soi-même six mois plus tard et qu’on ne peut pas justifier immédiatement.
Pourquoi pip install ne suffisait pas
Voici la partie qui explique réellement ce chaos.
Le framework hôte découvre les plugins de mémoire en parcourant des répertoires. Il regarde dans son propre dossier de plugins intégré, puis dans un dossier de plugins utilisateur, à la recherche d’un sous-répertoire contenant un __init__.py qui mentionne la bonne classe. C’est tout le mécanisme de découverte. Il n’inspecte jamais les packages Python installés, et il n’y a aucune registration de point d’entrée.
Donc pip install avait bien placé mon plugin sur la machine, dans l’environnement exact et correctement — mais le framework ne pouvait pas le voir. Parfaitement installé. Totalement invisible.
Face à cela, la solution évidente est de placer une copie là où le scanner va regarder. Copier le dossier dans le dépôt de déploiement. Le vérifier à côté du framework. Le lier par un symlink. Chacune de ces options est une décision locale raisonnable, et ensemble elles expliquent comment on se retrouve avec cinq copies et un déploiement que personne ne peut expliquer.
La correction : arrêter de lutter contre le scanner
La version que j’ai publiée cette semaine ajoute une commande, exécutée une fois après l’installation :
pip install hermes-memory-pgvector
hermes-pgvector install
Cette seconde commande écrit un petit shim dans le dossier que le scanner lit — quelques lignes dont le seul rôle est d’importer le vrai package installé via pip. Le scanner trouve un répertoire et s’en contente. Python résout l’import vers la bibliothèque correctement installée.
Cette seule indirection ramène cinq copies à une seule. Les mises à niveau deviennent un pip upgrade et un redémarrage. Revenir en arrière signifie verrouiller la version précédente. Rien n’est intégré au dépôt, rien n’est extrait par checkout, rien ne dérive, et le ménage courant dans le dépôt de quelqu’un d’autre ne peut plus emporter ma couche de mémoire avec lui.
Ce que cela a révélé d’autre
Le fait de consolider le déploiement m’a obligé à relire correctement le code, ce qui a révélé plusieurs vrais bugs dignes d’être nommés :
Une recherche de sous-chaîne qui n’en était pas une. La modification d’une mémoire enregistrée comparait votre texte à la base de données avec LIKE en SQL, où % est un joker. Une mémoire contenant "revenue grew 15% YoY" pouvait correspondre — et modifier — des lignes sans rapport. Les antislashs posaient le problème inverse : un chemin de fichier Windows ne correspondait silencieusement à rien du tout.
Une file d’attente qui supprimait du travail tout en prétendant avoir terminé. Le writer en arrière-plan acceptait des écritures, puis, à l’arrêt, s’il était occupé, abandonnait tout ce qui restait en file sans un mot dans les logs.
Une erreur trompeuse. Si l’on pointait le plugin vers un modèle d’embeddings avec la mauvaise taille de sortie, on obtenait un 404 provenant d’un fallback sans rapport, au lieu de "expected 768 dimensions, got 1024."
Rien de tout cela n’avait quoi que ce soit d’exotique. Ce sont toutes des choses qu’une lecture attentive révèle et qu’une suite de tests qui passe ne voit pas.
La leçon que je réapprends sans cesse
Quand un déploiement est chaotique, ce chaos dit généralement quelque chose de vrai sur la conception.
Les cinq copies n’étaient pas de la négligence. Chacune était un contournement rationnel. Elles étaient un symptôme, et le problème sous-jacent était que mon package ne pouvait pas être découvert de la manière dont il était censé être installé. Corriger les contournements un par un n’aurait produit qu’un chaos plus propre. Corriger la faille de découverte a rendu les contournements inutiles.
Si vous maintenez quelque chose que les gens installent d’une manière et déploient d’une autre, cet écart mérite d’être comblé. Il essaie probablement de vous dire quelque chose.
hermes-memory-pgvector est sous licence BSD-3, et se trouve sur GitHub et sur PyPI.
