1. 「まず完璧な計画を立てよ」という甘美な罠
AIエージェントに自律的なタスクを依頼する際、多くの開発者が最初に組み込むのが「計画生成フェーズ(Planning Phase)」だ。
プロンプトに「いきなりコードを書かず、まずはStep 1からStep 8までの詳細な実装計画を策定し、チェックリスト形式で提示してください」と指定する。するとLLMは、人間のシニアテックリードも顔負けの、理路整然とした美しいMarkdownのアウトラインを吐き出してくれる。
### 実装計画: ユーザー認証フローのOAuth2連携
- [ ] **Step 1**: `src/config/oauth.ts` にプロバイダ設定と環境変数バリデーションを追加
- [ ] **Step 2**: `src/services/auth.service.ts` にトークン交換メソッド `exchangeCode()` を実装
- [ ] **Step 3**: 既存の `UserSession` 型に `oauthProvider` フィールドを拡張
- [ ] **Step 4**: `src/routes/auth.routes.ts` に `/callback` エンドポイントを定義
- [ ] **Step 5**: 既存ミドルウェア `requireAuth` のセッション検証ロジックを修正
- [ ] **Step 6**: 単体テスト `tests/auth.service.test.ts` を作成して全件グリーン化
このアウトラインを眺めた開発者は、「素晴らしい、これなら完璧に自律実行できるはずだ」と胸をなでおろす。しかし、実際にエージェントループを走らせた瞬間、地獄の蓋が開く。
たとえば Step 2 の実装中、AIは既存の認証サービスがシングルトンではなく非同期ファクトリパターンでインスタンス化されていること、あるいはトークン交換がすでに社内共通ライブラリ側で抽象化されているという「現実」に初めて直面する。
人間のエンジニアであれば、この時点で「おっと、前提が違っていたな。Step 3以降の計画は全部破棄して、既存のファクトリを使う設計に組み立て直そう」と即座に方針転換するはずだ。
しかし、自律型AIエージェントは絶対に立ち止まらない。破綻した計画のStep 3、Step 4、Step 5を、何食わぬ顔でそのまま機械的に実行し続けるのだ。
2. 計画硬直(Plan Rigidity)の自己回帰病理
なぜAIエージェントは、目の前でコンパイルエラーが吹き荒れ、既存アーキテクチャと衝突しているにもかかわらず、愚直に当初の計画にしがみついてしまうのか?
その理由は、エージェントの賢さ不足ではなく、Transformerの自己回帰(Autoregressive)生成メカニズムとアテンション機構の構造的宿命にある。
強烈な「自己アンカー効果(Self-Anchoring)」
LLMにとって、コンテキストウィンドウの上部に鎮座する「自らが出力した詳細な計画テキスト」は、単なるメモ書きではない。後続のすべてのトークン生成確率を支配する、極めて強力な事前分布(Prior Distribution)として作用する。
計画が出力された瞬間、自己注意機構(Self-Attention)は「Step 3の次はStep 4を実行する」「Step 4ではこのファイルにこの関数を作る」という確率の山を極端に尖らせてしまう。たとえ途中でターミナルから Error: interface mismatch という観測結果(Observation)が返ってきても、コンテキスト上に長々と記述された計画のトークン量と重み付けに押し潰され、「計画を全面破棄する」という低確率の分岐を選べなくなるのだ。
「計画が間違っていたのではない。現実のコードベースが私の計画に合致していないのだ。」
——LLMのアテンション空間では、無意識にこの転倒が起きている。
プロクルステスのベッド:現実を歪めるAI
ギリシャ神話に登場する強盗プロクルステスは、旅人を鉄のベッドに寝かせ、ベッドより体が長ければ手足を切り落とし、短ければ体を無理やり引き伸ばしてベッドの長さに合わせたという。
計画硬直に陥ったAIエージェントが犯す破壊工作は、まさにこの「プロクルステスのベッド」そのものだ。
// AIエージェントが「計画のStep 3」を強行するために書いた珍妙なコード
// 既存のファクトリパターンを無視し、計画通りに動かすためだけの無理やりなラッパー
import { ExistingAuthFactory } from './factory';
// 計画にある `new AuthService()` を成立させるために強引なダミークラスを捏造
export class AuthService {
private instance: any;
constructor() {
// 非同期ファクトリを無理やり同期コンストラクタ内で未解決Promiseとして放置
ExistingAuthFactory.create().then(inst => {
this.instance = inst;
});
}
async exchangeCode(code: string) {
// インスタンスが初期化される前の呼び出しで未定義クラッシュを引き起こす
return this.instance.exchange(code);
}
}
AIは「計画に書いた通りのシグネチャのクラス」を生み出すために、プロジェクトの既存ルールを破壊し、未初期化の非同期レースコンディションを埋め込み、型定義をアンチパターンで上書きしてでも「Step 3 完了」のチェックマークを付けようとする。
事前計画が詳細であればあるほど、AIは自らの退路を断ち、現実に合わせて柔軟に設計を変更する知性を失っていく。
3. ウォーターフォールの亡霊:計画フェーズという最大のハルシネーション
ソフトウェア工学の歴史を振り返れば、人類は何十年もの失敗と挫折を経て「ビッグデザインアップフロント(事前詳細設計の原則)」を捨て、アジャイル開発へと舵を切ったはずだった。
ソフトウェア開発の本質は「学習プロセス」であり、最初の1行を実行した瞬間に、事前の想定はすべてアップデートされる。これこそがアジャイルの核心である。
にもかかわらず、昨今のAIエージェント界隈では、皮肉にも「LLMによるウォーターフォール開発の復活」が無邪気にもてはやされている。
コードを1行も実行していない「妄想の楼閣」
計画フェーズにおけるLLMの思考状態を冷静に分析してみてほしい。
エージェントはコードベースの全ファイルを読み込んでいるわけではない(コンテキスト窓の制約や探索コストのため、多くは部分的な検索結果しか見ていない)。コンパイラを通したわけでも、依存関係のバージョンを検証したわけでも、テストを実行したわけでもない。
つまり、計画フェーズで生成されるステップの1つひとつは、純度100%の「推測と妄想(ハルシネーション)」で構築された砂上の楼閣なのだ。
- 「このディレクトリには○○という命名規則でファイルが置かれているはずだ」
- 「この外部パッケージのメソッドは引数にオプションオブジェクトを取るはずだ」
- 「このDBテーブルにはすでに外部キー制約が張られているはずだ」
これら数多くの未検証の前提の上に、8ステップ、10ステップと積み上げた長大な計画は、1つのステップが崩れた瞬間に後続の全ステップが連鎖的に意味を失う。これほど脆弱で危険なアーキテクチャが他にあるだろうか。
4. 現場の処方箋:静的計画を捨て、OODAループを回せ
では、自律エージェントに実装を委ねる際、私たちはどのようにシステムを設計すべきなのか?
答えはシンプルだ。「事前の詳細計画(Static Plan)」をプロンプトから徹底的に排除し、「最小のNext Action(1ステップ)+環境フィードバック(Observation)による動的適応ループ」へと設計思想を転換することだ。
軍事戦略に学ぶ「OODAループ」の適用
空戦理論から生まれた OODAループ(Observe: 観察 → Orient: 状況判断 → Decide: 意思決定 → Act: 行動) は、不確実性の極めて高いソフトウェア開発とAIエージェントにおいて、まさに理想的なメンタルモデルとなる。
【アンチパターン: 静的Plan-and-Solve型】
[User Request]
│
▼
[LLM: 10ステップの計画を固定生成] ──┐ (自己アンカーの呪縛)
│ │
├─► Step 1 を実行 │
├─► Step 2 を実行 (破綻発生) ◄┘ (計画に縛られ現実を歪める)
├─► Step 3 を強行
└─► 破滅
───────────────────────────────────────────────────────
【ベストプラクティス: OODA型マイクロリアクティブ循環】
[User Request / Goal & Invariants]
│
▼
┌──► [Observe: 現在のファイル・型エラー・テスト結果を観察]
│ │
│ ▼
│ [Orient: ゴールと現状のギャップを評価し、前提を更新]
│ │
│ ▼
│ [Decide: いま取るべき「次の最小の1手(Next Single Action)」のみを決定]
│ │
│ ▼
└─── [Act: ツールの実行(ファイル編集またはコマンド実行)]
│
(ゴール条件を満たすまで超高速にサイクルを反復)
実装比較:エージェントループの設計差
TypeScriptでエージェントループを構築する場合の、具体的なコード構造の対比を見てみよう。
// ❌ BAD: 計画硬直を引き起こす事前計画型ループ
async function runRigidAgent(goal: string) {
// 1. 最初に行動計画をすべて出力させる(これが致命的なアンカーになる)
const plan = await llm.generatePlan(goal);
for (const step of plan.steps) {
console.log(`Executing: ${step.title}`);
// 過去のステップで想定外のエラーが起きても、計画の配列を愚直に消費し続ける
await llm.executeStep({
step,
planHistory: plan.steps, // コンテキストに計画全体が残り続け、アテンションを束縛する
});
}
}
// ⭕ GOOD: 観察とフィードバックに基づく動的リアクティブ・ループ
interface AgentState {
goal: string;
invariants: string[]; // 破ってはならない不変条件(例: 既存テストを破壊しない)
recentObservations: string[]; // 直近数回の実行結果のみを保持(コンテキストの軽量化)
}
async function runReactiveAgent(state: AgentState) {
while (true) {
// 1. 現状の観測(リンター、型チェック、テストの最新状態)
const observation = await environment.inspectState();
// 2. ゴール判定(計画の消化ではなく、客観的なテスト・型の通過で終了を判断)
if (observation.isGoalAchieved) {
console.log("Goal successfully satisfied!");
break;
}
// 3. 次の「最小の1手」のみを推論させる
// プロンプトには「未来の全手順」を書かせず、直前の観測結果からの判断を強制する
const nextAction = await llm.decideNextAction({
goal: state.goal,
invariants: state.invariants,
latestObservation: observation.summary,
instruction: `
あなたは現在の観測結果に基づき、ゴール達成に向けた「次の最小の1アクション」のみを選択してください。
将来のステップを予測して固定化してはなりません。
予期せぬエラーや既存実装を発見した場合は、過去の仮説を捨てて現在地に適応してください。
`,
});
// 4. アクションの実行と観測の更新
const result = await environment.executeAction(nextAction);
state.recentObservations.push(result);
}
}
5. エージェントに渡すべきは「工程表」ではなく「羅針盤」だ
AIエージェントに詳細な計画を立てさせたくなる心理の裏には、開発者自身の「不安」がある。
ブラックボックスであるLLMにコーディングを任せるのが怖いからこそ、人間が検閲できる「工程表」を吐き出させ、安心感を得ようとするのだ。
だが、ソフトウェア開発の現場において、紙の上の工程表ほど無意味なものはない。現実は常に計画を裏切る。ライブラリの型は想定と異なり、環境変数は未定義で、既存のコードにはドキュメント化されていない暗黙のルールが潜んでいる。
AIエージェントが真価を発揮するのは、「事前に完璧なレールを敷いてその上を走らせた時」ではない。「レールから脱線した瞬間に、コンパイラやテストのエラー出力を読み取り、即座にハンドルを切って別の道を見つけ出した時」なのだ。
結論:私たちが構築すべきプロトコル
- 計画のトークンをコンテキストから追放せよ: 将来のステップを長々と書いたプロンプトは、AIの認知柔軟性を奪う足枷にしかならない。
- 「終了条件(Assert)」のみを厳格に定義せよ: 手順を縛るのではなく、「どのテストが通り、どの型が解決されれば完了か」という不変条件(Invariants)を渡す。
- 観察の解像度を最大化せよ: AIに必要なのは未来を予言する計画力ではない。コードの現在地を正確に測るためのリンター、型チェッカー、テストランナーという「現実のセンサー」である。
自律エージェントを計画の呪縛から解放しよう。未来の工程表を捨て去り、現実のコードと1ステップずつ対話させた時、エージェントは初めて本物のエンジニアリング・パートナーとなる。