Sharing State Across Agent Boundaries: Context Contracts
Define type-safe context contracts with PydanticAI to share validated state safely across multi-agent handoffs and boundary transitions.
Published on • September 11, 2026
AI Assistant

When multi-agent systems hand off control between specialized agents—such as transferring a user session from a Customer Support Agent to a Billing Execution Agent—they must share state context. Passing unstructured chat strings or untyped dictionaries leads to key errors, missing parameters, and runtime failures.
To make multi-agent state transfers reliable, developers must define Context Contracts: strongly-typed Pydantic state models that validate session context at every agent boundary transition.
The Hazard of Untyped Context Sharing
Consider a naive state handover dictionary passed between agents:
# Untyped dictionary passed from Agent A to Agent B
state = {
"user_id": "USR-8812",
"amt": "250.00", # String instead of float
"tier": "gold"
}
If Agent B expects state["amount"] as a float, it will raise a KeyError or fail numeric validation inside a payment tool call. Context contracts prevent this by validating and coercing types before the receiving agent executes.
Defining Type-Safe Context Contracts in PydanticAI
PydanticAI enforces typed context dependencies (deps_type) across agent runtimes. By sharing a common Pydantic state contract model, agents guarantee data integrity across handoffs.
from pydantic import BaseModel, Field, EmailStr
from pydantic_ai import Agent, RunContext
from typing import Optional
# 1. Define Master Session Context Contract
class SharedAgentContext(BaseModel):
session_id: str = Field(..., description="Active session UUID")
user_id: str = Field(..., pattern=r"^USR-\d{6}$")
email: EmailStr
account_balance: float = Field(ge=0.0)
authenticated: bool = False
active_intent: Optional[str] = None
# 2. Agent A: Intake / Triage Agent
triage_agent = Agent(
"gemini-1.5-flash",
deps_type=SharedAgentContext,
system_prompt="You triage incoming user requests and update shared context."
)
# 3. Agent B: Billing Agent (Requires authenticated context)
billing_agent = Agent(
"gemini-1.5-pro",
deps_type=SharedAgentContext,
system_prompt="You execute billing operations using validated context dependencies."
)
@billing_agent.system_prompt
def verify_billing_authorization(ctx: RunContext[SharedAgentContext]) -> str:
# Access strongly typed attributes with IDE auto-complete and runtime validation guarantee
if not ctx.deps.authenticated:
raise PermissionError("Billing Agent cannot execute on unauthenticated context.")
return f"Authorized Billing Session for User {ctx.deps.user_id} (Balance: ${ctx.deps.account_balance:.2f})"
Handover Execution Pipeline
def execute_multi_agent_handover():
# 1. Instantiate shared validated context contract
initial_context = SharedAgentContext(
session_id="SESS-9001",
user_id="USR-449102",
email="customer@example.com",
account_balance=150.50,
authenticated=True,
active_intent="view_balance"
)
# 2. Run Triage Agent
triage_result = triage_agent.run_sync(
"Check my current balance",
deps=initial_context
)
print("Triage Output:", triage_result.data)
# 3. Safely pass identical validated context to Billing Agent
billing_result = billing_agent.run_sync(
"Process account statement",
deps=initial_context
)
print("Billing Output:", billing_result.data)
if __name__ == "__main__":
execute_multi_agent_handover()
Benefits of Pydantic Context Contracts
- Compile-Time & IDE Type Checking: Developers receive auto-complete and static type checking across all agent state references.
- Runtime Data Validation: Invalid data types or missing fields are caught immediately at the boundary before model inference begins.
- Seamless Serialization: Pydantic models convert cleanly to JSON (
model.model_dump_json()) for persistence in Redis, Postgres, or message queues during async state handoffs.
For full framework documentation, dependency injection patterns, and model integrations, visit the official PydanticAI Website.