Yyatmita

【第1回】サブエージェントをはじめよう——Claude Code に「別の自分」を呼び出させる

Claude Code のサブエージェント機能を初めて使った体験記。スキル・エージェント・Taskの違い、Hello World、カスタムエージェント作成まで

Claude Codeサブエージェント検証#claude-code#subagent#ai#automation

Claude Code を使っていると、会話が長くなるにつれてコンテキストが積み上がっていきます。検索結果や中間成果物が溜まって、本筋がどこにあるのかわからなくなる。

「この調べ物、別のところでやってほしいな」——そう思うことが増えていました。

サブエージェントという機能があるのは知っていました。でもスキルとエージェントとTaskの違いがわからない。ドキュメント(スキルエージェント)を読んでも、どちらも .claude/ 以下にマークダウンを置くだけに見える。何が違うのか。

読んでもわからないなら、触ってみよう。Claude Code に相談するところから始めました。

この連載では、12回の実験を全4回でお届けします。

  • 第1回(今回): Claude Code と対話しながら Hello World、カスタムエージェント作成まで
  • 第2回: エージェントの性質——戻り値・並列・同期非同期・ネスト
  • 第3回: 権限モデルを理解する——Write vs Edit の罠、同期/非同期の落とし穴
  • 第4回: エージェントを使いこなす——スキル→エージェント委任パターンとベストプラクティス

「ネタ探しエージェントを作りたい」

私の Obsidian には、長年の読書メモや実験記録が約3万件あります。以前、kbsearch という CLI ツールを作って横断検索できるようにしました(詳しくは前の記事に書きました)。

私: obsidian の kbsearch を使って yatmita のネタを探してくれるエージェントを作ってみたい。それで記事にする。

Claude Code: エージェントの形式は?Claude Code のスキル/エージェント(.claude/agents/)として作る?それともスタンドアロンのスクリプト?

私: なんかサブエージェントの使い方がよくわからないんだけど、別のプロジェクトではうまく動かなくて結局汎用エージェントにスキルを使わせるように指示した。

Claude Code: じゃあ今回もスキル版で?

私: いやせっかくだから両方試してみよう。

エージェントとスキル、両方作って比較する。その過程を記事にする——という方針が決まりました。

Hello World:「それは汎用エージェントじゃないの」

スキル・エージェント・Task の関係——ユーザー発話からスキル(トリガー)、エージェント(実行エンジン)への流れと、同期/非同期の実行モード

私: まずサブエージェントのハローワールドをやって。

Claude Code が general-purpose エージェント(組み込みの汎用タイプ)で kbsearch を叩いてくれました。約21秒で結果が返ってきた。KB 一覧取得、obsidian vault での「スムージー」検索、3件ヒット。

私: それは汎用エージェントじゃないの。

Claude Code: その通りです。今のは general-purpose サブエージェントに指示を渡しただけで、カスタムエージェント(.claude/agents/)ではありません。

汎用エージェントは動いた。でも私が作りたいのは「kbsearch でネタを探す」という専門知識を持った、カスタムのエージェントです。

カスタムエージェントを作る

Claude Code が .claude/agents/neta-research-agent.md を作ってくれました。

---
name: neta-research-agent
description: yatmita の記事ネタを Obsidian ナレッジベースから探す
tools: Bash, Read, Glob, Grep, Write
maxTurns: 20
---

toolsmaxTurns が何を意味するかは、この時点ではよくわかっていません。「とりあえずこう書くものらしい」程度の理解です。各フィールドの意味は第3回で改めて検証します。

本文には kbsearch の使い方、出力フォーマット、ネタ候補に必須の項目を書きました。この本文がそのままエージェントのシステムプロンプトになります。

呼んでみます。

認識されません。

Claude Code: カスタムエージェントが認識されていません。セッション中に作成したエージェントは再起動しないと読み込まれない可能性があります。

公式ドキュメントにも「Subagents are loaded at session start」と書いてあります。完全に見落としていました。後で調べると、同じところでつまずいている人が何人もいました。

セッションを再起動します。

Claude Code: 再起動後、neta-research-agent がエージェント一覧に出ています。呼んでみます。

今度は動きました。kbsearch を自律的に叩き、「料理 科学」で検索して「旨味の相乗効果」の記事ネタを提案してきた。汎用エージェントと違って、出力がきちんと構造化されています。

ここで、通信の仕組みが気になりました。

私: カスタムエージェントとメインとはどうやって通信するの?

Claude Code: Agent ツールの prompt(入力)と return(出力)だけです。途中経過は見えない。結果だけ返ってくる。つまり「関数呼び出し」に近い。

私: 非同期で返ってくるの?

Claude Code: run_in_background: true を付ければ非同期で、付けなければ同期です。

私: 並列化は?

Claude Code: 1つのメッセージで複数の Agent を同時に呼べます。やってみますか?

私: じゃあ並列処理してみて。

3ドメイン(smoothie, buddhaism, labo)でネタ探しを並列起動——したのですが、ここで壁にぶつかります。

最初の壁: 権限エラー

3つのエージェントを並列で起動したら、全員が同じエラーで帰ってきました。

neta-research-agent: Bash ツールの実行権限が現在拒否されています。kbsearch コマンドを実行するには Bash ツールの許可が必要です。

さっき汎用エージェントでは動いたのに。なぜ。

しばらく悩んで、気づきました。汎用エージェントで動いたのは、そのセッションで既に kbsearch の使用を手動承認していたからでした。セッションを再起動したら承認は白紙に戻る。

.claude/settings.local.jsonpermissions.allowBash(kbsearch:*) を追加して、再度並列起動。今度は3つとも成功しました。

{
  "permissions": {
    "allow": ["Bash(kbsearch:*)"]
  }
}

この時点では「settings.local.json に書けば解決するのか、なるほど」程度の理解でした。権限まわりにはもっと深い罠があるのですが、それは第3回で嫌というほど体験します。

動いた。そして疑問が増えた

カスタムエージェントが kbsearch を自律的に使って、ネタ候補を提案してくれました。しかも3ドメイン並列で。

「別の自分に仕事を頼む」感覚が、ようやく現実のものになりました。

同時に疑問も増えました。戻り値はテキストだけ? エージェントの中からさらにエージェントを呼べる? 同期と非同期で何が変わる?

次回: エージェントの性質を検証する

第2回では、エージェントの動作を実験で詳しく調べます。

戻り値の受け取り方、並列実行、フォアグラウンド/バックグラウンドの違い、ネスト呼び出しの挙動——実際に試してみると、予想通りにいかないことがいくつかありました。特にネスト呼び出しの結果は衝撃的でした。1時間近くハングしました。

「別の人に仕事を頼む」の比喩がどこまで成立するか、次回明らかにします。