TL;DR
- AI コードレビューのツールを検討するとき、多くの人は導入単位を「レビュー」だと無意識に決めています。つまり「diff を渡すと指摘が返ってくる箱」を入れるかどうか、という問いの立て方です。
- ところが一次ソースには、その箱を意図的に開けているモードがありました。Alibaba の Open Code Review(CLI は
ocr)の Delegation Mode です。公式ドキュメントはこう書いています。OCR handles deterministic engineering (file selection, rule resolution) while the host agent performs the actual code review using its own LLM capabilities. No LLM endpoint is required on the OCR side.(公式値/ 固定 SHAa694be56/ 2026-09-16 取得) - 実装を読むと、外に出ているものはサブコマンド 2 つでした。
delegate preview(レビュー対象ファイルと mode / ref メタデータ)とdelegate rule(解決済みルールを内容でグルーピングしたもの)です。diff そのものは渡ってきません。 公式手順の Step 3 は「gitを直接使え」です。 - ここが本記事の中心です。段取りだけを外に出すと、残りが見えるようになります。 本シリーズ 1 本目で挙げた決定的な 4 構造(対象選定・粒度・位置決め・検証層)のうち、委譲で外に出ているのは対象選定だけでした。粒度については、
previewの実装にNo Template is passed, which leaves the per-file diff-size ceiling disabledというコメントが明示的に置かれています。 - そして残ったものは、コードではなく Markdown に書かれた自然言語の指示として戻ってきます。委譲用スキル定義は
Coverage is mandatorydo not silently omit filesと書き、total_files/reviewed_files/skipped_files/coverage_rateの報告を要求します。決定的な構造で守っていたはずのカバレッジが、委譲後は「そう書いてある」に戻るわけです。 - 自分のリポジトリを数えたら、同じ比率でした。機械が実行する検査スクリプト
scripts/check_*.pyは 12 本、一方で自然言語の指示ファイル(.agents/skills/*/SKILL.md90 本 +.agents/agents/*.md25 本 +.agents/rules/*.md21 本)は 136 本です(社内データ/ca3b9182f7e1ff9ed0b0310e2f0d9cdb29b61a64/ 2026-09-16 実測)。 - これは**シリーズ 3 本目(最終回)**です。個別ツールの採否は勧めません。結論は 1 本目から変えていません。再現性はモデルの外側に置いた決定的な構造で決まり、その構造は「入れた」だけでは効かない。壊した入力を通す二面検証をしない限り、効いていないまま緑になる。
はじめに:「AI レビューを入れる」と言うとき、何を入れているのか
チームで AI コードレビューの導入を議論すると、話はたいてい次の形に落ち着きます。
ツール X を入れる。PR を開くとレビューコメントが自動で付く。使えるかどうかは、コメントの質を見て判断する。
この立て方は自然です。そして、導入単位が「レビュー」に固定されていることに、たぶん誰も気づきません。評価対象はコメントの質、つまり LLM の出力です。合わなければツールごと外す。それ以外の選択肢が議題に上がらない。
本シリーズの 1 本目、AIコードレビューの再現性は構造で決まるでは、再現性を作っているのは LLM の外側の決定的な構造(対象選定・粒度・位置決め・検証層)だと書きました。2 本目のAIレビューで絞ったツール集合は閉じているかでは、その構造を「閉じている」と要約した瞬間に落差が生まれることを扱いました。
だとすると、次の問いが残ります。再現性の源が LLM の外側にあるなら、導入単位も「レビュー」でなくてよいのではないか。 段取りだけ借りて、レビュー本体は手元のエージェントにやらせる、という単位はありうるのか。
一次ソースに、その形が実装として存在していました。この記事は、それを固定 SHA で読んで、段取りだけを外に出したときに手元へ何が残るかを数えた記録です。
前提を 2 つ置きます。
- 本記事は Open Code Review の採否を勧めません。筆者は本番運用していません。読んだのは固定 SHA
a694be568d9b9a935b2ba11a867d5a91d7ffd833のツリーに入っているファイルだけです。 - CLI のフラグの網羅仕様には依存させません。このリポジトリは日次で push されており、フラグの話はすぐ古くなります。扱うのは責務の分界をどこに引くかという設計の話だけです。
1. 一次ソースにあった「LLM を呼ばない」モード
1-1. 外に出ているのはサブコマンド 2 つ
公式ドキュメント Delegation Mode の冒頭が、分界の定義そのものになっています(2026-09-16 取得)。
OCR handles deterministic engineering (file selection, rule resolution) while the host agent performs the actual code review using its own LLM capabilities. No LLM endpoint is required on the OCR side.
同じページの Sub-commands reference に載っているのは 2 つだけです。実装 cmd/opencodereview/delegate_cmd.go の init() も、delegateCmd.AddCommand を preview と rule の 2 回しか呼んでいません。
# 1. 何をレビューするか(mode / ref メタデータつき)
ocr delegate preview --format json --from main --to feature
# 2. そのファイルに適用されるルール(内容でグルーピング済み)
ocr delegate rule --format json internal/agent/agent.go internal/llm/client.go
同ページの比較表は、3 つの統合モードを「誰が LLM を呼ぶか」で並べています。Agent Skill と Command は OCR、Delegation Mode だけが Host agent です。委譲モードの Prerequisites には No LLM configuration ... is needed — delegation mode never calls an LLM on the OCR side. と書かれています。
1-2. diff は渡ってこない
ここが最初に意外だった点です。ocr delegate は diff を返しません。
公式手順の Step 3 は Use git directly, based on the mode/ref info from Step 1 です。preview が返す mode(workspace / range / commit)と merge_base を使って、ホスト側が自分で git を叩きます。
# range モード(preview が merge_base を返す)
git diff <merge_base>..<to> -- <path>
# commit モード
git show <commit> -- <path>
# workspace モード(未追跡ファイルは diff ではなく全文を読む)
git diff HEAD -- <path>
cat <path>
つまり委譲されているのは「差分の中身」ではなく、「どこを見るべきか」という判断と、それを再現するのに必要なメタデータです。中身の取得は呼び出し側に残る。
この設計には代償もあって、同じ実装ファイルにこう書かれています。
// Security: reject ref-option injection.
reviewOpts := reviewOptions{from: opts.from, to: opts.to, commit: opts.commit}
if err := validateReviewRefs(cc.RepoDir, reviewOpts); err != nil {
return nil, err
}
ref をそのまま返してホスト側に git コマンドを組み立てさせる以上、返す ref がオプションとして解釈されない形であることを、渡す側が保証する必要があるわけです。段取りを外に出すと、境界を越える値の検証がその境界の責務になります。
2. 1 本目の 4 構造を、この分界に当てて数え直す
ここからは筆者の読み方です。以下の対応づけは一次ソースに書かれた分類ではなく、1 本目で筆者が置いた 4 分類を委譲モードに当てたものである点を先に断っておきます。当てた結果の各行には、一次ソースの記述を根拠として添えます。
| 1 本目の構造 | 委譲モードでどちら側にあるか | 根拠(固定 SHA a694be56) |
|---|---|---|
| 対象選定 | OCR 側 | preview が reviewable_files / excluded_files を exclude_reason つきで返す(delegate_cmd.go の delegatePreviewJSON) |
| ルール解決 | OCR 側 | rule が GroupRules の結果を返す(internal/delegate/rulegroup.go) |
| 粒度 | ホスト側 | preview() のコメント(下記) |
| 位置決め | ホスト側 | スキル定義の出力仕様で start_line / end_line が Required: no |
| 検証層(カバレッジ照合) | ホスト側 | スキル定義 Step 6 が coverage_rate の自己申告を要求 |
粒度については、実装に理由まで書かれています。
// preview runs the agent's file-selection logic and returns the preview result.
//
// No Template is passed, which leaves the per-file diff-size ceiling disabled:
// the host agent reviews with its own context window, so OCR's max_tokens is
// not the limit that applies to delegated work.
func (dc *delegateContext) preview(ctx context.Context) (*agent.DiffPreview, error) {
筋は通っています。ホスト側が自前のコンテキスト窓でレビューする以上、OCR 側の max_tokens を上限にする意味がない。意図的に外してあるのであって、落ちているのではありません。
ただし設計判断としての帰結は残ります。段取りを借りると、粒度の決定はこちらの仕事になる。 巨大な diff を前にしてどこで切るかは、ホスト側のエージェントが決めます。1 本目で「粒度は決定的な構造に置くべき層だ」と書いた以上、ここは自分で埋める前提で設計しないと、層がひとつ空きます。
一方で、OCR 側に残った部分は「ただ返すだけ」ではありませんでした。rulegroup.go の GroupRules は、ルール本文が同一でも来歴が違えば別グループに保ちます。
Files are placed in the same group only when their source, matched pattern, and rule text all coincide, so a group's Source/Pattern metadata is accurate for every file it contains. Two files with identical rule text but different provenance (e.g. matched by different patterns or resolved from different layers) stay in separate groups.
グルーピングは重複を減らすための圧縮ですが、圧縮で来歴を潰さないようにしてある。委譲先に渡す成果物としては妥当な選択で、ここは素直に参考になりました。受け取った側が「このルールはどのレイヤ由来か」を判断できる状態が保たれます。
3. 境界にスキーマ版が置いてある、しかし両側は独立に更新される
委譲の出力は JSON で取れて、先頭に版番号が入ります。
const delegateSchemaVersion = "1"
type delegateRulesJSON struct {
SchemaVersion string `json:"schema_version"`
Groups []delegateRuleGroupJSON `json:"groups"`
}
preview 側も同じ定数を使っていて、schema_version / mode / merge_base / reviewable_files / excluded_files などのフィールドを持ちます。責務を分けた境界に版番号を置く、というのは素直に良い形です。
面白いのはその先です。委譲用のスキル定義 skills/open-code-review-delegate/SKILL.md は、スキルと CLI が独立に更新されうる前提を明記し、その場合の手順まで書いています(2026-09-16 取得)。
The Skill and the installed CLI can be updated independently.
そして JSON 出力が使えない場合の指示が、次の一文です。
do not parse text output as JSON or invent missing schema fields
これは委譲という設計の本質的な難所を、そのまま文章にしたものだと思いました。責務を分けると、分けた両側のバージョンが独立に動く。 境界にスキーマ版を置いても、それは「ずれたときに気づける」だけで、「ずれない」ことは保証しません。埋め合わせはどこかがやるしかなく、ここではそれが受け取る側への自然言語の禁止事項として置かれています。
似た構造は --background-file の扱いにも出てきます。スキル定義は、生ファイル 1 MiB とサニタイズ後 8000 文字という 2 つの独立した上限があり、どちらかに触れるとコマンドが中断すると書いたうえで、こう指示しています。
Do not silently truncate the source file.
要約して渡し直せ、要約できないなら OCR に渡さず原資料を直接読め、と続きます。上限はコマンド側にあるが、上限に当たったときの振る舞いはホスト側の判断になっている。
4. 完成の定義は、Markdown に戻ってくる
ここが本記事でいちばん驚いたところです。
段取りを外に出した結果、「レビューが終わったとはどういう状態か」がコードから出て、Markdown に戻ってきています。
スキル定義の Step 6 はこうです。
Before reporting, verify that every previewed file is accounted for. Include
total_files,reviewed_files,skipped_files, andcoverage_ratein the summary. A skipped file must include its reason.
Gotchas にも再掲されています。
Coverage is mandatory — every
reviewable_filesentry must end as reviewed or explicitly skipped; do not silently omit files.
Step 4 には、チェックリストの同一性まで書かれています。
Use
(path, status)as the checklist identity. Workspace mode can report the same path twice when a staged deletion is followed by an untracked recreation.
これは非常に具体的で、実際に踏んだ人が書いた文だと分かります。そして同時に、これを守らせる機械が委譲モードにはいないことも分かります。preview が出した reviewable_files の全件をホスト側が消化したかどうかを検算する工程は、この分界の内側に存在しません。coverage_rate は自己申告です。
出力形式も同じ性質を持ちます。スキル定義 Step 5 のフィールド表では、必須は path と content の 2 つだけで、start_line / end_line / category / severity はいずれも Required: no です。1 本目で「位置決め」を決定的な構造のひとつに数えたのを思い出すと、対比がはっきりします。委譲モードの出力契約では、行位置は必須ですらない。
念のため書いておくと、これは実装の不備ではありません。委譲先のエージェントは任意で、その能力も出力形式もばらつきます。必須項目を増やすほど、渡せる相手が減る。緩い契約は、委譲という設計を成立させるために必要な緩さです。事実として記述したいのはその帰結だけで、どちらが良いかの評価ではありません。
帰結はこうです。
段取りを外注すると、レビューの実行は軽くなる。ただし完成の定義(何をもって終わりとするか)は外注されず、しかも機械可読でない形で手元に戻ってくる。
本リポジトリには、この論点を一般論として扱った記事が別にあります(Agents APIで何が外注できるか)。そちらは harness を層に分けて数え、外注できるのは実行層で、残るのは完成の定義だ、と結論しています。本記事はその一般論を繰り返すために書いたのではありません。実物を読んで分かったのは、「残る」だけでなく「残ったものが Markdown になる」という形の方でした。層が消えるのではなく、検査から指示へ、媒体が変わって残ります。
5. 自分のハーネスを、その軸で数え直す
同じ軸で自分のリポジトリを数えました。1 本目(失敗台帳 L-0069〜L-0080)とも 2 本目(台帳 72 行のうち免除・名指し・通行証に触れる 12 行)とも別の切り口です。今回数えるのは媒体です。機械が実行する検査と、人間・エージェント向けに自然言語で書かれた指示の、それぞれの本数。
ls scripts/check_*.py | wc -l # 12
ls .agents/skills/*/SKILL.md | wc -l # 90
ls .agents/agents/*.md | wc -l # 25
ls .agents/rules/*.md | wc -l # 21
| 媒体 | 対象(glob) | 本数 |
|---|---|---|
| 機械が実行する検査 | scripts/check_*.py | 12 |
| 自然言語の指示 | .agents/skills/*/SKILL.md | 90 |
| 自然言語の指示 | .agents/agents/*.md | 25 |
| 自然言語の指示 | .agents/rules/*.md | 21 |
| (自然言語の小計) | 136 |
(社内データ / ca3b9182f7e1ff9ed0b0310e2f0d9cdb29b61a64 / 2026-09-16 実測。上の 4 コマンドをそのまま実行した値です。scripts/ 配下には validate_* / audit_* も存在しますが、ここでは接頭辞 check_ の glob で数えた値だけを載せます。)
この比が悪いと言いたいのではありません。自然言語でしか書けないものは実際にあります。 判断の優先順位、語調、トレードオフの説明。これらをスクリプトにする方法を筆者は知りません。
言いたいのは 1 点だけです。136 対 12 という比を、自分は今日まで数えたことがありませんでした。 そして委譲モードの Coverage is mandatory を読んで初めて、自分のリポジトリにも同じ形のものが 136 本ある、と気づきました。1 本目で「構造は入れただけでは効かない」と書いたときに念頭にあったのは検査スクリプトの側でしたが、効いているかどうかを確かめる必要がより高いのは、むしろ 136 本の側です。機械が実行しない文は、読まれなかったことを誰も検出しません。
6. 導入単位を問いに変える
ここまでを踏まえて、冒頭の問いに戻ります。AI レビューツールの導入単位は「レビュー」なのか、「ファイル選定とルール解決」なのか。
筆者は答えを断定しません。運用実績がないので断定する資格がありませんし、そもそもチームによって答えが変わる種類の問いです。代わりに、判断の前に置ける問いを 3 つ挙げます。
- いま欲しいのは出力か、それとも段取りか。 レビューコメントの質に不満があるなら、単位は「レビュー」で正しい。不満が「見るべきファイルの選定がぶれる」「ルールの適用が人によって違う」なら、単位はそちらかもしれません。
- 手元のエージェントに、すでに LLM の枠があるか。 委譲モードの
When to useは、サブスクリプション型のコーディングエージェントを使っていて、その枠を再利用したい場合を最初に挙げています。ここは自分の契約形態の話なので、自分で確認できます。 - 完成の定義を、どちらの媒体で持つ覚悟があるか。 段取りを借りる設計を選ぶと、カバレッジと粒度は手元に残ります。残ったものを 136 本目の Markdown にするのか、13 本目の検査スクリプトにするのか。これは導入の副産物ではなく、導入と同時に決める設計判断です。
3 番目が本シリーズの結論とつながります。
まとめ:3 本を通して言えること
シリーズの主張は 1 本目から変えていません。
AI コードレビューの再現性は、モデルの賢さではなく、モデルの外側に置いた決定的な構造で決まる。そしてその構造は「入れた」だけでは効かず、壊した入力を通す二面検証をしない限り、効いていないまま緑になる。
3 本を通して、この主張は 3 つの角度から検算されました。
1 本目は「構造はどこにあるか」を見ました。対象選定・粒度・位置決め・検証層は、プロンプトの文ではなくファイル名として存在していました。2 本目は「その構造をどう要約すると嘘になるか」を見ました。件数で閉鎖性を語ると、拡張点が見えなくなります。そして 3 本目は「構造を分割するとどうなるか」を見ました。分割すると、外に出た側は明確になり、手元に残った側は媒体を変えて生き延びます。
3 本に共通しているのは、やったことが全部同じだという点です。書いてある要約を信じず、実物を数えた。 「6 個に絞っている」を allTools() から検算し、「段取りだけ外注」を AddCommand の回数と preview() のコメントから検算した。数えると、要約では見えなかったものが必ず 1 つは出てきました。
明日からやれること
- 検討中の AI レビューツールについて、「誰が LLM を呼ぶか」を 1 行で書いてみる。書けないなら、まだ導入単位が決まっていません。
- 自分のリポジトリで、機械が実行する検査の本数と、自然言語の指示の本数を数える。上の 4 コマンドを自分のパスに置き換えるだけです。
- 数えた自然言語の側から 1 本選び、その指示に違反した入力を作って、何かが赤くなるか確かめる。赤くならないなら、それは規約ではなく願望です。
この記事で扱わなかったこと
ocr の CLI フラグの網羅仕様、委譲モードの実運用ベンチマーク、ツールの採否。いずれも本記事の射程外です。前 2 つは筆者に一次情報がなく、最後のひとつは読者の文脈でしか決まりません。
FAQ
Q. 委譲モードを使えばレビューの再現性は上がりますか?
一次ソースから言えるのは、「ファイル選定とルール解決が決定的に行われる」ところまでです。レビュー本体はホスト側の LLM が行うので、その部分の再現性はホスト側の設計に依存します。筆者は実運用していないため、全体として上がるかどうかは判断できません。
Q. 段取りだけ外注するのと、レビュー全体を任せるのはどちらが良いですか?
本記事は優劣を判定しません。公式ドキュメントの比較表は 3 つの統合モードを「誰が LLM を呼ぶか」と「用途」で並べており、優劣の記述はありません。第 6 節の 3 つの問いを自分の文脈に当ててください。
Q. カバレッジが自己申告なら、信用できないということですか?
そうではありません。委譲先が任意である以上、緩い契約は設計上必要です。言えるのは、自己申告である以上、検算する工程を自分で足さないと検算されないという事実だけです。これは委譲を選ぶ側の責務であって、実装側の不備ではありません。
Q. 自然言語の指示が 136 本あるのは多すぎませんか?
多い・少ないを判断する基準を筆者は持っていません。数えて分かったのは比率そのものではなく、今日まで数えていなかったという事実の方です。まず数えて、そのうち何本が「違反したら何かが赤くなる」状態かを確かめるところから始めるのが順当だと思います。
Q. このシリーズを 1 本だけ読むならどれですか?
1 本目です。2 本目と 3 本目は、1 本目で置いた 4 つの構造という座標を前提にしています。
References
- Delegation Mode — Open Code Review docs(固定 SHA
a694be56)(2026-09-16 取得) skills/open-code-review-delegate/SKILL.md(固定 SHAa694be56)(2026-09-16 取得)cmd/opencodereview/delegate_cmd.go(固定 SHAa694be56)(2026-09-16 取得)internal/delegate/rulegroup.go(固定 SHAa694be56)(2026-09-16 取得)internal/delegate/format.go(固定 SHAa694be56)(2026-09-16 取得)plugins/open-code-review/claude-code/commands/delegate-review.md(固定 SHAa694be56)(2026-09-16 取得)
