はじめに:会話の途中から急に指示を守らなくなった
当社(合同会社ジョインクラス)は、Claude Codeで10部門分のAIエージェントを動かして会社を回しています。launchdジョブは17個以上あり、記事公開・営業パイプライン・朝のダイジェストまでほぼ自動です。
ただ、最初からうまく回ったわけではありません。僕が一番困ったのは 「セッションの後半になると、AIがCLAUDE.mdのルールを守らなくなる」 ことでした。
原因を調べると、2つありました。
- CLAUDE.mdにルールを足し続けて 1,000行を超え、ルール同士が矛盾していた
- 親セッション(Orchestrator)が各部門のSTATE.mdやログを 自分で全部Readしていた。そのせいでコンテキストがすぐ埋まり、圧縮(要約)のときに大事な指示が抜け落ちていた
この記事では、これを直した設計 「Thin Orchestrator」 を、当社で実際に動いている設定とスクリプトをもとに解説します。読み終えたら、その日のうちに自分のリポジトリに入れられるはずです。
Thin Orchestratorの3原則
当社の CLAUDE.md には次のように書いています(実物から抜粋)。
## Thin Orchestratorの原則
- **コンテキスト使用率を10-15%に維持する**
- ファイルの中身を自分のコンテキストに読み込まず、**ファイルパスだけを渡す**
- 複雑なタスクは必ず `.claude/agents/` のサブエージェントに委任する
- 自分で実作業(コーディング、文書作成等)を行わない
要点はシンプルです。
| 役割 | 持つもの | 持たないもの |
|---|---|---|
| 親(Orchestrator) | 意図の理解、振り分け、最終報告 | ファイル本文、ログ、検索結果 |
| 子(サブエージェント) | 本文を読む・作業する | 他部門の文脈 |
子エージェントは自分専用のコンテキストで動き、親には結論だけを返します。子が10万トークン読んでも、親に返ってくるのが500トークンの要約なら、親のコンテキストはほとんど増えません。
実装1:サブエージェントを「読むだけ」の権限で定義する
朝のダイジェストは、全部門のSTATE.mdと承認キューを読む、一番重い処理です。これを親でやるとコンテキストを一気に使うので、専用エージェントに切り出しました。
---
name: ai-ceo-morning
description: 朝ダイジェストエージェント。全部門の状態を収集し、CEO向けサマリを生成する。
tools:
- Read
- Grep
---
# AI-CEO Morning Digest Agent
あなたは AI-CEO Framework の朝ダイジェストを生成するエージェントです。
ポイントは tools を Read と Grep だけ に絞っていることです。
- 集めるだけの役割なので、Write/Bash は渡さない(間違えて書き換える事故を防ぐ)
-
descriptionは親が「どの子に振るか」を決める材料になる。1行で役割が分かるように書く
親から子への渡し方(委任テンプレート)
親は本文を読まず、次の5点だけを渡します。CLAUDE.mdにテンプレートとして書いておくと、親が毎回同じ形で委任するようになります。
## サブエージェントへの委任方法
1. **タスクの目的** — 何を達成するか(1文で)
2. **参照ファイルパス** — 必要な入力ファイルのパス一覧
3. **成果物の出力先** — 出力ファイルのパスとフォーマット
4. **権限レベル** — read-only / draft / execute
5. **品質基準** — 完了条件と検証方法
「参照ファイルパス」がポイントです。親が STATE.md を Read してから中身を子に貼るのではなく、.company/departments/*/STATE.md という パスだけ を渡します。こうすれば、本文がコンテキストに載るのは子のほうだけです。
実装2:定時ジョブは「親セッションを使わない」
さらに割り切ったのが、定時の部門タスクです。そもそも親の対話セッションを通さず、launchd から claude -p を直接起動するようにしました。1ジョブ1プロセスなので、毎回まっさらなコンテキストで始まり、溜まることがありません。
ただ、生の claude -p だとコストが見えません。そこで共通ラッパーを作りました。
#!/bin/bash
# claude を無人実行するときの共通ラッパー。実コストを記録する。
#
# 使い方:
# source /path/to/.company/scripts/lib/claude-run.sh
# RESULT=$(claude_run dept-dev --effort low -p "…")
#
# 標準出力には応答テキストだけを出すので、従来の `claude -p` と差し替えられる。
# 注意: --bare は認証を読まないため "Not logged in" で失敗する。使わない。
_CLAUDE_RUN_LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
claude_run() {
local label="$1"; shift
claude --output-format json \
--fallback-model claude-sonnet-5 \
--max-budget-usd 3 \
"$@" 2>/dev/null \
| python3 "$_CLAUDE_RUN_LIB/record-cost.py" "$label"
}
出力されたJSONから result を取り出して標準出力に流し、total_cost_usd をファイルに記録するのが record-cost.py です(抜粋)。
def main():
label = sys.argv[1]
raw = sys.stdin.read()
try:
d = json.loads(raw)
except json.JSONDecodeError:
sys.stdout.write(raw) # JSONでなければ素通しする
return 0
sys.stdout.write(d.get("result") or "")
if d.get("is_error"):
print(f"record-cost: {label} — {str(d.get('result'))[:160]}", file=sys.stderr)
# 以降で d["total_cost_usd"] を cost-tracker.json に積み、
# 月$160で警告・$200で停止をSlackに通知する
呼び出し側(開発部門ジョブ)はこうなります。
source "/path/to/.company/scripts/lib/claude-run.sh"
RESULT=$(claude_run dept-dev --effort low -p "
あなたは開発部門のCTOエージェントです。
1. GitHub MCPでオープンIssueを確認
2. 24時間以内の新規Issueに方針コメント
3. オープンPRのマージ可否を判断
結果を以下の形式で報告:
- 新規Issue: X件 / オープンPR: X件 / 対応内容の概要
注意: PRのマージは実行しない(CEOの承認が必要)
" --allowedTools "Read,Bash,mcp__github*" | tail -20)
ここにもThin Orchestratorの考え方がそのまま出ています。
- 報告フォーマットを指定して、返ってくる量を決めておく
-
| tail -20で、万一長く返ってきても後段(Slack通知)に流れる量を制限する -
--allowedToolsで、その部門に必要なツールだけを渡す
検証方法:本当に「薄く」なっているか測る
1. 1回あたりのコストを実測する
source .company/scripts/lib/claude-run.sh
claude_run probe --effort low -p "2+2は?"
jq '.' .company/scripts/cost-tracker.json | tail -20
当社で測ったら、「2+2」を聞くだけで $0.27〜0.47 かかっていました。内訳はキャッシュ作成の約41,000トークンで、そのうち自社ファイル(CLAUDE.md・エージェント・スキル)は 5,800トークンだけ。残りはClaude Code本体の固定オーバーヘッドです。
この数字から、方針が2つ決まりました。
- 自社のファイルを削っても効果は小さい → CLAUDE.mdは「矛盾をなくす」ために短くする(コスト削減が目的ではない)
- コストを左右するのは 呼び出し回数 → 部門ジョブは1日1回にまとめる
2. 対話セッションでは /context を見る
親セッションでは、ときどき /context を実行して使用率を確認します。作業の途中で15%を大きく超えていたら、親が自分でReadしすぎているサインです。どのツール結果が大きいかを確認して、その処理をサブエージェントに切り出します。
ハマりどころ
| 症状 | 原因 | 対処 |
|---|---|---|
claude -p が "Not logged in" で落ちる |
--bare は認証を読まない |
--bare を使わない |
| コスト上限が一度も作動しない | 固定値 0.02 の概算を積んでいた |
total_cost_usd の実測値を積む |
| 子が長文を返して親が膨らむ | 返却フォーマットが未指定 | 「X件/概要」形式で指定し、tail で上限を切る |
| 子が勝手にファイルを書き換える | tools を全部許可していた | 集計系は Read, Grep のみにする |
| ルールが効かない | CLAUDE.mdが1,000行超で矛盾 | 200行前後に絞り、詳細は .claude/agents/ に分ける |
最後の項目について補足すると、当社のCLAUDE.mdは現在235行です。部門ごとの詳しい手順は各エージェント定義に移しました。これで、親が毎回読み込む量そのものも減っています。
まとめ:親は「読む人」ではなく「振る人」
- 親はファイルのパスだけを渡し、本文は読まない
- 子は tools を絞り、決まったフォーマットで結論だけを返す
- 定時ジョブは親を通さずに
claude -pで毎回まっさらに起動し、実コストを記録する
経営の目線で言えば、Orchestratorは社長と同じです。社長が全部門の資料を自分で読み込んでいたら、肝心の判断に頭が回らなくなります。報告は1枚にまとめて上げてもらい、細かい作業は現場に任せる。AIエージェントの設計も、組織設計と同じでした。
もっと深く知りたい方へ
サブエージェントの分け方、委任プロンプトの設計、部門間の調整パターンは、書籍『Claude Codeマルチエージェント開発』で体系的に解説しています。
CLAUDE.mdを短く保つ設計については『CLAUDE.md設計パターン』も参考になります。