analysis_claude_code/docs/ja/s04-subagent.md
CrazyBoyM c6a27ef1d7 feat: build an AI agent from 0 to 1 -- 11 progressive sessions
- 11 sessions from basic agent loop to autonomous teams
- Python MVP implementations for each session
- Mental-model-first docs in en/zh/ja
- Interactive web platform with step-through visualizations
- Incremental architecture: each session adds one mechanism
2026-02-21 17:02:43 +08:00

6.2 KiB

s04: Subagents

サブエージェントは新しいメッセージリストで実行され、親とファイルシステムを共有し、要約のみを返す -- 親のコンテキストをクリーンに保つ。

問題

エージェントが作業するにつれ、メッセージ配列は膨張する。すべてのツール呼び出し、ファイル読み取り、bash出力が蓄積されていく。20-30回のツール呼び出しの後、コンテキストウィンドウは無関係な履歴で溢れる。ちょっとした質問に答えるために500行のファイルを読むと、永久に500行がコンテキストに追加される。

これは探索的タスクで特に深刻だ。「このプロジェクトはどのテストフレームワークを使っているか」という質問には5つのファイルを読む必要があるかもしれないが、親エージェントには5つのファイルの内容すべては不要だ -- 「pytest with conftest.py configuration」という回答だけが必要なのだ。

解決策はプロセスの分離だ: messages=[]で子エージェントを生成する。子は探索し、ファイルを読み、コマンドを実行する。終了時には最終的なテキストレスポンスだけが親に返される。子のメッセージ履歴全体は破棄される。

解決策

Parent agent                     Subagent
+------------------+             +------------------+
| messages=[...]   |             | messages=[]      | <-- fresh
|                  |  dispatch   |                  |
| tool: task       | ---------->| while tool_use:  |
|   prompt="..."   |            |   call tools     |
|                  |  summary   |   append results |
|   result = "..." | <--------- | return last text |
+------------------+             +------------------+
          |
Parent context stays clean.
Subagent context is discarded.

仕組み

  1. 親エージェントにサブエージェント生成をトリガーするtaskツールが追加される。子はtaskを除くすべての基本ツールを取得する(再帰的な生成は不可)。
PARENT_TOOLS = CHILD_TOOLS + [
    {"name": "task",
     "description": "Spawn a subagent with fresh context.",
     "input_schema": {
         "type": "object",
         "properties": {
             "prompt": {"type": "string"},
             "description": {"type": "string"},
         },
         "required": ["prompt"],
     }},
]
  1. サブエージェントは委譲されたプロンプトのみを含む新しいメッセージリストで開始する。ファイルシステムは共有される。
def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]
    for _ in range(30):  # safety limit
        response = client.messages.create(
            model=MODEL, system=SUBAGENT_SYSTEM,
            messages=sub_messages,
            tools=CHILD_TOOLS, max_tokens=8000,
        )
        sub_messages.append({
            "role": "assistant", "content": response.content
        })
        if response.stop_reason != "tool_use":
            break
        # execute tools, append results...
  1. 最終テキストのみが親に返される。子の30回以上のツール呼び出し履歴は破棄される。
    return "".join(
        b.text for b in response.content if hasattr(b, "text")
    ) or "(no summary)"
  1. 親はこの要約を通常のtool_resultとして受け取る。
if block.name == "task":
    output = run_subagent(block.input["prompt"])
results.append({
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": str(output),
})

主要コード

サブエージェント関数(agents/s04_subagent.py 110-128行目):

def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]
    for _ in range(30):
        response = client.messages.create(
            model=MODEL, system=SUBAGENT_SYSTEM,
            messages=sub_messages,
            tools=CHILD_TOOLS, max_tokens=8000,
        )
        sub_messages.append({"role": "assistant",
                             "content": response.content})
        if response.stop_reason != "tool_use":
            break
        results = []
        for block in response.content:
            if block.type == "tool_use":
                handler = TOOL_HANDLERS.get(block.name)
                output = handler(**block.input)
                results.append({"type": "tool_result",
                    "tool_use_id": block.id,
                    "content": str(output)[:50000]})
        sub_messages.append({"role": "user", "content": results})
    return "".join(
        b.text for b in response.content if hasattr(b, "text")
    ) or "(no summary)"

s03からの変更点

Component Before (s03) After (s04)
Tools 5 5 (base) + task (parent)
Context Single shared Parent + child isolation
Subagent None run_subagent() function
Return value N/A Summary text only
Todo system TodoManager Removed (not needed here)

設計原理

プロセス分離はコンテキスト分離を無料で提供する。新しいmessages[]は、サブエージェントが親の会話履歴に混乱させられないことを意味する。トレードオフは通信オーバーヘッドだ -- 結果は親に圧縮して返す必要があり、詳細が失われる。これはOSのプロセス分離と同じトレードオフだ: シリアライゼーションコストと引き換えに安全性とクリーンさを得る。サブエージェントの深さ制限(再帰的なスポーンは不可)は無制限のリソース消費を防ぎ、最大反復回数は暴走した子プロセスの終了を保証する。

試してみる

cd learn-claude-code
python agents/s04_subagent.py

試せるプロンプト例:

  1. Use a subtask to find what testing framework this project uses
  2. Delegate: read all .py files and summarize what each one does
  3. Use a task to create a new module, then verify it from here