Agent · SDK · 解體新書
卷一《Claude Code 解體新書》畫的是已經裝好的代理人 — 那是一具完整的身體。這一卷不同:我們拆的是「工廠」。Claude Agent SDK(原名 claude-code-sdk)是 Anthropic 釋出的 Python / TypeScript 函式庫,它讓你能用程式碼組裝出自己的 agent,而不是只能使用現成的 CLI。
SDK 的內核與 Claude Code 共用同一套 harness 引擎 — 同樣的 agentic loop、同樣的 tool dispatch、同樣的 MCP 通道。差別在於:SDK 把那些在 CLI 裡藏起來的旋鈕,全部變成函式參數與 callback。你可以自訂工具、自訂權限邏輯、自訂 hook、自訂 subagent、甚至把整台 agent 嵌進你自己的後端服務裡。
本卷分十回,從最簡單的 query() 一次呼叫開始,一層一層往上蓋:訊息、客戶端、選項、工具、MCP、權限、hook、分身、續命。由淺入深,每章可立即動手。程式碼以 Python 為主軸、TypeScript 為對照。
query() 之魂
the one-shot call · async iterator as stream
Agent SDK 的入口只有一個函式:query()。你餵它一段 prompt 與一組 options,它回傳一個 async iterator — 不是一次性結果,而是一條流。模型與工具每產生一則訊息,iterator 就吐出一項,直到最後一則 ResultMessage 為止。這是最簡入口,也是最常用的入口。
str(最簡用法)或 AsyncIterable[dict](串流模式,適合多段持續輸入)。ClaudeAgentOptions 的實例 — 決定用哪個 model、有哪些 tools、怎麼判定權限等等。預設空物件等於「內建 Claude Code 預設行為」。async for 或 TS 的 for await 逐筆讀取。每個項目是一個 Message 實例;順序即為發生順序。query() 內部可能包含多個 agentic turn(模型→工具→模型→…)。流模式讓你即時看見進展,不用等到全部跑完。query() 每次呼叫都建立新 session。若你要多輪對話維持上下文,請用下一章的 ClaudeSDKClient。.py、pip install claude-agent-sdk、asyncio.run(main())— 就會看見 message 流出來。訊息之型
message types · the shape of what streams back
從 query() 流出來的每一項都是某種 Message。SDK 定義了四個主要型別 — SystemMessage、UserMessage、AssistantMessage、ResultMessage — 外加一個可選的 StreamEvent(細粒度串流用)。其中 AssistantMessage 的 content 欄位又裝著一串 content block:文字、工具呼叫、思考區段。看懂這個型別樹,就能寫出穩健的訊息處理迴圈。
parent_tool_use_id 可追溯來源。content 是 list,內含 TextBlock(文字)、ToolUseBlock(工具呼叫)、ThinkingBlock(思考區段)。total_cost_usd、num_turns、duration_ms、usage、session_id — 給你結算這次跑了多少。if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(block.text)。msg.type === "assistant" 判別;欄位名採 camelCase(totalCostUsd 等)。客身持續
ClaudeSDKClient · the stateful multi-turn client
query() 是一次性的 — 每呼叫一次都建立新 session。如果你要做多輪對話、讓模型記得前面說過的話,就要用 ClaudeSDKClient。它是一個 async context manager:進入時 connect,期間可以 query / receive_response 多次,最後 disconnect。同一個 client 內部自動維持 session id,不用你手動管理。
session id · 自動維持
ClaudeSDKClient 在 connect() 時建立 session,之後每次 query() 自動沿用同一個 session id。模型看得見之前的對話歷史。若要起全新 session,就開新 client 實例(或見第拾回的 fork_session)。
interrupt · 中斷生成
若模型在長篇輸出中、你想立刻打斷:await client.interrupt()。harness 會立刻終止當前 turn,你接著可以發下一則 query()。這對實作「Esc 鍵」或倒數計時終止很有用。
系統之諭
ClaudeAgentOptions · the configuration dict that shapes everything
ClaudeAgentOptions 是 SDK 所有可調參數的集合 — system prompt、model、工具白名單、權限 callback、hook 註冊、MCP server、subagent 定義、session 恢復等都寫在這一份 dataclass(Python)或 object(TypeScript)裡。本章把它分成六大區,逐區標註。
system_prompt、allowed_tools);TypeScript 用 camelCase(systemPrompt、allowedTools)。語義相同。"Read");MCP 工具用 mcp__{server}__{tool} 或通配 mcp__{server}__*。CLAUDE.md — 必須明確 setting_sources=["project"] 才載入。這是刻意的隔離設計。default(照 settings.json)/ plan(只讀)/ acceptEdits(自動寫入)/ bypassPermissions(跳過所有檢查,極少使用)。extra_args 直接傳給底層 CLI。器具自製
Custom Tools · @tool decorator & tool() helper
內建的 Bash / Read / Edit 不夠用?SDK 讓你寫自己的 — 只要一個 decorator(Python)或一個 helper(TypeScript)。你提供名稱、描述、輸入 schema、與一個 async handler,SDK 會把它包成 MCP tool 註冊進 registry,從此模型看得見、叫得動。核心公式:name + description + schema + handler = tool。
schema choice · schema 選擇
Python 的 {"key": type} dict shape 最簡單,但只支援基本型別與扁平結構。若需要 enum、optional、nested,請改用完整 JSON Schema(傳 dict 進 @tool)。TypeScript 端統一用 zod,型別推導同時賦予 handler 的 args 正確型別。
wiring up · 接上去
定義好的 tool 函式物件要放進 create_sdk_mcp_server(name, version, tools=[...]),再把 server 傳給 options.mcp_servers={"myserver": server}。最後記得在 allowed_tools 加上 "mcp__myserver__tool_name" — 沒加等於沒註冊。詳見下一章。
協議內融
In-Process MCP · create_sdk_mcp_server()
傳統的 MCP server 是 另一個進程 — 你的 host 用 stdio 或 SSE 與它通訊,開銷是啟動、序列化、IPC。Agent SDK 獨有的 create_sdk_mcp_server 把這層全部蒸發:你的工具函式直接在當前 Python / Node 進程裡執行,沒有跨進程呼叫,沒有序列化,共享同一記憶體空間。部署、除錯、效能都更舒服。
tools 是一個 list,每個元素是 @tool 裝飾過的 async 函式物件。mcp__{server_name}__{tool_name}。必須在 allowed_tools 列出這個完整名稱或用 mcp__{server_name}__* 通配。mcp_servers dict。外部 server 在 dict 值改放 {"command": "...", "args": [...]}。權限之掌
canUseTool & permissionMode · custom gate logic
內建的 allow / ask / deny 清單已經能擋下大多數問題,但若你要的是「對 Bash 的 rm 一律拒絕、對 ls 一律放行、對其他指令依靠 LLM 判斷」這種更細緻的規則 —— 就需要 canUseTool。它是一個 async callback,接住 harness 傳來的工具呼叫,自己決定 allow / deny,甚至改寫參數。加上 permission_mode,你可以組合出從寬鬆到嚴厲的各種姿態。
input 改寫 · sanitize
PermissionResultAllow 可以帶 updated_input — 意思是「允許執行,但把參數換成這個」。可用來把使用者輸入的路徑強制改為相對路徑、把 Bash 命令加上 --dry-run、或遮蔽敏感欄位。這是一個比「全拒」更細緻的控制點。
interrupt · 打斷整個 turn
PermissionResultDeny(interrupt=True) 不只是拒絕這次工具呼叫,還會直接中止當前 agentic loop — 模型不會收到「被拒」訊息,整個 receive_response() 收到 ResultMessage(subtype="error") 就結束。用在「紅線被踩」情境。
鉤子入碼
Programmatic Hooks · HookMatcher & HookCallback
Claude Code 的 hook 寫在 settings.json 裡、呼叫 shell 指令。SDK 的 hook 則寫在程式碼裡、呼叫你的 async 函式 —— 同一個引擎,但對 SDK 使用者來說更直接:不需要離開 Python / TypeScript 生態,可以直接讀寫記憶體、呼叫 ORM、拒絕工具、改寫 prompt。本章拆解 HookMatcher 的註冊方式與 callback 的回傳格式。
matcher 是字串或正規表達式,用來過濾工具名(或 prompt)。hooks 是 callbacks 列表。timeout 以秒為單位,逾時就跳過。async (input_data, tool_use_id, context) -> dict。input_data 的欄位依事件型別而異;context 含 session_id / cwd 等。hookSpecificOutput.permissionDecision = "deny";要注入脈絡時填 additionalContext。HookMatcher。它們會按順序呼叫,任何一個回傳 deny 就擋下工具。分身召喚
Custom Subagents · the agents option
在 Claude Code 卷一的第玖回,我們看過 subagent 的運作 —— 隔離的 context、curated tools、獨立 system prompt。SDK 讓你用程式碼定義這些 subagent,而不是在 .claude/agents/ 擺 Markdown 檔。每個 AgentDefinition 是四件事:描述、可用工具、system prompt、模型。註冊到 options.agents 後,它就能被主 agent 用 Agent 工具呼叫。
Agent(subagent_type="the-key") 呼叫它。取名要語意清楚。tools 必須是主 agent 可用工具的子集。不能在這裡放主 agent 沒有的工具(例如沒註冊的 MCP tool)。prompt 參數。兩者構成它的完整脈絡。agents 選項不會覆蓋磁碟上的 agent 定義,兩者合併。同名時 SDK 寫的為準。Agent() 呼叫,SDK 會並行執行、所有結果幾乎同時回來。詳見卷一第玖回。會話續命
Sessions · resume & fork
如果你的 agent 是一個長壽的後端服務,使用者今天問一半、明天回來繼續 — 你需要的是 resume。SDK 把每個 session 的歷史透明地保存在 transcript 檔裡;下次只要把 session id 丟進 options.resume,agent 就能從斷點接續,模型記得昨天講過的話。fork_session=True 則是另一招:基於舊會話建立一個分歧新會話 — 適合「從這個狀態開始試兩條不同路線」。
storage · 儲存在哪
transcript 檔以 .jsonl 存在 ~/.claude/projects/<encoded_cwd>/。每個 session 一份檔案、每則 message 一行 JSON。你可以自己讀這些檔做分析或備份;但寫入請一律透過 SDK,否則會破壞 schema。
session_id from where · 從哪拿 id
ResultMessage.session_id 是第一手來源 — 每次 query() 或 client.receive_response() 的最後一則結果都會帶這個欄位。把它存起來(DB、cookie、檔案),下次丟進 options.resume 就能續命。