Antigravity 2.0 は、ビルドの途中で実際の Chrome を立ち上げます。生成したUIを自分で開き、ボタンを押し、フォームに文字を入れ、スクリーンショットを撮り、DevTools を人間が開かなくても不具合を見つけて直そうとします。初めて見たときは、正直に言って少し怖くなりました。速いのです。数分で「動くように見えるもの」が返ってきます。
問題は、その「見える」の中身です。エージェントが何を確認し、どのボタンを押し、どこで直したのかが手元に残らないと、本番に出す判断ができません。速さと引き換えに、検証の透明性が失われては本末転倒です。実ブラウザ自己デバッグを日常運用に組み込むために、証跡(evidence)の残し方と、承認境界の引き方を具体的に設計していきます。
なぜ実ブラウザ自己デバッグは速く、そのままでは怖いのか
従来のエージェントは、コードを書いて「たぶん動く」と言い切るところで止まりがちでした。実ブラウザ自己デバッグは、その先の「実際に開いて確かめる」までを一続きで行います。レンダリング崩れ、クリックしても反応しないボタン、コンソールに出ている例外——これらは静的解析では拾えません。実際に触ってはじめて分かる不具合を、エージェント自身が拾って直せるのは大きな前進です。静的解析だけの検証に比べ、実際に触る検証は不具合の発見率が体感で2倍近く上がります。
一方で、実ブラウザは副作用を持ちます。フォーム送信は本物のリクエストを飛ばし、リンクは本物のページに遷移します。もし開発用と本番用の環境が曖昧なまま自己デバッグが走れば、テストのつもりが本番データを書き換えてしまう可能性があります。さらに、修復の過程が記録されなければ、「なぜ直ったのか」も「本当に直ったのか」も後から検証できません。
つまり必要なのは、速さを殺さずに二つを足すことです。ひとつは証跡、もうひとつは承認境界です。
証跡を3層で残す
自己デバッグの一回の実行を「run」と呼び、run ごとにタイムスタンプ付きのディレクトリを切ります。その中に3種類の証拠を残します。スクリーンショットは人間が一目で状態を掴むため、DOM スナップショットは差分比較のため、ネットワークログはどんな副作用が起きたかを確かめるためです。
| 層 | 残すもの | 役立つ場面 |
|---|---|---|
| スクリーンショット | 各ステップ前後のPNG | 崩れ・空白・エラー画面を目視で即判定 |
| DOMスナップショット | outerHTMLの整形テキスト | run間の差分(textダイフ)で「何が変わったか」を機械比較 |
| ネットワークログ | メソッド・URL・ステータス・宛先ホスト | 本番宛のPOST等、危険な副作用の検知 |
次のスクリプトは、Playwright でブラウザセッションをラップし、エージェントが操作するたびに3層を保存する最小の実装です。エージェントのブラウザ操作を、この薄いラッパー越しに実行させる前提です。
// evidence-session.mjs — 実ブラウザ操作を証跡付きで包む薄いラッパー
import { chromium } from 'playwright';
import { mkdir, writeFile, appendFile } from 'node:fs/promises';
import { join } from 'node:path';
const RUN_ID = new Date().toISOString().replace(/[:.]/g, '-');
const RUN_DIR = join('evidence', RUN_ID);
export async function openEvidenceSession() {
await mkdir(RUN_DIR, { recursive: true });
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
// ネットワーク層: すべてのレスポンスを1行JSONで追記
page.on('response', async (res) => {
const req = res.request();
const line = JSON.stringify({
t: Date.now(),
method: req.method(),
url: res.url(),
status: res.status(),
host: new URL(res.url()).host,
});
await appendFile(join(RUN_DIR, 'network.jsonl'), line + '\n');
});
let step = 0;
// スクリーンショット層 + DOM層をステップ単位で保存
async function capture(label) {
const n = String(++step).padStart(3, '0');
await page.screenshot({ path: join(RUN_DIR, `${n}-${label}.png`) });
const html = await page.evaluate(() => document.documentElement.outerHTML);
await writeFile(join(RUN_DIR, `${n}-${label}.html`), html);
return n;
}
return { browser, page, capture, RUN_DIR };
}このラッパーの狙いは、エージェントの操作を止めないことです。人間が逐一確認するのではなく、あとから確認できる材料を黙って積み上げます。速さは維持したまま、検証可能性だけを足します。