This skill should be used when building applications with the Claude Agent SDK (Python). Use for creating orchestrators with subagents, configuring agents programmatically, setting up hooks and permissions, and following SDK best practices. Trigger when implementing agentic workflows, multi-agent systems, or SDK-based automation.
Build production-ready multi-agent applications using the Claude Agent SDK for Python. Use when creating orchestrators with subagents, configuring programmatic agents, or implementing SDK hooks and permissions.
/plugin marketplace add basher83/lunar-claude/plugin install python-tools@lunar-claudeThis skill inherits all available tools. When active, it can use any tool Claude has access to.
assets/sdk-template.pyassets/sdk-validation-checklist.mdexamples/agents.pyexamples/basic-orchestrator.pyexamples/hooks.pyexamples/mcp_calculator.pyexamples/plugin_example.pyexamples/quick_start.pyexamples/setting_sources.pyexamples/streaming_mode.pyexamples/system_prompt.pyexamples/tool_permission_callback.pyreferences/agent-patterns.mdreferences/api-reference.mdreferences/best-practices.mdreferences/custom-tools.mdreferences/hooks-guide.mdreferences/sessions.mdreferences/skills.mdreferences/slash-commands.mdBuild production-ready applications using the Claude Agent SDK for Python.
SDK Version: This skill targets claude-agent-sdk>=0.1.6 (Python)
This skill provides patterns, examples, and best practices for building SDK applications that orchestrate Claude agents.
Copy the template and customize:
cp assets/sdk-template.py my-app.py
# Edit my-app.py - customize agents and workflow
chmod +x my-app.py
./my-app.py
The template includes proper uv script headers, agent definitions, and async patterns.
The SDK provides two ways to interact with Claude: the query() function for simple one-shot tasks, and ClaudeSDKClient for continuous conversations.
| Feature | query() | ClaudeSDKClient |
|---|---|---|
| Conversation memory | No - each call is independent | Yes - maintains context across queries |
| Use case | One-off tasks, single questions | Multi-turn conversations, complex workflows |
| Complexity | Simple - one function call | More setup - context manager pattern |
| Hooks support | No | Yes |
| Custom tools | No | Yes |
| Interrupts | No | Yes - can interrupt ongoing operations |
| Session control | New session each time | Single persistent session |
Important: Hooks and custom tools (SDK MCP servers) are only supported with
ClaudeSDKClient, not withquery(). If you need hooks or custom tools, you must useClaudeSDKClient.Note on Async Runtimes: The SDK works with both
asyncioandanyio. The official SDK examples preferanyio.run()for better async library compatibility, butasyncio.run()works equally well. Use whichever fits your project's async runtime.
Use query() for simple, independent tasks where you don't need conversation history:
import anyio # or: import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def analyze_file():
"""One-shot file analysis - no conversation needed."""
options = ClaudeAgentOptions(
system_prompt="You are a code analyzer",
allowed_tools=["Read", "Grep", "Glob"],
permission_mode="acceptEdits"
)
async for message in query(
prompt="Analyze /path/to/file.py for bugs",
options=options
):
print(message)
anyio.run(analyze_file) # or: asyncio.run(analyze_file())
Best for:
Key limitation: Each query() call creates a new session with no memory of previous calls.
Use ClaudeSDKClient when you need conversation context across multiple interactions:
import anyio # or: import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
async def interactive_debugging():
"""Multi-turn debugging conversation with context."""
options = ClaudeAgentOptions(
system_prompt="You are a debugging assistant",
allowed_tools=["Read", "Grep", "Bash"],
permission_mode="acceptEdits"
)
async with ClaudeSDKClient(options=options) as client:
# First query
await client.query("Find all TODO comments in /path/to/project")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
# Follow-up - Claude remembers the TODOs found above
await client.query("Now prioritize them by complexity")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
# Another follow-up - still in same conversation
await client.query("Create a plan to address the top 3")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
anyio.run(interactive_debugging) # or: asyncio.run(interactive_debugging())
Best for:
Key advantage: Claude remembers all previous queries and responses in the session.
See: examples/streaming_mode.py - Comprehensive ClaudeSDKClient examples with all patterns
Only ClaudeSDKClient supports interrupting ongoing operations:
import anyio # or: import asyncio
from claude_agent_sdk import ClaudeSDKClient
async def interruptible_task():
async with ClaudeSDKClient() as client:
await client.query("Run a long analysis on /large/codebase")
# Start processing in background
async with anyio.create_task_group() as tg:
tg.start_soon(process_messages, client)
# Simulate user interrupt after 5 seconds
await anyio.sleep(5)
await client.interrupt()
async def process_messages(client):
async for message in client.receive_response():
print(message)
anyio.run(interruptible_task) # or: asyncio.run(interruptible_task())
Use query() if:
Use ClaudeSDKClient if:
Define a main orchestrator that delegates work to specialized subagents.
Critical requirements:
system_prompt={"type": "preset", "preset": "claude_code"} (provides Task tool knowledge)agents={} parameter (SDK best practice)"Task" in allowed_toolsExample:
from claude_agent_sdk import AgentDefinition, ClaudeAgentOptions
options = ClaudeAgentOptions(
system_prompt={"type": "preset", "preset": "claude_code"}, # REQUIRED for orchestrators
allowed_tools=["Bash", "Task", "Read", "Write"],
agents={
"analyzer": AgentDefinition(
description="Analyzes code structure and patterns",
prompt="You are a code analyzer...",
tools=["Read", "Grep", "Glob"],
model="sonnet"
),
"fixer": AgentDefinition(
description="Fixes identified issues",
prompt="You are a code fixer...",
tools=["Read", "Edit", "Bash"],
model="sonnet"
)
},
permission_mode="acceptEdits",
model="claude-sonnet-4-5"
)
See:
references/agent-patterns.md - Complete agent definition patternsexamples/agents.py - Official SDK agent examples with different agent typesChoose the appropriate system prompt pattern:
# Orchestrator (use claude_code preset) - dict format (official examples prefer this)
system_prompt={"type": "preset", "preset": "claude_code"}
# Shorthand format (equivalent, but less explicit)
system_prompt="claude_code"
# Custom behavior
system_prompt="You are a Python expert..."
# Extend preset with additional instructions
system_prompt={
"type": "preset",
"preset": "claude_code",
"append": "Additional domain-specific instructions"
}
Note: The shorthand system_prompt="claude_code" is equivalent to {"type": "preset", "preset": "claude_code"}. Both are valid. Official examples prefer the dict format for explicitness.
See:
references/system-prompts.md - Complete system prompt documentationexamples/system_prompt.py - Official SDK system prompt examplesLimit subagent tools to minimum needed:
# Read-only analyzer
tools=["Read", "Grep", "Glob"]
# Code modifier
tools=["Read", "Edit", "Bash"]
# Test runner
tools=["Bash", "Read"]
See: references/agent-patterns.md for common tool combinations
Intercept SDK events to control behavior:
from claude_agent_sdk import HookMatcher
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash", hooks=[check_bash_command])
],
"PostToolUse": [
HookMatcher(matcher="Bash", hooks=[review_output])
]
}
)
See:
references/hooks-guide.md - Complete hook patterns documentationexamples/hooks.py - Official SDK hook examples with all hook typesFine-grained control over tool usage:
async def permission_callback(tool_name, input_data, context):
# Allow read operations
if tool_name in ["Read", "Grep", "Glob"]:
return PermissionResultAllow()
# Block dangerous commands
if tool_name == "Bash" and "rm -rf" in input_data.get("command", ""):
return PermissionResultDeny(message="Dangerous command")
return PermissionResultAllow()
options = ClaudeAgentOptions(
can_use_tool=permission_callback,
permission_mode="default"
)
See:
references/tool-permissions.md - Complete permission patterns and decision guideexamples/tool_permission_callback.py - Official SDK permission callback exampleFollow these steps to build an effective orchestrator:
1. Define agent purposes
2. Create agent definitions
agents={
"agent-name": AgentDefinition(
description="When to use this agent",
prompt="Agent's role and behavior",
tools=["Tool1", "Tool2"],
model="sonnet"
)
}
3. Configure orchestrator
options = ClaudeAgentOptions(
system_prompt={"type": "preset", "preset": "claude_code"}, # CRITICAL
allowed_tools=["Bash", "Task", "Read", "Write"],
agents=agents,
permission_mode="acceptEdits"
)
4. Implement workflow
async with ClaudeSDKClient(options=options) as client:
await client.query("Use 'agent-name' to perform task")
async for message in client.receive_response():
# Process responses
pass
See: examples/basic-orchestrator.py for complete working example
While programmatic registration is recommended, agent content can be stored in markdown files:
import yaml
def load_agent_definition(path: str) -> AgentDefinition:
"""Load agent from markdown file with YAML frontmatter."""
with open(path) as f:
content = f.read()
parts = content.split("---")
frontmatter = yaml.safe_load(parts[1])
prompt = parts[2].strip()
# Parse tools (comma-separated string or array)
tools = frontmatter.get("tools", [])
if isinstance(tools, str):
tools = [t.strip() for t in tools.split(",")]
return AgentDefinition(
description=frontmatter["description"],
prompt=prompt,
tools=tools,
model=frontmatter.get("model", "inherit")
)
# Load and register programmatically
agent = load_agent_definition(".claude/agents/my-agent.md")
options = ClaudeAgentOptions(agents={"my-agent": agent})
See: references/agent-patterns.md for complete loading pattern
Avoid these common mistakes:
❌ Missing orchestrator system prompt
# Orchestrator won't know how to use Task tool
options = ClaudeAgentOptions(agents={...})
✅ Correct orchestrator configuration
options = ClaudeAgentOptions(
system_prompt="claude_code",
agents={...}
)
❌ Mismatched agent names
agents={"investigator": AgentDefinition(...)}
await client.query("Use 'markdown-investigator'...") # Wrong name
✅ Exact name matching
agents={"investigator": AgentDefinition(...)}
await client.query("Use 'investigator'...") # Matches
❌ Tool/prompt mismatch
system_prompt="Fix bugs you find"
allowed_tools=["Read", "Grep"] # Can't fix, only read
✅ Aligned tools and behavior
system_prompt="Analyze code for bugs"
allowed_tools=["Read", "Grep", "Glob"]
See: references/best-practices.md for complete anti-patterns list
In-depth documentation loaded as needed:
api-reference.md - Complete Python SDK API reference (types, functions, examples)agent-patterns.md - Agent definition patterns, tool restrictions, best practicessubagents.md - Comprehensive subagent patterns and SDK integrationsystem-prompts.md - System prompt configuration (preset, custom, append)hooks-guide.md - Hook patterns for all hook types with examplestool-permissions.md - Permission callback patterns and examplesbest-practices.md - SDK best practices, anti-patterns, debugging tipscustom-tools.md - Creating custom tools with SDK MCP servers (Python-only)sessions.md - Session management and resumption patterns (Python-only)skills.md - Using Agent Skills with the SDK (Python-only)slash-commands.md - Slash commands and custom command creation (Python-only)Ready-to-run code examples from official SDK:
Getting Started:
quick_start.py - Basic query() usage and message handling (start here!)basic-orchestrator.py - Complete orchestrator with analyzer and fixer subagentsCore Patterns:
agents.py - Programmatic agent definitions with different agent typeshooks.py - Comprehensive hook patterns (PreToolUse, PostToolUse, UserPromptSubmit, etc.)system_prompt.py - System prompt patterns (preset, custom, append)streaming_mode.py - Complete ClaudeSDKClient patterns with multi-turn conversationsAdvanced Features:
mcp_calculator.py - Custom tools with SDK MCP server (in-process tool server)tool_permission_callback.py - Permission callbacks with logging and controlsetting_sources.py - Settings isolation and loading (user/project/local)plugin_example.py - Using plugins with the SDK (relevant for plugin marketplace!)Templates and validation tools:
sdk-template.py - Project template with uv script headers and agent structuresdk-validation-checklist.md - Comprehensive checklist for validating SDK applications against best practicesUse this skill when:
assets/sdk-validation-checklist.md)Do not use for:
examples/quick_start.py - Learn basic query() usageassets/sdk-template.py - Template for new projectsexamples/basic-orchestrator.py - See orchestrator patternexamples/agents.py - Agent definitionsexamples/system_prompt.py - System prompt patternsexamples/streaming_mode.py - Multi-turn conversationsexamples/hooks.py - Hook patternsexamples/tool_permission_callback.py - Permission controlexamples/mcp_calculator.py - Custom toolsexamples/setting_sources.py - Settings managementexamples/plugin_example.py - Plugin integrationassets/sdk-validation-checklist.mdreferences/best-practices.mdreferences/ as needed for detailed patternsThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.