Last week I went looking for a small bug and found five copies of the same software.
The software is hermes-memory-pgvector, an open-source plugin I maintain. It gives a fleet of AI agents shared, durable memory on PostgreSQL and pgvector — so what one agent learns, the others can recall. It is published on PyPI. You can install it with pip.
And yet on my own server it was running from a git checkout hidden inside another project's source tree, one routine cleanup away from being wiped. There was also a vendored copy in my infrastructure repo that nothing used, a stale build directory, a package cache, and a hand-made symlink created minutes earlier to stop the whole arrangement from falling over.
That is the kind of setup you inherit from yourself six months later and cannot immediately justify.
Why pip install was not enough
Here is the part that actually explains the mess.
The host framework discovers memory plugins by scanning directories. It looks inside its own bundled plugins folder, and inside a user plugins folder, for a subdirectory containing an __init__.py that mentions the right class. That is the entire discovery mechanism. It never inspects installed Python packages, and there is no entry-point registration.
So pip install put my plugin on the machine, correctly, in exactly the right environment — and the framework could not see it. Perfectly installed. Completely invisible.
Faced with that, the obvious move is to put a copy where the scanner will look. Copy the folder into the deployment repo. Check it out next to the framework. Symlink it. Each of those is a reasonable local decision, and together they are how you end up with five copies and a deployment nobody can explain.
The fix: stop fighting the scanner
The release I shipped this week adds one command, run once after installing:
pip install hermes-memory-pgvector
hermes-pgvector install
That second command writes a small shim into the folder the scanner reads — a few lines whose only job is to import the real, pip-installed package. The scanner finds a directory and is satisfied. Python resolves the import to the properly installed library.
That one indirection collapses five copies into one. Upgrades become a pip upgrade and a restart. Rolling back means pinning the previous version. Nothing is vendored, nothing is checked out, nothing drifts, and routine housekeeping in someone else's repository can no longer take my memory layer with it.
What else fell out of it
Consolidating the deployment meant re-reading the code properly, which turned up several real bugs worth naming:
A substring search that was not one. Editing a stored memory matched your text against the database using SQL LIKE, where % is a wildcard. A memory containing "revenue grew 15% YoY" could match — and modify — unrelated rows. Backslashes had the opposite problem: a Windows file path silently matched nothing at all.
A queue that dropped work while claiming to finish. The background writer accepted writes, and then at shutdown, if it happened to be busy, walked away from everything still queued without a word in the logs.
A misleading error. Point the plugin at an embedding model with the wrong output size and you would get a 404 from an unrelated fallback, instead of "expected 768 dimensions, got 1024."
None of these were exotic. They were all things a careful read finds and a passing test suite does not.
The lesson I keep relearning
When a deployment is a mess, the mess is usually saying something true about the design.
The five copies were not carelessness. Each one was a rational workaround. They were a symptom, and the underlying problem was that my package could not be discovered the way it was meant to be installed. Fixing the workarounds one at a time would have produced a tidier mess. Fixing the discovery gap made the workarounds unnecessary.
If you maintain something that people install one way and deploy another, that gap is worth closing. It is probably trying to tell you something.
hermes-memory-pgvector is BSD-3 licensed, and lives on GitHub and on PyPI.
