Typed Handoffs

Schema-validated agent handoffs with Pydantic — catch payload mismatches at the boundary instead of as garbled LLM output

Schema-validated handoffs ensure data integrity between agents using Pydantic models, raising validation errors immediately instead of producing incorrect LLM output.

python
from pydantic import BaseModel
from praisonaiagents import Agent, TypedHandoff

class Brief(BaseModel):
    topic: str
    summary: str

writer = Agent(name="Writer", instructions="Write from the validated brief.")
researcher = Agent(name="Researcher", instructions="Research and hand off structured results.")
handoff = TypedHandoff(agent=writer, input_schema=Brief)
handoff.execute_programmatic(researcher, Brief(topic="AI safety", summary="Key risks and mitigations"))

The user receives polished content; the researcher hands off a schema-validated brief before the writer runs.

Diagram

Typed Handoff

Valid

Invalid

📤 Source Agent

🛡️ Validate Schema

📦 JSON Payload

🤖 Target Agent

❌ HandoffValidationError

Quick Start

  1. Define Schema + Create TypedHandoff
    python
    from pydantic import BaseModel
    from praisonaiagents import Agent, TypedHandoff
    
    class ResearchResult(BaseModel):
        summary: str
        citations: list[str]
        confidence: float
    
    writer = Agent(name="Writer", instructions="Write articles from research.")
    researcher = Agent(name="Researcher", instructions="Research topics.")
    
    handoff = TypedHandoff(agent=writer, input_schema=ResearchResult)
  2. Execute with Validated Payload
    python
    payload = ResearchResult(
        summary="AI safety research findings",
        citations=["paper-1", "paper-2"],
        confidence=0.92,
    )
    result = handoff.execute_programmatic(researcher, payload)
    print(result.response)
  3. Invalid Payload Raises Immediately
    python
    from praisonaiagents import HandoffValidationError
    
    try:
        handoff.execute_programmatic(researcher, {"summary": "x", "citations": "not-a-list"})
    except HandoffValidationError as e:
        print(e.validation_errors)

How It Works

Diagram
Target AgentPydantic ValidatorTypedHandoffSource AgentTarget AgentPydantic ValidatorTypedHandoffSource Agentalt[valid][invalid]execute_programmatic(payload)validate against input_schemavalidated modelchat(JSON payload)responseHandoffResult(success=True)ValidationErrorraise HandoffValidationError
StepDescription
ValidationPayload validated against Pydantic schema
SerializationValid payload converted to structured JSON
HandoffTarget agent receives formatted JSON context
Error HandlingSchema mismatches raise HandoffValidationError

Configuration

ParameterTypeDefaultDescription
agentAgentRequiredTarget agent to hand off to
input_schemaType[BaseModel]RequiredPydantic model class for validation
tool_name_overridestrNoneCustom tool name
tool_description_overridestrNoneCustom tool description
on_handoffCallableNoneCallback when handoff starts
input_filterCallableNoneFunction to filter input data
configHandoffConfigNoneAdvanced handoff configuration

Accepted Payload Types

Payload typeBehaviour
Instance of input_schemaRe-validated via model_dump()
dictValidated directly
Any Pydantic model with model_dump()Converted to dict, validated
Object with __dict__Vars extracted, validated
strBypasses validation — falls back to plain Handoff (backward compatibility)

Common Patterns

Research → Writer Pipeline

python
from pydantic import BaseModel
from praisonaiagents import Agent, TypedHandoff

class ResearchData(BaseModel):
    topic: str
    findings: list[str]
    sources: list[str]
    confidence_score: float

research_agent = Agent(name="Researcher", instructions="Research topics thoroughly")
writer_agent = Agent(name="Writer", instructions="Write based on research data")

typed_handoff = TypedHandoff(agent=writer_agent, input_schema=ResearchData)

# Execute with validated data
research_result = ResearchData(
    topic="AI Ethics",
    findings=["Finding 1", "Finding 2"],
    sources=["source1.com", "source2.org"],
    confidence_score=0.85
)
result = typed_handoff.execute_programmatic(research_agent, research_result)

Customer Info Pipeline

python
class CustomerRequest(BaseModel):
    customer_id: str
    issue_type: str
    priority: int
    description: str

support_agent = Agent(name="Support", instructions="Handle customer requests")
specialist_agent = Agent(name="Specialist", instructions="Resolve complex issues")

customer_handoff = TypedHandoff(agent=specialist_agent, input_schema=CustomerRequest)

Async Typed Handoffs

python
async def process_requests():
    result = await typed_handoff.execute_async(source_agent, validated_payload)
    return result

Combining with HandoffConfig

python
from praisonaiagents import HandoffConfig

config = HandoffConfig(timeout_seconds=30, detect_cycles=True)
typed_handoff = TypedHandoff(
    agent=target_agent,
    input_schema=MySchema,
    config=config
)

Receiving agent: input_payload_schema

Use input_payload_schema on the receiving agent to declare the Pydantic model it expects as incoming payload. This ensures that any agent sending data to this agent produces a payload that matches the declared schema — mismatches raise HandoffValidationError at the boundary.

python
from pydantic import BaseModel
from praisonaiagents import Agent

class DeployPayload(BaseModel):
    service: str
    version: str

receiver = Agent(
    name="Deployer",
    instructions="Deploy the given service and version.",
    input_payload_schema=DeployPayload,
)

When the sending agent calls TypedHandoff(agent=receiver, input_schema=DeployPayload).execute_programmatic(...) with an invalid payload, HandoffValidationError is raised:

python
from praisonaiagents import HandoffValidationError

try:
    handoff.execute_programmatic(source_agent, {"service": "api"})  # missing 'version'
except HandoffValidationError as e:
    print(e.validation_errors)

Best Practices

Keep schemas small

Only include fields the receiving agent actually needs. Large schemas increase validation overhead and complexity.

Wrap execute_programmatic in try/except

Always catch HandoffValidationError at orchestration boundaries to handle schema mismatches gracefully:

python
try:
    result = typed_handoff.execute_programmatic(source, payload)
except HandoffValidationError as e:
    logger.error(f"Schema validation failed: {e.validation_errors}")
Inspect validation_errors for remediation

Use e.validation_errors to surface field-level issues to users:

python
except HandoffValidationError as e:
    for error in e.validation_errors:
        print(f"Field error: {error}")
Prefer typed handoffs for multi-field contracts

Use typed handoffs when passing more than 2 fields between agents. The validation cost is much lower than debugging wrong LLM output.

Handle Pydantic dependency

TypedHandoff requires Pydantic. Document the requirement:

bash
pip install pydantic

Instantiating without Pydantic raises ImportError.


Related