Message Schemas Reference¶
Learning Path: Reference
Difficulty: Intermediate
Prerequisites: Protocol Messages, Message Validation
Completion Time: 45-60 minutes
Navigation¶
← Previous: Message Validation Patterns | Next: Capability Vocabulary →
↑ Up: A2A Overview
🎯 What You'll Learn¶
This reference document provides complete, production-ready schema definitions for all A2A message types:
- Complete JSON Schema definitions (JSON Schema Draft 7)
- Python validation code with Pydantic models
- TypeScript type definitions
- Field-by-field specifications
- Validation patterns and constraints
- Example messages for each type
- Schema versioning guidance
📚 Overview¶
The A2A Protocol uses JSON (JavaScript Object Notation) for all message serialization. This document provides:
- Formal Schemas: JSON Schema definitions for validation
- Type Definitions: TypeScript interfaces for type safety
- Python Models: Pydantic models for runtime validation
- Validation Code: Ready-to-use validators
- Examples: Real message examples from project
Schema Format Standards¶
All schemas in this document conform to: - JSON Schema: Draft 7 (http://json-schema.org/draft-07/schema#) - TypeScript: TypeScript 5.0+ - Python: Python 3.10+ with Pydantic 2.0+
🏗️ Base Message Schema¶
JSON Schema Definition¶
All A2A messages share this base structure:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/base-message.json",
"title": "A2A Base Message",
"description": "Base schema for all A2A protocol messages",
"type": "object",
"required": [
"message_id",
"message_type",
"sender_id",
"recipient_id",
"timestamp",
"payload"
],
"properties": {
"message_id": {
"type": "string",
"description": "Globally unique message identifier",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
"examples": ["a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g"]
},
"message_type": {
"type": "string",
"description": "Type of message being sent",
"enum": [
"request",
"response",
"handshake",
"handshake_ack",
"error",
"discover_agents",
"agent_announcement",
"goodbye"
]
},
"sender_id": {
"type": "string",
"description": "Identifier of the sending agent",
"minLength": 3,
"maxLength": 128,
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$"
},
"recipient_id": {
"type": "string",
"description": "Identifier of the receiving agent",
"minLength": 3,
"maxLength": 128,
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$"
},
"timestamp": {
"type": "string",
"description": "ISO 8601 timestamp in UTC",
"format": "date-time",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z$",
"examples": ["2025-12-09T15:30:00.000Z"]
},
"payload": {
"type": "object",
"description": "Message-type-specific content"
},
"correlation_id": {
"type": ["string", "null"],
"description": "Links response to original request",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
},
"auth": {
"type": "object",
"description": "Optional authentication tag",
"properties": {
"agent_id": {"type": "string"},
"timestamp": {"type": "string", "format": "date-time"},
"nonce": {"type": "string", "pattern": "^[0-9a-f]{32}$"},
"signature": {"type": "string"},
"public_key_fingerprint": {"type": "string"}
},
"required": ["agent_id", "timestamp", "nonce", "signature"]
}
},
"additionalProperties": false
}
TypeScript Definition¶
/**
* Base A2A Message
* All messages extend this interface
*/
export interface BaseMessage {
message_id: string; // UUID v4
message_type: MessageType;
sender_id: string;
recipient_id: string;
timestamp: string; // ISO 8601
payload: Record<string, any>;
correlation_id?: string | null;
auth?: AuthenticationTag;
}
export type MessageType =
| "request"
| "response"
| "handshake"
| "handshake_ack"
| "error"
| "discover_agents"
| "agent_announcement"
| "goodbye";
export interface AuthenticationTag {
agent_id: string;
timestamp: string;
nonce: string; // 32 hex chars
signature: string; // Base64
public_key_fingerprint?: string;
}
Python Pydantic Model¶
from datetime import datetime
from typing import Any, Dict, Literal, Optional
from uuid import UUID
from pydantic import BaseModel, Field, field_validator
class AuthenticationTag(BaseModel):
"""Authentication tag for secure messages"""
agent_id: str = Field(..., min_length=3, max_length=128)
timestamp: datetime
nonce: str = Field(..., pattern=r'^[0-9a-f]{32}$')
signature: str
public_key_fingerprint: Optional[str] = None
class Config:
json_schema_extra = {
"example": {
"agent_id": "crypto-agent-001",
"timestamp": "2025-12-09T15:30:00.000Z",
"nonce": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"signature": "SGVsbG8gV29ybGQ="
}
}
class BaseMessage(BaseModel):
"""Base A2A message structure"""
message_id: UUID = Field(..., description="Globally unique message ID")
message_type: Literal[
"request", "response", "handshake", "handshake_ack",
"error", "discover_agents", "agent_announcement", "goodbye"
]
sender_id: str = Field(..., min_length=3, max_length=128)
recipient_id: str = Field(..., min_length=3, max_length=128)
timestamp: datetime = Field(..., description="UTC timestamp")
payload: Dict[str, Any]
correlation_id: Optional[UUID] = None
auth: Optional[AuthenticationTag] = None
@field_validator('sender_id', 'recipient_id')
@classmethod
def validate_agent_id(cls, v: str) -> str:
"""Validate agent ID format"""
if not v[0].isalnum() or not v[-1].isalnum():
raise ValueError("Agent ID must start and end with alphanumeric")
return v
@field_validator('timestamp')
@classmethod
def validate_timestamp_freshness(cls, v: datetime) -> datetime:
"""Validate timestamp is reasonably fresh"""
now = datetime.utcnow()
age = (now - v).total_seconds()
if age < -60: # 1 minute clock skew tolerance
raise ValueError("Timestamp is in the future")
if age > 300: # 5 minute max age
raise ValueError("Timestamp too old")
return v
class Config:
json_schema_extra = {
"example": {
"message_id": "a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g",
"message_type": "request",
"sender_id": "client-agent-001",
"recipient_id": "crypto-agent-001",
"timestamp": "2025-12-09T15:30:00.000Z",
"payload": {"method": "get_price", "parameters": {"currency": "BTC"}},
"correlation_id": None
}
}
1️⃣ REQUEST Message Schema¶
JSON Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/request-message.json",
"title": "A2A Request Message",
"description": "Request an action or query from another agent",
"allOf": [
{"$ref": "base-message.json"},
{
"properties": {
"message_type": {"const": "request"},
"correlation_id": {"type": "null"},
"payload": {
"type": "object",
"required": ["method"],
"properties": {
"method": {
"type": "string",
"description": "Action to perform",
"minLength": 1,
"maxLength": 128,
"examples": ["get_price", "upload_report", "create_project"]
},
"parameters": {
"type": "object",
"description": "Method-specific parameters",
"additionalProperties": true
}
},
"additionalProperties": false
}
}
}
]
}
TypeScript Definition¶
export interface RequestMessage extends BaseMessage {
message_type: "request";
correlation_id: null;
payload: {
method: string;
parameters?: Record<string, any>;
};
}
// Example: Crypto price request
export interface GetPriceRequest extends RequestMessage {
payload: {
method: "get_price";
parameters: {
currency: "BTC" | "ETH" | "XRP";
};
};
}
// Example: File upload request
export interface UploadReportRequest extends RequestMessage {
payload: {
method: "upload_report";
parameters: {
filename: string;
content: string; // Base64 encoded
content_type: string;
};
};
}
Python Pydantic Model¶
from typing import Any, Dict, Optional
from pydantic import Field
class RequestPayload(BaseModel):
"""Payload for REQUEST messages"""
method: str = Field(..., min_length=1, max_length=128)
parameters: Optional[Dict[str, Any]] = Field(default_factory=dict)
class Config:
json_schema_extra = {
"examples": [
{
"method": "get_price",
"parameters": {"currency": "BTC"}
},
{
"method": "upload_report",
"parameters": {
"filename": "report.json",
"content": "eyAi..."
}
}
]
}
class RequestMessage(BaseMessage):
"""REQUEST message type"""
message_type: Literal["request"]
correlation_id: Literal[None] = None
payload: RequestPayload
# Usage
request = RequestMessage(
message_id=UUID("a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g"),
message_type="request",
sender_id="client-001",
recipient_id="server-001",
timestamp=datetime.utcnow(),
payload=RequestPayload(
method="get_price",
parameters={"currency": "BTC"}
)
)
Complete Example¶
{
"message_id": "a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g",
"message_type": "request",
"sender_id": "client-agent-001",
"recipient_id": "crypto-agent-001",
"timestamp": "2025-12-09T15:30:00.000Z",
"payload": {
"method": "get_price",
"parameters": {
"currency": "BTC"
}
},
"correlation_id": null
}
2️⃣ RESPONSE Message Schema¶
JSON Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/response-message.json",
"title": "A2A Response Message",
"description": "Response to a previous request",
"allOf": [
{"$ref": "base-message.json"},
{
"properties": {
"message_type": {"const": "response"},
"correlation_id": {
"type": "string",
"description": "Must match request message_id"
},
"payload": {
"type": "object",
"required": ["status"],
"properties": {
"status": {
"type": "string",
"enum": ["success", "error"],
"description": "Outcome of the request"
},
"data": {
"type": "object",
"description": "Response data (required if status=success)"
},
"error": {
"type": "object",
"description": "Error details (required if status=error)",
"properties": {
"code": {"type": "string"},
"message": {"type": "string"},
"details": {"type": "object"}
},
"required": ["code", "message"]
}
},
"oneOf": [
{
"properties": {
"status": {"const": "success"},
"data": {"type": "object"}
},
"required": ["data"]
},
{
"properties": {
"status": {"const": "error"},
"error": {"type": "object"}
},
"required": ["error"]
}
],
"additionalProperties": false
}
},
"required": ["correlation_id"]
}
]
}
TypeScript Definition¶
export interface ResponseMessage extends BaseMessage {
message_type: "response";
correlation_id: string; // Must be set
payload: SuccessPayload | ErrorPayload;
}
export interface SuccessPayload {
status: "success";
data: Record<string, any>;
}
export interface ErrorPayload {
status: "error";
error: {
code: string;
message: string;
details?: Record<string, any>;
};
}
// Example: Success response
export interface PriceResponse extends ResponseMessage {
payload: {
status: "success";
data: {
currency: string;
price_usd: number;
timestamp: string;
};
};
}
Python Pydantic Model¶
from typing import Union
from pydantic import model_validator
class ErrorObject(BaseModel):
"""Error details in response"""
code: str = Field(..., pattern=r'^[A-Z][A-Z0-9_]*[A-Z0-9]$')
message: str = Field(..., min_length=1)
details: Optional[Dict[str, Any]] = None
class SuccessResponsePayload(BaseModel):
"""Success response payload"""
status: Literal["success"]
data: Dict[str, Any]
class ErrorResponsePayload(BaseModel):
"""Error response payload"""
status: Literal["error"]
error: ErrorObject
ResponsePayload = Union[SuccessResponsePayload, ErrorResponsePayload]
class ResponseMessage(BaseMessage):
"""RESPONSE message type"""
message_type: Literal["response"]
correlation_id: UUID # Required for responses
payload: ResponsePayload
@model_validator(mode='after')
def validate_payload_consistency(self):
"""Ensure payload is consistent with status"""
payload = self.payload
if payload.status == "success":
if not isinstance(payload, SuccessResponsePayload):
raise ValueError("Success status requires data field")
elif payload.status == "error":
if not isinstance(payload, ErrorResponsePayload):
raise ValueError("Error status requires error field")
return self
Complete Examples¶
Success Response:
{
"message_id": "b8g9e0f3-4d5c-6e7f-8g9a-0c1d2e3f4g5h",
"message_type": "response",
"sender_id": "crypto-agent-001",
"recipient_id": "client-agent-001",
"timestamp": "2025-12-09T15:30:00.125Z",
"payload": {
"status": "success",
"data": {
"currency": "BTC",
"price_usd": 125000.50,
"timestamp": "2025-12-09T15:30:00.000Z"
}
},
"correlation_id": "a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g"
}
Error Response:
{
"message_id": "c9h0f1g4-5e6d-7f8g-9h0b-1d2e3f4g5h6i",
"message_type": "response",
"sender_id": "crypto-agent-001",
"recipient_id": "client-agent-001",
"timestamp": "2025-12-09T15:30:00.200Z",
"payload": {
"status": "error",
"error": {
"code": "INVALID_CURRENCY",
"message": "Currency 'XYZ' is not supported",
"details": {
"supported_currencies": ["BTC", "ETH", "XRP"]
}
}
},
"correlation_id": "a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g"
}
3️⃣ HANDSHAKE Message Schema¶
JSON Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/handshake-message.json",
"title": "A2A Handshake Message",
"description": "Initiate connection and exchange capabilities",
"allOf": [
{"$ref": "base-message.json"},
{
"properties": {
"message_type": {"const": "handshake"},
"correlation_id": {"type": "null"},
"payload": {
"type": "object",
"required": ["agent_card"],
"properties": {
"agent_card": {
"type": "object",
"required": [
"agent_id",
"name",
"version",
"description",
"capabilities",
"supported_protocols"
],
"properties": {
"agent_id": {"type": "string"},
"name": {"type": "string"},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"description": {"type": "string"},
"capabilities": {
"type": "array",
"items": {"type": "string"},
"minItems": 1,
"maxItems": 50
},
"supported_protocols": {
"type": "array",
"items": {"type": "string"},
"minItems": 1
},
"metadata": {"type": "object"}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
}
}
]
}
TypeScript Definition¶
export interface HandshakeMessage extends BaseMessage {
message_type: "handshake";
correlation_id: null;
payload: {
agent_card: AgentCard;
};
}
export interface AgentCard {
agent_id: string;
name: string;
version: string; // Semantic versioning: "1.0.0"
description: string;
capabilities: string[];
supported_protocols: string[];
metadata?: Record<string, any>;
}
Python Pydantic Model¶
from pydantic import Field, field_validator
import re
class AgentCard(BaseModel):
"""Agent capability card"""
agent_id: str = Field(..., min_length=3, max_length=128)
name: str = Field(..., min_length=1, max_length=100)
version: str = Field(..., pattern=r'^\d+\.\d+\.\d+$')
description: str = Field(..., max_length=500)
capabilities: list[str] = Field(..., min_items=1, max_items=50)
supported_protocols: list[str] = Field(..., min_items=1)
metadata: Optional[Dict[str, Any]] = None
@field_validator('version')
@classmethod
def validate_semver(cls, v: str) -> str:
"""Validate semantic versioning"""
parts = v.split('.')
if len(parts) != 3:
raise ValueError("Version must be major.minor.patch")
for part in parts:
if not part.isdigit():
raise ValueError("Version parts must be integers")
return v
class HandshakePayload(BaseModel):
"""Payload for HANDSHAKE messages"""
agent_card: AgentCard
class HandshakeMessage(BaseMessage):
"""HANDSHAKE message type"""
message_type: Literal["handshake"]
correlation_id: Literal[None] = None
payload: HandshakePayload
Complete Example¶
{
"message_id": "c9h0f1g4-5e6d-7f8g-9h0b-1d2e3f4g5h6i",
"message_type": "handshake",
"sender_id": "crypto-agent-001",
"recipient_id": "client-agent-001",
"timestamp": "2025-12-09T15:29:55.000Z",
"payload": {
"agent_card": {
"agent_id": "crypto-agent-001",
"name": "CryptoPriceAgent",
"version": "1.0.0",
"description": "AI Agent providing cryptocurrency prices",
"capabilities": [
"price_query",
"currency_list",
"no_streaming"
],
"supported_protocols": ["A2A/1.0"],
"metadata": {
"supported_currencies": ["BTC", "ETH", "XRP"],
"update_frequency": "on_request",
"data_type": "fictitious"
}
}
},
"correlation_id": null
}
4️⃣ ERROR Message Schema¶
JSON Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/error-message.json",
"title": "A2A Error Message",
"description": "Error occurred during message processing",
"allOf": [
{"$ref": "base-message.json"},
{
"properties": {
"message_type": {"const": "error"},
"correlation_id": {
"type": "string",
"description": "Message that caused the error"
},
"payload": {
"type": "object",
"required": ["error"],
"properties": {
"error": {
"type": "object",
"required": ["code", "message"],
"properties": {
"code": {
"type": "string",
"pattern": "^[A-Z][A-Z0-9_]*[A-Z0-9]$",
"examples": ["RATE_LIMIT_EXCEEDED", "INVALID_MESSAGE"]
},
"message": {
"type": "string",
"minLength": 1,
"maxLength": 500
},
"details": {
"type": "object",
"description": "Additional error context"
},
"retry_after": {
"type": "integer",
"minimum": 0,
"description": "Seconds to wait before retry"
},
"documentation_url": {
"type": "string",
"format": "uri"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"required": ["correlation_id"]
}
]
}
TypeScript Definition¶
export interface ErrorMessage extends BaseMessage {
message_type: "error";
correlation_id: string;
payload: {
error: {
code: string; // UPPERCASE_UNDERSCORE
message: string;
details?: Record<string, any>;
retry_after?: number;
documentation_url?: string;
};
};
}
// Standard error codes
export type ErrorCode =
| "INVALID_MESSAGE"
| "VALIDATION_FAILED"
| "AUTHENTICATION_FAILED"
| "FORBIDDEN"
| "NOT_FOUND"
| "METHOD_NOT_ALLOWED"
| "RATE_LIMIT_EXCEEDED"
| "PAYLOAD_TOO_LARGE"
| "INTERNAL_ERROR"
| "SERVICE_UNAVAILABLE"
| "TIMEOUT";
Python Pydantic Model¶
class ErrorDetails(BaseModel):
"""Error details in ERROR message"""
code: str = Field(..., pattern=r'^[A-Z][A-Z0-9_]*[A-Z0-9]$')
message: str = Field(..., min_length=1, max_length=500)
details: Optional[Dict[str, Any]] = None
retry_after: Optional[int] = Field(None, ge=0)
documentation_url: Optional[str] = None
class ErrorPayload(BaseModel):
"""Payload for ERROR messages"""
error: ErrorDetails
class ErrorMessage(BaseMessage):
"""ERROR message type"""
message_type: Literal["error"]
correlation_id: UUID # Required
payload: ErrorPayload
Complete Example¶
{
"message_id": "j6o7m8n1-2l3k-4m5n-6o7i-8k9l0m1n2o3p",
"message_type": "error",
"sender_id": "crypto-agent-001",
"recipient_id": "client-agent-001",
"timestamp": "2025-12-09T15:38:00.000Z",
"payload": {
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again later.",
"details": {
"limit": 100,
"window": "1 minute",
"reset_at": "2025-12-09T15:39:00.000Z"
},
"retry_after": 60,
"documentation_url": "https://docs.a2a.example.com/errors#rate-limit"
}
},
"correlation_id": "a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g"
}
5️⃣ DISCOVER_AGENTS Message Schema¶
JSON Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/discover-agents-message.json",
"title": "A2A Discover Agents Message",
"description": "Query for available agents",
"allOf": [
{"$ref": "base-message.json"},
{
"properties": {
"message_type": {"const": "discover_agents"},
"recipient_id": {"const": "registry"},
"correlation_id": {"type": "null"},
"payload": {
"type": "object",
"properties": {
"capabilities": {
"type": "array",
"items": {"type": "string"},
"description": "Required capabilities"
},
"filters": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["healthy", "unhealthy", "all"]
},
"max_results": {
"type": "integer",
"minimum": 1,
"maximum": 100
}
}
}
},
"additionalProperties": false
}
}
}
]
}
TypeScript Definition¶
export interface DiscoverAgentsMessage extends BaseMessage {
message_type: "discover_agents";
recipient_id: "registry";
correlation_id: null;
payload: {
capabilities?: string[];
filters?: {
status?: "healthy" | "unhealthy" | "all";
max_results?: number;
};
};
}
Python Pydantic Model¶
class DiscoverFilters(BaseModel):
"""Filters for agent discovery"""
status: Optional[Literal["healthy", "unhealthy", "all"]] = "all"
max_results: Optional[int] = Field(100, ge=1, le=100)
class DiscoverAgentsPayload(BaseModel):
"""Payload for DISCOVER_AGENTS messages"""
capabilities: Optional[list[str]] = None
filters: Optional[DiscoverFilters] = None
class DiscoverAgentsMessage(BaseMessage):
"""DISCOVER_AGENTS message type"""
message_type: Literal["discover_agents"]
recipient_id: Literal["registry"]
correlation_id: Literal[None] = None
payload: DiscoverAgentsPayload
Complete Example¶
{
"message_id": "d0i1g2h5-6f7e-8g9h-0i1c-2e3f4g5h6i7j",
"message_type": "discover_agents",
"sender_id": "client-agent-001",
"recipient_id": "registry",
"timestamp": "2025-12-09T15:35:00.000Z",
"payload": {
"capabilities": ["price_query", "real_time_data"],
"filters": {
"status": "healthy",
"max_results": 10
}
},
"correlation_id": null
}
6️⃣ AGENT_ANNOUNCEMENT Message Schema¶
JSON Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/agent-announcement-message.json",
"title": "A2A Agent Announcement Message",
"description": "Response to discovery query with matching agents",
"allOf": [
{"$ref": "base-message.json"},
{
"properties": {
"message_type": {"const": "agent_announcement"},
"sender_id": {"const": "registry"},
"correlation_id": {"type": "string"},
"payload": {
"type": "object",
"required": ["agents", "total_count"],
"properties": {
"agents": {
"type": "array",
"items": {
"type": "object",
"required": [
"agent_id",
"name",
"capabilities",
"status",
"endpoint"
],
"properties": {
"agent_id": {"type": "string"},
"name": {"type": "string"},
"capabilities": {
"type": "array",
"items": {"type": "string"}
},
"status": {
"type": "string",
"enum": ["healthy", "unhealthy"]
},
"endpoint": {
"type": "string",
"format": "uri"
},
"last_heartbeat": {
"type": "string",
"format": "date-time"
}
}
}
},
"total_count": {
"type": "integer",
"minimum": 0
},
"query_time_ms": {
"type": "number",
"minimum": 0
}
},
"additionalProperties": false
}
},
"required": ["correlation_id"]
}
]
}
TypeScript Definition¶
export interface AgentAnnouncementMessage extends BaseMessage {
message_type: "agent_announcement";
sender_id: "registry";
correlation_id: string;
payload: {
agents: AgentInfo[];
total_count: number;
query_time_ms?: number;
};
}
export interface AgentInfo {
agent_id: string;
name: string;
capabilities: string[];
status: "healthy" | "unhealthy";
endpoint: string;
last_heartbeat?: string;
}
Python Pydantic Model¶
from pydantic import HttpUrl
class AgentInfo(BaseModel):
"""Information about discovered agent"""
agent_id: str
name: str
capabilities: list[str]
status: Literal["healthy", "unhealthy"]
endpoint: HttpUrl
last_heartbeat: Optional[datetime] = None
class AgentAnnouncementPayload(BaseModel):
"""Payload for AGENT_ANNOUNCEMENT messages"""
agents: list[AgentInfo]
total_count: int = Field(..., ge=0)
query_time_ms: Optional[float] = Field(None, ge=0)
class AgentAnnouncementMessage(BaseMessage):
"""AGENT_ANNOUNCEMENT message type"""
message_type: Literal["agent_announcement"]
sender_id: Literal["registry"]
correlation_id: UUID
payload: AgentAnnouncementPayload
Complete Example¶
{
"message_id": "e1j2h3i6-7g8f-9h0i-1j2d-3f4g5h6i7j8k",
"message_type": "agent_announcement",
"sender_id": "registry",
"recipient_id": "client-agent-001",
"timestamp": "2025-12-09T15:35:00.050Z",
"payload": {
"agents": [
{
"agent_id": "crypto-agent-001",
"name": "CryptoPriceAgent",
"capabilities": ["price_query", "currency_list"],
"status": "healthy",
"endpoint": "http://localhost:8888",
"last_heartbeat": "2025-12-09T15:34:55.000Z"
},
{
"agent_id": "crypto-agent-002",
"name": "CryptoAnalysisAgent",
"capabilities": ["price_query", "trend_analysis"],
"status": "healthy",
"endpoint": "http://localhost:8889",
"last_heartbeat": "2025-12-09T15:34:58.000Z"
}
],
"total_count": 2,
"query_time_ms": 15.3
},
"correlation_id": "d0i1g2h5-6f7e-8g9h-0i1c-2e3f4g5h6i7j"
}
🔐 Authenticated Message Schema¶
For production systems, all messages should include authentication tags.
JSON Schema Extension¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/authenticated-message.json",
"title": "A2A Authenticated Message",
"description": "Message with authentication tag",
"allOf": [
{"$ref": "base-message.json"},
{
"required": ["auth"],
"properties": {
"auth": {
"type": "object",
"required": ["agent_id", "timestamp", "nonce", "signature"],
"properties": {
"agent_id": {
"type": "string",
"description": "Must match sender_id"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"nonce": {
"type": "string",
"pattern": "^[0-9a-f]{32}$",
"description": "32 hex chars for replay protection"
},
"signature": {
"type": "string",
"description": "RSA/ECC signature in Base64"
},
"public_key_fingerprint": {
"type": "string",
"description": "SHA256 fingerprint of public key"
}
},
"additionalProperties": false
}
}
}
]
}
Complete Authenticated Example¶
{
"message_id": "a7f8d9e2-3c4b-5d6e-7f8a-9b0c1d2e3f4g",
"message_type": "request",
"sender_id": "client-agent-001",
"recipient_id": "crypto-agent-001",
"timestamp": "2025-12-09T15:30:00.000Z",
"payload": {
"method": "get_price",
"parameters": {
"currency": "BTC"
}
},
"correlation_id": null,
"auth": {
"agent_id": "client-agent-001",
"timestamp": "2025-12-09T15:30:00.000Z",
"nonce": "a1b2c3d4e5f6789012345678901234567",
"signature": "SGVsbG8gV29ybGQhIFRoaXMgaXMgYSBzaWduYXR1cmUu",
"public_key_fingerprint": "sha256:1234567890abcdef"
}
}
📦 Schema Validation Code¶
Python Validator Using JSON Schema¶
import json
import jsonschema
from pathlib import Path
class SchemaValidator:
"""Validate A2A messages against JSON schemas"""
def __init__(self, schema_dir: Path):
"""Load all schemas from directory"""
self.schemas = {}
# Load base schema
with open(schema_dir / "base-message.json") as f:
self.schemas["base"] = json.load(f)
# Load message type schemas
for schema_file in schema_dir.glob("*-message.json"):
msg_type = schema_file.stem.replace("-message", "")
with open(schema_file) as f:
self.schemas[msg_type] = json.load(f)
def validate(self, message: dict) -> tuple[bool, list[str]]:
"""
Validate message against appropriate schema
Returns: (is_valid, errors)
"""
errors = []
# Validate against base schema first
try:
jsonschema.validate(message, self.schemas["base"])
except jsonschema.ValidationError as e:
errors.append(f"Base schema error: {e.message}")
return False, errors
# Validate against message-type-specific schema
msg_type = message.get("message_type")
if msg_type in self.schemas:
try:
jsonschema.validate(message, self.schemas[msg_type])
except jsonschema.ValidationError as e:
errors.append(f"{msg_type} schema error: {e.message}")
return False, errors
else:
errors.append(f"Unknown message_type: {msg_type}")
return False, errors
return True, []
# Usage
validator = SchemaValidator(Path("schemas/"))
message = json.loads(raw_message)
is_valid, errors = validator.validate(message)
if not is_valid:
print(f"Validation failed: {errors}")
TypeScript Validator Using AJV¶
import Ajv from "ajv";
import addFormats from "ajv-formats";
import { readFileSync } from "fs";
class SchemaValidator {
private ajv: Ajv;
private schemas: Map<string, any>;
constructor(schemaDir: string) {
this.ajv = new Ajv({ allErrors: true });
addFormats(this.ajv);
this.schemas = new Map();
// Load schemas
const baseSchema = JSON.parse(
readFileSync(`${schemaDir}/base-message.json`, "utf-8")
);
this.ajv.addSchema(baseSchema, "base");
// Load message type schemas
const messageTypes = [
"request", "response", "handshake", "error",
"discover-agents", "agent-announcement"
];
for (const type of messageTypes) {
const schema = JSON.parse(
readFileSync(`${schemaDir}/${type}-message.json`, "utf-8")
);
this.schemas.set(type, schema);
this.ajv.addSchema(schema, type);
}
}
validate(message: any): { valid: boolean; errors: string[] } {
const messageType = message.message_type;
const schema = this.schemas.get(messageType);
if (!schema) {
return {
valid: false,
errors: [`Unknown message_type: ${messageType}`]
};
}
const valid = this.ajv.validate(messageType, message);
if (!valid) {
const errors = this.ajv.errors?.map(e =>
`${e.instancePath}: ${e.message}`
) || [];
return { valid: false, errors };
}
return { valid: true, errors: [] };
}
}
// Usage
const validator = new SchemaValidator("./schemas");
const result = validator.validate(message);
if (!result.valid) {
console.error("Validation errors:", result.errors);
}
🔄 Schema Versioning¶
Version Compatibility¶
The A2A protocol uses semantic versioning for schemas:
- Major version (1.x.x): Breaking changes
- Minor version (x.1.x): Backward-compatible additions
- Patch version (x.x.1): Bug fixes
Schema Version Header¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://a2a-protocol.org/schemas/v1.0.0/base-message.json",
"version": "1.0.0",
"deprecated": false,
"changelog": "https://a2a-protocol.org/schemas/CHANGELOG.md"
}
Version Negotiation¶
Agents should declare supported schema versions in handshake:
{
"agent_card": {
"supported_protocols": [
"A2A/1.0",
"A2A/1.1"
],
"schema_versions": [
"1.0.0",
"1.1.0"
]
}
}
📚 Schema Files Package¶
Directory Structure¶
schemas/
├── README.md
├── CHANGELOG.md
├── package.json
├── base-message.json
├── request-message.json
├── response-message.json
├── handshake-message.json
├── error-message.json
├── discover-agents-message.json
├── agent-announcement-message.json
├── authenticated-message.json
├── python/
│ ├── __init__.py
│ ├── models.py # Pydantic models
│ └── validator.py # JSON Schema validator
└── typescript/
├── types.ts # Type definitions
├── validator.ts # AJV validator
└── package.json
NPM Package¶
{
"name": "@a2a-protocol/schemas",
"version": "1.0.0",
"description": "JSON schemas and type definitions for A2A protocol",
"main": "typescript/types.js",
"types": "typescript/types.d.ts",
"files": [
"*.json",
"typescript/**/*",
"python/**/*"
],
"keywords": ["a2a", "agent", "schema", "validation"],
"license": "MIT"
}
PyPI Package¶
# setup.py
from setuptools import setup, find_packages
setup(
name="a2a-protocol-schemas",
version="1.0.0",
description="JSON schemas and Pydantic models for A2A protocol",
packages=find_packages(),
package_data={
"a2a_schemas": ["*.json"]
},
install_requires=[
"pydantic>=2.0.0",
"jsonschema>=4.0.0"
],
python_requires=">=3.10"
)
🎓 Best Practices¶
Schema Design¶
- Use
additionalProperties: false- Reject unexpected fields (security) - Require all critical fields - Don't make security fields optional
- Use enums for fixed values - Prevents typos and invalid values
- Include examples - Helps users understand expected format
- Add descriptions - Document purpose of each field
- Version schemas - Track changes over time
Validation¶
- Validate early - Before any processing
- Fail fast - Reject invalid messages immediately
- Provide clear errors - Help developers fix issues
- Cache compiled schemas - Improve performance
- Test edge cases - Empty strings, null, max values
- Fuzz test - Random inputs to find bugs
Documentation¶
- Keep schemas and docs in sync - Single source of truth
- Provide examples - Show valid and invalid messages
- Document changes - Maintain changelog
- Version compatibility matrix - Which versions work together
- Migration guides - How to upgrade between versions
🔗 Related Documentation¶
- Protocol Messages - Message usage guide
- Message Validation - Validation patterns
- Error Handling - Error responses
- Authentication Tags - Security authentication
📦 Downloads¶
Schema Package: Download complete schema package with validators
Repository: https://github.com/a2a-protocol/schemas
NPM: npm install @a2a-protocol/schemas
PyPI: pip install a2a-protocol-schemas
💡 Quick Reference¶
Message Type Summary¶
| Type | Purpose | correlation_id | Response To |
|---|---|---|---|
request | Ask for action | null | None |
response | Return result | required | request |
handshake | Exchange capabilities | null | None |
handshake_ack | Acknowledge handshake | required | handshake |
error | Report error | required | Any message |
discover_agents | Find agents | null | None |
agent_announcement | Return agents | required | discover_agents |
goodbye | Close connection | null | None |
Validation Checklist¶
- Valid JSON structure
- All required fields present
-
message_idis UUID v4 -
message_typeis known value -
timestampis ISO 8601 UTC -
timestampis fresh (<5 min) -
sender_idvalid format -
recipient_idvalid format -
payloadstructure matches type -
correlation_idcorrect (if response/error) - No unexpected fields
- Payload under size limit (10MB)
Document Version: 1.0
Last Updated: December 2025
Schema Version: 1.0.0
Status: Complete
Maintainer: A2A Protocol Working Group