Contents
Map

16 ยท Agent Frameworks

Claude Agent SDK

View as:

Claude Agent SDK

The Claude Agent SDK exposes the harness behind Claude Code - its loop, built-in tools, context management, permissions, hooks, subagents and skills - as a Python and TypeScript library. Where other agent SDKs give you a loop to fill with your own tools, this one starts with a working agent that can read and edit files, run commands and search the web, and you restrict and extend it. It was renamed from the Claude Code SDK in September 2025.

Learning objectives 40 min
By the end of this page you will be able to:
  • Explain how the Claude Agent SDK differs from a Messages API tool loop, the Tool Runner and Managed Agents
  • Configure built-in tools, permission modes, allowed and disallowed tools, and a can_use_tool callback
  • Add custom tools through an in-process MCP server, and hooks that block or audit tool calls
  • Define subagents and load skills and project settings
Prerequisites

Where It Sits

OptionYou writeLoop and toolsHosting
Messages API, manual loopThe loop and every toolYoursYours
Messages API Tool RunnerTool functionsSDK loop, your toolsYours
Claude Agent SDKA prompt and optionsClaude Code harness; built-in Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch, plus MCP and your toolsYours (the SDK drives a local Claude Code process)
Claude Managed AgentsAgent configurationAnthropic's harnessAnthropic runs the loop and a per-session sandbox

Choose the Agent SDK when the agent's work looks like Claude Code's - files, shells, codebases, research across documents - and you want context compaction, permissions and subagents already solved. It runs only Claude models, and each session drives a Claude Code CLI process, so it needs the CLI installed and an Anthropic API key (or a Bedrock, Vertex AI or Foundry configuration).

flowchart LR
    APP["๐Ÿ Your app"] -->|"query() / ClaudeSDKClient"| CLI["โš™๏ธ Claude Code process<br/>loop, context compaction"]
    CLI --> BT["๐Ÿ› ๏ธ Built-in tools<br/>Read, Edit, Bash, Grep, WebFetch..."]
    CLI --> MCP["๐Ÿ”Œ MCP servers<br/>(in-process or external)"]
    CLI --> SUB["๐Ÿค– Subagents"]
    CLI -.->|"permission check"| CB["โœ… can_use_tool / hooks<br/>(your code)"]
    CLI --> API["๐Ÿง  Claude API"]

    style APP fill:#e8e2d9,stroke:#ccc4b8
    style CLI fill:#d8dfe8,stroke:#b0bac8
    style CB fill:#dde4dc,stroke:#b0c4b0

Custom Tools, Permissions and Approval

The lab's version serves the shop tools from an in-process MCP server and removes every built-in tool:

from claude_agent_sdk import (ClaudeAgentOptions, ClaudeSDKClient, PermissionResultAllow,
                              PermissionResultDeny, ResultMessage, create_sdk_mcp_server, tool)

@tool("refund_item", "Refund one line item of a delivered order.", {"order_id": str, "item_id": int})
async def refund_item(args):
    result = shop.refund_item(args["order_id"], args["item_id"])
    return {"content": [{"type": "text", "text": json.dumps(result)}]}

async def approve(tool_name, tool_input, context):
    if tool_name == "mcp__shop__refund_item" and not human_says_yes(tool_input):
        return PermissionResultDeny(message="A reviewer declined this refund.")
    return PermissionResultAllow()

options = ClaudeAgentOptions(
    system_prompt=POLICY,
    mcp_servers={"shop": create_sdk_mcp_server("shop", tools=[find_customer, ..., refund_item])},
    tools=[],                                              # no built-in tools at all
    allowed_tools=["mcp__shop__find_customer", "mcp__shop__list_orders", "mcp__shop__get_order"],
    can_use_tool=approve,                                  # everything not pre-allowed asks here
    max_turns=20,
)
async with ClaudeSDKClient(options=options) as client:    # can_use_tool needs a streaming session
    await client.query("I'm ana.silva@example.com. Refund the trekking poles, please.")
    async for message in client.receive_response():
        if isinstance(message, ResultMessage):
            print(message.result, message.total_cost_usd)

MCP tool names are namespaced mcp__<server>__<tool>. Permission evaluation runs in layers: hooks first, then deny rules (disallowed_tools), the permission mode and allow rules (allowed_tools), and finally can_use_tool for anything still undecided.

Permission modeBehaviour
defaultUnmatched tools go to can_use_tool
acceptEditsFile edits are auto-approved
planRead-only planning: no edits or commands run
dontAskAnything not pre-approved is denied instead of asking
autoA classifier approves or denies each call
bypassPermissionsEverything runs - only inside a sandbox you trust

Hooks

Hooks are callbacks at points in the loop: PreToolUse, PostToolUse, PostToolUseFailure, UserPromptSubmit, Stop, SubagentStart, SubagentStop, PreCompact, PermissionRequest, Notification. A PreToolUse hook can deny a call before permissions are even considered - policy in code:

from claude_agent_sdk import HookMatcher

async def no_force_push(input_data, tool_use_id, context):
    command = input_data["tool_input"].get("command", "")
    if "git push" in command and ("--force" in command or " -f" in command):
        return {"hookSpecificOutput": {"hookEventName": "PreToolUse",
                                       "permissionDecision": "deny",
                                       "permissionDecisionReason": "Force-push is not allowed."}}
    return {}

options = ClaudeAgentOptions(hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[no_force_push])]})

Use PostToolUse for audit logs, Stop to run a verification step before the agent may finish, and PreCompact to save the transcript before context is compacted.

Subagents, Skills and Settings

  • Subagents (agents={"reviewer": AgentDefinition(description=..., prompt=..., tools=["Read", "Grep"], model=...)}) run in their own context window with their own tools, and return a summary to the main agent - the context-isolation pattern from Multi-Agent Engineering.
  • Skills are folders with a SKILL.md (instructions, scripts, resources) that the agent loads on demand; see Agent Engineering.
  • Settings: setting_sources=["project"] loads CLAUDE.md, .claude/settings.json, project skills and slash commands, so an SDK agent behaves like Claude Code in that repository. Omit it for a fully programmatic agent.
  • Sessions can be resumed (resume=session_id) or forked; max_budget_usd caps spend; sandbox settings restrict commands' file-system and network access.

Check Yourself

Check yourself
0 / 4 answered
  1. What does the Claude Agent SDK provide that the Messages API Tool Runner does not?
  2. What name does the model see for tool refund_item on in-process MCP server 'shop'?
  3. Which runs first when the agent tries to run a Bash command?
  4. Why is bypassPermissions acceptable only in a sandbox?

Exercises

Exercise - A read-only code reviewer

Configure a Claude Agent SDK agent that reviews the current repository's diff and writes nothing: only Read, Grep, Glob and a Bash hook that allows git diff and git log but denies everything else. Test it by asking it to fix a bug.

Solution

tools=["Read", "Grep", "Glob", "Bash"], permission_mode="dontAsk", allowed_tools=["Read", "Grep", "Glob"], and a PreToolUse hook on Bash that returns permissionDecision "allow" only when the command starts with "git diff" or "git log" and "deny" otherwise. Asked to fix a bug, it can explain the fix but every write or other command is denied.

Exercise - Verify before stopping

Add a Stop hook that runs the project's tests when the agent tries to finish and, if they fail, blocks the stop with the failing output as the reason. Why is this stronger than telling the agent "always run the tests"?

Solution

The Stop hook runs the tests itself; on failure it returns a decision to block with the test output as the reason, so the agent continues with real feedback. A prompt instruction can be skipped or claimed without being done (the claimed-action failure from Lab 13); the hook makes verification a property of the harness.

Study Notes

  • The Claude Code harness as a library: built-in tools, compaction, permissions, hooks, subagents, skills, sessions; Claude models only; drives a local Claude Code process
  • Custom tools via in-process MCP (@tool, create_sdk_mcp_server); names mcp__server__tool
  • Permission order: hooks -> deny rules -> permission mode and allow rules -> can_use_tool
  • Hooks for policy (PreToolUse deny), audit (PostToolUse), verification (Stop)
  • setting_sources loads CLAUDE.md and project settings; max_budget_usd, sandbox, session resume/fork

References

Last reviewed: 2026-09

โšกAI-assisted content - always verify, always explore multiple perspectivesยท