1. はじめに
モンスト開発部クライアントエンジニアの太田です。
本記事では、実際のプロダクト開発で得られた経験をもとに、LLM コーディングエージェントを業務に組み込む際の運用方法や工夫を紹介します。
同じように大規模なコードベースで LLM を活用してみたい方への、何かしらのヒントになれば幸いです。
2. モンストクライアント開発の背景
モンストのクライアントは C++ / Cocos2d-x で開発しています。
私は主に、アウトゲーム を開発するグループで、期間限定イベント関連の機能開発を担当することが多いです。
特徴として、
- イベント関連の機能は、そのイベント期間が終わったあとにリポジトリから削除される
- 過去イベントと類似したイベントの実装では、過去イベントのコードを参考にするケースが多い
といった点が挙げられます。
モンストのクライアントサイドは運用開始から 12 年の歴史があり、リポジトリ内はかなり巨大なコードベースになっています。
3. serena の補完として逆引き辞書を作成する
MCP サーバーの serena を利用するだけでなく、
逆引き辞書/ライブラリのリファレンスを Markdown ファイルで用意して Claude Code に読み込ませるようにしたところ、生成されるコードの品質が上がる傾向がありました。
3.1. 背景
コードベースが巨大なため、既存コードとの整合性を取りながら実装するには、常にそれなりの調査コストがかかります。
最初は MCP サーバーの serena を試しましたが、体感としては
- 「参照先コードは見つかるものの、それをどう使うのが正解かまでは分からない」
という場面が多く、あまり手応えがありませんでした。
そこで、「どう使うのか」という観点に特化した逆引き辞書ドキュメントを自前で作成し、Claude Code に参照させたところ、出力されるコードの品質が明らかに向上しました。
3.2. 逆引き辞書の作成
逆引き辞書は、主に自分が担当しているイベント系の機能まわりから作成を進めています。
内容としては、
- ライブラリや既存コードを「利用する側の視点」からまとめた説明
- 最小限のサンプルコード
- どこに定義されているか(所在)
といった情報を書いています。
実際にプロジェクトで使用している逆引き辞書のファイルそのものはお見せできませんが、下記のような Markdown を用意して Claude Code に読み込ませています。
※ 実際のドキュメントとは異なりますが、同種の項目を記載しています。
逆引き辞書の例
# 逆引きリファレンス
---
## クイック逆引きガイド
| やりたいこと | 推奨シンボル | 備考 |
| ---------------------------- | --------------------------- | ---------------- |
| アチーブメントを検索する | `Static::GetAchievement()` | 条件指定での取得 |
| アチーブメント一覧を取得する | `Static::GetAchievements()` | カテゴリ別取得 |
## モジュール索引
### 1. BaseStatic.h
役割: 静的ユーティリティ機能
公開シンボル:
- `BaseStatic` (クラス)
- `BaseStatic::UI_TAG` (列挙)
### 2. BaseCore.h
役割: コア機能とフレームワーク
公開シンボル:
- ...
---
## 詳細リファレンス
説明: アチーブメントがクリア済みかどうかを判定する
引数:
- `achievement`: 判定対象のアチーブメント
戻り値: クリア済みの場合 `true`、そうでなければ `false`
最小サンプルコード:
```cpp
#include "BaseStatic.h"
Achievement* achievement = /* アチーブメント取得 */;
if (BaseStatic::IsClearedAchievement(achievement)) {
// クリア済みの処理
} else {
// 未クリアの処理
}
所在: `source/BaseStatic.h:59`
---
## 用語集
| 用語 | 説明 |
| ----------- | ------------------- |
| Achievement | プレイヤーが達成可能な目標やミッション |
## 設計原則
### メモリ管理
1. RAII: CCObjectContainer による自動管理
serena 自体は、「静的コード解析を使って参照先を token 消費を抑えて探索するための仕組み」という認識です。
ただし、serena で「参照すべきコード」は見つかっても、
- そのコードをどういう前提で
- どんな制約のもとで
- どのように組み合わせて使うべきか
といった情報は、ソースコードだけでは十分に読み取れないことが多いです。
そのため、目的と利用方法をセットで記載した逆引き辞書の方が、コーディングエージェントにとっても(人間にとっても)コードを書く際に参照しやすいと感じています。
3.3. 今後の課題
現状は、手作業+ CodeX を使って逆引き辞書を作成しています。
課題としては、
- 新規実装時に、逆引き辞書の記載漏れを防ぐこと
- ソースコードの変更に合わせて、逆引き辞書を継続的に更新し続けること
の 2 点が大きいです。
今後は Code Wiki や MCP など、公式の仕組みも検証しながら、逆引き辞書の自動生成・自動更新にどこまで寄せられるかを試していく予定です。
4. Pull Request 等のまとまった変更単位の履歴情報をまとめて渡す
リポジトリ内の最新ソースコードだけでなく、Pull Request(以下 PR)の単位でまとまったコミット履歴を LLM に渡すと、実装の理解度や提案の精度が上がることが多いと感じています。
4.1. モンストでのイベント系機能実装の特徴
モンストのクライアントコードは、先述の通り 12 年の歴史があります。
影響範囲が広く、表面上の実装だけをトレースしていると、想定外の影響でバグを埋め込みやすい状況です。
- 終了したイベントで使ったコードは定期的に削除される
- 一方で、過去のイベントと類似した実装を行うケースは多い
という事情から、現在のアクティブなブランチには存在しないコードを参考にしたい場面が頻出します。
この「過去には存在していたが、今は消えているコード」をどう LLM に見せるかがポイントになりました。
4.2. 具体的な手順
自分のワークフローでは、まず AgentSpace で過去対応チケットを検索し、類似タスクのコミットハッシュや PR ブランチを特定してから作業を開始しています。
Claude Code に参照させる情報としては、特定の PR のブランチのすべての変更を追加することを最重視しています。
補足として:
- アクティブなブランチのコードを参照
- 特定の 1 つのコミットだけを追加
といった形でも一定の効果はありますが、PR 全体の変更を渡したときと比べると限定的でした。
また、「特定の PR のブランチのすべての変更」に加えて「JIRA チケットに記載された仕様」を追加したパターンも試しましたが、体感としては PR 全体を読ませた時点でかなりの部分をカバーできている印象でした。
特定 PR のブランチに含まれる一連の変更履歴を Claude Code に参照させることで、
- どんな経緯で設計がこうなったのか
- 途中で仕様変更やロールバックがなかったか
といった実装の文脈をつかみやすくなっているのだと思います。
実際の運用では、PlanMode で要件定義をする際にコミットハッシュを書くだけで、Claude Code が該当 diff を読んでくれるため、ここも合わせて活用しています。
5. サブエージェントによるオーケストレーション
逆引き辞書や PR 単位の履歴情報に加えて、サブエージェントを役割ごとに分けてオーケストレーションすることで、コード生成の精度と安定性がさらに上がりました。
これもあくまで、私個人が試している構成の一例です。
5.1 サブエージェントの役割分担
1 つのエージェントに「設計〜実装〜レビュー〜テスト〜原因調査」まで全部任せるのではなく、役割を分割し、それぞれ専任のサブエージェントに担当させるようにしています。
現在はおおよそ、次のような構成で運用しています。
| ロール | モデル | 役割 |
|---|---|---|
| architect | Opus | 要件整理・設計方針の検討 |
| coder | Sonnet | コーディングと Lint チェック |
| reviewer | Opus | コードの構文確認・レビュー |
| tester | Sonnet | ビルドチェック・簡易テスト |
| troubleshooter | Opus | エラーログ解析・原因調査 |
5.2 ワークフローとエラー時のループ
テスト(ビルド)に失敗した場合は、
- tester がエラーログと状況をまとめて coder に差し戻す
- coder がログを見て修正し、再び reviewer → tester へと流す
ここで重要にしているのは「同じバグ/同じ箇所でこのループが何度回っているか」です。
- 1 回目の失敗 → tester → coder に差し戻し(修正)
- 2 回目の失敗 → 再度 tester → coder に差し戻し(再修正)
と、同じ問題でこのループを 2 回まわしても解決しなかった場合、
3 回目はもう coder に戻さず、troubleshooter に直接投げるようにしています。
troubleshooter には、
- 連続して失敗しているビルドのエラーログ
- 該当コードの差分
- architect が最初に書いた仕様・設計の指示
などを丸ごと渡し、
- そもそも仕様指示レベルでおかしいのか
- 実装の解釈がズレているのか
- 既存コードとの整合性に問題があるのか
を含めて、仕様〜実装までを一度フラットに見直してもらう役割を担わせています。
5.3 モデルコストと推論レベルの切り分け
この構成には、モデル費用を抑えるという意図もあります。
- architect / reviewer / troubleshooter には Opus を使い、
設計・レビュー・原因調査など、重たい推論が必要なところに集中して使う - coder / tester には Sonnet を使い、
実装とビルドチェックといった、回数が多くパターンが比較的安定している処理を任せる
という住み分けになっています。
特に architect で、
- 「この仕様なら、この関数群とこのデータ構造を使ってほしい」
- 「このフラグは複数箇所で参照されるので、命名とライフサイクルに注意」
といったレベルまで、推論不要なくらい詳細に設計を言語化しておくことで、実装側の coder は Sonnet で十分、という状態を作ることを意識しています。
その代わり、
- architect の指示そのものが間違っている
- 仕様レベルの前提がズレている
といったケースは必ず残ります。
そこで、troubleshooter には「仕様や設計の指示に立ち戻って、まとめて再検証する」役割を与えています。
5.4 コンテキスト汚染を避けるための工夫
サブエージェント構成で重要だと感じたのは、「1 つのエージェントに何でもかんでも見せない」ことです。
- architect には「要件と既存設計の情報」を中心に渡し、ビルドログなどは基本渡さない
- coder には「設計方針と関連コード・逆引き辞書」を渡し、余計な議論ログは極力渡さない
- troubleshooter には「失敗したビルドログと差分」にフォーカスして渡す
といった具合に、それぞれのサブエージェントに役割を明確に定めて、コンテキストを絞るようにしています。
こうすることで、
- 1 つのエージェントに無関係な情報が大量に入って混乱する
- 過去の議論や別タスクの情報が混ざって、妙な提案をし始める
といったコンテキスト汚染をかなり抑えられています。
特に大規模なコードベースを扱う場合は、
「たくさん情報を見せれば賢くなる」ではなく、
「役割ごとに見る情報を減らしたほうが、むしろ安定する」という感覚が強くなりました。
6. AGENTS.md と外部設定ファイルの住み分け
エージェントの設定管理まわりの話を少しだけ触れておきます。
エージェントの基本設定は、プロジェクト直下の AGENTS.md に記述しています。
ここには、チーム全体に影響する汎用的な設定(禁止事項・トーン&マナー・共通で読み込むべきリファレンスなど)を書いており、必ずチームメンバーと相談しながら慎重に追加・変更する運用にしています。
一方で、本記事で紹介してきたような手法については、AGENTS.md とは別の外部ファイルに切り出す形で管理しています。
こうすることで、
- チーム全体の設定(
AGENTS.md)を壊さない安心感を保ちつつ - 自分のローカルな環境で、新しいアイデアを他者へ影響を与えず高速に検証できる
というバランスを取ることができました。
7. おわりに
長期タイトルで培われた文化に対してエージェントなどの新しい開発手法を取り入れていくのは大変です。
それでも、巨大なコードベースに対して新しい価値を生み出すための重要な投資と捉え、これからもチームと相談しつつ、エージェントとの付き合い方をアップデートしていきたいと考えています。