The ClaudeSDKClient provides full control over interactive conversations with Claude. Unlike the one-shot query() function, the client maintains conversation state and allows bidirectional communication.
The simplest way to use ClaudeSDKClient is with an async context manager:
import asynciofrom claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlockasync def main(): async with ClaudeSDKClient() as client: # Send a query await client.query("What is 2+2?") # Receive the response async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}")asyncio.run(main())
The client maintains conversation context across multiple exchanges:
import asynciofrom claude_agent_sdk import ( ClaudeSDKClient, AssistantMessage, ResultMessage, TextBlock)async def multi_turn_example(): async with ClaudeSDKClient() as client: # First turn print("User: What's the capital of France?") await client.query("What's the capital of France?") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}") # Second turn - Claude remembers the context print("\nUser: What's the population of that city?") await client.query("What's the population of that city?") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}")asyncio.run(multi_turn_example())
Automatically stops after receiving a ResultMessage:
async with ClaudeSDKClient() as client: await client.query("What is Python?") # Receives until ResultMessage, then stops async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): print(msg.content) elif isinstance(msg, ResultMessage): print(f"Cost: ${msg.total_cost_usd:.4f}") # Automatically stops here
Continuous stream - keeps receiving until disconnected:
async with ClaudeSDKClient() as client: # Start receiving in background async def receive_all(): async for msg in client.receive_messages(): print(msg) if isinstance(msg, ResultMessage): # Must manually break if desired break receive_task = asyncio.create_task(receive_all()) # Send multiple queries await client.query("Question 1") await asyncio.sleep(2) await client.query("Question 2") await receive_task
import asynciofrom claude_agent_sdk import ClaudeSDKClientasync def concurrent_example(): async with ClaudeSDKClient() as client: # Background task to receive all messages async def receive_messages(): async for message in client.receive_messages(): print(f"Received: {type(message).__name__}") # Start receiving in background receive_task = asyncio.create_task(receive_messages()) # Send multiple messages with delays questions = [ "What is 2 + 2?", "What is the square root of 144?", "What is 10% of 80?" ] for question in questions: print(f"Sending: {question}") await client.query(question) await asyncio.sleep(3) # Wait between messages # Wait for responses await asyncio.sleep(2) # Clean up receive_task.cancel() try: await receive_task except asyncio.CancelledError: passasyncio.run(concurrent_example())
Important: Interrupts only work when messages are being actively consumed. You must have a background task receiving messages for interrupts to process.
import asyncioimport contextlibfrom claude_agent_sdk import ClaudeSDKClientasync def interrupt_example(): async with ClaudeSDKClient() as client: # Start a long-running task print("User: Count from 1 to 100 slowly") await client.query( "Count from 1 to 100 slowly, with a brief pause between each number" ) # Create background task to consume messages messages_received = [] async def consume_messages(): async for message in client.receive_response(): messages_received.append(message) # Display progress if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(f"Claude: {block.text[:30]}...") # Start consuming in background consume_task = asyncio.create_task(consume_messages()) # Wait 2 seconds then interrupt await asyncio.sleep(2) print("\n[Sending interrupt...]") await client.interrupt() # Wait for interrupt to process await consume_task # Send new instruction print("\nUser: Never mind, just tell me a joke") await client.query("Never mind, just tell me a quick joke") async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}")asyncio.run(interrupt_example())
async with ClaudeSDKClient() as client: # Start with default permissions await client.query("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 fixes") async for msg in client.receive_response(): print(msg)
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-opus-4-20250514') await client.query("Now implement the solution") async for msg in client.receive_response(): print(msg)
Here’s a complete example of a simple chat application:
import asynciofrom claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, ResultMessage, TextBlock)async def chat_app(): """Simple interactive chat application.""" options = ClaudeAgentOptions( system_prompt="You are a helpful and friendly assistant.", model="claude-sonnet-4-5" ) async with ClaudeSDKClient(options=options) as client: print("Chat started! Type 'quit' to exit.\n") while True: # Get user input (in real app, use async input) user_input = input("You: ") if user_input.lower() == 'quit': print("Goodbye!") break # Send to Claude await client.query(user_input) # Receive and display response async for msg in client.receive_response(): if isinstance(msg, AssistantMessage): for block in msg.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}") elif isinstance(msg, ResultMessage): # Show cost for transparency if msg.total_cost_usd: print(f"[Cost: ${msg.total_cost_usd:.6f}]") print() # Blank line between exchangesif __name__ == "__main__": asyncio.run(chat_app())
Async Runtime Context: 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 maintains a persistent task group that remains active from connect() until disconnect(). Complete all operations within the same async context where the client was connected.