Skip to content
Blog

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

  1. Compile-Time & IDE Type Checking: Developers receive auto-complete and static type checking across all agent state references.
  2. Runtime Data Validation: Invalid data types or missing fields are caught immediately at the boundary before model inference begins.
  3. 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.