Handler Overview
Your agent’s logic is defined in a Handler class in handle_query.py. The handler receives user messages and yields responses using the Claude Agent SDK.
Handler Structure
The handler implements a single async method:
from typing import AsyncIterator, Any
from claude_agent_sdk import ClaudeAgentOptions, query, Message
from sb0_runner.logging_handler import get_logger
logger = get_logger(__name__)
class Handler:
def __init__(self) -> None:
"""Initialize handler.
Called once when the handler is loaded.
Use this to initialize shared resources like API clients or DB connections.
"""
logger.info("Handler initialized")
async def handle_query(
self,
prompt: str,
session_id: str | None = None,
**kwargs: Any,
) -> AsyncIterator[Message]:
"""Stream Claude responses.
Args:
prompt: The user's input prompt.
session_id: Optional session ID for conversation continuity.
**kwargs: Additional parameters from the request, which may include:
- ANTHROPIC_API_KEY (str): API key from environment
- debug (bool): Enable debug log output
- Any custom fields from your middleware
Yields:
Message: Claude SDK message objects.
"""
logger.info("Processing query, session_id=%s", session_id)
opts = ClaudeAgentOptions(
include_partial_messages=True,
model="claude-haiku-4-5"
)
if session_id:
opts.resume = session_id
logger.debug("Resuming session: %s", session_id)
async for message in query(prompt=prompt, options=opts):
logger.debug("Yielding message type=%s", type(message).__name__)
yield message
logger.info("Query completed")
Key Components
The handle_query Method
prompt (str) - The user’s input message
session_id (str | None) - Optional session ID for conversation continuity
kwargs (Any) - Additional parameters from the request
- Returns - AsyncIterator[Message] that yields Claude SDK messages
Claude Agent Options
Configure the Claude Agent SDK behavior:
include_partial_messages (bool) - Stream partial responses as they’re generated
model (str) - Claude model to use (e.g., “claude-haiku-4-5”, “claude-sonnet-4”)
resume (str) - Session ID to resume a previous conversation
Structured Logging
The handler includes a structured logging system:
from sb0_runner.logging_handler import get_logger
logger = get_logger(__name__)
Log Levels
logger.debug() - Development debugging, verbose information
logger.info() - Important operational events
logger.warning() - Warning messages
logger.error() - Error messages
Viewing Logs
By default, only yielded messages reach clients. To see logs during development, enable debug mode:
# When running locally
sb0 run --prompt "test" --extras '{"debug": true}'
Or when calling the API:
{
"parts": [{"type": "text", "text": "Hello"}],
"kwargs": {"debug": true}
}
Print statements are captured as DEBUG logs. Use logger.debug() for cleaner structured logging.
Customizing Your Handler
Extend the handler to add custom functionality:
Example: Custom Preprocessing
class Handler:
async def handle_query(
self,
prompt: str,
session_id: str | None = None,
**kwargs: Any,
) -> AsyncIterator[Message]:
# Preprocess the prompt
processed_prompt = self._preprocess(prompt)
opts = ClaudeAgentOptions(include_partial_messages=True)
if session_id:
opts.resume = session_id
async for message in query(prompt=processed_prompt, options=opts):
yield message
def _preprocess(self, prompt: str) -> str:
"""Custom preprocessing logic"""
return prompt.strip().lower()
Example: External API Integration
import httpx
class Handler:
def __init__(self) -> None:
self.client = httpx.AsyncClient()
async def handle_query(
self,
prompt: str,
session_id: str | None = None,
**kwargs: Any,
) -> AsyncIterator[Message]:
# Fetch external data
response = await self.client.get("https://api.example.com/data")
context = response.json()
# Include context in prompt
enhanced_prompt = f"{prompt}\n\nContext: {context}"
opts = ClaudeAgentOptions(include_partial_messages=True)
if session_id:
opts.resume = session_id
async for message in query(prompt=enhanced_prompt, options=opts):
yield message
Next Steps