本文へスキップ
やす研究所 AI Lab
戻る

Claude Agent SDK実践2026:Pythonで自作エージェントをheadless運用する手順

Claude Agent SDK実践2026:Pythonで自作エージェントをheadless運用する手順

Claude Code は対話で使う分には強力ですが、「夜中にcronで回したい」「自分のFastAPIに組み込みたい」となると話が変わります。私もそこで詰まりました。

そこで触り始めたのが Claude Agent SDK です。Claude Code とほぼ同じエージェントループを、自分のPythonコードから呼べる公式SDK。先週、社内向けの依存ライブラリ監査ボットを2時間半でプロトタイプできて、正直拍子抜けしました。

この記事では、Agent SDK の最小構成から、ツール登録、permission 設計、unattended 運用での落とし穴までを、実際に動いたコードベースでまとめます。

結論:Claude Agent SDK は「Claude Code の中身をライブラリ化したもの」

先に結論から書きます。Claude Agent SDK は新しいフレームワークというより、Claude Code の agent loop を Python / TypeScript から直接ドライブできるようにした薄いラッパーです。

公式リポジトリの説明によると、SDK は CLI バイナリを内包した単一の pip パッケージとして配布されており、別途 CLI を入れる必要はありません。Anthropic の Messages API を直接叩くのとは別物で、ファイル操作・シェル・WebSearch などの組み込みツールを最初から持っているのが特徴です。

つまり、こういう用途にハマります。

逆に「ターミナルで自分が叩く」だけなら、素の Claude Code CLI で十分です。SDK を選ぶ理由は headless で動かしたい から、ここに尽きます。

なぜ素のMessages APIではなくAgent SDKを使うのか

私が最初にハマったのがここでした。「Anthropic の Python SDK(anthropic パッケージ)で tool_use ループを自分で書けばいいのでは?」と。

結論、書けるけど書きたくないです。

Messages API を直接使うと、tool_use ブロックを受け取ってツール実行して結果を tool_result で返す、というループを自分で組む必要があります。さらに context compaction、permission gating、streaming、MCP クライアント実装、エラーリトライ、これら全部を自前で抱えることになります。

Agent SDK はこの「ループ周りのグルーコード」を肩代わりしてくれます。実際に SDK ベースで書き直したとき、500行近くあったループ処理が query() の async for 一発で消えました。これは想像以上に効きました。

もう一点、地味だけど効いてくる差があります。anthropic パッケージと claude-agent-sdk は別物 という点。クラス名も async パターンも違うので、ネット上のチュートリアルを混ぜて読むと事故ります。新規で組むなら最初から SDK 一本に寄せたほうが安全です。

Claude Agent SDK のインストールと最小コード

環境要件はシンプルで、Python 3.10 以上(3.10〜3.13 サポート)、それと Node.js 18+ があれば動きます。Node が必要なのは、内部の CLI が node 製だからです。

pip install claude-agent-sdk
export ANTHROPIC_API_KEY="sk-ant-..."

これだけで動きます。CLI を別途 install する必要はありません。最小の動作確認はこう。

import anyio
from claude_agent_sdk import query

async def main():
    async for message in query(prompt="What is 2 + 2?"):
        print(message)

anyio.run(main)

query() は async iterator を返すので、async for で1メッセージずつストリームできます。各メッセージは Claude の思考、ツール呼び出し、ツール結果、最終出力のどれかです。

古い claude-code-sdk パッケージを触ったことがある人向けの注意。ClaudeCodeOptionsClaudeAgentOptions にリネームされています。2025年9月のリブランディングで、コード用途以外(法務 bot、SRE bot など)にも使われ始めたことが理由らしいです。古いコードはそのままだと動きません。

ClaudeAgentOptions で挙動を制御する

ここからが本番です。query() に options を渡して、エージェントの権限・モデル・思考予算を絞っていきます。

from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    system_prompt="You are a senior Python reviewer. Follow PEP 8.",
    allowed_tools=["Read", "Edit", "Glob", "Grep"],
    permission_mode="acceptEdits",
    cwd="/path/to/project",
    max_turns=20,
)

async for msg in query(prompt="Refactor utils.py for readability", options=options):
    print(msg)

押さえるべきは、unattended 運用で 絶対に省略してはいけない2つの設定 です。

permission_mode には default / acceptEdits / plan / dontAsk / bypassPermissions があり、特に cron で回すなら dontAsk が安全 です。事前に許可していないツール呼び出しは、プロンプトを出さずに deny します。bypassPermissions は名前の通り全部素通しなので、本番で使うのは怖いです。

もうひとつ、2026年4月時点で 1Mトークンコンテキストのβヘッダ(context-1m-2025-08-07)は廃止されています。Sonnet 4.6 や Opus 4.6 以降は標準で 1M context を持っているので、ヘッダ無しでそのまま使えます。古いサンプルコードをコピペすると、エラーで返ってくるので注意してください。

カスタムツールを@toolデコレータで足す

ここが SDK のいちばん気持ちいいところでした。

組み込みツール(Read / Write / Edit / Bash / Glob / Grep / WebSearch / WebFetch)に加えて、自分の Python 関数をそのままツールとして登録できます。仕組みは in-process の MCP サーバー で、外部プロセスを立てなくていいので起動オーバーヘッドがゼロです。

from claude_agent_sdk import (
    tool, create_sdk_mcp_server, ClaudeAgentOptions, ClaudeSDKClient,
)

@tool("fetch_order", "Fetch order status by order ID", {"order_id": str})
async def fetch_order(args):
    # 実際は DB や内部 API を叩く
    return {
        "content": [
            {"type": "text", "text": f"Order {args['order_id']} is shipped"}
        ]
    }

server = create_sdk_mcp_server(
    name="internal-tools",
    version="1.0.0",
    tools=[fetch_order],
)

options = ClaudeAgentOptions(
    mcp_servers={"internal": server},
    allowed_tools=["mcp__internal__fetch_order"],
)

ツール権限の名前は mcp__<server-name>__<tool-name> という規則です。これを allowed_tools に書かないと、登録しても Claude が呼べません。最初これでハマって30分溶かしました。

地味だけど効くTipsをひとつ。ツールの description は関数名より重要 です。Claude はスキーマじゃなくて自然言語の説明文を読んでツールを選びます。“Order fetcher” より “Fetch order status by order ID” のように命令形+目的語で書いたほうが、選択精度が体感で変わります。

hookで「rm -rf /」を確実にブロックする

permission_mode だけでは不安なケース、あります。たとえば Bash ツールを許可した瞬間、理論上は rm -rf / を打たれる可能性が残ります。

そこで hook の出番です。SDK では Python 関数として hook を渡せます。CLI 版の settings.json と違って、シェルじゃなく直接コールバックを書けるのが楽。

from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher

async def validate_bash(input_data, tool_use_id, context):
    if input_data["tool_name"] == "Bash":
        cmd = input_data["tool_input"].get("command", "")
        if "rm -rf /" in cmd or "sudo" in cmd:
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "Dangerous command blocked",
                }
            }
    return {}

options = ClaudeAgentOptions(
    allowed_tools=["Bash", "Read", "Edit"],
    hooks={"PreToolUse": [HookMatcher(hooks=[validate_bash])]},
)

フック可能なイベントは PreToolUse / PostToolUse / Stop / SessionStart / SessionEnd / UserPromptSubmit などがあります。ここで一つクセを覚えておくと、SessionStart / SessionEnd を SDK のコールバックとして登録できるのは TypeScript だけで、Python では settings ファイル側のシェルフックで定義する ことになります。クロス言語の細かい差なので、ドキュメントは都度確認したほうが安全です。

もう一点、Python のフック関数では async_ というアンダースコア付きの名前を使います。CLI 送信時に自動で async に変換される仕様で、これは Python の予約語回避のためです。

2026年に追加されたAdvisor toolという選択肢

2026年5月の Code with Claude で、新しい組み合わせが発表されました。Advisor tool です。

これは「Opus を advisor(計画・レビュー担当)、Sonnet を executor(実行担当)としてペアにする」API 機能で、ベータヘッダ advisor-tool-2026-03-01 で利用できます。Opus がタスクを計画してレビューし、Sonnet が各ステップを実行する、という構造。

なぜこれが効くかというと、シニアエンジニアの実際の働き方に近いからです。計画とレビューはモデル能力が要る一方、コード生成のような実行は throughput が要る。性質が違う仕事を別のモデルで分担するほうが、コスト・品質の両面で理にかなっています。

私のチームで試した感覚では、長めの refactor タスク(3〜5ファイル横断)で Opus 単体より体感のレビュー品質が上がりました。コスト面は計測中なので断言は避けますが、執行部分が Sonnet なのでフル Opus よりは確実に下がるはず、というのが Anthropic 公式の説明です。

headless運用で詰まる3つの落とし穴

3週間ほど SDK で動くエージェントを回してみて、踏んだ落とし穴を共有します。

1. CLINotFoundError で起動失敗

他のパッケージと環境を共有していると、bundle されたCLIがPATHで見つからないことがあります。ClaudeAgentOptions(cli_path="/path/to/claude") で明示するか、curl -fsSL https://claude.ai/install.sh | bash で system-wide に入れて回避します。

2. query() は会話を覚えない

デフォルトでは query() を呼ぶたびに新しいセッションが始まります。前回の続きをやらせたいなら continue_conversation=True を渡すか、resumesession_id を渡す必要があります。これに気づかず「なぜ毎回ゼロから自己紹介してくるんだ…」と1時間悩みました。

3. ストリーム停止のタイムアウト挙動

2026年6月の Claude Code 更新で、API無音時の警告メッセージは20秒沈黙してから出るようになりました(以前は10秒)。つまり「20秒程度応答が来ない」のは正常範囲です。 unattended スクリプト側でこれより短いタイムアウトを切ると、無駄に再起動してしまいます。

まとめ:今日からやる3つのアクション

ここまでで Agent SDK の全体像と、unattended で動かすための最低限の設計が見えたはずです。最後に、今日から手を動かすための具体的な3ステップを置いておきます。

  1. pip install claude-agent-sdk して query() のhello worldを動かす:5分で終わります。動いた瞬間に「これは Claude Code を Python に閉じ込めただけだ」と腹落ちするはず
  2. @tool デコレータで自分の業務関数を1つだけツール化する:DB読み取り関数や社内API ラッパーが現実的。description は命令形で書く
  3. permission_mode="dontAsk" + PreToolUse hook の二段構えで cron に乗せる:いきなり bypassPermissions で本番に流すと事故ります。私もヒヤッとしました

Agent SDK は「Claude Code を1日中触っている人ほど、その延長として組み込める」設計になっています。CLI で使い慣れた permission / hook / subagent の概念がそのまま API で出てくるので、学習コストは思ったより低いはずです。

夜中に勝手に動いてくれるエージェント、一度作るとやめられなくなります。

参考リンク


この記事をシェア:

次の記事
有料MCPサーバーで作る月額収益|個人開発者の新副業ルート2026