CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-claude-agent-sdk

Python SDK for programmatically interacting with Claude Code, enabling AI-powered automation workflows with support for bidirectional conversations, custom in-process tools, hooks, and fine-grained permission control

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

The risk profile of this skill

This plugin was archived by the owner on Jun 3, 2026

Reason: Retiring all tiles created prior to the transition to plugin support

Overview
Eval results
Files

core-query-interface.mddocs/

Core Query Interface

The core query interface provides two interaction patterns for communicating with Claude: a simple query() function for stateless interactions and a ClaudeSDKClient class for bidirectional streaming with state management.

Capabilities

Simple Query Function

The query() function provides the simplest way to interact with Claude for one-shot or unidirectional streaming queries.

async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None,
    transport: Transport | None = None,
) -> AsyncIterator[Message]:
    """
    One-shot or unidirectional streaming query to Claude.

    Args:
        prompt: User message string or async iterable of message dicts
        options: Configuration options (default: ClaudeAgentOptions())
        transport: Custom transport implementation (default: subprocess CLI)

    Returns:
        AsyncIterator yielding Message objects

    Raises:
        CLINotFoundError: When Claude Code CLI is not found
        CLIConnectionError: When unable to connect to CLI
        ProcessError: When CLI process fails
    """

Parameters:

  • prompt (str | AsyncIterable[dict[str, Any]]): The prompt to send to Claude. Can be a string for single-shot queries or an AsyncIterable[dict] for streaming mode with continuous interaction. In streaming mode, each dict should have the structure:

    {
        "type": "user",
        "message": {"role": "user", "content": "..."},
        "parent_tool_use_id": None,
        "session_id": "..."
    }
  • options (ClaudeAgentOptions | None): Configuration options. If None, defaults to ClaudeAgentOptions(). Use this to configure tools, permissions, working directory, models, and more.

  • transport (Transport | None): Custom transport implementation. If provided, this will be used instead of the default subprocess CLI transport. The transport will be automatically configured with the prompt and options.

Returns:

AsyncIterator[Message] that yields messages from the conversation, including:

  • UserMessage: Messages from the user
  • AssistantMessage: Messages from Claude
  • SystemMessage: System-level messages
  • ResultMessage: Final result with cost and usage information
  • StreamEvent: Raw stream events from the API

When to use query():

  • Simple one-off questions ("What is 2+2?")
  • Batch processing of independent prompts
  • Code generation or analysis tasks
  • Automated scripts and CI/CD pipelines
  • When you know all inputs upfront
  • Stateless operations

When to use ClaudeSDKClient instead:

  • Interactive conversations with follow-ups
  • Chat applications or REPL-like interfaces
  • When you need to send messages based on responses
  • When you need interrupt capabilities
  • Long-running sessions with state

Usage Example - Simple Query:

import anyio
from claude_agent_sdk import query, AssistantMessage, TextBlock

async def main():
    # Simple question
    async for message in query(prompt="What is 2 + 2?"):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    print(block.text)

anyio.run(main)

Usage Example - With Options:

from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write", "Bash"],
        permission_mode="acceptEdits",
        cwd="/path/to/project",
        system_prompt="You are an expert Python developer"
    )

    async for message in query(
        prompt="Create a hello.py file that prints Hello World",
        options=options
    ):
        print(message)

anyio.run(main)

Usage Example - Streaming Mode:

async def prompts():
    yield {"type": "user", "message": {"role": "user", "content": "Hello"}}
    yield {"type": "user", "message": {"role": "user", "content": "How are you?"}}

# All prompts are sent, then all responses received
async for message in query(prompt=prompts()):
    print(message)

Interactive Client

The ClaudeSDKClient class provides bidirectional streaming for interactive conversations with full state management.

class ClaudeSDKClient:
    """
    Bidirectional streaming client for interactive conversations.

    Key features:
    - Bidirectional: Send and receive messages at any time
    - Stateful: Maintains conversation context across messages
    - Interactive: Send follow-ups based on responses
    - Control flow: Support for interrupts and session management
    """

    def __init__(
        self,
        options: ClaudeAgentOptions | None = None,
        transport: Transport | None = None,
    ):
        """
        Initialize client.

        Args:
            options: Configuration options (default: ClaudeAgentOptions())
            transport: Custom transport implementation
        """

    async def connect(
        self, prompt: str | AsyncIterable[dict[str, Any]] | None = None
    ) -> None:
        """
        Establish connection and optionally send initial prompt.

        Args:
            prompt: Initial prompt string, message stream, or None for empty connection

        Raises:
            CLIConnectionError: When unable to connect
            ValueError: When can_use_tool is used incorrectly
        """

    async def query(
        self, prompt: str | AsyncIterable[dict[str, Any]], session_id: str = "default"
    ) -> None:
        """
        Send a message to Claude in streaming mode.

        Args:
            prompt: String message or async iterable of message dicts
            session_id: Session identifier for the conversation

        Raises:
            CLIConnectionError: When not connected
        """

    async def receive_messages(self) -> AsyncIterator[Message]:
        """
        Receive all messages until connection closes.

        Yields:
            Message: Each message received from Claude

        Raises:
            CLIConnectionError: When not connected
        """

    async def receive_response(self) -> AsyncIterator[Message]:
        """
        Receive messages until ResultMessage is received.

        This is a convenience method that automatically terminates after
        receiving a ResultMessage, which indicates the response is complete.

        Yields:
            Message: Each message including the final ResultMessage

        Raises:
            CLIConnectionError: When not connected
        """

    async def interrupt(self) -> None:
        """
        Send interrupt signal to stop current operation.

        Only works in streaming mode.

        Raises:
            CLIConnectionError: When not connected
        """

    async def set_permission_mode(self, mode: str) -> None:
        """
        Change permission mode during conversation.

        Args:
            mode: Permission mode ("default", "acceptEdits", "plan", "bypassPermissions")

        Raises:
            CLIConnectionError: When not connected
        """

    async def set_model(self, model: str | None = None) -> None:
        """
        Switch AI model during conversation.

        Args:
            model: Model identifier or None for default

        Raises:
            CLIConnectionError: When not connected
        """

    async def get_server_info(self) -> dict[str, Any] | None:
        """
        Get server capabilities and information.

        Returns:
            Server info dict with commands and capabilities, or None

        Raises:
            CLIConnectionError: When not connected
        """

    async def disconnect(self) -> None:
        """
        Close connection and clean up resources.
        """

    async def __aenter__(self) -> ClaudeSDKClient:
        """Context manager entry - automatically connects."""

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> bool:
        """Context manager exit - automatically disconnects."""

Usage Example - Basic Interactive Session:

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Write"],
        permission_mode="acceptEdits"
    )

    async with ClaudeSDKClient(options=options) as client:
        # First query
        await client.query("Create a todo.txt file")
        async for msg in client.receive_response():
            print(msg)

        # Follow-up query in same session
        await client.query("Add 'Buy groceries' to the file")
        async for msg in client.receive_response():
            print(msg)

anyio.run(main)

Usage Example - Dynamic Permission Changes:

async with ClaudeSDKClient() as client:
    # Start with default permissions
    await client.query("Help me analyze this codebase")
    async for msg in client.receive_response():
        print(msg)

    # Switch to auto-accept edits
    await client.set_permission_mode('acceptEdits')
    await client.query("Now implement the fix we discussed")
    async for msg in client.receive_response():
        print(msg)

Usage Example - Interrupt Support:

async with ClaudeSDKClient() as client:
    await client.query("Generate 1000 test files")

    # Receive some messages
    count = 0
    async for msg in client.receive_messages():
        print(msg)
        count += 1
        if count > 5:
            # Stop the operation
            await client.interrupt()
            break

Usage Example - Model Switching:

async with ClaudeSDKClient() as client:
    # Start with default model
    await client.query("Help me understand this problem")
    async for msg in client.receive_response():
        print(msg)

    # Switch to a different model
    await client.set_model('claude-sonnet-4-5')
    await client.query("Now implement the solution")
    async for msg in client.receive_response():
        print(msg)

Important Notes:

  • As of v0.0.20, you cannot use a ClaudeSDKClient instance across different async runtime contexts (e.g., different trio nurseries or asyncio task groups)
  • The client internally maintains a persistent anyio task group that remains active from connect() until disconnect()
  • Complete all operations within the same async context where the client was connected
  • Always use the context manager (async with) or manually call disconnect() to clean up resources

Transport Interface

The Transport class provides an abstract interface for custom transport implementations.

class Transport:
    """
    Abstract transport interface for custom implementations.

    Note: This is an internal API that may change in future releases.
    """

    async def connect(self) -> None:
        """Establish connection to Claude Code."""

    async def write(self, data: str) -> None:
        """Write data to the transport."""

    def read_messages(self) -> AsyncIterator[dict[str, Any]]:
        """Read messages stream from the transport."""

    async def close(self) -> None:
        """Close connection and clean up resources."""

    def is_ready(self) -> bool:
        """Check if transport is ready for communication."""

    async def end_input(self) -> None:
        """Signal end of input stream."""

Usage Example - Custom Transport:

from claude_agent_sdk import Transport, query

class MyCustomTransport(Transport):
    async def connect(self) -> None:
        # Custom connection logic
        pass

    async def write(self, data: str) -> None:
        # Custom write logic
        pass

    def read_messages(self) -> AsyncIterator[dict[str, Any]]:
        # Custom read logic
        pass

    async def close(self) -> None:
        # Custom cleanup logic
        pass

    def is_ready(self) -> bool:
        return True

    async def end_input(self) -> None:
        pass

# Use custom transport
transport = MyCustomTransport()
async for message in query(prompt="Hello", transport=transport):
    print(message)

Comparison: query() vs ClaudeSDKClient

Featurequery()ClaudeSDKClient
CommunicationUnidirectionalBidirectional
StateStatelessStateful
Use caseOne-shot queriesMulti-turn conversations
InterruptsNoYes
Dynamic configNoYes (permissions, model)
Follow-upsNoYes
ComplexitySimpleFull-featured
Context managerNoYes

Choose query() for simple, stateless interactions. Choose ClaudeSDKClient for interactive, stateful conversations with full control flow.

docs

agent-definitions.md

agents.md

client.md

configuration-options.md

content-blocks.md

core-query-interface.md

custom-tools.md

error-handling.md

errors.md

hook-system.md

hooks.md

index.md

mcp-config.md

mcp-server-configuration.md

messages-and-content.md

messages.md

options.md

permission-control.md

permissions.md

query.md

transport.md

COMPLETION_SUMMARY.md

tile.json