はじめに
当社(合同会社ジョインクラス)では、Claude Codeのサブエージェントを17個運用し、launchdで17本以上のジョブを無人で回しています。「経理」「出版」「朝ダイジェスト」など、社内の部門をそのままサブエージェントにした構成です。
最初からうまくいったわけではありません。以前の僕はルールをすべて CLAUDE.md に書き足していて、1,000行を超えたあたりでルール同士が矛盾し、指示と逆の動きをするようになりました。もう一つの失敗は、AIが git push --force を勝手に実行してブランチが消えた事故です。
この2つの失敗から学んだことは次の2つでした。
-
責務はサブエージェントに分けて、
CLAUDE.mdは薄くする(今は200行以内) - ツール権限は「その責務に必要な最小限」にする
この記事では、実際に動いている定義ファイルとスクリプトを使って、その日のうちに真似できる手順を紹介します。
1. サブエージェント定義の最小形
サブエージェントは .claude/agents/*.md に置くMarkdownファイルです。フロントマターの tools で使えるツールを限定できます。以下は当社で毎朝動いている「朝ダイジェスト」エージェントの実物(冒頭部分)です。
---
name: ai-ceo-morning
description: 朝ダイジェストエージェント。全部門の状態を収集し、CEO向けサマリを生成する。
tools:
- Read
- Grep
---
# AI-CEO Morning Digest Agent
あなたは AI-CEO Framework の朝ダイジェストを生成するエージェントです。
ポイントは次の3つです。
-
descriptionはルーティングの判断材料になる。 親エージェントは、この1行を見て委任先を決めます。「何をするか」に加えて「何の担当か」を書くと、誤った委任が減ります。 -
toolsを省略すると全ツールが継承される。 「とりあえず省略」が一番危険です。 -
本文は役割・入力ファイル・出力フォーマットに絞る。 会社全体のルールは親の
CLAUDE.md側に置きます。
2. 責務分割の基準:「書き込む先」で切る
当社で効果があった分け方は、機能単位でも部門単位でもなく、「どのファイル(状態)に書き込むか」で分ける方法でした。
| エージェント | 読む | 書く | tools |
|---|---|---|---|
| ai-ceo-morning | 全部門の STATE.md
|
なし(標準出力のみ) | Read, Grep |
| ai-ceo-cfo | 会計データ | departments/finance/ |
Read, Write, Edit, Grep |
| ai-ceo-publisher | 書籍リポジトリ |
departments/publishing/, 書籍原稿 |
Read, Write, Edit, Bash, Grep |
| ai-ceo-sdr | リードリスト | 営業ドラフト | +WebFetch, WebSearch |
書き込み先が重なるエージェントが2つあると、同じ STATE.md を互いに上書きし合う事故が起きます。逆に、書き込み先が一意に決まっていれば、並列で走らせても衝突しません。
判断に迷ったら、次の順で確認します。
- このエージェントは何も書かなくてよいか → Read / Grep だけ(集計・レビュー系)
- 書くのは自部門のMarkdownだけか → +Write / Edit
- コマンドの実行が本当に必要か → +Bash(ここで一段慎重に)
- 外部への通信が必要か → +WebFetch / WebSearch(営業リサーチなど最小限に)
経理(CFO)エージェントにBashを渡していないのは意図的です。財務データを扱う担当が任意のコマンドを実行できる必要はありません。
3. 無人実行では --allowedTools で二重に絞る
対話セッションならエージェント定義の tools だけでも足ります。ただ、launchdから claude -p を無人で叩く場合は、呼び出し側でも --allowedTools で絞るようにしています。定義ファイルを誰か(またはAI自身)が書き換えても、実行時の上限は変わらないからです。
以下は、毎週動いている出版部門ジョブの実物(抜粋)です。
source "/Users/kyoagun/workspace/one-ceo/.company/scripts/lib/claude-run.sh"
cd "$PROJECT_ROOT"
RESULT=$(claude_run dept-publishing --effort low -p "
あなたは出版部門のPublisherエージェントです。以下のタスクを実行してください。
1. 全書籍の状態を確認:
- Zenn: /Users/kyoagun/workspace/zenn-content-company/books/ の各config.yaml
- Kindle: .company/departments/publishing/STATE.md
...
" --allowedTools "Read,Bash" 2>/dev/null | tail -20)
このジョブはレポートを作るだけなので、Write / Edit は渡していません。レポートの書き込みはシェル側(echo >> $LOG_FILE)で行います。**「AIは判断と文章生成、ファイルへの書き込みはシェル」**と分けておくと、どこが何を壊したのかを追いやすくなります。
4. 共通ラッパーで予算の上限とコストの記録を強制する
サブエージェントを増やすと、次に効いてくるのはコストです。当社では、すべての無人実行を claude_run という関数経由に統一しました。
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"
}
-
--max-budget-usd 3:暴走しても1回あたりの上限が決まります -
--output-format json:total_cost_usdの実測値を取り出し、ラベル別にcost-tracker.jsonへ記録します - 標準出力には応答テキストだけを返すので、既存の
claude -pと置き換えられます
実際に測ってわかったのは、「2+2」を聞くだけの1回でも $0.27〜0.47 かかることでした。内訳の大半はキャッシュ作成の約41,000トークンです。そのうち自社のファイル(CLAUDE.md・エージェント・スキル)は5,800トークンしかなく、残りはClaude Code側の固定オーバーヘッドでした。つまり、サブエージェントの定義を短くしても大きくは減らず、効くのは呼び出し回数を減らすことです。「1部門1ジョブを毎日」ではなく、「必要な曜日だけ、--effort low で」が当社の結論です。
落とし穴
-
--bareは認証情報を読まないため、無人実行だとNot logged inで失敗します。コメントに残しておきましょう。 - macOSのcronはフルディスクアクセスの問題でジョブが全滅しました。launchdを使ってください。
- launchdの環境では
PATHが最小限です。スクリプトの冒頭で nvm や Homebrew のパスをexportしてください。
5. 検証:エージェントの健全性を定期チェックする
エージェントは作りっぱなしにすると劣化します。当社では月1回、次のスクリプトで「30日以上 STATE.md が更新されていない部門」を検出し、Slackに通知しています。
AGENT_COUNT=$(ls "$AGENTS_DIR"/*.md 2>/dev/null | wc -l | tr -d '[:space:]')
CURRENT_DATE=$(date +%s)
for state in "$DEPTS_DIR"/*/STATE.md; do
if [ -f "$state" ]; then
dept=$(basename "$(dirname "$state")")
FILE_MOD=$(stat -f %m "$state" 2>/dev/null || echo 0)
DAYS_OLD=$(( (CURRENT_DATE - FILE_MOD) / 86400 ))
if [ "$DAYS_OLD" -gt 30 ]; then
STALE_DEPTS="${STALE_DEPTS} - ${dept}: ${DAYS_OLD}日前\n"
fi
fi
done
「書き込み先で責務を切る」設計にしておくと、更新日時を見るだけで、そのエージェントが動いているかがわかります。これも分割基準を揃えておく利点です。
あわせて、各エージェントの tools を一覧にするワンライナーも用意しておくと、権限の棚卸しが数秒で終わります。
# 各エージェントに付与されているツールを一覧表示
for f in .claude/agents/*.md; do
printf '%-28s ' "$(basename "$f" .md)"
awk '/^tools:/{f=1;next} f&&/^ - /{printf "%s ", $2} f&&!/^ - /{exit}' "$f"
echo
done
Bashを持つエージェントの一覧を眺めて、「この担当に本当に必要か」を毎月見直しています。
まとめ:今日やること
-
.claude/agents/の全ファイルに、toolsが明示されているか確認する(省略は全権限) - 責務を「書き込み先」で分け、書き込み先が重なっていないか表にしてみる
- 無人実行では
--allowedToolsと--max-budget-usdを呼び出し側で必ず指定する - 状態ファイルの更新日時で、エージェントが生きているかを監視する
当社は一時期10事業を同時に回し、そのうち9事業の売上がゼロでした。そこから3事業に絞り込めたのは、AIの運用自体がほぼ手離れしたからです。サブエージェントの設計は、地味ですが経営の時間を生み出す土台になっています。
もっと深く知りたい方へ
サブエージェント同士の連携パターン、並列実行、親子間のコンテキスト受け渡しなどは、書籍『Claude Codeマルチエージェント開発』で体系的に解説しています。