自律的なコーディングエージェントが失敗し続ける理由――そして本当に効く対策

Dev.to / 2026/5/1

💬 オピニオンDeveloper Stack & InfrastructureIdeas & Deep AnalysisModels & Research

要点

  • この記事は、自律的なコーディングエージェントの失敗は主にアーキテクチャやシステム設計の問題であり、現在のAIモデルの能力不足が原因ではないと主張しています。
  • 実運用で役に立つための重要要件として、plan→execute→test→失敗タイプの分類→狙いを定めた修正プロンプト生成、再実行とロールバックを含む「構造化された修復ループ」を挙げています。
  • 多くの失敗は、生成されたコードが間違っているというより「テストが弱い/誤ったシグナルになっている」ことに起因すると指摘し、テスト品質を評価する必要(変異テストなど)を強調しています。
  • Python、Go、TypeScript、Rust にわたる多数のベンチマーク実行から、失敗の要因が複数のカテゴリにまとまることを報告し、LLMに指示するだけでは不十分だと結論づけています。
  • 全体として、エージェントの信頼性はベンチマーク中心のモデル問題ではなく、実行エンジン、テストランナー、修復ロジック、検証設計といった工学的な設計課題だと位置付けています。

過去6か月、ゼロから自律的に動くコーディングエージェントを作っては壊し、作り直してきました。誰かの既存フレームワークは使わずに。GPT-4をループでラップするようなこともしない。実際の実行エンジン、テストランナー、修復ロジック、そしてLLMカスケードまで——全部です。
ここでは、ベンチマーク論文では誰も話していないことを、私が学んだ内容を紹介します。

誇大広告(バズ) vs. 現実
毎週、新しいデモがあります。「AIエージェントが30秒でアプリを丸ごと書いた」。クリップは拡散されます。コメント欄は大荒れ。ところが、実際に開発者が試すと分かります:

デモ用の入力では動く
少しでも違う入力だと失敗する
失敗すると、静かに(エラーが見えない形で)落ちる
何をしたかを再現する方法がない

これはモデルの問題ではありません。モデルは本当に能力があります。問題はアーキテクチャです。

実際にエージェントを壊すもの
Python、Go、TypeScript、Rustで何千ものベンチマークケースを回した結果、失敗は5つのカテゴリに固まることが分かりました——それらはどれも「生のLLM知能」についてではありません。

  1. 修復ループがない ほとんどのエージェントデモは1回で終わります:プロンプト → コード → 完了。実際のコードは、最初の試行でうまくいくことはほぼありません。構文エラー、importの欠落、型の不一致、ロジックバグ——これらは普通に起こります。 構造化された修復ループのないエージェントは、デモであってツールではありません。 実際に機能するもの: 計画 → 実行 → テスト → 失敗? ↓ 失敗の種類を分類 ↓ 対象を絞った修復プロンプトを作成 ↓ 再実行 → 再テスト ↓(最大3回の試行) それでも失敗ならロールバック キーワードは「分類」です。「エラーはこれだから直して。」ではなく、「ファイルXの行YにあるこれはSyntaxErrorで、Zパターンが原因——関連するコードはこれ。」のようにする。 エージェントがエラー出力をそのままLLMに渡すとトークンを浪費し、構造化された失敗コンテキストを抽出するエージェントより結果が悪くなります。
  2. 不安定なテスト実行 意外かもしれませんが、エージェントが失敗する理由は、生成されたコードが間違っているからというより、書いたテストが弱いからであることがよくあります。 間違った実装でも通ってしまうテストは、テストではありません。誤ったシグナルです。これは突然変異テスト(ミューテーションテスト)の問題です。 たとえば: pythondef is_even(n): return n % 2 == 0 通るテスト: pythondef test_is_even(): assert is_even(4) == True コードを突然変異させる: pythondef is_even(n): return n % 2 != 0 # 間違い! このテストは、is_even(4)しか見ていないならまだ通ります。4 % 2 != 0 は False で、False == True は……待って、それは失敗します。ですが、突然変異後のコードで is_even(3) は True を返すので、テストが「偶数だけ」を確認しているなら見逃します。 ミューテーションテストを強制しないエージェントは、テストでは通るコードを作っても、本番で失敗します。 修正方法:毎回の成功したテスト実行の後に、主要な演算子を突然変異させる(== → !=、+ → -、if x: → if not x:)そして、それをテストが検出できるか確認します。検出できないなら、コードではなくテストが弱いということです。
  3. ワークスペースを理解できない 「Goサービスのバグを直して」とエージェントに頼むと、エージェントは次を知りません:

go.modはある?モジュール名は?
go test ./... が正しいコマンド?それとも go test -run=TestXxx ./...?
go mod tidy が必要な外部依存はある?
それは単一パッケージ?それともマルチモジュールのワークスペース?

この文脈がないと、エージェントは(誤った)推測をするか、(うるさい)質問をするかのどちらかになります。どちらも許されません。
機能するもの:計画が始まる前にプロジェクト構造をスキャンする「ワークスペース・オラクル」
検出:/src/go.mod にGoモジュールあり(module: myapp)
検出:/src/cmd/、/src/internal/
テストコマンド:go test ./... -v
依存関係:標準ライブラリのみ
これは当然に聞こえます。ですが、ほとんどのエージェントはこれを適切にやれていません。

  1. ロールバックがない ファイルを変更して失敗したエージェントは、ワークスペースを壊れた状態のままにします。これは机上の話ではなく、複数ステップの修復中には常に起きます。 解決策は恥ずかしいくらい簡単です:実行のたびにgit stashし、ロールバック時にgit stash pop、成功時にgit stash drop。 実行前: git stash create ← スナップショット 実行中: agent がファイルを変更 テスト失敗時: git stash pop ← きれいな状態に復元 テスト成功時: git stash drop ← 変更を受け入れる すべてのエージェントがこれをすべきです。ほとんどはしません。
  2. LLMプロバイダの不安定さ 単一のLLMプロバイダに依存して構築すると、信頼性上の負債になります。レート制限、日次クォータ、APIエラー——これらはいずれも、タスクの途中でエージェントを殺します。 カスケードの方がうまくいきます: プロバイダ1 → レート制限 → プロバイダ2 → 日次制限 → プロバイダ3 → … ただし重要な細部があります。いったんのレート制限(30秒待ってリトライ)と、日次で使い切ったクォータ(このセッション中はこのプロバイダを完全にスキップ)を区別すること。同じ扱いにすると時間を無駄にします。

ベンチマークの問題
現在のコーディングエージェントのベンチマークは「最終出力が動いたか」を測ります。これは重要なことを全部見落とします:

何回の試行で到達したか?
どれくらいのコンテキストを消費したか?
解決策は決定的か?もう一度実行して同じコードが得られるか?
生成されたテストは回帰(退行)を捕捉できるか?

「合格率85%」というベンチマークスコアには、まったく異なる意味があり得ます:

良い:最初の1回で85%、残りは構造化された修復、すべてのテストにミューテーションカバレッジあり
悪い:5回試行して85%、テストは退屈な内容、実行のたびに別のコードになる

最も重要な指標は、合格率ではありません。修復率です。平均修復回数が0.1でタスクの95%を通すエージェントは、平均修復回数が2.3で95%を通すエージェントより役に立ちます——見出しの数字が同じに見えてもです。

決定性:欠けている性質
コーディングエージェントで最も過小評価されている性質は「決定性」だ、と私は主張したいです。
同じタスクを2回走らせて、同じ結果が得られるでしょうか?
ほとんどのエージェントでは、いいえ。LLMは設計上非決定的です(temperature > 0)。コンテキストが変わる可能性もありますし、ツール呼び出しが変動するかもしれません。
なぜそれが重要なのか?

デバッグ:失敗した場合、再現できません。なぜ失敗したのか理解できません。
CI/CD:非決定的なエージェントをパイプラインに入れられません。ある実行では通り、次の実行では失敗します。
信頼:開発者は予測できないツールを信頼しません。

機能するアプローチの1つ:録画/リプレイ。エージェントがタスクに成功したら、LLMのやり取り全体——入力、出力、推論——を記録します。その後の実行では、LLMを呼び出すのではなく、記録したやり取りをリプレイします。
これにより得られるのは:

繰り返しタスクでのLLMコストがゼロ
100%の決定的な出力
監査可能な履歴(「3月3日にエージェントが実際に何をしたか?」)

記録された軌跡(トラジェクトリ)は、学習データにもなります——ただしそれは別の記事の話です。

良いアーキテクチャはどう見えるか
すべての失敗と反復の後に、実際に信頼できる結果を生み出すアーキテクチャはこうなります:
┌─────────────────────────────────────────────────┐
│ Goal Parser │
│ 言語・依存関係・ワークスペースの種類を検出 │
└─────────────────────────┬───────────────────────┘

┌─────────────────────────▼───────────────────────┐
│ Workspace Oracle │
│ プロジェクト構造をスキャンし、テストコマンドを見つける │
└─────────────────────────┬───────────────────────┘

┌─────────────────────────▼───────────────────────┐
│ Scaffold Engine │
│ 環境を準備する(venv、node_modules など) │
└─────────────────────────┬───────────────────────┘

┌─────────────────────────▼───────────────────────┐
│ LLM Planning (Cascade) │
│ 構造化されたコマンド計画を生成する │
└─────────────────────────┬───────────────────────┘

┌─────────────────────────▼───────────────────────┐
│ Executor + Snapshot │

Gitのstashのロールバック保護付きでコマンドを実行


└─────────────────────────┬───────────────────────┘

┌───────────┴───────────┐
│ │
✅ テストは通過 ❌ テスト失敗
│ │
変更を受け入れる 失敗を分類
スナップショットを削除 │
修復プロンプトを作成

修復(最大3回)

まだ失敗している?

ロールバック+レポート
各ボックスはそれぞれ別の懸念事項です。それぞれは独立して失敗し得ます。それぞれは独立してテストできます。

実務上の要点
エージェントを作る、または評価するなら:
作る:

生のエラーフォワーディングではなく、構造化された失敗分類
突然の変異(ミューテーション)テストの強制 — 任意にしない
ワークスペースを認識したテスト検出
Gitスナップショットのロールバック — 常に
複数プロバイダーのLLMカスケード(クォータ追跡付き)

測る:

成功したタスクあたりの平均修復回数(目標:< 0.3)
ロールバック率(目標:< 10%)
生成されたテストのミューテーションスコア(目標:100%)
決定性:同じタスクを3回実行し、出力を比較

避ける:

単一プロバイダーのLLM依存
ロールバック保護なしでファイルを変更するエージェント
最終的な合否しか測らないベンチマーク
デモ動画への信頼

これからどうなるか
勝つエージェントは、最も強力な基盤モデルを持つものではありません。信頼できる実行レイヤーを最も備えたものです。モデルはコモディティです — GPT-4、Claude、Llama、Qwen — どれも十分に使えます。差別化要因はモデルの周りにあるすべてです。
信頼性、決定性、監査可能性、ロールバック — これらは派手なエンジニアリング課題ではありません。派手なデモにもなりません。ですが、開発者が実際に信頼して毎日使うツールにするのは、まさにここです。
ベンチマークは最終的に追いついてきます。それまでは、本当に重要なタスクでエージェントを動かし、実際の修復率を測り、自分自身の失敗から回復できないものは捨ててください。

私はこの種のシステムを作る細部について、進めながら書き続けています。もし同様のものに取り組んでいる、またはエージェントのアーキテクチャについて考えがあるなら、ぜひ — コメントをください。

タグ:ai rust プログラミング開発ツール machinelearning
カバー画像の提案:テスト結果が表示されるターミナル — 緑の合格、1つの赤い失敗、次に修復、そしてまた緑。