チームでは Claude Code、Cursor、Copilot が動いています。どれも、作業に入る前にファイルを 1 つ読みます。CLAUDE.md、.cursor/rules/*.mdc、.github/copilot-instructions.md。そしてそのファイルはコピーされます。別のリポジトリへ、別のノート PC へ、誰かの作業用ディレクトリへ。
そのあと、誰かがコピーの 1 つを編集します。
エラーは出ません。警告も差分も、落ちるテストもありません。そのマシンのエージェントの挙動だけが変わります。6 週間前に消したはずの指針が生きていたり、チームの半分のエージェントしか知らない規約があったり。表に出るときは「自分の環境では動く」という形で、二分探索する手がかりもありません。
そういうファイルを見つけて、どのコピーが食い違っているかを出すスクリプトを書きました。
curl -O https://raw.githubusercontent.com/untactit/agent-drift/main/agent_drift.py
python3 agent_drift.py ~/work
Scanned 47 instruction files.
DRIFT: 2 documents, 5 distinct versions between them.
claude-code:CLAUDE.md
6 copies, 3 versions
9b01aeaa204d 3 files, 406 lines
~/work/api/CLAUDE.md
~/work/web/CLAUDE.md
2dc3c616c279 2 files, 411 lines
~/work/infra/CLAUDE.md
1 ファイル、標準ライブラリのみ、Python 3.8 以上。読み取り専用で、スキャンしたファイルに書き込むことはありません。MIT ライセンス。
素朴な実装は 10 分で書けて、使い物になりませんでした。話の中身は、そこから直した 3 点です。
最初の版は、CLAUDE.md を全部集めてハッシュを取り、違うものを報告する、というものでした。
自分のマシンで回すと、21 個のファイルを 1 つの文書と見なし、18 バージョンあると報告してきました。
当然です。無関係なプロジェクトの指示は無関係で、そもそも一致するはずがありません。それをズレとして報告するツールはノイズになり、ノイズを出すツールはツールがないより悪くなります。3 回目の実行で、出力は読まれなくなります。
同じ文書かどうかを決めるのは 中身 であって、ファイル名ではありません。
次は、空白を正規化し、トークンに分割し、Jaccard 係数で比較。しきい値は 0.7。
それでも巨大なクラスタが 1 つできるだけでした。理由を知るために、実際のペアを測りました。
012Hki23QBMr ↔ 016ct6suZ14M : 0.000
012Hki23QBMr ↔ 016avoYh97Eu : 0.764
同じツール向けに書かれた指示ファイルは、語彙をほとんど共有します。claude、skill、agent、file、commit、never、always。内容として共通点のない 2 つの文書でも、単語の集合としては大きく重なります。
そこで 5 語シングル(連続する 5 語の並びの集合。shingle)に切り替えました。語彙は衝突しますが、言い回しはめったに衝突しません。測り直すと、無関係なペアは 0.000 まで落ち、本当に派生した文書は 0.53–0.76 に収まりました。欲しかったのはこの分離です。考えて出したのではなく、測って出ました。
クラスタはまだ 1 つのままでした。これは古典的な話です。
「メンバーの誰かに似ていれば入れる」という条件で Union-Find を使うと、A が B に、B が C につながり、共通点のない A と C が同じクラスタに入ります。弱いつながりが 1 本あるだけで、クラスタがツリーを飲み込みます。
いまはクラスタごとに 代表 を持たせています。代表はそのクラスタの最大のメンバーで、ファイルは代表に直接似ている場合だけ入ります。これで、スキャンの順番に関係なくクラスタの中身が揃います。
自分のマシンでの最終結果は 15 ファイル、うち 9 個が代表と 0.53–0.65 の類似度。これは本当に 1 つのテンプレートから派生したものです。検出として正しく、しかも手を打てる大きさの数字です。
- run: python3 agent_drift.py . --fail-on-drift
ズレがあれば exit 1 を返します。チームの指示のコピー 2 つが一致しなくなった、その日にビルドが落ちます。半年後に「なぜエージェントがあの指針を守らなくなったのか」と聞かれて初めて気づく、という順番ではなくなります。
後段で処理したい場合は --json で全体のレポートが出ます。
Claude Code、Codex/AGENTS.md、Cursor、GitHub Copilot、Gemini、Windsurf、Cline、Aider、Continue、Zed。パターンはファイル先頭の定数 1 つにまとめてあります。使っているツールが入っていなければ PR か issue をください。
検出は、2 つあるうちの軽いほうです。コピーには存在する理由があり、その理由が消えるまでズレは戻ってきます。消えるのは、レビュー済みの 1 つが、誰も貼り付けることなくどのマシンにも届く状態になったときです。触らないから、ばらつかない。
その問題を軸に untactit を作っています。現在は公開前です。このスクリプトは untactit に依存していませんし、今後も依存させません。