「最初は優秀だったAI」が突然バカになる怪現象

AIコーディングエージェント(Claude Code、Cursor、Windsurf、Aider、Antigravityなど)を使い倒しているエンジニアなら、一度は次の光景に直面し、頭を抱えたことがあるはずです。

機能開発を始めた最初の数ターンは、驚くほど的確なアーキテクチャを提案し、クリーンなコードを生成していた。ところが、実装とデバッグを同じチャットセッションの中で延々と続け、対話が15〜20往復を超えたあたりから、目に見えて挙動がおかしくなり始めます。

エンジニアは思わず「モデルが急にバカになったのか?」「APIの裏側でモデルのダウングレードでも起きたのか?」と疑いたくなります。しかし、これはモデルの気まぐれでも一時的なバグでもありません。

「100万トークンの巨大コンテキストウィンドウがあるから、朝から晩まで1つのセッションでペアプロできる」という言説は、現場のエンジニアを疲弊させる最大の罠である。セッション長が伸びたとき、LLMは構造的に知能を喪失していく。

この現象の正体こそが、LLMの注意機構に内在する宿痾、「文脈漂流(Context Drift)」です。

なぜ長時間セッションでAIは壊れるのか?文脈漂流の病理

なぜ会話履歴が長くなるだけで、あんなにスマートだったエージェントが狂い出すのでしょうか。原因はプロンプトの文字数そのものではなく、Transformerモデルの自己注意(Self-Attention)メカニズムと確率的生成プロセスの特性にあります。

1. ノイズトークンによる「アテンション・ハイジャック」

開発中のエージェントセッションには、人間同士のチャットとは比較にならないほどの「低密度かつ巨大なゴミトークン」が高速で流れ込みます。

TransformerのSelf-Attentionは、すべての入力トークン間の関連度を重み付けして計算します。理論上は遠くのトークンも参照できるはずですが、Softmax演算の性質上、Attentionの総和は常に 1.0 に制限されます。コンテキスト後半に数千〜数万のノイズトークンが雪崩れ込むと、物理的に初期の重要指示(システムプロンプトや設計哲学、制約ルール)へ割り振られるAttentionの重みがゼロ付近まで希釈(Attention Dilution)されてしまうのです。

[初期セッション: 高S/N比]
System Prompt (厳格な制約)  ====> Attention: 60%
User Intent (タスクゴール)   ====> Attention: 30%
Code Context (最小限の差分)  ====> Attention: 10%
結果: ルールを完全に遵守した安全なコード生成

[20ターン経過後: アテンション・ハイジャック発生]
System Prompt (厳格な制約)  ====> Attention: 3% (事実上の忘却)
User Intent (元のゴール)     ====> Attention: 5%
エラーログ・試行錯誤の残骸  ====> Attention: 75% (直近のノイズに過剰反応)
直前のツール出力             ====> Attention: 17%
結果: 目の前のエラー文字列を消すためだけに、全体設計を平気で破壊する

最新のトークンに引きずられる「Recency Bias(新近性バイアス)」と「Attention Sinks(注意力の吸い込み口)」が組み合わさることで、エージェントは当初のゴールを見失い、直前のエラーメッセージの表面的な単語だけを反射的にモグラ叩きするゾンビと化します。

2. 失敗の履歴が刷り込む「ネガティブ・プライミング」

さらに深刻なのが、失敗ログそのものがもたらす心理学的なプライミング効果に似た統計的歪みです。

LLMは純粋な自己回帰モデル(Autoregressive Model)であり、入力コンテキストに続く「最も自然な続き」を予測して出力します。セッションの中に「エラーが出た」「修正したが別の型エラーになった」「例外が飛んでやり直した」という試行錯誤が延々と記録されていると、モデルの入力空間において「エラーと不完全なコードが連続して出現している状態」が強い前提コンテキストとして形成されます。

つまり、エージェント自身が出力した過去の稚拙なコードや失敗の履歴が、その後の推論の質を下方へ引きずり下ろす「自己汚染」が起きるのです。これが、一度迷走し始めたエージェントが、いくら人間が「違う、そうじゃない」と叱責しても泥沼から抜け出せなくなる根本原因です。

3. 局所最適の嘘が雪だるま式に増殖する自己参照ループ

長時間セッションでは、小さなハルシネーション(事実無根の仮定)が「確定した過去の事実」として会話履歴に固定されます。

例えば、エージェントが途中で「このプロジェクトには utils/format.ts が存在するはずだ」と誤認してコードを書いたとします。ユーザーがその誤りに気づかず、別のコンパイルエラーを指摘すると、エージェントは「自分がさっき言及した utils/format.ts は実在する」という前提をコンテキスト履歴から読み取り、後続のターンでその架空の関数を前提としたモジュールをさらに量産し始めます。嘘が嘘を呼び、セッション全体が虚構のアーキテクチャの上に積み上がっていくのです。

「セッションを育てる」という擬人化の罠

多くの開発者が長時間セッションに依存してしまう最大の理由は、「AIに対する無意識の擬人化」にあります。

人間の同僚とペアプログラミングをする場合、会話を重ねるほど相手はプロジェクトの文脈や阿吽の呼吸を学び、コミュニケーションコストは下がっていきます。だからこそ私たちは「セッションを長く維持して、AIを育てたい」「せっかくここまで文脈を教え込んだのだから、チャットを閉じるのはもったいない」と感じてしまうのです。

しかし、LLMは人間ではありません。経験を蓄積して賢くなる学習器官ではなく、「渡されたトークン列から次のトークンを計算して吐き出す、純粋なステートレス写像」です。会話履歴を肥大化させる行為は、AIを教育しているのではなく、メモリリークを起こしているプロセスを放置してスワップ領域を食いつぶさせているに過ぎません。

現場のアーキテクチャ解:セッションを使い捨てる「ステートレス・リレー設計」

では、複雑な機能を破綻させずにAIエージェントと開発を進めるには、どのようなアプローチをとるべきなのでしょうか。その答えが、セッションを極限まで短命に保ち、状態をドキュメントで引き継ぐ「ステートレス・リレー設計」です。

エージェントは「ペット」ではなく「家畜(Cattle)」として扱え

クラウドインフラの設計思想に「Pets vs Cattle(ペットではなく家畜として扱え)」という有名な原則があります。サーバーを名前をつけて大事に手入れする(ペット)のではなく、障害が起きたり役目を終えたら即座に破棄して新しいインスタンスを立ち上げる(家畜)という考え方です。

AIエージェントのセッションも全く同じです。1つのセッションに愛着を持って長生きさせてはいけません。エージェントセッションは「数往復で使い捨てるエフェメラル(一時的)なワーカー」として設計するのが正解です。

会話履歴ではなく「ファイルシステム」を唯一の記憶(State)とする

セッションをリセットすると、それまでの文脈が消えてしまうのではないか? という懸念が生じます。これに対する現場の解法は、「会話履歴(Context)に状態を持たせるのをやめ、ファイルシステム(Git/Markdown)に状態を永続化する」ことです。

具体的には、リポジトリ内にタスク管理ドキュメント(例: docs/Task.md)と要件定義(例: docs/Spec.md)を配置し、これを唯一の信頼できる情報源(Single Source of Truth)とします。

# docs/Task.md

## Phase 1: 認証トークンのリフレッシュ機構実装
- [x] トークン有効期限切れを検知するインターセプター作成
- [x] リフレッシュAPIエンドポイント呼び出し関数の実装
- [ ] 並列リクエスト時の二重リフレッシュ防止ロック(Mutex)の導入
  - 方針: sync.Mutexまたはchannelを用いて後続リクエストを待機させる
  - 制約: タイムアウトは3秒でコンテキストキャンセルすること

## Phase 2: 単体テストとエッジケース検証
- [ ] 期限切れ直前の並列5リクエストが正常に1回のみリフレッシュされることの検証

エージェントが担当する作業単位は、このチェックボックス「1〜2個分」だけに限定します。作業が終わったら、エージェント自身に Task.md のチェックを付けさせ、次に取り組むべき方針をメモさせた上で、セッションを躊躇なくKill(破棄)します。

実践:ステートレス・リレーの運用サイクル

このステートレス・リレーの基本ライフサイクルは、以下の4ステップで完結します。

  1. Spawn(起動): クリーンなコンテキストで新しいセッションを立ち上げ、Task.md と対象ファイルのみを読み込ませる
  2. Execute(実行): 明確に絞り込まれた単一タスク(関数の実装、テストの修正など)を3〜5ターン以内で実行させる
  3. Commit & Persist(永続化): コードを確定し、Task.md を更新させ、Gitコミットを作成する
  4. Purge(破棄): セッションを完全終了し、会話履歴とノイズトークンを破棄する
# 長時間セッションを禁じ、短命セッションをリレーさせる実行イメージ
# 1. 設計とタスク分解だけを行うセッション
agy "docs/Spec.md を読み込み、未実装タスクを docs/Task.md にチェックリスト形式で書き出せ"

# 2. タスク1つだけを実装するセッション(コンテキストは完全新規)
agy "docs/Task.md の直近未完了タスク [ ] を1つだけ実装せよ。完了したら [x] に更新し終了せよ"

# 3. テストの検証とコミットを行うセッション(エラー履歴を引きずらない)
agy "変更箇所のテストを実行し、パスすることを確認したら git commit を作成せよ"

このアプローチを採用すると、どのセッションでもエージェントのコンテキストは常に「清浄な初期状態」に保たれます。スタックトレースの山や過去の試行錯誤によるアテンションのハイジャックは一切発生せず、モデルは常に最大の知能(100%の推論能力)を発揮してタスクを完遂できるのです。

開発者が直面する3つの疑問と現場のトレードオフ

ステートレス・リレー設計を導入しようとすると、必ずチームや個人から次の疑問が投げかけられます。これらに対する現場のリアルな回答を整理しておきます。

Q1: セッションをリセットすると、試行錯誤の学びが失われないか?

「このライブラリのバージョンではこのオプションが動かない」といった試行錯誤で得た教訓が、セッション破棄によって消えてしまうのではないかという疑問です。

回答は明確です。「試行錯誤の生ログを残すな。抽出された教訓だけをドキュメントに1行で記録せよ」です。何十行ものエラーログをコンテキストに残すからAIは迷走します。判明した事実(例: ※注: v2.4ではオプションXが非推奨のためYを使用すること)を Task.md や設計メモに1行追記させてからセッションを終了すれば、次代のエージェントはノイズゼロでその知見を100%活用できます。

Q2: プロンプトキャッシュの効率が落ちるのではないか?

最近のLLM APIはプロンプトキャッシュ(Prompt Caching)を備えており、セッションを維持した方がトークン代が安くなるのではないかという懸念です。

しかし現実は逆です。セッションが長引くほど、会話末尾のツール実行結果やエラーログが毎回変動するため、キャッシュが効くプレフィックスが相対的に薄まります。何より、コンテキストウィンドウが10万トークンを超えた状態で何十回も往復するコストは、たとえキャッシュが効いたとしても、短命なセッション(5,000〜10,000トークン)を数回立ち上げ直すコストの数倍〜十倍に跳ね上がります。「コンテキストの肥大化」こそがトークン課金の最大の無駄遣いです。

Q3: セッションを破棄すべき「明確な引き際」はどこか?

セッションを切るべきタイミングのルールは、以下の3つに集約されます。

まとめ:コンテキストの「断捨離」ができる者がAI駆動開発を制す

コンテキストウィンドウが200万トークン、あるいは無限に近づいたとしても、計算機科学における「信号対雑音比(Signal-to-Noise Ratio)」の法則からは逃れられません。どれほど巨大なメモリ空間があろうと、ノイズを詰め込めば推論の純度は濁り、出力は劣化します。

AI駆動開発において、優秀なエンジニアとそうでない者を分ける境界線は、「いかにプロンプトを長く書き込めるか」ではありません。「いかに不要なコンテキストを冷徹に切り捨て、最小限のクリーンな状態だけを次のエージェントに渡せるか」という、コンテキストの断捨離能力にあります。

AIエージェントに無駄な長旅をさせてはいけません。彼らには短く鮮やかに仕事をさせ、結果をファイルに刻んだら静かに退場してもらう。この「ステートレス・リレー」の冷徹な規律こそが、AIエージェントを現場で真の戦力に変える唯一の道なのです。