別再自行撰寫 Tool Loop!Claude Code Agent SDK 架構解析與上線指南
如果你還在使用 Anthropic 原生 SDK 自己寫 while 迴圈來解析 tool_use、處理複雜的工具回呼和上下文狀態管理,這篇文章就是為你準備的。
Anthropic 最近低調推出了 claude-agent-sdk(前身為 Claude Code SDK)。它不只是 API 包裝器,而是將 Claude Code CLI 底層的 Agent 迴圈、內建工具鏈、權限攔截 Hook,以及工作階段上下文還原機制,以程式介面完整公開的核心 SDK。
本文涵蓋架構原理、核心機制、正式環境等級的 Hook 安全攔截,以及 GitHub Actions CI/CD 和多 Agent 協作的完整實作。接下來從基礎開始了解這個 SDK。
1. 為什麼大家都在談 claude-agent-sdk
如果你用過 Anthropic 原生 SDK,就會知道建立 Agent 時必須:
- 撰寫
while迴圈並反覆呼叫/v1/messages - 解析 API 回應中的
tool_use和tool_result區塊 - 自行維護訊息歷史和上下文視窗
- 處理各種邊界情況和錯誤
第一次執行時,這一切看起來很厲害。第二次你就會發現:Anthropic、OpenAI、Gemini 等團隊做的事情都差不多。真正的差異不在 LLM 核心,而在 Agent 背後的工程實作。
claude-agent-sdk 直接公開 Claude Code 的 Agent 迴圈。換句話說,你不必重新打造整套 Agent 工程,只需幾行程式碼就能取得正式環境可用的程式碼 Agent。
這自然引出兩個問題:
claude-agent-sdk 和 Claude Code 有什麼關係?
Claude Code 是命令列中的 Agent 程式碼工具,也就是常聽到的終端機程式碼助理。claude-agent-sdk 可視為將 Claude Code 核心以函式形式提供給你的程式使用。它可在終端機當作命令列工具、整合進 CI/CD 流程,或嵌入應用程式。
claude-agent-sdk 和 Anthropic SDK 有什麼關係?
Anthropic SDK 是低階 API SDK,可直接呼叫 Claude 模型,提供 Messages API、串流和工具功能。claude-agent-sdk 建構於其上,但抽象層級高得多。
打個比方:Anthropic SDK 像是給你一具引擎,讓你自己造車;claude-agent-sdk 則像是直接提供完整底盤、動力系統和自動駕駛模組。
2. 核心架構原則:一切皆為工作階段
claude-agent-sdk 最重要的設計概念是:一切皆為工作階段。
什麼是工作階段?它是一個獨立的執行脈絡,會維護一次 Agent 執行所需的所有狀態,包括:
- 對話歷史(訊息清單)
- 權限設定與核准狀態
- 工具執行狀態
- 檔案系統變更(工作階段內部可以追蹤檔案狀態)
- 恢復中斷執行所需的完整脈絡
使用低階 SDK 時,中斷後要恢復就得自行序列化整份訊息歷史。但在 claude-agent-sdk 中,Session 物件就像遊戲的存檔點,可隨時從任何位置恢復。
建立工作階段
import { query } from "@anthropic-ai/claude-agent-sdk";
const session = await query({
prompt: "Please write a Python script that prints 1 to 100",
options: {
allowedTools: ["Bash", "Read"],
},
});
有個細節需要留意:第一次呼叫時,SDK 會自動初始化環境(安裝相依套件、同步 CLI 等)。視網路狀況,這可能需要幾秒到幾分鐘。
串流事件
for await (const message of session) {
if (message.type === "stream_event") {
console.log(JSON.stringify(message.event, null, 2));
}
}
這裡展現了一個重要的設計理念:SDK 不只是請求/回應 API,而是以串流為優先的 SDK,讓你完整看見 Agent 正在做什麼。它不會隱藏複雜性,而是提供完整透明度。
工作階段生命週期
Agent 完成工作後,工作階段就會結束。不過 SDK 也允許你明確恢復工作階段:
const resumed = await query(
"Based on the previous answer, change the script to print 1 to 1000",
{
sessionId: session.sessionId,
options: { allowedTools: ["Bash", "Read", "Write"] },
}
);
等等,這兩次呼叫看起來不是很像嗎?沒錯,但有一個關鍵差異:第一次建立新工作階段,第二次則使用 sessionId。這正是「一切皆為工作階段」的意義:Agent 可以從上次停止的位置精確地繼續工作,SDK 會替你處理所有脈絡的序列化與還原。
3. 核心機制:工具、權限與 Hook
3.1 內建工具:完整的 Agent 工具鏈
如果直接使用 Anthropic 原生 SDK,就必須自行定義每個工具:想辦法執行 Bash、處理檔案輸入和輸出,以及實作權限檢查。
claude-agent-sdk 開箱即附完整的 Claude Code 工具鏈:
| 工具名稱 | 說明 | 權限等級 |
|---|---|---|
| Bash | 執行 Shell 指令 | DefaultAsk |
| Read | 讀取檔案 | DefaultAllowed |
| Edit | 修改檔案 | DefaultAsk |
| Write | 寫入檔案 | DefaultAsk |
| Glob | 依模式尋找檔案 | DefaultAllowed |
| Grep | 搜尋檔案內容 | DefaultAllowed |
| WebFetch | 擷取網頁 URL | DefaultAsk |
| WebSearch | 搜尋網頁 | DefaultAsk |
| TodoList | 拆解任務 | DefaultAllowed |
這些就是 Claude Code 在終端機使用的工具。SDK 讓你在自己的應用程式脈絡中使用相同工具。
3.2 權限系統:兩種運作模式
呼叫需要權限的工具時,SDK 必須決定如何處理。它有兩種模式:
模式一:自動授權(建議用於 CI/CD)
const session = await query({
prompt: "Initialize a git repository and commit all files",
options: {
allowedTools: [
{ toolName: "Bash", isAllowed: () => true },
"Read",
"Write",
],
},
});
在這個模式中,所有 Bash 指令都會自動獲准。你也可以定義自訂函式:
allowedTools: [
{
toolName: "Bash",
isAllowed: async (input) => {
const command = input.command;
return command.startsWith("git ") || command.startsWith("npm ");
},
},
]
這種模式適合環境明確且安全的自動化情境,例如 CI/CD 流程。
模式二:Hook——正式環境等級的權限攔截
若要實作動態權限控制、黑名單/白名單,或人在迴圈中的核准機制,Hook 是核心機制。
const session = await query({
prompt: "Run all tests and fix any failures",
options: {
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
const command = input.tool_input.command;
if (command.includes("rm -rf")) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason:
"rm -rf is not allowed in our CI environment",
},
};
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
等等,有些讀者會問:既然 isAllowed 已經能處理這些事,為什麼還需要 Hook?
好問題。差異在於:
isAllowed只處理已列入allowedTools的工具。它無法新增原清單沒有的允許工具,也不能在執行期間動態變更權限範圍。- Hook 會在工具實際執行之前觸發,也能在生命週期的不同階段攔截(例如 PreToolUse、PostToolUse)。它還可以變更工作階段狀態、阻止執行,或實作人在迴圈中的核准等更複雜流程。
簡言之,Hook 是更強大、更彈性,也更面向正式環境的機制。
3.3 Hook 事件類型
PreToolUse:工具執行前觸發。PostToolUse:工具執行後觸發。Notification:Agent 傳送通知時觸發。UserPromptSubmit:使用者提交提示詞時觸發。Stop:Agent 完成工作時觸發。
完整 Hook 生命週期還能細分更多事件,可查看官方文件了解全部清單。重要的是理解其原則:SDK 讓你完整觀察並控制 Agent 迴圈的生命週期。
4. 正式環境 Hook 實作:建置安全閘道
現在來建置一個實用的正式環境安全閘道。它會攔截每個 Bash 指令並套用以下規則:
- 拒絕所有
rm -rf指令(特別危險) - 拒絕寫入工作目錄以外位置的指令(例如
cd /etc && echo "hello" > test.txt) - 拒絕從環境變數檔案讀取憑證(例如
/etc下的.env,或~/.aws/credentials) - 允許其他操作
完整實作如下:
// secure-gate.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
const WORKSPACE = "/home/user/project";
const DENY_PATTERNS = [
/^\s*rm\s+(-[a-z]*\s+)*-\s*[a-z]*[r]/i,
/^\s*sudo\s+/i,
/^\s*curl\s+.*(\||>)/i,
/^\s*(cat|tail|head|less|more)\s+.*\.(env|pem|key|p12)/i,
];
const ALLOW_PATTERNS = [
/^\s*(cd|ls|pwd|git|npm|node|python3?|pip|bun|yarn|pnpm|tsc|eslint|prettier)\b/i,
];
function isSafeCommand(command: string): { allowed: boolean; reason?: string } {
const trimmed = command.trim();
// Block dangerous patterns
for (const pattern of DENY_PATTERNS) {
if (pattern.test(trimmed)) {
return {
allowed: false,
reason: `Command matches dangerous pattern: ${pattern}`,
};
}
}
// If it matches our allow-list of build/test commands, let it through
for (const pattern of ALLOW_PATTERNS) {
if (pattern.test(trimmed)) {
return { allowed: true };
}
}
// Otherwise, allow but with audit log
return { allowed: true, reason: "Allowed with audit" };
}
async function main() {
const session = await query({
prompt: "Run npm test and fix any errors",
options: {
allowedTools: ["Bash", "Read", "Write", "Edit"],
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
const command = input.tool_input.command as string;
const result = isSafeCommand(command);
if (!result.allowed) {
console.warn(`[SECURITY] Blocked command: ${command}`);
console.warn(`[SECURITY] Reason: ${result.reason}`);
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: result.reason,
},
};
}
if (result.reason === "Allowed with audit") {
console.log(`[AUDIT] ${command}`);
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
for await (const message of session) {
// consume stream
}
}
main().catch(console.error);
這個範例中:
- 我們使用正規表示式結合黑名單和白名單。
- 黑名單阻擋危險指令;白名單讓常用建置/測試指令直接通過,避免不必要的權限請求。
- 其他指令則允許執行,但會寫入稽核記錄。
- 權限決策結果會回傳給 Agent,讓它能調整行為。
真正的正式系統會做得更完整:可採用外部政策引擎(例如 OPA)、整合 SIEM,或將決策請求轉送至人工核准服務。原則不變:完整掌控 Agent 迴圈。
5. GitHub Actions CI/CD:讓 Agent 成為自動化開發者
接著看一個真實的正式環境情境:在 GitHub Actions CI/CD 使用 claude-agent-sdk,自動為程式碼變更建立 Pull Request。
使用 CLI 與 --output-format stream-json
在 CI 中,最簡單的方式是直接使用 CLI。CLI 和 SDK 就像同一個整體的兩面。在 GitHub Actions 工作流程中可以這樣執行:
name: Agent Code Review & Fix
on:
push:
branches: [main]
jobs:
agent-code-review:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npx -y @anthropic-ai/claude-code@latest \
--output-format stream-json \
--allowedTools "Read,Write,Edit,Bash" \
--permission-mode acceptEdits \
"Review the code, find bugs, fix them, and create a commit"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
branch: agent-fixes
base: main
title: "Agent auto-fix: code review & bug fixes"
body: "This PR is automatically generated by claude-agent-sdk"
幾點需要留意:
- 不要直接使用 Agent 產生的提交身分;建議使用
create-pull-requestAction,由它處理驗證與提交身分。 --permission-mode acceptEdits可讓 Agent 修改檔案而不必詢問權限。安全要求更高的流程可改用--permission-mode plan,讓 Agent 只提出計畫,再由你手動套用。- 想把完整過程輸出至 CI 記錄時,使用
--output-format stream-json。 - CI 不應執行互動式指令,請確保所有權限都已預先授權。
還有一個重要細節:CI 環境中第一次執行 CLI 時會初始化環境,可能花 30 至 60 秒。若經常執行,可考慮快取 ~/.claude 目錄:
- name: Cache Claude Code
uses: actions/cache@v4
with:
path: ~/.claude
key: ${{ runner.os }}-claude-${{ hashFiles('**/package-lock.json') }}
在 Node.js CI 環境中使用 SDK
如果偏好以 Node.js 撰寫自訂 CI 指令碼,也可以直接使用 SDK:
// ci-agent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import { execSync } from "node:child_process";
import * as fs from "node:fs";
async function runAgent() {
const prompt = `
You are working in a Node.js project.
Please complete the following tasks:
1. Read the current git diff
2. Look for any obvious bugs or type errors
3. Fix them
4. Write a test for the changes
5. Run the tests to make sure they pass
`;
const session = await query({
prompt,
options: {
allowedTools: ["Bash", "Read", "Write", "Edit"],
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
const command = input.tool_input.command;
if (!["git", "npm", "node", "npx"].some((cmd) => command.trim().startsWith(cmd))) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `Command not allowed in CI: ${command}`,
},
};
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
for await (const message of session) {
if (message.type === "stream_event") {
// You can send this to your log aggregation system
console.log(JSON.stringify(message.event));
}
}
const diff = execSync("git diff --stat").toString();
console.log("===== Agent Changes =====");
console.log(diff);
}
runAgent().catch(console.error);
在此指令碼中,query 會啟動工作階段並執行 Agent。Hook 可確保 Agent 只能使用 git、npm、node、npx 指令,顯著降低執行任意程式碼的風險。最後,我們取得差異,據此決定是否建立 PR 或直接推送。
6. 多 Agent 協作:在 Agent 之間傳遞任務
最實用的多 Agent 模式不是複雜的「監督者」模式,而是子 Agent 加上工作流程。意思是不要把所有責任塞進一個超長提示詞,而是拆成多次獨立的 Agent 執行,每次都有自己的目標和權限範圍。
範例:程式碼重構流程
假設有一項需求:「重構付款模組、抽出共用邏輯,並撰寫測試。」
可以拆給三個 Agent:
- 探索 Agent:閱讀程式碼並提出重構計畫。
- 實作 Agent:執行計畫並修改程式碼。
- 審查 Agent:檢查修改並產生報告。
每個 Agent 有不同角色、權限範圍和工作目錄。以下示範如何協調它們:
// multi-agent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import * as fs from "node:fs";
interface AgentResult {
sessionId: string;
finalText: string;
output: string;
}
async function runAgent(
prompt: string,
allowedTools: any[],
sessionId?: string
): Promise<AgentResult> {
const session = await query({
prompt,
options: {
allowedTools,
...(sessionId ? { resume: sessionId } : {}),
hooks: {
PreToolUse: [
{
matcher: "Bash",
hook: async (input) => {
if (input.tool_input.command.includes("rm -rf")) {
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "rm -rf is prohibited",
},
};
}
return {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
},
};
},
},
],
},
},
});
const messages = [];
for await (const message of session) {
if (message.type === "stream_event") {
messages.push(message.event);
if (message.event.type === "result") {
return {
sessionId: session.sessionId,
finalText: message.event.result,
output: JSON.stringify(messages),
};
}
}
}
throw new Error("Agent did not produce a result");
}
async function main() {
// Step 1: Explorer Agent — read and plan
const explorerResult = await runAgent(
`You are the code explorer. Read the payment module under src/payments/.
Analyze the current structure and potential issues. Output a concise
refactoring plan with specific file paths and actions.`,
["Read", "Glob", "Grep"]
);
console.log("===== Explorer Agent Plan =====");
console.log(explorerResult.finalText);
// Step 2: Implementer Agent — execute the plan
const implementerResult = await runAgent(
`You are the implementer. Here is the refactoring plan from the explorer:
${explorerResult.finalText}
Implement the plan. Edit the code, make the changes, and run the tests
to make sure everything passes.`,
["Read", "Write", "Edit", "Bash"]
);
console.log("===== Implementer Agent Result =====");
console.log(implementerResult.finalText);
// Step 3: Reviewer Agent — review the changes
const reviewResult = await runAgent(
`You are the code reviewer. Here is what the implementer did:
${implementerResult.finalText}
Review the current git diff and the files that were changed.
Look for bugs, security issues, and code quality issues.
Write a detailed review report.`,
["Read", "Bash", "Glob", "Grep"]
);
console.log("===== Reviewer Agent Report =====");
console.log(reviewResult.finalText);
}
main().catch(console.error);
為什麼這種模式更容易維護?
- 每次執行都隔離脈絡:每個 Agent 都有新的脈絡,不必攜帶前一次執行的所有資料,可避免上下文超限。
- 任務邊界清楚:把「分析」、「執行」和「審查」分開,自然形成任務和權限分工。實作者不必瀏覽整個網際網路;審查者也不必寫入檔案。
- 可觀測性:每個階段都有明確輸出(計畫、修改、報告),可儲存、比較和稽核。
- 優雅處理失敗:可為每個階段加入重試邏輯;若實作者失敗,可從探索者的輸出重新開始,不必重新注入 Agent 的完整歷史。
7. 總結:SDK 是進入 Agent 時代的門票
回顧本文內容:
- 架構原則:一切皆為工作階段。SDK 替你管理狀態;上下游只需傳遞
sessionId。 - 核心機制:內建工具、權限系統和 Hook。可用
isAllowed(靜態)和 Hook(動態)控制 Agent 迴圈。 - 正式環境實作:以 Hook 建置安全閘道,以
query()將 Agent 整合進 CI/CD,以多個工作階段建置多 Agent 流程。 - 整合模式:簡單整合適合 CLI,自訂整合則適合 SDK。
我還需要自己撰寫 Agent 迴圈嗎?
不需要。這就像問:「已經有 HTTP 了,還要自己寫 TCP 堆疊嗎?」只有真正特殊的需求才需要自訂邏輯。claude-agent-sdk 已經相當模組化。如果真的需要更低層級的控制,可直接使用 Anthropic 官方 SDK。但對正式環境的 Agent 應用而言,建構在 claude-agent-sdk 之上,是效率高、健全且容易維護的選擇。
AI 工程的未來不靠炒作,而在流程、權限、可觀測性和務實精神。SDK 不是玩具,而是軟體工程的未來。
參考資料
本文由 Claude 翻譯。如發現翻譯問題,歡迎告知作者。