Antigravity のエージェントに数百行のコードを書かせ、翌朝になってビルドが通らないことに気づいた経験はないでしょうか。私は何度かあります。エージェントが「完了しました」と返してきたあとに、実は型エラーがいくつも残っていたり、テストが落ちる状態のままコミットされていたり。指示の出し方を工夫しても、出力を信頼してそのまま受け取るかぎり、この種の事故はゼロにはなりません。
そこで導入したいのが Validation Loop(検証ループ)です。生成 → 検証 → 不合格なら修正 → 再検証、というサイクルをエージェント自身に回させる仕組みで、人間がいちいちレビューしなくても出力の品質をある程度担保できます。ここではAntigravity のエージェントに組み込みやすい3つのパターン — Self-Verifier、Critic-Approver、Test-Driven — を、AgentKit 2.0 の SDK を使った実装例つきで紹介します。
なぜ単なる「指示の精緻化」ではダメなのか
「テストが通るまで直して」「型エラーをゼロにして」とプロンプトに書けば、ある程度は効きます。ただ、それだけだとエージェントが本当にチェックしたかどうかは検証されません。実際には「チェックしたつもりで完了報告する」という挙動が頻発します。
検証フェーズを独立したステップとして分離し、明示的な合否判定を返させる。これが Validation Loop の基本的な発想です。Anthropic の研究でも「自己評価のステップを分けて投げる」だけで一貫性が改善されると報告されています。Antigravity の AgentKit 2.0 は、こうしたループ構造を runWithValidator() のようなヘルパーで明示的に書ける設計になっており、合否を返さなかったエージェントを「ループ未完了」として扱えます。
パターン1: Self-Verifier — 自分で書いて自分でチェックする
最もシンプルなのが、生成したエージェントに同じセッション内で検証させる方式です。コンテキストを共有しているので追加のトークン消費が少なく、軽い品質チェックには向きます。
// agents/self-verifier.ts
import { Agent, runWithValidator } from "@google/agentkit";
const codeAgent = new Agent({
model: "gemini-2.5-pro",
systemPrompt: `あなたは TypeScript の実装担当です。要件を満たすコードを書いたあと、
自身で以下の3項目を検証して JSON で返してください。
{ "code": "...", "checks": { "compiles": bool, "handlesEdgeCases": bool, "hasTests": bool }, "passed": bool }`,
});
const result = await runWithValidator(codeAgent, {
task: "ユーザー入力をバリデーションする zod スキーマを TypeScript で書いてください",
validator: (output) => output.passed === true,
maxRetries: 3,
});
if (!result.success) {
// 3回試して通らなかった場合のフォールバック
console.error("Self-verification failed:", result.lastOutput.checks);
}注意点として、Self-Verifier は「自分の答えに甘くなりがち」です。私の経験では、compiles を true と返してきても実際にはビルドが通らないケースが2割ほどありました。軽い品質チェック(タイプミス・命名規則・コメントの有無)には十分ですが、正しさの最終判定には信用しすぎない ようにしてください。
パターン2: Critic-Approver — 別のエージェントに批評させる
Self-Verifier の弱点を補うのが、生成と検証を別エージェントに分ける Critic-Approver パターンです。批評役は同じモデルでも構いませんが、プロンプトを「重箱の隅をつつく品質保証担当」として設計する のがコツです。
// agents/critic-approver.ts
import { Agent } from "@google/agentkit";
const writer = new Agent({
model: "gemini-2.5-pro",
systemPrompt: "あなたは実装担当です。要件に対するコードを書いてください。",
});
const critic = new Agent({
model: "gemini-2.5-flash", // 検証は軽量モデルで充分
systemPrompt: `あなたは厳格なコードレビュアーです。
以下のコードに対して、以下の観点で必ず最低3つの「改善すべき点」を出力してください。
- 型安全性
- エラーハンドリング
- 想定外入力への耐性
最後に { "verdict": "APPROVE" | "REJECT", "reasons": string[] } を返してください。`,
});
let code = await writer.run("OAuth2 のトークン更新処理を書いて");
for (let i = 0; i < 3; i++) {
const review = await critic.run(`次のコードを批評してください:\n${code}`);
if (review.verdict === "APPROVE") break;
code = await writer.run(
`元のコードと指摘を踏まえて修正してください。\nコード: ${code}\n指摘: ${review.reasons.join("\n")}`
);
}ポイントは、Critic に「最低3つの改善点を出せ」と義務づけることです。これがないと「特に問題ありません」と即座に承認してしまい、検証として機能しません。評価軸を最初から強めに与える のがコツで、ここを甘くすると Self-Verifier と変わらない結果になります。
パターン3: Test-Driven — 機械的なテストで合否を決める
最も信頼できるのが、人間が書いた(あるいは別途生成した)テストを実行し、その結果を Validation の根拠にする方式です。AI の主観に頼らないので、合否が明確になります。
# agents/test_driven.py
from agentkit import Agent, run_with_validator
import subprocess
writer = Agent(model="gemini-2.5-pro", system="あなたは Python の実装担当です。")
def run_pytest(code: str) -> dict:
"""生成されたコードをファイルに書き、pytest で検証する"""
with open("/tmp/generated.py", "w") as f:
f.write(code)
result = subprocess.run(
["pytest", "tests/test_generated.py", "-q"],
capture_output=True, text=True, timeout=60,
)
return {
"passed": result.returncode == 0,
"stdout": result.stdout[-2000:], # 直近のみ
"stderr": result.stderr[-2000:],
}
result = run_with_validator(
writer,
task="リスト内の重複を順序を保ったまま除去する関数 dedupe(items) を書いてください",
validator=lambda output: run_pytest(output["code"])["passed"],
on_retry=lambda output: f"テストが失敗しました。エラー出力:\n{run_pytest(output['code'])['stderr']}",
max_retries=5,
)このパターンの強みは「テストが通るまで直す」というシンプルな合格基準を機械的に判定できることです。代わりに、テスト自体が網羅的でないとループが早期終了してしまう という弱点があります。テスト設計の質がそのまま品質の上限になるので、エッジケースを意図的に書き込むのが大切です。
どのパターンを選ぶか — 私の使い分け
3パターンを組み合わせて使うのが理想ですが、最初は1つに絞った方が運用しやすいです。私は普段、こんな基準で選んでいます。
- 設計書のドラフトやリファクタの提案など、人間が最終確認する前提のタスク: Self-Verifier で十分
- API 実装やバックエンドのビジネスロジックなど、間違えると影響が大きいタスク: Critic-Approver
- ユーティリティ関数やデータ変換など、入出力が明確でテストが書けるタスク: Test-Driven
組み合わせるなら、Test-Driven を基盤にしつつ、テストでカバーしきれない部分(命名・コメント・読みやすさ)を Critic-Approver が担当する二段構えが安定します。
詰まりやすいポイント
実装で踏みやすい落とし穴を3つ書いておきます。
1つめは無限ループ。 maxRetries を必ず設定してください。エージェントが「修正しました」と言いながら同じ間違いを繰り返すケースは普通にあります。AgentKit 2.0 の runWithValidator はデフォルトで3回までですが、コストを抑えたいなら2回でも構いません。
2つめは検証コストの増大。 単純計算で、Critic-Approver は2倍、Test-Driven はテスト実行分だけ時間がかかります。私のプロジェクトでは、検証つきのタスクは Antigravity の Background Agent に投げて、フォアグラウンドの作業を止めない設計にしています。
3つめは「検証エージェントの暴走」。Critic 側に厳しすぎる基準を与えると、永遠に APPROVE が出ません。「致命的でない指摘は警告として出力するが APPROVE はする」というルールを入れておくと、現実的な合格ラインに収まりやすくなります。
検証ログを残しておくと運用が楽になる
Validation Loop を導入したあと、地味ですが効くのが「検証ログを残すこと」です。エージェントが何回目の試行で通ったか、どんな指摘で REJECT になったかを記録しておくと、プロンプト改善の指針になります。私のプロジェクトでは AgentKit の onValidationStep フックで、各試行の入出力を JSON Lines に書き出しています。
import { runWithValidator } from "@google/agentkit";
import { appendFile } from "fs/promises";
await runWithValidator(writer, {
task: "...",
validator: critic,
maxRetries: 3,
onValidationStep: async (step) => {
await appendFile(
"logs/validation.jsonl",
JSON.stringify({
ts: Date.now(), attempt: step.attempt,
verdict: step.verdict, reasons: step.reasons,
}) + "\n"
);
},
});このログを週に1回眺めるだけで、「このタスクは毎回2回目で通る → 初回プロンプトを強化できる」「特定の指摘が頻出 → そもそもの設計指針を変える」など、改善ネタが見えてきます。Antigravity の Langfuse 連携を使っている方なら、検証ステップを span として送るとさらに分析しやすくなります。
検証ループを「入れない」ほうがよいとき
Validation Loop はトークン費用と実行時間の両方でコストがかかるので、なんでもかんでも入れればよいわけではありません。試行錯誤の段階で出力を自分の目で必ず読むタスク、創造性が問われて合否が主観的なタスク、失敗しても修正コストがほぼゼロのタスク — このあたりは Validation Loop を外しておくほうが軽快です。本番に流すコードや、長時間放置したいタスクにこそ、このパターンが効いてきます。
実例: Stripe Webhook ハンドラの検証
3パターンの違いを実感したのは、Stripe の Webhook ハンドラをエージェントに書かせたときでした。署名検証 → イベント振り分け → リプレイ耐性、という3要素を満たす実装が必要で、各パターンを順番に試してみたのです。
Self-Verifier だけのときは、初回の出力は一見すると正しく見えましたが、signature_invalid 例外を黙って握り潰す実装になっていました。エージェントは passed: true を返してきたのですが、それは自分で定義したチェック項目を全部通したからにすぎません。「例外を握り潰してはいけない」がそのチェックリストに入っていなかったのです。Self-Verifier は「自分が見ていないもの」には弱いんですよね。
Critic-Approver を足したら2回目で捕まえました。批評役にエラーハンドリングを重点的に見ろと指示していたので、握り潰しを REJECT として返し、書き手が修正してくれました。コストは単発生成の約1.7倍ですが、目に見えて堅牢な実装になりました。
Test-Driven なら同じ問題を検出できたはずですが、それは私のテストスイートに「署名検証が失敗したらどうなるか」というケースが入っていれば、です。これが Test-Driven の落とし穴で、強力な合否判定を得られる代わりに、自分で気づけた範囲しかカバーできません。Critic-Approver は逆に「気づいていなかった次元」を開いてくれる代わりに、判定が決定論的ではなくなります。
本番では、Test-Driven をメインの門にして、その後ろに Critic を1回通す構成に落ち着きました。Critic がテストで見落としていた点を指摘してきたら、それを「テストケースを追加するシグナル」として扱い、警告を無視しない運用にしています。
まずは1つ、Self-Verifier から
Validation Loop は概念としてはシンプルですが、実装すると意外なコーナーケースが出てきます。最初から3パターン全部を組み込むより、まずは最も軽い Self-Verifier を1つのタスクに入れて、エージェントの「自己申告」と実際の品質のズレを観察してみるのがおすすめです。そのズレが大きいと感じたら、Critic-Approver か Test-Driven に進めばよいでしょう。
検証ループの設計をさらに掘り下げたい方は、Antigravity Agent の完了検証を設計する や Antigravity Agent の失敗履歴を学習ループに変える も合わせて読むと、運用面の知見が補強されます。