Hub Claude Code 教學

高手篇 · 第 12 章

自建 Agent SDK 與自訂代理人

這一章是全書最深的一章,給「想用寫程式的方式,打造自己的 AI 代理人」的進階者看。如果你是完全沒寫過程式的新手,這章可以放心跳過——你只要知道有「Agent SDK」這個東西、未來想自己做代理人時回來看就好,不會影響你前面學到的任何操作。準備好的話,我們就來看它是什麼、怎麼跑出第一個會自己動手的代理人。

12.1 它是什麼、什麼時候用

前面每一章,都是「親自打字、跟 Claude Code 對話」。這一章換個角度:Agent SDK 讓你用程式碼,打造一個「你自己的 AI 代理人」——換句話說,不是你坐在終端機前下指令,而是你寫一段程式,讓它在背後自動跑、自動完成你交代的任務。

最棒的是,你不用從零造輪子。Agent SDK 內建了跟 Claude Code 一模一樣的那套工具(讀檔、改檔、搜尋、跑指令)、同一套 (代理迴圈),以及上下文管理。你只要描述「我要它做什麼」,剩下的它自己張羅。

名字換過:Claude Code SDK 已更名為 Claude Agent SDK

你如果在比較舊的文章或影片看到「Claude Code SDK」,別困惑——那就是現在的 Claude Agent SDK,同一個東西改了名字而已。安裝套件、寫法都以本章(新名字)為準。

那這東西實際能做什麼?打個比方:你可以把它想成「請一位只負責一件事、但 24 小時待命的數位員工」。常見的例子有——線上系統出狀況時自動診斷、嘗試修復的 ;幫團隊跑程式碼審查、強制大家照同一套風格的審查代理人;值班時自動把事件分類、分流給對的人的助手;甚至是幫忙初步審閱合約的法務助理。它們的共同點是:規則清楚、可以一直重複做,很適合交給代理人。

重點:工具呼叫的迴圈,Claude 自己會跑

用一般的 API(Client SDK),你得自己寫一大段「要它用工具 → 等它回 → 再餵回去 → 再問」的來回迴圈,很繁瑣。改用 Agent SDK,這個來回的迴圈 Claude 會自己處理——你只要把任務講清楚,它自己決定要用哪個工具、用幾次,直到把事情做完。

想知道原理:Agent SDK 跟一般 Client SDK,到底差在哪?

差別只有一句話:誰來寫那個「工具呼叫迴圈」。用一般的 Client SDK,模型回你「我想用某個工具」,你得自己接住、去執行那個工具、把結果再餵回模型、再問下一步,這整個來回得你親手寫程式控制。Agent SDK 把這段內建好了——它幫你跑 agent loop,模型自己決定用哪些工具、用幾輪,你只負責描述任務和設定可用範圍。簡單說:Client SDK 是「你開車」,Agent SDK 是「你說目的地、它自己開」。

剛剛那個「想知道原理」的方塊,講的是 Client SDK 跟 Agent SDK 的差別——誰來寫那段工具呼叫迴圈。把視野再拉大一點,外面其實還有第三層,三層放在一起看,你會更清楚 Agent SDK 站在哪個位置:

① Client SDK

最原始的 API 呼叫,迴圈自己接(就是剛剛比較過的那個)。彈性最大,工也最多。

② Agent SDK(本章主角)

迴圈交給 Claude 自己跑,你只管交代任務、開放工具。但代理人仍跑在你自己的機器或伺服器上,資源、部署都是你的事。

③ Managed Agents

連「跑在哪裡」都不用你管,Anthropic 直接在自家沙箱代管執行,你只要呼叫一個 REST API 拿結果。更省心,也更受限。

本章接下來全部圍繞第二層。第三層目前還在早期階段,實際能力與計費方式請以官方文件為準,這裡先讓你知道「有這個更省心的選項存在」就好。

12.2 怎麼開始:選語言、裝起來、跑第一個範例

講完概念,來動手。Agent SDK 支援兩種程式語言,你挑你熟的那種就好——下面先用一張表幫你對照,再帶你裝起來、跑出第一個會自己動手的小代理人。

TypeScript(給用 Node.js/寫網頁的人)

在 Node.js 環境跑。如果你平常寫網頁、用 JavaScript/TypeScript,選這個最順手。本章的最小範例就是用 TypeScript 寫的。

Python(給用 Python 的人)

需要 Python 3.10 以上版本。如果你平常用 Python 做資料、自動化,選這個。安裝方式和概念跟 TypeScript 一樣,只是換套件。

先把 SDK 裝起來

依你上面選的語言,挑對應那一行裝起來就好。兩種各跑一行指令,不用兩個都裝。

建議執行:依你選的語言,把 SDK 裝起來

# TypeScript:用 npm 安裝 Agent SDK
npm install @anthropic-ai/claude-agent-sdk
# Python:用 pip 安裝(需 Python 3.10 以上)
pip install claude-agent-sdk

裝之前先看一眼版本,不然會卡在一句看不懂的錯誤訊息

TypeScript 這邊,npm install 那個套件其實內建打包了對應平台的 Claude Code 執行檔,不用你另外裝 Claude Code CLI,但 Node.js 本身要先有 18 以上版本。Python 這邊要 3.10 以上——版本不夠時,pip install 常見會噴一句 No matching distribution found for claude-agent-sdk,第一次看到很容易誤以為是網路或套件庫壞了,其實九成是 Python 版本太舊。裝之前不妨先確認一次:

# Mac/Linux:確認 Python 版本
python3 --version

# Windows:確認 Python 版本
py --version

跑出第一個會自己動手的代理人

下面帶你跑出第一個代理人:它會去讀你資料夾、用 Glob 工具把檔案列出來,再告訴你結果——全程它自己決定要用哪個工具,你只是按下執行。我們用 TypeScript 示範(這是本書唯一一段「真的程式碼」,看不懂沒關係,照著貼、跑起來、看它動,就達標了)。

  1. 動手做

    確認你裝好了 Node.js 和 Agent SDK

    這個範例用 TypeScript 跑,需要你的電腦有 Node.js,而且已經照上面 12.2 裝好 @anthropic-ai/claude-agent-sdk。沒裝 Node.js 的話,先到 nodejs.org 裝好再回來。

  2. 動手做

    新建一個檔案叫 agent.ts,貼進下面這段

    在你想試的資料夾裡,建立一個檔名為 agent.ts 的檔案,把下面整段程式碼貼進去存檔。看不懂每一行沒關係——重點是它在說「請列出這個資料夾的檔案,只准用 Glob 和 Read 這兩個工具」。

    import { query } from "@anthropic-ai/claude-agent-sdk";
    
    for await (const message of query({
      prompt: "這個資料夾裡有哪些檔案?",
      options: { model: "opus", allowedTools: ["Glob", "Read"], maxTurns: 250 }
    })) {
      if (message.type === "assistant") {
        for (const block of message.message.content) {
          if ("text" in block) console.log(block.text);
        }
      }
    }
  3. 動手做

    在終端機執行它

    回到終端機,在 agent.ts 所在的資料夾,打下面這行按 Enter。npx tsx 會幫你把這段 TypeScript 直接跑起來,不用先編譯。

    npx tsx agent.ts
    預期會看到
    # 它會先用 Glob 工具掃一遍資料夾,然後用文字回覆你
    # 大致會印出像這樣的內容(實際清單依你的資料夾而定):
    這個資料夾裡有這些檔案:agent.ts、package.json、README.md …
想知道原理:上面那段程式碼,每一行在做什麼?

拆開看其實不難。第一行 import 這個函式拿進來,它是整個 SDK 的入口。query() 裡你交代三件事:prompt 是你要它做的事(「列出檔案」);options 裡的 model 指定用哪個模型;allowedTools 是「只准用這幾個工具」(這裡只給 Glob 和 Read,它就沒辦法亂改你的檔案,很安全);maxTurns 是「最多來回幾輪」的上限,避免它無止盡跑下去。下面那段 for await 迴圈,就是一邊接它回傳的訊息、一邊把文字印出來給你看。你會發現你從頭到尾沒寫「怎麼呼叫 Glob」——那就是 agent loop 替你做掉的事。

跑起來之後你會發現:你從頭到尾沒教它「怎麼列檔案」,是它自己決定去用 Glob 工具的。這就是 Agent SDK 的價值——你可以精細控制它能用哪些工具)、最多跑幾輪),還能用「系統提示」定義這個代理人的角色和行為。給它的權限越小,它越安全、越可控。

關於費用

用 Agent SDK 跑代理人會用到你的 Claude 用量,實際怎麼計費、訂閱方案有沒有專屬額度,以官方公告為準。開始大量自動化前,先到官方文件確認當前的計費方式,比較不會有意外。

認證:SDK 怎麼知道你是誰

剛剛那個範例能跑起來,是因為你的電腦已經在前面章節登入過 Claude Code,Agent SDK 直接沿用了那個登入狀態。但如果你要把代理人搬到別的地方跑——例如一台 CI 伺服器、雲端主機,或任何沒有互動登入過的環境——就得改用API 金鑰:設定環境變數 ANTHROPIC_API_KEY,SDK 會自動去讀。

如果你的組織是走第三方雲端平台接 Claude(不是直接用 Anthropic 的 API),SDK 也支援,各自開一個環境變數就好:

平台 怎麼開啟
Amazon Bedrock CLAUDE_CODE_USE_BEDROCK=1
Claude Platform on AWS CLAUDE_CODE_USE_ANTHROPIC_AWS=1,還要補一個 ANTHROPIC_AWS_WORKSPACE_ID
Google Vertex AI CLAUDE_CODE_USE_VERTEX=1
Microsoft Foundry CLAUDE_CODE_USE_FOUNDRY=1

這幾個平台的名字、設定細節都可能隨時間調整,正式串接前務必回官方文件核對最新版本,這裡只是先讓你知道「有這條路可走」。

不能拿 claude.ai 的登入去養你的代理人

官方明文規定:沒有經過核准,第三方開發者不能讓自己用 SDK 寫的代理人去用 claude.ai 的登入狀態或它的用量額度——一定要走 API 金鑰認證。如果你想的是「反正我已經訂閱了,代理人就順便用那個額度」,這條路官方不允許,得另外申請 API 金鑰、另外計費。

訂閱方案現在會附送 Agent SDK 額度

2026 年 6 月中旬起,Pro/Max/Team/Enterprise 訂閱方案每月會附贈一筆 Agent SDK 專屬用量額度,涵蓋 Agent SDK 本身、claude -p,以及別人用這套 SDK 幫你做的第三方 app。實際額度多少、怎麼算,變動得比書快,出手前直接查官方公告最保險。

12.3 給代理人配工具:內建工具與自訂工具

代理人能做事,靠的就是「工具」。上一節你已經看過 allowedTools 怎麼限定它能用哪些——這一節退回來一步,帶你認識工具本身:SDK 內建了哪些現成工具,以及怎麼自己寫一個工具,讓代理人做內建工具做不到的事。

內建工具:跟你平常用的 Claude Code 是同一套實作

Agent SDK 內建的工具,跟你直接在終端機打字用 Claude Code 時看到的是同一套實作,不是另外簡化過的版本:

工具 做什麼
Read/Write/Edit 讀檔、新建檔案、修改既有檔案的內容
Bash 執行終端機指令
Monitor 盯著背景跑的腳本,對每一行新輸出即時反應
Glob/Grep 用檔名規則找檔案/用內容關鍵字搜尋
WebSearch/WebFetch 上網搜尋/抓某個網址的內容回來讀
AskUserQuestion 代理人跑到一半需要人做選擇時,跳出來問一句

也因為底層共用,第 9 章你學過的 MCP 外部工具連線(接資料庫、Slack 之類),一樣能透過 mcpServers 這個選項掛進 Agent SDK 的代理人,設定方式跟你在 .mcp.json 裡寫的大同小異。

自訂工具:讓代理人做內建工具做不到的事

內建工具之外,你也可以自己寫一個工具,接你自己的 API、資料庫,或任何內部系統。定義一個自訂工具要交代四件事:名字給 Claude 看的說明輸入參數長什麼樣(input schema)、以及真正執行的那段程式(handler)。Python 寫法最精簡——用一個 dict 把參數名對到型別就好,SDK 會自動轉成 Claude 看得懂的格式;真的需要列舉值、數字範圍、巢狀物件這種複雜結構時,也能直接塞一份完整的 JSON Schema 進去。

定義好之後還有一步別漏掉:用 create_sdk_mcp_server(TypeScript 是 createSdkMcpServer)把一個或多個工具包成一台迷你 MCP 伺服器——這台伺服器跑在你自己的程式行程裡,不是另外開一個子行程,啟動快,也不必額外管理生命週期。包好之後透過 query()mcpServers 選項掛上去就能用:

# 一個查天氣的自訂工具,示範四要素:名字、說明、輸入 schema、handler
from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions

@tool("get_weather", "查詢指定城市目前的天氣", {"city": str})
async def get_weather(args: dict[str, Any]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.example.com/weather", params={"city": args["city"]}
        )
        data = resp.json()
    return {
        "content": [{"type": "text", "text": f"{args['city']} 目前 {data['temp']}°C"}]
    }

# 包成一台跑在你程式裡的迷你 MCP 伺服器
weather_server = create_sdk_mcp_server(name="weather", version="1.0.0", tools=[get_weather])

options = ClaudeAgentOptions(
    mcp_servers={"weather": weather_server},
    allowed_tools=["mcp__weather__get_weather"],
)

工具名字會被自動加字首,放進 allowedTools 記得用「全名」

注意上面範例最後一行——工具原本叫 get_weather,但 Claude 實際看到、你要放進 allowedTools 的名字是 mcp__weather__get_weather(server 名字 + 工具名字)。忘記加這個字首,是自訂工具「怎麼設都拿不到免確認執行」最常見的原因。嫌一個個列太麻煩,也可以用萬用字元 mcp__weather__* 一次放行整台伺服器的所有工具。

工具出錯了怎麼辦:isError 的用法

如果你的 handler 執行到一半噴出未捕捉的例外,SDK 不會讓整個 agent loop 中斷——它會自動把這次呼叫轉成一個錯誤結果,將原始錯誤訊息丟給 Claude 自己判斷怎麼辦。這個自動接住的行為很方便,但訊息通常是程式語言原生的錯誤字串,Claude 不一定看得懂上下文。想給更有用的說明,可以自己接住錯誤、回傳 is_error: True(TypeScript 是 isError: true),夾帶你自己組的訊息:

# 自己接住錯誤,給 Claude 一句看得懂上下文的說明
return {
    "content": [{"type": "text", "text": f"查詢失敗:氣象站回應 {resp.status_code},可能是城市名稱打錯"}],
    "is_error": True,
}

記住一個容易搞混的地方:isError 只決定「Claude 讀到的是什麼訊息」,跟這次 query() 呼叫本身有沒有失敗是兩件事——就算工具回了錯誤,整個代理人流程還是會繼續跑,Claude 通常會嘗試換個方式,或照實跟你回報做不到。

12.4 權限護欄:從 allowedTools 到六步驟判定順序

代理人會自己動手改檔、跑指令,「它能做什麼」這件事的控制權,比前面幾章互動式操作時更重要——沒有人在旁邊隨時盯著它、隨時喊停。這一節把 Agent SDK 的權限系統攤開講清楚,尤其是一個很多人會誤踩、而且錯了不容易發現的地方。

先分清楚:「看不看得到」跟「能不能用」是兩回事

tools 決定的是看不看得到——這個工具存不存在於 Claude 的 context 裡。allowedTools 決定的是能不能用——工具就算看得到,呼叫前要不要先問過你。這兩層很容易被當成同一件事,但行為差很多:disallowedTools 如果寫的是工具的裸名字(像 "Bash"),效果是把整個工具從 context 移除,Claude 根本不會嘗試去用;但如果寫的是帶條件的規則(像 "Bash(rm *)"),工具本身還在,只是符合條件的那次呼叫會被擋下來。

六步驟判定順序:一次工具呼叫怎麼被放行或擋下

每一次代理人想用工具,SDK 都照固定的六個步驟依序判斷,前面的步驟一旦有結論,後面就不會再看:

  1. Hooks——PreToolUse 這類生命週期 hook 最先跑,它可以直接擋下呼叫,就算後面每一步都會放行也沒用。
  2. Deny 規則——disallowedToolssettings.json 裡的 deny 清單,命中就直接擋,連下面第 4 步的 bypassPermissions 都擋不掉它。
  3. Ask 規則——settings.json 裡明確寫「這個要問」的規則,命中會落到第 6 步的 canUseTool;但如果權限模式是 dontAsk,就直接改判拒絕,不會真的跑去問。
  4. Permission mode——依你設定的模式判斷(下面表格細講),例如 bypassPermissions 到這步幾乎全部放行、acceptEdits 會放行檔案編輯。
  5. Allow 規則——allowedToolssettings.json 裡的 allow 清單,命中直接核准。
  6. canUseTool callback——前面五步都沒給出結論,才輪到你自己寫的這個函式做最後判斷。

canUseTool 常常「以為會檢查、其實沒被呼叫到」

看清楚上面第 4、5 步——只要呼叫在前面就被自動放行(命中 allow 規則、或權限模式是 acceptEditsbypassPermissions),流程根本不會走到第 6 步。你寫在 canUseTool 裡的檢查邏輯,對這些呼叫完全沒生效——很多人以為自己已經用 canUseTool 把關了每一次呼叫,其實一大半呼叫在半路就被放行、從沒進過那個函式。真的要保證「每一次呼叫都被檢查一遍」,該用的是第 1 步的 PreToolUse hook,不是依賴排在最後面的 canUseTool——hook 甚至連 bypassPermissions 模式都攔得住。

canUseTool:真的被呼叫到時,它能做什麼

走到第 6 步時,canUseTool 是你能檢視 Claude 提出的確切參數、做最後判斷的地方——可以直接拒絕,也可以核准前先動手改寫參數再放行:

// 示意:擋下危險的 rm -rf,其餘照常放行
async function canUseTool(toolName, input) {
  if (toolName === "Bash" && /rm\s+-rf/.test(input.command)) {
    return { behavior: "deny", message: "不准跑 rm -rf,太危險了" };
  }
  return { behavior: "allow", updatedInput: input };
}

實際的型別名稱、寫法細節請以官方 SDK 文件為準(版本變動快),這裡只示意判斷邏輯長什麼樣。生產環境更常見的做法,其實是把上一段的坑反過來用:allowedTools 列清楚該給的工具,搭配 permissionMode: "dontAsk"——列出的自動核准,沒列出的一律直接拒絕,而不是賭一個可能忘記寫、或根本不會被呼叫到的 callback。

permissionMode 六種模式一覽

模式 行為
default 標準逐一詢問,跟你平常互動用 Claude Code 時一樣
acceptEdits 自動核准檔案編輯,以及 mkdir/touch/rm/mv/cp/sed 這類檔案系統指令(僅限工作目錄或額外指定的資料夾內)
bypassPermissions 跳過幾乎所有權限提示(hooks 仍會執行、仍擋得住)——風險最高,下面單獨警告
dontAsk 沒有事先核准的一律直接拒絕,canUseTool 完全不會被呼叫
plan 唯讀探索規劃模式,檔案編輯永遠不會自動核准
auto 背景分類器逐一審查指令與受保護目錄的寫入

bypassPermissions 不是「完全不設防」,但也沒你想的安全

官方明文警告:就算開了 bypassPermissions,Claude 仍可能寫入 .git.claude.vscode.devcontainer 這類設定目錄;只有明確寫出的 ask 規則、以及對整個根目錄或家目錄的刪除操作(像 rm -rf /)才還攔得住。子代理如果父層是這個模式,會被強制繼承、且不能在子代理定義裡覆寫回更嚴格的設定。這跟第 16 章提過的「子代理省略 tools 欄位=預設拿到全部工具」是同一種陷阱:權限相關的設定,只要沒寫清楚,永遠假設它是最寬鬆、而不是最嚴格的那個結果。

12.5 讓子代理人變成程式的一部分

第 10 章你已經在對話裡用 /agents 建過子代理人,第 16 章又講了怎麼編排一群子代理人。Agent SDK 讓你用兩種方式做同一件事:直接寫在程式碼裡,或繼續沿用 .claude/agents/ 底下的檔案。這一節帶你看這兩條路怎麼接、以及只有寫程式才會摸到的細節欄位。

直接在程式碼裡定義:AgentDefinition

agents 這個選項,直接把子代理人的定義寫進 query() 裡,不必另外開檔案。每個定義要給 description(Claude 依它判斷什麼時候該委派)、prompt(相當於檔案版子代理人開頭那段系統提示)、tools,也可以指定 model。要讓 Claude 真的能呼叫某個子代理人,記得把 Agent 這個工具本身加進主 query()allowedTools——子代理人是透過呼叫 Agent 這個工具被叫出來的:

from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Glob", "Grep", "Agent"],
    agents={
        "code-reviewer": AgentDefinition(
            description="專門做程式碼品質與安全審查的審查員",
            prompt="檢查程式碼品質與潛在的安全問題,給出具體改善建議。",
            tools=["Read", "Glob", "Grep"],
        )
    },
)

子代理人跑起來後,它送回的每則訊息會帶一個 parent_tool_use_id 欄位,標明這則訊息屬於哪個子代理人——自己刻監看畫面、想分開顯示不同子代理人的輸出時用得到。

沿用 .claude/agents/ 檔案:SDK 跟互動模式認同一套格式

好消息是你不用為了 SDK 重寫一份子代理人定義。SDK 預設會讀你專案裡 .claude/skills/.claude/commands/.claude/agents/CLAUDE.md 這些檔案系統設定——跟你平常互動操作 Claude Code 時是同一批檔案。想限制它只讀特定來源,用 setting_sources(TypeScript 是 settingSources)調整。子代理人檔案的 frontmatter 除了第 16 章講過的 tools,還有幾個只有深入用才會碰到、但很好用的欄位:

欄位 作用
model 指定 sonnet/opus/haiku/完整 model ID,或 inherit(預設值,跟主對話同一個模型)
permissionMode 這個子代理人專屬的權限模式(見 12.4 的六種模式),可以比主對話更嚴或更鬆
maxTurns 最多來回幾輪就強制停下,避免它悶頭跑個沒完
skills 指定的技能,內容會在子代理人一啟動就整份塞進 context,不用它自己現查現找
mcpServers 只給這個子代理人用的 MCP 連線設定,工具描述不會進主對話的 context,子代理人結束連線也跟著斷
hooks 只在這個子代理人身上生效的生命週期 hook(跟第 19 章的 hooks 是同一套機制)
memory userprojectlocal 三種持久記憶範圍,開啟後系統提示會自動附上讀寫記憶的指示
isolation: worktree 讓子代理人在獨立的 git worktree 裡跑,沒有變更的話結束會自動清掉,第 18 章有 worktree 的完整介紹

另外還有 background(強制它一律背景執行)、effort(覆蓋這個子代理人的推理力氣,第 8 章講過這個概念)、color(面板顯示用的顏色)、initialPrompt(這個定義被當成主 session 執行時自動送出的第一句話)幾個比較少用到的欄位,知道有就好,用到的時候再回頭查。

用 memory 讓子代理人越用越懂你的專案

設定 memory: project 之後,子代理人會在 .claude/agent-memory/<名字>/ 底下累積跨對話的知識,像是你們專案的命名慣例、常見的 bug 樣式。訣竅是在子代理人的系統提示裡明講一句「開始前先查記憶,做完後把學到的存回去」——這樣它才會真的養成習慣,而不是有這個功能卻沒用到。project 範圍建議進版控,整個團隊共用同一份累積知識。

存放位置與優先序:同名子代理人,SDK 選哪一個

子代理人檔案能放在好幾個地方,同名時 SDK 依下面順序、由高到低取用最先命中的那個:

  1. Managed settings(組織層級統一部署)
  2. --agents 這個 CLI 參數(只在那次執行有效,不會存檔,適合臨時測試)
  3. .claude/agents/(專案層級,會沿著工作目錄往上層逐層找到 repo 最頂端;建議進版控跟團隊共享)
  4. ~/.claude/agents/(使用者層級,你自己所有專案共用)
  5. Plugin 裡的 agents/ 資料夾(隨外掛啟用範圍生效,優先序最低)

專案與使用者層級都能整理成子資料夾(像 agents/review/),子資料夾路徑不影響識別,Claude 只認 name 欄位;但如果是外掛提供的子代理人,子資料夾就會變成識別字串的一部分,例如 agents/review/security.mdmy-plugin 這個外掛裡會註冊成 my-plugin:review:security

內建的三個系統子代理人

不用你自己定義,SDK 內建就有三個現成的子代理人可以直接委派:

Explore

唯讀、專攻程式碼搜尋。求快求便宜,它會跳過讀取 CLAUDE.md 與檢查 git status,這兩步沒有欄位可以打開,真的有規則必須讓它知道,得在委派時的指示裡重講一次。

Plan

plan 模式專用的唯讀研究子代理人,同樣跳過 CLAUDE.md 與 git status。

general-purpose

可讀寫,處理需要「先探索、再修改」的複雜多步任務,完整繼承主對話的模型與工具,行為最接近主對話本身。

Explore/Plan 是一次性的,做完不能續接

這兩個內建子代理人是 one-shot(一次性)——做完不會回傳一個能繼續對話的 ID。想接著上次的探索繼續問,得改用 general-purpose 或你自己定義的子代理人,Explore/Plan 沒辦法回頭接。真要停用它們,單一擋一個可以在權限設定裡 deny Agent(Explore);整批不給用,設環境變數 CLAUDE_CODE_DISABLE_EXPLORE_PLAN_AGENTS=1;SDK/非互動情境想把三個內建子代理人全部拔掉、只用自己定義的,設 CLAUDE_AGENT_SDK_DISABLE_BUILTIN_AGENTS=1。環境變數名稱與確切版本以官方文件為準。

巢狀 spawn、前景背景、還有一個便宜的分身:/fork

巢狀 spawn(最深 5 層)

子代理人可以再派生自己的子代理人(例如一個審查子代理人,為每條發現各派一個驗證子代理人)。深度從主對話算起固定上限 5 層,第 5 層拿不到 Agent 工具、不能再往下派生,這個上限不能調。

前景 vs 背景

前景子代理人會擋住主對話直到跑完,需要權限時直接把提示丟給你;背景子代理人讓你邊等邊做別的事,需要權限時提示會浮到主對話、並標明是哪個子代理人在問。

/fork:便宜的分身

跟具名子代理人不同,/fork 繼承整條對話歷史,只有它自己的工具呼叫過程不進主 context。因為系統提示、工具定義都跟主對話一樣,第一個請求能直接沿用已經建好的 prompt cache,比重新派一個具名子代理人便宜。適合「這件事需要太多背景,重講一次給子代理人聽反而不划算」的旁支任務。

12.6 動手前先知道的坑:常見錯誤與排除法

下面整理幾個「文件沒明講、真的寫下去才會撞到」的狀況,多半是社群和個別開發者實測回報的經驗,不是官方規格保證的行為——遇到時當作排除方向的起點,實際訊息以你當下版本印出來的為準。

spawn node ENOENT/spawn claude ENOENT社群

TypeScript SDK 本質是包了一層 Claude Code 的執行檔,在 Docker 容器、打包後的桌面 app、或 Windows 環境常見「PATH 找不到 node/claude」。Windows 特別容易中:Node 內建的 spawn 預設只解析 .exe,找不到 npm 裝的 .cmd 版本。解法:自訂執行檔路徑的參數指到正確位置、確認 Docker image 裡 node 有進 PATH,或打包時記得把對應的執行檔從壓縮包裡解出來。

settings.json 裡的憑證蓋過你程式指定的社群

如果 ~/.claude/settings.json 裡已經設定了 API 金鑰,SDK 會優先用那組,蓋掉你在程式碼裡想用的另一組憑證。要讓 SDK 端用不同於互動模式的憑證,得把 'user'setting_sources 排除——但副作用是使用者層級的全域 Skills 也會一起停止載入,兩者要自己取捨。

自訂子代理人怎麼樣都不會被自動委派社群

即使 description 寫得再詳細,Claude 對自訂子代理人的自動委派仍常常不可靠。想確保真的會叫到你要的那個,直接在提示裡明講「用 xxx 這個 subagent 處理」,或用 @-mention 強制指定。官方建議在 description 裡加一句「use proactively」這類字句,能提高自動委派的機率。

多子代理人架構,token 花費是單一對話的 4~7 倍社群

每個子代理人都要重新建立 context、重新讀取相關檔案,實測整套多子代理人流程大約吃掉單一 session 的 4 到 7 倍 token。用量計費的方案上這筆帳會很有感,開工前先想清楚這個任務值不值得拆給一群子代理人做,別為了「看起來乾淨」多付好幾倍代價。

子代理人裡的 cd 不會留下來,也不會反過來影響主對話

子代理人一律從主對話當下的工作目錄開始;它內部呼叫 Bash 執行 cd,不會在下一次工具呼叫之間保留,更不可能反過來改到主對話的工作目錄。真的要給子代理人一份獨立的檔案系統副本,用 12.5 提過的 isolation: worktree,別指望用 cd 湊合。

Python 的 structuredContent 會被靜默丟掉社群

Python 的 @tool decorator 目前只轉發 handler 回傳值裡的 contentis_error 兩個欄位——structuredContent 會被直接丟掉,而且不會報任何錯誤,很容易寫了程式碼卻怎麼測都拿不到結構化欄位。要用到 structuredContent,得改跑獨立行程的 standalone MCP server,而不是本章一直用的 in-process SDK server。

12.7 小結

這一章是全書最深的地方,範圍也拉得比其他章寬:從 query() 這個最小入口開始,一路看到怎麼自己寫工具給代理人用、六步驟的權限判定順序怎麼保護你的檔案、怎麼用程式碼定義子代理人,還有一票只有真的寫下去才會踩到的坑。你不一定要現在就把每一節都記住——這一章比較像一份「未來要動手時回來查」的參考地圖:什麼時候該用 allowedTools、什麼時候該寫 canUseTool、子代理人的 frontmatter 有哪些欄位,都留在這裡等你。真要動手時,先回 12.2 跑一次最小範例,跑得動、有感覺了,再一節一節往下挑需要的部分讀。接下來我們要進入本書的「大師篇」,從第 14 章的 Context 工程開始,看更底層的東西——Claude 怎麼管理它看到的那些資訊。