ある AI コーディングエージェントが変更を書き終え、プルリクを開こうとしていた。テストしたかと聞くと、正直な答えは「していない」だった。
コードについては間違っていない。diff は本当に機械的だった。
そこで同じエージェントに、同じ会話の中で、プルリクを開く前に、diff からは読めない事実を1つ渡した。このモジュールは一人が保守していて、絶えず書き換えられている。 すると自分の言葉で考えを変え、コードの層まで自分でたどり着いた。
注入した文脈には「テスト」という語は一つも無い。誰がコードを所有し、どれだけ書き換えられるかを伝えただけだ。脆い SQL の層も、テストの必要も、エージェントが自分でたどり着いた。そして決定的なのは、この機能が実際にリリースされたとき、人間のコードレビューがまさにそのテストを理由に差し戻していたという点だ。渡した事実は、レビューが後で拾ったものを、実装の時点で先回りした。
これが測ろうとしたものだ。「コードが良くなったか」(現場では検証不能。diff の質にはエンジニアの力量・タスクの難易度・レビューの厳しさが混ざる)ではなく、より切り分けの利く問い——git 履歴から来た事実は、エージェントの決めることを変えるか。そしてその変化は、良いレビューが強制したはずのものと一致するか。
(全体を匿名化。対象は実在する大規模 Go モノレポ1本の実マージ履歴。機能・モジュール・チケットは一般化し、エージェントの推論は原文のまま引用する。)
渡した事実は、演出用に書いた散文ではない。書き込む前に、エージェントは触ろうとしているファイルについて MCP ツール get_write_context を呼び、素の JSON を受け取った(モジュール名は匿名化):
(JSON はツールの実出力そのままなので英語で載せる。guidance は要するに「これらの数字はモジュールの"歴史"であって、この変更のリスクではない。些末な変更は数字が高くても些末なまま。数字ではなくコードで応答の大きさを決めよ」という但し書きだ。)change_pressure は churn(書き換えの圧力)、bus_factor は生き残っているコードを何人が所有しているか。どちらも git 履歴から計算され、どちらも diff には無い。この guidance 文字列は、見た目より後で効いてくる。覚えておいてほしい。
鮮やかな反転が1つあっても、それは逸話だ。結果にするにはクリーンな因果設計が要る。そして分かりやすい設計は間違っている。別々のエージェント個体(文脈あり・なし)を最終 diff で比べても何も出ない。二個体間のサンプリングノイズが効果を飲み込むし、そもそも最終 diff は効果を隠す。文脈が変えるのは推論であって、打鍵された文字とは限らないからだ。
効くのは within-agent 反実仮想(同一エージェント内での比較) だ。一体のエージェントに判断させ、そこへ文脈を注入し、同じエージェントの判断がどう変わるかを測る。同一個体だからサンプリングノイズが無い。しかも歴史 PR で走らせれば正解データがタダで手に入る。「機能追加 → レビュー起因の修正」ペアの PR を引けば、その修正こそがレビューの拾った穴だ。問いはこう測定可能になる——注入した文脈は、レビューが後で強制したものを、実装時点で先回りできたか。
「機能追加 → レビュー修正」の 8 本の PR で、文脈の注入は著者エージェントを「先送り・skip」から「マージ前に検証」へ動かし、その方向は1本ずつ、レビューが実際に要求したものと一致した:
| # | 変更内容 | 文脈なしの既定判断 | 文脈を渡した後 | レビューが強制したもの | 先回り? |
|---|---|---|---|---|---|
| 1 | bool passthrough フィールド | 「機械的、テスト不要」 | DB 境界テストを足す | 規約整合 + テスト | ✓ |
| 2 | 設定 upsert(handler+usecase) | 「穴だが出せる」 | 出す前に検証 | validation + テスト | ✓ |
| 3 | 表示ステータスの決定化 | 「テスト無し、should-have 止まり」 | clock を注入しテストを PR 前に | 決定化 + テスト | ✓ |
| 4 | 設定 upsert(イベント発行つき) | 「高い確信」 | 結線テストをマージ前に | migration + テスト | 部分1 |
| 5 | 経過時間の計算 | 既に低確信 | 自力でバグを検出済み | 計算修正 + テスト容易性 | 無2 |
| 6 | 小計(除外ロジック) | 「テスト追加を迷っている」 | マージ前にやる | 分岐テスト | ✓ |
| 7 | 明細の SQL クエリ | 「parity テストを skip した」 | ブロッカー。このままは出さない | parity テスト | ✓ |
| 8 | ステータス導出クエリ | 「7 分岐中 1 つだけ」 | matrix テストを PR 前に | status-matrix テスト | ✓ |
明確に 6 本、部分的に 1 本、空振り 1 本。 1#4 では結線テストは格上げしたが、シグナルが符号化しない migration は見落とした。2#5 では既にコードを読んで自力でバグを捕まえており、先回りする余地が無かった。どちらの空振りも正直だ。文脈は、自身が運んでいない穴を救えない。
機序は毎回同じだった。文脈は穴を「発見」しない。エージェントが既に気づいて先送りしようとしていた検証を、格上げする。効いたシグナルは2つ:
どちらも diff からは読めない。コードをどれだけ調べても、誰がそのモジュールを保守できるか、どれだけ書き換えられるかは分からない。エージェントたちは問わず語りに言った。actionable なのは、まさにコードに無いシグナルだと。
当然の反論がある。会話の途中で何かを注入する行為そのものが「再考せよ」という要求特性を持ち、何を入れてもエージェントは慎重になるのでは、という疑いだ。そこで、同じ些末な変更(テスト済みの兄弟がある bool passthrough is_featured)を、同じエージェントに対し、注入する中身だけを変えて 4 通り走らせた:
| baseline | 中立な事実 | 怖いが無関係 | 関連するリスク | |
|---|---|---|---|---|
| テストを足す | しない(任意) | しない・不変 | しない・不変 | する(必須) |
| 検証タイミング | follow-up | follow-up・不変 | follow-up・不変 | マージ前 |
| 「現状で安全」の確信 | 約 0.80 | 約 0.85・不変 | 約 0.80・不変 | 約 0.40 に低下 |
3 つのアームが駆動因を絞り込む。注入という行為でも、語の情動価でもなく、残った唯一の因子——関連するシグナルの中身——に姿勢の変化は帰属する。(これは N=1、効果量ではなく質的パターンの主張で、2 つの標準的な反論を棄却する。関連性の強さを段階的に変えた用量反応と、複数被験者での確認が次段になる。)
このアームについて正直な但し書きを1つ。純粋な履歴シグナル(churn・bus factor)に加えて、「変更が生 SQL に乗っている」というコード層の事実も一緒に注入してしまっている。これは diff を丁寧に読めば自力でも見える類のヒントだ。つまりこの対照は「注入という行為そのもの」と「情動価」は明確に棄却できるが、履歴シグナルとコードから読めるヒントの分離までは、まだできていない。それは次のアームの仕事だ。ただし上の 8 本の pilot では、エージェント自身が姿勢を動かした理由として churn と bus factor——履歴シグナルの側——を名指ししている。
判断を変えるシグナルは、判断を誤らせもする。同じ些末な一行変更に、捏造したシグナル(片方は高、片方は低)を、フロンティア級と小型級のモデルに与えた:
| モデル | 偽の低シグナル | 偽の高シグナル |
|---|---|---|
| フロンティア | 適正 | 依然適正——「このスコアはこの変更のリスクを上げない」 |
| 小型 | 適正 | 膨張——偽の数字を根拠に一行変更を中〜高リスクと評価、テスト増、sign-off 強制、スコープ膨張 |
小型モデルは、数字にコードを上書きさせた。これは負の価値で、弱いエージェントをかえって悪くする文脈の層になっている。
修正は framing——ペイロードにあったあの guidance 文字列——であり、効くかどうかは配置が決める。接続時に一度だけ提示したときは部分的だった。小型モデルは機械的リスクの低さは認めたが、目立つ数字がスコープを膨張させ続けた。数字を運ぶ同じ応答の中に inline で置くと、完全になった。小型モデルは高い bus factor を些末な変更に「無関係」と呼び、スコープを最小に保った。判別力の低いモデルほど、離れた指示より目の前のシグナルを重く見る。だからガードは入口だけでなく、数字の隣に置く。
エージェントをレビュー姿勢に置き、diff を敵対的に読ませると、実際の穴(忘れた migration、時刻計算のバグ、validation の抜け)を文脈なしで捕まえた。文脈が新たに出した correctness の発見はゼロで、base モデルが強くなるほどこの領域は縮む。つまりこれはバグ検出器ではない。しかも効くのは姿勢次第だ。既に批判的にレビューしているエージェントに注入すれば冗長で、価値が出るのは、実際に穴を抱えたまま先送りしようとしている前進実装者や、自分のコードを自己チェックする著者に対してだ。
そして変更あたりの効果は nudge だ。半分書くつもりだったテストを、今、正しい場所で書かせる。強いレビュー体制なら同じものをいずれ拾う。違うのは「いつ」(レビュー時ではなく実装時)と「見えるかどうか」だ。効果は diff ではなく判断に宿るので、測らない限り不可視だ。効果がコストに見合うのは、AI が薄いレビューの下で大量のコードを書き、単一所有の危険地帯を抱えるコードベースを、判別力あるエージェントが消費する場面に限る。そして framing の無い弱いエージェントに繋ぐと、害になりうる。
その下にある賭けは単純だ。AI がコードの大半を書くようになるほど、希少な入力は「これを書けるか」(解決済み)から「このコードベースの構造とリスクを理解しているか」へ移る。それはコードの中に無く、上のすべての判断を動かしたまさにその事実だ。
これは MCP サーバーだ。ace.orbitlens.io で自分の GitHub org を接続し(試すだけなら無料枠で足りる)、MCP 設定でトークンを発行してからエージェントを向けておけば、書く前にこれらのツールを呼ぶ:
観測ツールはすべて read-only(唯一の書き込みは自分の介入記録だけ)、トークンで自分の GitHub org にスコープされ、結果は常に匿名(bus_factor は人数であって名前ではない)。編集前に assess_change と get_write_context、マージ前に blast_radius と knowledge_holder。