Learn Claude Code
s04

Subagent

Core Loop

Fresh Context per Subtask|200 LOC|5 tools

A subagent is mainly a context boundary, not a process trick.

s00 > s01 > s02 > s03 > [ s04 ] > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > s13 > s14 > s15 > s16 > s17 > s18 > s19

一個大任務,不一定要塞進一個上下文裡做完。

這一章到底要解決什麼問題

當 agent 連續做很多事時,messages 會越來越長。

比如使用者只問:

“這個專案用什麼測試框架?”

但 agent 可能為了回答這個問題:

  • 讀了 pyproject.toml
  • 讀了 requirements.txt
  • 搜了 pytest
  • 跑了測試命令

真正有價值的最終答案,可能只有一句話:

“這個專案主要用 pytest。”

如果這些中間過程都永久堆在父對話裡,後面的問題會越來越難回答,因為上下文被大量區域性任務的噪聲填滿了。

這就是子智慧體要解決的問題:

把區域性任務放進獨立上下文裡做,做完只把必要結果帶回來。

先解釋幾個名詞

什麼是“父智慧體”

當前正在和使用者對話、持有主 messages 的 agent,就是父智慧體。

什麼是“子智慧體”

父智慧體臨時派生出來,專門處理某個子任務的 agent,就是子智慧體。

什麼叫“上下文隔離”

意思是:

  • 父智慧體有自己的 messages
  • 子智慧體也有自己的 messages
  • 子智慧體的中間過程不會自動寫回父智慧體

最小心智模型

Parent agent
  |
  | 1. 決定把一個區域性任務外包出去
  v
Subagent
  |
  | 2. 在自己的上下文裡讀檔案 / 搜尋 / 執行工具
  v
Summary
  |
  | 3. 只把最終摘要或結果帶回父智慧體
  v
Parent agent continues

最重要的點只有一個:

子智慧體的價值,不是“多一個模型例項”本身,而是“多一個乾淨上下文”。

最小實現長什麼樣

第一步:給父智慧體一個 task 工具

父智慧體需要一個工具,讓模型可以主動說:

“這個子任務我想交給一個獨立上下文去做。”

最小 schema 可以非常簡單:

{
    "name": "task",
    "description": "Run a subtask in a clean context and return a summary.",
    "input_schema": {
        "type": "object",
        "properties": {
            "prompt": {"type": "string"}
        },
        "required": ["prompt"]
    }
}

第二步:子智慧體使用自己的訊息列表

def run_subagent(prompt: str) -> str:
    sub_messages = [{"role": "user", "content": prompt}]
    ...

這就是隔離的關鍵。

不是共享父智慧體的 messages,而是從一份新的列表開始。

第三步:子智慧體只拿必要工具

子智慧體通常不需要擁有和父智慧體完全一樣的能力。

最小版本里,常見做法是:

  • 給它檔案讀取、搜尋、bash 之類的基礎工具
  • 不給它繼續派生子智慧體的能力

這樣可以防止它無限遞迴。

第四步:只把結果帶回父智慧體

子智慧體做完事後,不把全部內部歷史寫回去,而是返回一段總結。

return {
    "type": "tool_result",
    "tool_use_id": block.id,
    "content": summary_text,
}

這一章最關鍵的資料結構

如果你只記一個結構,就記這個:

class SubagentContext:
    messages: list
    tools: list
    handlers: dict
    max_turns: int

解釋一下:

  • messages:子智慧體自己的上下文
  • tools:子智慧體可以呼叫哪些工具
  • handlers:這些工具到底對應哪些 Python 函式
  • max_turns:防止子智慧體無限跑

這就是最小子智慧體的骨架。

為什麼它真的有用

用處 1:給父上下文減負

區域性任務的中間噪聲不會全都留在主對話裡。

用處 2:讓任務描述更清楚

一個子智慧體接到的 prompt 可以非常聚焦:

  • “讀完這幾個檔案,給我一句總結”
  • “檢查這個目錄裡有沒有測試”
  • “對這個函式寫一個最小修復”

用處 3:讓後面的多 agent 協作有基礎

你可以把子智慧體理解成多 agent 系統的最小起點。

先把一次性子任務外包做明白,後面再升級到長期 teammate、任務認領、團隊協議,會順很多。

從 0 到 1 的實現順序

推薦按這個順序寫:

版本 1:空白上下文子智慧體

先只實現:

  • 一個 task 工具
  • 一個 run_subagent(prompt) 函式
  • 子智慧體自己的 messages
  • 子智慧體最後返回摘要

這已經夠了。

版本 2:限制工具集

給子智慧體一個更小、更安全的工具集。

比如:

  • 允許 read_file
  • 允許 grep
  • 允許只讀 bash
  • 不允許 task

版本 3:加入最大輪數和失敗保護

至少補兩個保護:

  • 最多跑多少輪
  • 工具出錯時怎麼退出

版本 4:再考慮 fork

只有當你已經穩定跑通前面三步,才考慮 fork。

什麼是 fork,為什麼它是“下一步”,不是“起步”

前面的最小實現是:

  • 子智慧體從空白上下文開始

這叫最樸素的子智慧體。

但有時一個子任務必須知道父智慧體之前在聊什麼。

例如:

“基於我們剛才已經討論出來的方案,去補測試。”

這時可以用 fork

  • 不是從空白 messages 開始
  • 而是先複製父智慧體的已有上下文,再追加子任務 prompt
sub_messages = list(parent_messages)
sub_messages.append({"role": "user", "content": prompt})

這就是 fork 的本質:

繼承上下文,而不是重頭開始。

初學者最容易踩的坑

坑 1:把子智慧體當成“為了炫技的併發”

子智慧體首先是為了解決上下文問題,不是為了展示“我有很多 agent”。

坑 2:把父歷史全部原樣灌回去

如果你最後又把子智慧體全量歷史粘回父對話,那隔離價值就幾乎沒了。

坑 3:一上來就做特別複雜的角色系統

比如一開始就加:

  • explorer
  • reviewer
  • planner
  • tester
  • implementer

這些都可以做,但不應該先做。

先把“一個乾淨上下文的子任務執行器”做對,後面角色化只是在它上面再包一層。

坑 4:忘記給子智慧體設定停止條件

如果沒有:

  • 最大輪數
  • 異常處理
  • 工具過濾

子智慧體很容易無限轉。

教學邊界

這章要先打牢的,不是“多 agent 很高階”,而是:

子智慧體首先是一個上下文邊界。

所以教學版先停在這裡就夠了:

  • 一次性子任務就夠
  • 摘要返回就夠
  • messages + 工具過濾就夠

不要提前把 fork、後臺執行、transcript 持久化、worktree 繫結一起塞進來。

真正該守住的順序仍然是:

先做隔離,再做高階化。

和後續章節的關係

  • s04 解決的是“區域性任務的上下文隔離”
  • s15-s17 解決的是“多個長期角色如何協作”
  • s18 解決的是“多個執行者如何在檔案系統層面隔離”

它們不是重複關係,而是遞進關係。

這一章學完後,你應該能回答

  • 為什麼大任務不應該總塞在一個 messages 裡?
  • 子智慧體最小版為什麼只需要獨立上下文和摘要返回?
  • fork 是什麼,為什麼它不該成為第一步?
  • 為什麼子智慧體的第一價值是“減噪”,而不是“炫多 agent”?

一句話記住:子智慧體的核心,不是多一個角色,而是多一個乾淨上下文。