plugins/devops/python-tools/skills/claude-agent-sdk/SKILL.md
Builds production-ready applications with the Claude Agent SDK for Python, covering orchestrators with subagents, programmatic agent configuration, hooks, and permissions.
npx skillsauth add basher83/lunar-claude claude-agent-sdkInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build 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 patternstesting
Audit and improve CLAUDE.md files in repositories. Use when user asks to check, audit, update, improve, or fix CLAUDE.md files. Scans for all CLAUDE.md files, evaluates quality against templates, outputs quality report, then makes targeted updates. Also use when the user mentions "CLAUDE.md maintenance" or "project memory optimization".
tools
Operational tooling for Talos Linux Kubernetes clusters via Sidero Omni with Proxmox infrastructure provider, covering machine classes, CEL storage selectors, and provider lifecycle management.
tools
Best practices for git workflow automation including atomic commits, branch naming, conventional commit format, and changelog generation.
tools
Summarize the current state of the git repository