1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeのコンテキスト枯渇を防ぐ「Thin Orchestrator」設計 — 親は読まない、パスだけ渡す

1
Posted at

はじめに:会話の途中から急に指示を守らなくなった

当社(合同会社ジョインクラス)は、Claude Codeで10部門分のAIエージェントを動かして会社を回しています。launchdジョブは17個以上あり、記事公開・営業パイプライン・朝のダイジェストまでほぼ自動です。

ただ、最初からうまく回ったわけではありません。僕が一番困ったのは 「セッションの後半になると、AIがCLAUDE.mdのルールを守らなくなる」 ことでした。

原因を調べると、2つありました。

  1. CLAUDE.mdにルールを足し続けて 1,000行を超え、ルール同士が矛盾していた
  2. 親セッション(Orchestrator)が各部門のSTATE.mdやログを 自分で全部Readしていた。そのせいでコンテキストがすぐ埋まり、圧縮(要約)のときに大事な指示が抜け落ちていた

この記事では、これを直した設計 「Thin Orchestrator」 を、当社で実際に動いている設定とスクリプトをもとに解説します。読み終えたら、その日のうちに自分のリポジトリに入れられるはずです。

Thin Orchestratorの3原則

当社の CLAUDE.md には次のように書いています(実物から抜粋)。

## Thin Orchestratorの原則

- **コンテキスト使用率を10-15%に維持する**
- ファイルの中身を自分のコンテキストに読み込まず、**ファイルパスだけを渡す**
- 複雑なタスクは必ず `.claude/agents/` のサブエージェントに委任する
- 自分で実作業(コーディング、文書作成等)を行わない

要点はシンプルです。

役割 持つもの 持たないもの
親(Orchestrator) 意図の理解、振り分け、最終報告 ファイル本文、ログ、検索結果
子(サブエージェント) 本文を読む・作業する 他部門の文脈

子エージェントは自分専用のコンテキストで動き、親には結論だけを返します。子が10万トークン読んでも、親に返ってくるのが500トークンの要約なら、親のコンテキストはほとんど増えません。

実装1:サブエージェントを「読むだけ」の権限で定義する

朝のダイジェストは、全部門のSTATE.mdと承認キューを読む、一番重い処理です。これを親でやるとコンテキストを一気に使うので、専用エージェントに切り出しました。

.claude/agents/ai-ceo-morning.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 だとコストが見えません。そこで共通ラッパーを作りました。

.company/scripts/lib/claude-run.sh
#!/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 です(抜粋)。

.company/scripts/lib/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に通知する

呼び出し側(開発部門ジョブ)はこうなります。

.company/scripts/auto-dept-dev.sh
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設計パターン』も参考になります。

書籍一覧: https://zenn.dev/joinclass?tab=books

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?