Introduction

Welcome back to Agent SDK! In the first unit, we explored the fundamentals of the Agent SDK and learned how to build basic agents using the query function. We saw how the SDK gives us programmatic control over Claude's capabilities, allowing us to create automated workflows that go beyond simple command-line scripts.

In this second unit, we'll dive deeper into the Python SDK's features and patterns. We'll explore two different approaches to using the SDK: simple one-off queries versus maintaining continuous conversations. We'll examine the complete toolkit of built-in capabilities, learn how to configure permissions for different security levels, and master error handling to build robust automation. By the end of this lesson, we'll have the knowledge to build sophisticated agents that handle complex, multi-turn interactions safely and reliably.

Two Approaches to SDK Usage

The Python SDK offers two distinct patterns for interacting with Claude, each suited to different automation scenarios. Understanding when to use each approach is crucial for building effective agents.

The first approach uses the query() function, which we explored in the previous unit. This pattern creates a new session for each call, making it ideal for independent, one-off tasks. Think of it as running claude -p from within Python: each query is self-contained and doesn't remember previous interactions.

The second approach uses the ClaudeSDKClient class, which maintains a persistent session across multiple exchanges. This client remembers the entire conversation history, allowing Claude to reference previous context, build on earlier responses, and engage in iterative problem-solving. This pattern is essential for interactive applications and complex workflows that require multiple steps.

Simple Tasks with query()

The query() function excels at straightforward automation where each task is independent. When we call query(), the SDK creates a fresh session, processes our prompt, and streams back the results:

This pattern works perfectly when we need Claude to perform a specific task without requiring follow-up questions or building on previous work. Each call to query() is isolated, making it predictable and easy to reason about. Notice how we filter for messages with a result attribute, which contains Claude's final answer after all tool use is complete.

Continuous Conversations with ClaudeSDKClient

For tasks requiring multiple turns of interaction, we use ClaudeSDKClient. This class manages a persistent session that maintains a full conversation history:

The client is used as a context manager with async with, which ensures the proper cleanup of resources when we're done. We call client.query() to send a prompt, then iterate over client.receive_response() to receive the streaming results. The key difference is that this session persists, allowing subsequent queries to reference what came before.

Building on Context

The real power of ClaudeSDKClient becomes clear when we make multiple related queries in the same session:

Notice how we can refer to "that file" in the second query; Claude remembers we just read docs/intro.md. In the third query, we simply say "Fix them," and Claude understands we mean the broken links from the previous exchange. This conversational context makes iterative workflows natural and efficient, without needing to repeat information in every prompt.

Available Built-in Tools

The SDK provides a comprehensive set of built-in tools that Claude can use to interact with the environment. By default, all tools are available unless we explicitly block them using the disallowed_tools option.

File operations include:

  • Read: Reads file contents from the workspace
  • Write: Creates or completely overwrites files
  • Edit: Makes targeted modifications to existing files

Execution capabilities:

  • Bash: Runs shell commands in the workspace

Search and discovery tools:

  • Glob: Finds files matching patterns like **/*.py
  • Grep: Searches for text content across files

Web interaction:

  • WebSearch: Performs web searches and returns results
  • WebFetch: Retrieves content from URLs

Special capabilities:

  • Task: Spawns subagents for delegating work
  • AskUserQuestion: Requests clarifying information interactively
Permission Modes Explained

The SDK implements three permission modes that control how tool usage is approved. These modes balance automation convenience with safety, allowing us to choose the right level of control for each agent.

Default Permission Mode

When we don't specify a permission mode, the SDK uses the default interactive mode:

In this mode, Claude can see which tools are available and propose their use, but each tool call triggers a prompt:

This mode is ideal for interactive development sessions where we want to review Claude's actions before they take effect. It prevents unintended modifications while still giving Claude the flexibility to propose solutions.

Auto-Approving Edits

The acceptEdits mode is designed for scenarios where file modifications are expected and safe to automate:

With this configuration, Claude can freely read, write, and edit files without prompting. This works well for automated refactoring, documentation updates, or code generation tasks where file changes are the expected outcome. However, we've blocked Bash in this example, so the SDK would still prompt before executing shell commands if we removed that restriction, providing an extra layer of protection.

Bypassing All Permissions

The most permissive mode is bypassPermissions, which removes all interactive confirmations:

This mode is safe when we carefully restrict the available tools to read-only operations. In this example, we block write and execute capabilities, leaving Claude with only read and search tools. We should only use bypassPermissions with write or execute capabilities in controlled environments: sandboxed containers, disposable workspaces, or trusted automation pipelines with proper oversight.

Tool Restrictions for Safety

The key to safe automation is combining tool restrictions with appropriate permission modes. By blocking dangerous tools with disallowed_tools, we create agents that are both capable and secure:

Each configuration follows the principle of least privilege: we block any tools that aren't necessary for the task. The read-only analyzers can bypass permissions safely because they cannot modify anything. The code editor auto-approves edits because that's its purpose, but we block Bash to prevent unintended command execution.

Error Handling Overview

Robust agents need to handle errors gracefully. The SDK defines four primary exception types that correspond to different failure modes in the agent runtime.

CLINotFoundError occurs when the underlying Claude CLI executable is not installed or not discoverable on the system PATH. This typically happens in new environments or when the CLI hasn't been properly configured.

CLIConnectionError indicates that a connection to Claude Code could not be established. This may occur due to network issues, service availability problems, or configuration errors.

ProcessError indicates that the Claude runtime started but failed during execution. This could be due to authentication issues, model availability problems, timeout limits, or the agent encountering an unrecoverable state.

CLIJSONDecodeError happens when the SDK cannot parse the structured output from the CLI. This suggests a version mismatch between the SDK and CLI, corrupted output, or the CLI producing an unexpected format.

Handling CLI Errors

When the Claude CLI isn't available, we should provide clear guidance to the user:

This error is usually straightforward to resolve: the user needs to install the CLI or ensure it's on their PATH. We should catch this exception early in our application startup to provide immediate feedback rather than failing mysteriously during operation.

Handling Connection Errors

Connection failures require checking network and service availability:

The CLIConnectionError indicates that the CLI is installed but cannot reach the Claude Code service. This may be due to network issues, firewall restrictions, or the service being temporarily unavailable. We should provide guidance on checking connectivity and retrying the operation.

Handling Process Errors

Process failures require more detailed diagnosis:

The ProcessError exception includes the exit_code from the CLI process, which helps identify the type of failure. Common issues include missing API keys, network connectivity problems, or attempting to use a model that's not available to the account. We should log these errors with enough context to debug the underlying issue.

Handling JSON Errors

JSON parsing failures often indicate version incompatibility or output corruption:

When we encounter this error, we should verify that our SDK version is compatible with the installed CLI version. The SDK expects a specific JSON schema from the CLI, and changes in either component can break this contract.

Building a Doc Quality Checker

Let's apply what we've learned to build a practical agent that checks documentation quality. This Doc Quality Checker agent will analyze a documentation file for common issues like broken links, missing alt text, and formatting inconsistencies.

We'll structure this as a command-line tool that takes a file path as an argument and reports quality issues. The agent needs Read access to files, the ability to search for patterns with Grep, and web access via WebFetch to verify links. Since this is a read-only analysis task, we can safely bypass permissions.

Setting Up the Options

First, we configure the agent with appropriate tools and a specialized system_prompt:

The system_prompt gives Claude a clear role and a specific checklist. This helps ensure consistent results across different files. Notice we block potentially dangerous tools (Write, Edit, Bash, WebSearch), leaving Claude with Read, Grep, and WebFetch: exactly what's needed to analyze text and verify links, with no modification capabilities.

Implementing the Check Logic

With options configured, we make the query and extract results:

We iterate through the streamed messages and return the final result. The agent will read the file, search for issues, fetch URLs to verify links, and compile a comprehensive quality report. Using query() here is appropriate because each file check is independent; we don't need conversation history between files.

The Main Function

Finally, we add the command-line interface using sys.argv:

This creates a simple but complete tool. We check for the required argument, call our checking function, and display the results. The agent handles all the complexity of analyzing the file, checking links, and identifying issues; we just provide the interface and error handling.

Conclusion and Next Steps

We've explored the depth of the Python SDK, learning to choose between query() for simple tasks and ClaudeSDKClient for conversations. We examined the complete set of built-in tools, mastered the three permission modes, and learned how to combine tool restrictions with permission settings for safe automation. We also covered comprehensive error handling for the four main failure modes.

Through the documentation quality checker example, we saw how these concepts come together in a practical tool. We configured tools appropriately for a read-only task, used bypassPermissions safely, provided a focused system_prompt, and built a clean command-line interface.

The key insight is that the SDK gives us precise control over what Claude can do and how it operates. By carefully blocking dangerous tools, choosing the right permission mode, and handling errors gracefully, we build agents that are both powerful and safe. Now it's time to get hands-on: the practice exercises ahead will challenge you to build increasingly sophisticated agents, solidifying these patterns and preparing you for the advanced techniques in upcoming units!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal