はじめに
生成AI(いわゆるLLM。あとで説明します)にコードを書かせる、というのはもう珍しい話ではなくなりました。しかし「1体のAIにひたすら頼み続ける」使い方と、「複数のAIに役割分担させ、上下関係を作って動かす」使い方とでは、こなせる仕事の規模がまったく違います。
今回、筆者は自作のRust製ソフトウェア recisdb-rs(テレビ放送のストリームを受信・録画するためのツール群)に対して、Claude Codeという開発支援ツールの「マルチエージェント」機能を使い、実際に大規模なリファクタリング作業を任せてみました。具体的には、
- 一番上に立つ「統括役」のAI(Fableという最も高性能なモデル)が全体を仕切り
- その下に「中堅」のAI(Sonnet 5)と「新人・速攻要員」のAI(Haiku)を並列に何体も配置し
- 手分けしてコードレビューとリファクタリングを実行させる
という体制を組みました。結果として、2日ほどの作業で、XSS脆弱性の是正、4200行超の「神クラス」の分割、データベースのロック問題の解消、2822行のファイルの9モジュールへの分割など、いくつもの実質的な改善を積み上げることができました。
この記事では、「何を」「どういう体制で」「どうやって」やったのか、そしてそこから得られた実践的な知見を、生成AIをあまり触ったことがない開発者・技術好きの方にもわかるように解説します。結論を先に言うと、AIが全部やってくれたわけではありません。
方針を決め、レビュー結果を検証し、要所要所で人間が実機検証するという役割は最後まで人間の仕事でした。AIは「手を動かす人数」を劇的に増やしてくれる道具、という位置づけです。
前提知識:LLM・エージェント・モデルの大小って何?
本題に入る前に、言葉の意味を簡単に整理しておきます。
LLM(大規模言語モデル) とは、大量の文章を学習して、人間の指示に対して自然な文章やコードを生成できるようになったAIプログラムのことです。ChatGPTやClaudeがその代表格です。
エージェント とは、LLMに「自分で考えて、ツールを使い、複数のステップの作業をこなす」能力を持たせたものだと考えてください。人間が1行1行指示しなくても、「このバグを直して」と言えば、AI自身がコードを検索し、原因を突き止め、修正し、テストを実行する、というところまで自律的にやってくれます。Claude Codeはこのエージェント機能を持つ開発支援ツールです。
サブエージェント(マルチエージェント) は、1つのエージェントが自分の分身のような別のエージェントを何体も呼び出し、並列に作業を分担させる仕組みです。人間の職場に例えると、マネージャーが部下に「このファイルの担当はあなた、あのファイルの担当はあなた」と仕事を割り振り、同時並行で進めさせるイメージに近いです。
モデルの大小 というのは、AIの「地頭の良さ」と「コスト・速度」のトレードオフのことです。今回使ったモデルで言うと、
- Fable: 最も高性能で、複雑な判断や大量の情報の統合が得意。その分、時間もコストもかかる。
- Sonnet 5: バランス型。実装作業や中規模のレビューに向く。
- Haiku: 軽量・高速・低コスト。機械的な調査や単純作業を大量にこなすのに向く。
人間の組織で「部長・課長・新人」に近い役割分担があるように、AIにも「得意分野に応じた配置」が有効だ、というのが今回の実践の出発点です。
体制図
今回組んだ体制はこのような階層構造です。
┌─────────────────────┐
│ Fable (統括役) │
│ ・全体レビューの統合 │
│ ・リファクタ計画の立案 │
│ ・難しい設計判断 │
│ ・結果の検証と統合 │
└──────────┬───────────┘
│ 並列にサブエージェントを起動
┌────────────┬───────┼───────┬────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ... ┌──────────┐ ┌──────────┐
│ Sonnet 5 │ │ Sonnet 5 │ │ Haiku │ │ Haiku │
│サブシステム │ │サブシステム │ │機械的探索 │ │単純修正 │
│レビュー/実装 │ │レビュー/実装 │ │grep調査 │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘Fableが「今回のコードベースは5つの領域に分けられそうだ」と判断し、それぞれの領域担当としてSonnet 5のサブエージェントを並列に立ち上げる。探索や機械的な下調べが必要な場面ではHaikuを使う。そして各サブエージェントから返ってきた報告をFableが読み込み、矛盾がないか、重複がないかを確認しながら1つの文書にまとめる、という流れです。
実際の流れ(時系列)
1. 並列システムレビュー(2026年7月10日)
対象は recisdb-rs というワークスペースで、`b25-sys` / recisdb-rs / recisdb-protocol / recisdb-proxy / bondriver-proxy-client の5つのクレート(Rustのパッケージ単位)から構成されています。中でも recisdb-proxy は、Windows用のテレビチューナー抽象化DLL(BonDriver)をネットワーク越しに共有するプロキシサーバで、独自のTCPプロトコル(BNDP)、axumによるWebダッシュボード、SQLiteデータベース、C++ DLLとのFFI(他言語の関数を呼び出す仕組み)、tokioによる非同期処理が絡み合った、なかなか複雑なシステムです。
これをまるごと1体のAIに読ませようとしても、AIが一度に処理できる情報量(コンテキスト)には限りがあり、破綻します。そこでFableは、システムを次の5系統に分割しました。
- BNDPサーバ・セッション層
- チューナー層
- Web層
- DB・スキャン・TS解析層
- クライアント・横断的関心事
この5系統それぞれに対して、別々のサブエージェントを並列に走らせてレビューさせ、Fableがすべての結果を統合しました。最終的に、重要度順(高:H1〜H8、中:M1〜M11、低)に整理された問題リストと、3フェーズのロードマップが docs/SYSTEM_REVIEW_2026-07.md という文書にまとまりました。
2. 見つかった問題の例
レビューで見つかった代表的な問題は次のようなものでした。
- H1: Webダッシュボードのコードで
JSON.stringifyの結果をそのままHTML属性に埋め込んでおり、XSS(悪意あるスクリプトを紛れ込ませる攻撃)の脆弱性になっていた。 - H2: セッションを表すオブジェクトが約60フィールド・4200行超に膨れ上がった「神クラス」になっており、選局成功時の後処理コードが5箇所にコピー&ペーストされていた。
- H3: データベースのミューテックス(排他制御のロック)を
awaitをまたいで保持しており、本来並列に処理できるはずの全セッションが直列化されてしまっていた。 - H5: リングバッファ(TSストリームを扱う内部バッファ)がSPSC(単一生産者・単一消費者)の前提を満たしておらず、満杯時にストリームが破損する恐れがあった。
- H6: クライアント側に自動再接続の仕組みがなく、サーバを再起動するとストリームが恒久的に止まってしまっていた。
- M4: ダッシュボードの実装が、HTML・CSS・JavaScriptが全部同居した3900行の単一のRust文字列になっていた。
そのほか、どこからも呼ばれていないデッドコードが数千行規模で見つかりました。
3. リファクタ実行(2026年7月10日〜11日、約2日間)
文書化されたロードマップに沿って、Phase 0(即効性があり低リスクな5項目)とPhase 1(中期的な10項目)を完了させました。1つの問題を1つのコミットに対応させる方針で進めたため、途中経過が追いやすく、何かおかしければすぐに戻せる状態を保てました。
見つかった問題と対応のハイライト
区分 | 問題 | 対応内容 |
|---|---|---|
セキュリティ | ダッシュボードのXSS(H1) |
|
保守性 | Session god object・5重複コピペ(H2) | 選局成功後処理をヘルパー関数に集約 |
性能 | DBロックがawaitを跨いで直列化(H3) | ロック保持区間の縮小、NID/TSIDの直接クエリ化 |
正確性 | リングバッファのSPSC不変条件違反(H5) | 満杯時のレースを解消 |
可用性 | クライアントの自動再接続なし(H6) | 指数バックオフ付き自動再接続 + セッション復元を実装 |
保守性 | ダッシュボードが3900行の単一文字列(M4) | 段階的な分離に着手(Phase 2で継続) |
データ整合性 | スキーマ移行が場当たり的 |
|
保守性 | api.rsが2822行の巨大ファイル | ドメイン別9モジュールに分割、 |
正確性 | 失敗時も | 適切なHTTPステータスコードへ是正 |
品質保証 | 既知失敗テストが放置 | テスト4件を修正し、全クレートでグリーンに |
運用 | 設定ロード処理が散在 |
|
信頼性 | SQLiteのジャーナルモード | WAL(Write-Ahead Logging)化 |
作業終了時点では、テストスイートは全クレートでグリーンになっています。なお、大規模な構造変更(共有クレート化、選局ポリシーエンジンの抽出、Session状態機械の型による強制、ダッシュボードの静的アセット化、DBアクセス戦略の刷新など)はPhase 2として残されており、こちらは今後の課題です。
得られたナレッジ
ここが今回の記事で一番伝えたい部分です。実際にやってみて分かった、マルチエージェント運用のコツをまとめます。
レビューの分割統治が効く
巨大なコードベースは、AIが一度に読める分量(コンテキスト)に収まりません。人間が大きな仕様書を1人で読み切れずに章ごとに分担するのと同じで、サブシステム単位で並列にレビューさせ、それを上位のモデルが統合するというやり方は、網羅性(見落としの少なさ)と一貫性(矛盾のなさ)を両立させやすいと感じました。
モデルの適材適所がコストと品質を両立させる
すべての作業を最上位モデルにやらせると、時間もコストも跳ね上がります。単純な検索や機械的な調査はHaiku、実装や中規模レビューはSonnet、難しい判断と最終統合はFable、という階層分担にすることで、全体としての効率が上がりました。「難しい問題ほど賢いモデルに」という単純な原則ですが、意外と徹底するのが難しく、意識してモデルを使い分ける価値がありました。
「レビュー文書を先に作る」のが重要
いきなり修正させるのではなく、まず問題リスト・重要度・フェーズ分けをまとめたロードマップ文書を作らせてから着手する、という順番がとても効きました。この文書は、複数のエージェントが情報を共有するための「共通の記憶」として機能します。人間が途中で進捗を確認するときの手がかりにもなり、手戻りの防止にもつながります。
コミット粒度は1問題=1コミット
1つの問題に対して1つのコミットを作らせることで、あとからレビューしやすく、何か問題が起きたときにロールバック(元に戻す)しやすくなります。地味ですが、AIに大量の変更をさせるときほど効いてくる基本です。
テストを先にグリーンにする
既知の失敗テストを放置したままリファクタリングを進めると、「新しく壊れたのか、元から壊れていたのか」の判別がつかなくなります。Phase 0の最初にテストの是正を持ってきたことで、以降のリファクタリングの成否を機械的に判定できる土台ができました。
うまくいかなかったこと・人間の役割
すべてが自動でうまくいったわけではありません。特にFFI(他言語の関数呼び出し)や unsafe が絡む部分は、AIの静的なレビューだけでは不十分でした。
recisdb-proxy はWindowsのBonDriver(チューナー抽象化DLL)をC++経由で呼び出しています。この手のコードは、未定義動作(UB)やSEH例外(Windows特有の例外機構)が絡み、リリースビルドでだけクラッシュする、といった厄介な現象が起こります。今回のプロジェクトでも過去に、
GetTsStreamのポインタコピー版はBonDriverProxyEx経由だと未実装・不具合があることが多く、必ずゼロコピー版のGetTsStream2を使うべきこと- C++側のFFIラッパーには必ず
try { ... } catch (...) { ... }を書き、`/EHa` コンパイラフラグでSEH例外も拾えるようにしておくべきこと
といった知見が、実機での動作確認を通じて人間側から得られており、これをAIにフィードバックする形で今回のレビューにも反映しました。つまり、「AIが提案したコードが正しく動くかどうか」の最終的な保証は、実機で動かして確認するという人間の作業に依存していた、ということです。AIは広く速く見る力に長けていますが、実機の癖や過去の障害経験のような「暗黙知」を持っているわけではありません。この部分の橋渡しは、今回も一貫して人間の役割でした。
次にやりたいこと
今回の経験を踏まえて、次に試したいと考えていることをいくつか挙げます。
Workflow(ワークフロー)機能によるパイプライン化
レビュー→検証→修正という流れを、都度その場の判断に任せるのではなく、スクリプトで決定的に制御したいと考えています。特に、「見つかった問題を、別のエージェントが敵対的に検証する(本当にバグなのか反証を試みる)」という adversarial verify(敵対的検証)パターンを導入したいです。これにより、「それっぽいが実は問題ない指摘」といった誤検出を減らせると見込んでいます。
git worktreeによる並列実装
今回はレビューこそ並列でしたが、実際のリファクタリングの実装は1つずつ直列に進めました。git worktree(同じリポジトリの別々の作業コピーを同時に持てる仕組み)を使ってエージェントごとに独立した作業コピーを割り当てれば、互いに独立した修正を並列に実装し、後でマージする、という進め方ができるはずです。
loop-until-dryパターン
レビューの回数をあらかじめ決めてしまうと、固定回数では拾いきれない問題が残ることがあります。「新しい問題が2ラウンド連続で出なくなるまでレビューを繰り返す」というやり方(loop-until-dry)を導入し、残存する問題をできる限り絞り出したいと考えています。
Phase 2への適用
共有クレート化のような大規模な構造変更は、単純なレビュー→修正だけでは済みません。影響調査をHaikuで並列に行い、複数の設計案を生成させて審査(judge panel、複数の案を比較検討させる仕組み)にかけ、そのうえで実装・検証に進む、という多段構成が向いていると考えています。
CIとの統合
最終的には、プルリクエストが作られるたびにレビューエージェントが自動的に起動する運用まで持っていきたいと考えています。