はじめに
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 | 正規表現でファイル内検索 | パターン検索 |
| WebSearch | Web検索 | 最新情報の取得 |
| WebFetch | Webページ取得 | ドキュメント参照 |
用途に応じたツールセットの考え方はこうだ。
// 読み取り専用コードレビュー(安全)
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] }]
}
}
canUseTool と hooks は組み合わせて使うのが効果的だ。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 API | Claude 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に切り替えるサインだ。

コメント