別再自行撰寫 Tool Loop!Claude Code Agent SDK 架構解析與上線指南

AI Router Aug 30, 2026

如果你還在使用 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-request Action,由它處理驗證與提交身分。
  • --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:

  1. 探索 Agent:閱讀程式碼並提出重構計畫。
  2. 實作 Agent:執行計畫並修改程式碼。
  3. 審查 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 時代的門票

回顧本文內容:

  1. 架構原則:一切皆為工作階段。SDK 替你管理狀態;上下游只需傳遞 sessionId。
  2. 核心機制:內建工具、權限系統和 Hook。可用 isAllowed(靜態)和 Hook(動態)控制 Agent 迴圈。
  3. 正式環境實作:以 Hook 建置安全閘道,以 query() 將 Agent 整合進 CI/CD,以多個工作階段建置多 Agent 流程。
  4. 整合模式:簡單整合適合 CLI,自訂整合則適合 SDK。

我還需要自己撰寫 Agent 迴圈嗎?

不需要。這就像問:「已經有 HTTP 了,還要自己寫 TCP 堆疊嗎?」只有真正特殊的需求才需要自訂邏輯。claude-agent-sdk 已經相當模組化。如果真的需要更低層級的控制,可直接使用 Anthropic 官方 SDK。但對正式環境的 Agent 應用而言,建構在 claude-agent-sdk 之上,是效率高、健全且容易維護的選擇。

AI 工程的未來不靠炒作,而在流程、權限、可觀測性和務實精神。SDK 不是玩具,而是軟體工程的未來。

參考資料


本文由 Claude 翻譯。如發現翻譯問題,歡迎告知作者。