先週、私は小さなバグを探しに行き、同じソフトウェアのコピーを5つ見つけました。
そのソフトウェアは hermes-memory-pgvector で、私が保守しているオープンソースのプラグインです。これは、AIエージェントの群れに PostgreSQL と pgvector 上で共有される永続的なメモリを与えます。つまり、あるエージェントが学んだことを、ほかのエージェントも思い出せるのです。PyPI で公開されています。pip でインストールできます。
それにもかかわらず、私自身のサーバーでは、別のプロジェクトのソースツリーの中に隠れた git checkout から実行されていました。しかもそれは、通常のクリーンアップのひと手間で消えてしまう場所でした。さらに、誰も使っていないインフラ用リポジトリ内の vendored コピー、古いビルドディレクトリ、パッケージキャッシュ、そして全体が崩れ落ちないように数分前に作られた手作りのシンボリックリンクまでありました。
そういう構成は、6か月後の自分から受け継いで、すぐには正当化できない類のものです。
なぜ pip install だけでは不十分だったのか
ここが、実際にこの混乱を説明している部分です。
ホスト側のフレームワークは、ディレクトリを走査することでメモリプラグインを見つけます。組み込みのプラグインフォルダと、ユーザー用プラグインフォルダの中を見て、正しいクラス名に触れている __init__.py を含むサブディレクトリを探します。仕組みはそれだけです。インストール済みの Python パッケージは調べませんし、エントリーポイント登録もありません。
そのため pip install によって、私のプラグインは正しい環境に、正しく、マシン上へ配置されました。しかしフレームワークからは見えなかったのです。正しくインストールされているのに、完全に不可視でした。
そうなると、当然の対応はスキャナが見る場所にコピーを置くことです。フォルダをデプロイ用リポジトリにコピーする。フレームワークの隣にチェックアウトする。シンボリックリンクを張る。どれも局所的には合理的な判断で、しかしそれらが積み重なると、5つのコピーと、誰にも説明できないデプロイが出来上がります。
解決策:スキャナと戦うのをやめる
今週出荷したリリースでは、インストール後に一度だけ実行するコマンドを1つ追加しました。
pip install hermes-memory-pgvector
hermes-pgvector install
2つ目のコマンドは、スキャナが読むフォルダに小さな shim を書き込みます。役割は数行だけで、本物の、pip でインストールされたパッケージを import することです。スキャナはディレクトリを見つけて満足し、Python は import を適切にインストールされたライブラリへ解決します。
この1つの間接化で、5つのコピーは1つにまとまります。アップグレードは pip の更新と再起動だけになります。ロールバックは、前のバージョンを固定するだけです。vendored されたものはなく、チェックアウトされたものもなく、ずれもなくなり、他人のリポジトリでの通常の整理整頓が、もはや私のメモリ層を巻き込むことはありません。
そこから他に見つかったこと
デプロイを一本化したことで、コードをきちんと読み直すことになり、名称を挙げる価値のある本物のバグがいくつも見つかりました。
部分文字列検索なのに、そうではなかったもの。 保存済みメモリの編集では、SQL LIKE を使ってテキストをデータベースと照合していました。% はワイルドカードです。"revenue grew 15% YoY" を含むメモリが、無関係な行に一致し、その行を変更してしまう可能性がありました。バックスラッシュには逆の問題がありました。Windows のファイルパスは、黙って何にも一致しませんでした。
終わったふりをしながら仕事を落としていたキュー。 バックグラウンドの writer は書き込みを受け付けていましたが、終了時に、たまたま処理中だった場合、ログに一言も残さずに、まだキューに残っているものすべてを置き去りにしていました。
誤解を招くエラー。 プラグインを出力サイズの異なる埋め込みモデルに向けると、"expected 768 dimensions, got 1024." ではなく、無関係なフォールバックからの 404 が返ってきました。
どれも珍しいものではありませんでした。注意深く読めば見つかるが、通り過ぎるテストスイートでは見つからないものばかりでした。
私が何度も学び直している教訓
デプロイが散らかっているとき、その散らかりはたいてい設計について何か本当のことを言っています。
5つのコピーは、不注意の結果ではありませんでした。それぞれが合理的な回避策でした。それらは症状であり、根本問題は、私のパッケージが、本来インストールされるべき方法で検出できなかったことでした。回避策を1つずつ直しても、見た目が少し整っただけの混乱になっていたでしょう。検出のギャップを直したことで、回避策は不要になりました。
人々が1つの方法でインストールし、別の方法でデプロイするものを保守しているなら、そのギャップは埋める価値があります。たぶん、何かを伝えようとしているのです。
