Claude Code Agent SDK——「Claude Codeをライブラリとして使う」10行で動くカスタムエージェントの設計パターン

はじめに

claude -p "..." でシェルスクリプトからClaude Codeを呼び出す使い方には、一つの限界がある。Claude Codeがブラックボックスになってしまうことだ。

エージェントループの途中で何が起きているか見えない。ツールの実行前後に自社のロジックを挟めない。「このコマンドは実行してはいけない」というルールを動的に適用できない。ヘッドレスモードは便利だが、制御の粒度に限界がある。

Claude Code Agent SDKはこの問題を解決するために設計された。Anthropicは自らAgent SDKを「Claude Code as a library」と説明している。Claude Codeが動くエージェントループ・ツール群・コンテキスト管理を、TypeScript/Pythonコードから直接呼び出せるライブラリとして提供する。

この記事では query() 関数の基本から、パーミッション制御、Hooksによる自社ロジック挿入、CI/CDへの組み込みパターンまでを解説する。


「ブラックボックス」から「ホワイトボックス」へ

ヘッドレスモードとAgent SDKの違いを一言で言うと、「ブラックボックスとして使うか、ホワイトボックスとして使うか」だ。

Claude Code CLI        Claude Agent SDK
─────────────────      ─────────────────────────
ターミナルで実行        TypeScript/Pythonで実行
会話型インターフェース   プログラム的インターフェース
手動での権限承認        canUseTool関数で自動制御
標準のツールセット      カスタムツールを注入可能

Agent SDKには3つの特徴的な設計がある。永続的なshell/environmentを前提にしていること、working directoryの中でファイルとコマンドを扱うこと、そして長いエージェントループを前提にしていること(短いQ&Aではなく、複数ステップの作業が対象だ)。

これはGoogleのADKのような「汎用LLM orchestration SDK」ではない。あくまでClaude Codeのエージェントループをプログラムから操作するための専用ライブラリだ。


10行で動かす

まずインストールから。

npm install @anthropic-ai/claude-agent-sdk

# Python版
pip install claude-agent-sdk

TypeScriptでの最小構成はこうなる。

import { query } from '@anthropic-ai/claude-agent-sdk';

for await (const msg of query({
prompt: "src/ 配下の全パブリック関数のAPIドキュメントをdocs/api.mdに生成して",
options: {
allowedTools: ["Read", "Write", "Glob"],
permissionMode: "acceptEdits"
}
})) {
if (msg.type === 'result') console.log("完了:", msg.result);
}

Pythonでも同様だ。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep"],
permission_mode="acceptEdits",
cwd="/path/to/project"
)
async for message in query(
prompt="auth.py のバグを見つけて修正して",
options=options
):
if message.type == "result":
print("結果:", message.result)

asyncio.run(main())

cwd オプションでworking directoryを指定できる。CI/CDではリポジトリのルートを指定するのが基本だ。

エージェントループは5段階のサイクルで動作する。プロンプト受信→評価・応答→ツール実行→繰り返し(ツール呼び出しがなくなるまで)→結果返却。for await のループがこのサイクル全体を抽象化しており、各ステップのメッセージが順番に流れてくる。


組み込みツール8種——自前実装ゼロで動く

Agent SDKには8種類のツールが標準装備されている。生のMessages APIでは全て自前実装が必要だが、Agent SDKはゼロから始められる。

ツール機能主な用途
Readファイル読み取りソースコード解析
Write新規ファイル作成レポート・コード生成
Edit既存ファイル編集コード修正・リファクタリング
Bashシェルコマンド実行テスト・ビルド・デプロイ
Globパターンでファイル検索*.ts の一括処理
Grep正規表現でファイル内検索パターン検索
WebSearchWeb検索最新情報の取得
WebFetchWebページ取得ドキュメント参照

用途に応じたツールセットの考え方はこうだ。

// 読み取り専用コードレビュー(安全)
allowedTools: ["Read", "Glob", "Grep"]

// コード自動修正(書き込みあり)
allowedTools: ["Read", "Edit", "Bash"]

// ドキュメント生成(書き込みあり・Web参照あり)
allowedTools: ["Read", "Glob", "Write", "WebFetch"]

原則は「必要最小限のツールのみを許可する」だ。Bash は強力だが危険なコマンドを実行できてしまうため、不要なら外す。さらに "Bash(npm:*)" のようにスコープを絞ると、Bashを許可しつつnpm関連のコマンドのみに制限することもできる。


パーミッション制御——canUseTool で細粒度制御する

権限モードは5種類ある。

モード動作用途
default未許可ツールは承認コールバックを呼ぶ開発環境
acceptEditsファイル編集を自動承認CI/CD(書き込みあり)
planツール実行なし・計画のみ生成プレビュー確認
dontAsk(TS限定)事前承認済みのみ実行、他は拒否制限付き自動化
bypassPermissions全ツール無承認隔離コンテナ専用

permissionMode だけでは対応できない細粒度の制御には canUseTool を使う。例えば、.env ファイルへの書き込みをブロックしたい場合。

options: {
  canUseTool: async (toolName, input) => {
    if (toolName === "Write" && input.file_path?.includes(".env")) {
      return {
        behavior: "deny",
        message: ".env ファイルへの書き込みは禁止されています"
      };
    }
    if (["Read", "Glob", "Grep"].includes(toolName)) {
      return { behavior: "allow", updatedInput: input };
    }
    return { behavior: "ask", updatedInput: input };
  }
}

特定のディレクトリ外へのアクセスをブロックする場合はこうなる。

const ALLOWED_PATHS = ["/project/src", "/project/docs"];

options: {
canUseTool: async (toolName, input) => {
const filePath = input.file_path || "";
const isAllowed = ALLOWED_PATHS.some(path => filePath.startsWith(path));
if (!isAllowed) {
return {
behavior: "deny",
message: アクセス拒否: ${filePath} は許可されたパス外です
};
}
return { behavior: "allow", updatedInput: input };
}
}

これらの制御ロジックは、ヘッドレスモードの claude -p では実現できない。


Hooks——ツール実行の前後に自社ロジックを挿入する

Hooksはエージェントループのライフサイクルにフックして自社ロジックを実行する仕組みだ。

Hookタイミング主な用途
PreToolUseツール実行前監査ログ・危険コマンドブロック・入力検証
PostToolUseツール実行後結果の記録・エラーハンドリング

全ツールの実行を監査ログに記録しつつ、危険なコマンドをブロックする例だ。

const auditLogger = async (input) => {
  if (input.hook_event_name === "PreToolUse") {
    console.log([AUDIT] ${input.tool_name}: ${JSON.stringify(input.tool_input)});

if (input.tool_name === "Bash") {
const cmd = input.tool_input?.command || "";
if (cmd.includes("rm -rf") || cmd.includes("drop table")) {
return { decision: "block", reason: "危険なコマンドを検出しました" };
}
}
}
return {};
};

options: {
hooks: {
PreToolUse: [{ hooks: [auditLogger] }],
PostToolUse: [{ hooks: [logResults] }]
}
}

canUseToolhooks は組み合わせて使うのが効果的だ。canUseTool で大まかな権限制御を行い、hooks で細かいロギングとセキュリティチェックを担当する。社内エージェントを本番環境で動かす際に、「誰が何をいつ実行したか」のトレーサビリティを確保できる。


CI/CDへの組み込みパターン

PRレビューbot(並列サブエージェント)

3つのレビュアーを並列実行してPRを多角的にレビューする例だ。

const reviewBot = async (prDiff: string) => {
  const [styleResult, securityResult, coverageResult] = await Promise.all([
    runReviewer("コードスタイル・命名規則", prDiff),
    runReviewer("OWASP Top 10セキュリティチェック", prDiff),
    runReviewer("テストカバレッジ・エッジケース", prDiff),
  ]);

return { style: styleResult, security: securityResult, coverage: coverageResult };
};

const runReviewer = async (role: string, diff: string) => {
const messages = [];
for await (const msg of query({
prompt: ${role}の観点でこのPR差分をレビューして: ${diff},
options: {
allowedTools: ["Read", "Glob", "Grep"],
maxTurns: 5,
permissionMode: "default"
}
})) {
if (msg.type === 'result') messages.push(msg.result);
}
return messages;
};

maxTurns でループの上限を設定できる。maxBudgetUsd でコスト上限も設定可能だ。

社内カスタムCLIツール

settingSources: ["project"] を指定すると、プロジェクトのCLAUDE.md・Skills・Hooksを読み込んで動く。CI/CD環境では "project" のみを指定し、ローカル設定の影響を排除するのがベストプラクティスだ。

options: {
  settingSources: ["project"],  // .claude/settings.json・CLAUDE.md・Skillsを読み込む
  allowedTools: ["Read", "Glob", "Grep"],
  permissionMode: "acceptEdits"
}

settingSources を指定しないと、エージェントはプロジェクトのCLAUDE.mdを無視して動く。チームで育てたCLAUDE.mdの内容をCI/CDでも活かすには、この指定が必要だ。

Agent Teams(実験的機能)

複数エージェントが協調する「Agent Teams」は、環境変数で有効化できる実験的機能だ。

const options = {
  permissionMode: "bypassPermissions",
  env: {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  }
};

有効にすると、メインエージェントが複雑なタスクに応じて必要なサブエージェントを自律的に生成・調整して対応する。まだ実験段階の機能のため、本番環境への組み込みは慎重に評価した上で行うことを推奨する。


生のMessages APIとの違い

正直、最初は「生のMessages APIで十分では」と思っていた。使い始めて考えが変わったポイントを書いておく。

項目生のMessages APIClaude Agent SDK
ツールループ管理自前実装(while文)自動(for await で簡潔)
組み込みツール全て自前実装8種類が標準装備
コンテキスト管理メッセージ履歴を手動管理自動管理
コード量数十〜数百行10行程度
カスタム制御自由だが複雑Hooks・canUseTool で体系化
向いている用途チャット・単発Q&Aコーディング・ファイル操作・長いループ

チャットUIの実装や単発のQ&Aには生のMessages APIが向いている。一方、ファイルを操作する、テストを実行する、複数ステップにわたる作業をするといったコーディングタスクにはAgent SDKの方が圧倒的にコードが短くなる。


まとめ

Agent SDKを使ってみて一番変わったのは、エージェントが「何をしているか分からない」というストレスが消えたことだ。ヘッドレスモードでは見えなかった各ステップが、for await のループを通じて手元に届く。

実際に試してみるなら、こういう順番が無理なく進められると思う。

今すぐ(5分)npm install @anthropic-ai/claude-agent-sdk でインストールして query() の最小構成を動かす。既存のClaude Codeのヘッドレス呼び出しと比較すれば違いがすぐ分かる。
今日中:既存のCI/CDパイプラインの1ステップ(コードレビューやドキュメント生成)をAgent SDKに置き換え、canUseTool で触れてはいけないファイルやコマンドをブロックする設定を追加する。
チーム展開:社内固有のルール(触ってはいけないファイル・実行してはいけないコマンド)を canUseTool に実装してチームの全CIパイプラインで共有する。settingSources: ["project"] でCLAUDE.mdも反映させると、ターミナルでの対話とCI自動化の挙動を揃えられる。

claude -p では物足りなくなってきた」と感じたタイミングが、Agent SDKに切り替えるサインだ。

コメント

タイトルとURLをコピーしました