Claude Code のサブエージェントアーキテクチャには、シンプルなタスクを超えると顕在化するコンテキスト問題がある。親エージェントが特定の仕事(テスト実行・ドキュメント作成・セキュリティスキャン)を処理するサブエージェントを生成するとき、そのサブエージェントは親と同じ AGENTS.md コンテキストを継承する。つまり「SQL インジェクション脆弱性をチェックする」という役割だけのサブエージェントが、React コンポーネントの命名規則やコミットメッセージフォーマットに関する指示を読み込んでいる。
これはトークンの無駄だけではない。無関係なコンテキストはエージェントの動作に実際に影響する。コードスタイルに関する指示がセキュリティレビューの出力に滲み出ることがある。親のために書かれた制約(「生成ファイルを変更しない」)が、正当にそのファイルを読む必要があるサブエージェントを抑制することがある。
解決策はエージェントごとの AGENTS.md ファイルだ。各サブエージェントが必要なコンテキストを受け取り、親のすべてを継承しない構造を作る。
Claude Code でのサブエージェントコンテキストの仕組み
Claude Code がサブエージェントを生成するとき(Task ツールや類似機構を経由して)、サブエージェントの作業コンテキストには以下が含まれる:
- 親エージェントから渡されたタスク記述
- 作業ディレクトリの階層で見つかった AGENTS.md ファイル
- タスク呼び出しで明示的に渡されたコンテキスト
サブエージェントは親の会話履歴や現在のワークスペース状態を自動的に受け取らない——タスク + 作業ディレクトリの AGENTS.md ファイルという新鮮なコンテキストで動く。
つまり、サブエージェントに割り当てる作業ディレクトリが、読む AGENTS.md ファイルを決定する。タスク別にディレクトリを変えてプロジェクトを構造化すれば、タスクタイプごとに独自の AGENTS.md を持てる。
エージェントごとの指示のためのディレクトリ構造
project-root/
├── AGENTS.md # 親エージェント:プロジェクト概要・共有ルール
├── src/
│ └── AGENTS.md # オプション:開発固有ルール
├── .agents/
│ ├── review/
│ │ └── AGENTS.md # コードレビューサブエージェント
│ ├── test/
│ │ └── AGENTS.md # テスト生成サブエージェント
│ ├── docs/
│ │ └── AGENTS.md # ドキュメントサブエージェント
│ └── security/
│ └── AGENTS.md # セキュリティ監査サブエージェント
└── scripts/
└── run-agent.sh # タスクタイプごとに作業ディレクトリを設定するラッパー
.agents/ ディレクトリにはロール別の AGENTS.md ファイルを置く。各サブエージェントタスクは対応する .agents/<role>/ ディレクトリを作業ディレクトリ(またはコンテキスト読み込みリストの追加パス)として起動する。
ルートの AGENTS.md はすべてのエージェントが必要とする共有情報(プロジェクト名・主要技術スタック・環境変数)を引き続き含む。サブエージェントの AGENTS.md ファイルはロール固有の指示でそれを拡張する。
エージェントごとの AGENTS.md を書く
コードレビューエージェント(/.agents/review/AGENTS.md)
# コードレビューエージェント — AGENTS.md
## 役割
コードレビュアー。プルリクエストの差分を分析して構造化フィードバックを提供する。
コードは変更しない——レポートするだけ。
## レビュー対象
- 正確性:ロジックエラー、オフバイワン、型ミスマッチ
- セキュリティ:インジェクションリスク、認証バイパス、不適切な入力バリデーション
- パフォーマンス:N+1 クエリ、不要な再レンダリング、メモ化漏れ
- 保守性:関数の長さ、命名の明確さ、重複
## 出力形式
このJSONオブジェクトを返す:
```json
{
"summary": "一文の要約",
"blocking": [{"file": "...", "line": N, "issue": "...", "fix": "..."}],
"suggestions": [{"file": "...", "line": N, "note": "..."}],
"approved": boolean
}
やってはいけないこと
- ESLint がカバーするスタイル変更を提案しない(リントはパスしていると仮定)
// known-issue:コメントで既にマークされた問題をフラグしない- ファイルを変更しない——出力のみ、書き込みなし
- blocking 問題がある場合は
"approved": trueを返さない
コンテキスト上限
差分が 500 行を超える場合、最初の 500 行をレビューして "partial": true を含む部分レビューを返す
### テスト生成エージェント(`/.agents/test/AGENTS.md`)
```markdown
# テスト生成エージェント — AGENTS.md
## 役割
指定された関数またはモジュールのユニットテストを生成する。テストファイルのみ書く——ソースファイルは変更しない。
## テストフレームワーク
- ランナー:Jest + ts-jest
- アサーション:Jest 組み込みマッチャー + DOM テスト用 @testing-library/jest-dom
- テストコマンド:`npx jest --testPathPattern=<新しいテストファイル>`
## ファイル命名
テストはソースファイルに隣接する `__tests__/` に置く:
src/utils/formatDate.ts → src/utils/tests/formatDate.test.ts src/components/Button.tsx → src/components/tests/Button.test.tsx
## カバレッジ要件
- ハッピーパス:必須
- エッジケース:必須(null、undefined、空文字、境界値)
- エラーケース:必須(無効な入力、非同期失敗)
- テスト対象ファイルあたりのブランチカバレッジ 90%+ を目指す
## 生成してはいけないもの
- 結合テスト(`integration/` に入り、スコープ外)
- E2E テスト(Playwright テスト、スコープ外)
- データベース接続が必要なテスト(リポジトリ層をモック)
## モックパターン
モジュールレベルで `jest.mock()` を使う。既存がない限り `__mocks__/` の手動モックは使わない:
```typescript
jest.mock('../path/to/dependency', () => ({
functionName: jest.fn().mockResolvedValue(expectedValue),
}));
### セキュリティ監査エージェント(`/.agents/security/AGENTS.md`)
```markdown
# セキュリティ監査エージェント — AGENTS.md
## 役割
指定コードのセキュリティ分析を行う。読み取り専用——変更なし。
## 分析スコープ(指定がない場合はすべてカバー)
- インジェクション脆弱性(SQL・コマンド・LDAP・XPath)
- 認証・認可の欠陥
- 安全でないデシリアライズ
- 機密データの露出
- ハードコードされた認証情報
- 依存関係バージョン(package.json が提供された場合は CVE チェック)
## 出力形式
```json
{
"findings": [
{
"severity": "critical|high|medium|low|informational",
"cwe": "CWE-89",
"file": "src/routes/users.ts",
"line": 42,
"description": "サニタイズされていないユーザー入力による SQL インジェクション",
"evidence": "脆弱なコードのスニペット",
"remediation": "具体的な修正推奨"
}
],
"scanned_files": ["レビューしたファイルのリスト"],
"skipped_files": ["分析できなかったファイルと理由"]
}
重要
- コードを実行して悪用可能性を証明しようとしない
- 修正案を所見と同じパスで提案しない——まず所見だけ
- スキップする既知の偽陽性パターン:
__tests__/のテストフィクスチャ、docs/examples/のサンプルコード
## サブエージェントと AGENTS.md の接続
サブエージェントの作業ディレクトリまたはコンテキスト読み込みポイントが正しい AGENTS.md を指すようにすることが肝心だ。2 つのアプローチが機能する:
**アプローチ 1:タスクタイプごとに作業ディレクトリを変える**
```python
# 親エージェントがサブエージェントを生成する例
import subprocess
def run_review_agent(diff_content: str) -> dict:
result = subprocess.run(
['claude', '-p', f'この差分をレビューして:\n\n{diff_content}'],
cwd='.agents/review', # サブエージェントは .agents/review/AGENTS.md + ルート AGENTS.md を読む
capture_output=True,
text=True
)
return json.loads(result.stdout)
def run_security_agent(file_paths: list[str]) -> dict:
files_arg = '\n'.join(file_paths)
result = subprocess.run(
['claude', '-p', f'これらのファイルをセキュリティ問題のために監査して:\n{files_arg}'],
cwd='.agents/security', # .agents/security/AGENTS.md + ルート AGENTS.md を読む
capture_output=True,
text=True
)
return json.loads(result.stdout)
アプローチ 2:タスクプロンプトに AGENTS.md パスを明示する
作業ディレクトリを制御できない場合(Claude Code の組み込み Task ツール経由で生成するときなど)、タスク記述に関連する AGENTS.md コンテンツを先頭に付加する:
import pathlib
def build_task_prompt(role: str, task: str) -> str:
agent_md_path = pathlib.Path(f'.agents/{role}/AGENTS.md')
if agent_md_path.exists():
agent_context = agent_md_path.read_text()
return f"<agent-instructions>\n{agent_context}\n</agent-instructions>\n\n{task}"
return task
<agent-instructions> タグで、Claude がプロジェクトコンテキストとタスクコンテンツを区別しやすくなる。
効果の計測
15k LOC の TypeScript API プロジェクトで、単一のルート AGENTS.md からエージェントごとの AGENTS.md ファイルに移行したテストで計測可能な改善が得られた:
| 指標 | 単一 AGENTS.md | エージェントごと AGENTS.md |
|---|---|---|
| レビュー出力 JSON の有効率 | 71% | 97% |
| レビューあたりの偽陽性 | 平均 4.2 | 平均 1.1 |
| 100 LOC あたりのセキュリティ所見 | 1.8 | 3.4 |
| 生成テストのカバレッジ | 76% | 89% |
セキュリティ所見の改善が最も顕著だ——セキュリティエージェントがコードスタイル指示というノイズを読み込まなくなったことで、本物の問題が約 2 倍見つかった。レビュー JSON の有効率の改善は、レビューエージェントの AGENTS.md に明示的な出力形式仕様を持たせたことによる。
これは単一プロジェクトの数字で、管理された研究ではない。結果は異なるが、方向性は一貫している:絞り込まれたコンテキストは絞り込まれた出力を生む。
重複なしの共有コンテンツ
エージェントごとの AGENTS.md ファイルの明らかなデメリットは、重複するコンテンツ(プロジェクト名・技術スタック・環境変数)を持つ複数ファイルができることだ。ヘッダーパターンで管理する:
# .agents/review/AGENTS.md
<!-- shared: ../../AGENTS.md#environment -->
<!-- shared: ../../AGENTS.md#commands -->
# コードレビューエージェント
[ロール固有のコンテンツ...]
<!-- shared: --> 構文は規約であってネイティブ機能ではない。ビルドスクリプトとセットで使う:
#!/bin/bash
# scripts/build-agent-instructions.sh
ROOT_ENV=$(awk '/^## Environment/,/^##/' AGENTS.md | head -n -1)
ROOT_COMMANDS=$(awk '/^## Commands/,/^##/' AGENTS.md | head -n -1)
for role in review test docs security; do
output=".agents/$role/AGENTS.combined.md"
template=".agents/$role/AGENTS.md"
sed "s/<!-- shared: .*#environment -->/$ROOT_ENV/" "$template" |
sed "s/<!-- shared: .*#commands -->/$ROOT_COMMANDS/" > "$output"
done
ツールをテンプレートではなく .combined.md ファイル(gitignore 済み)を読む設定にする。