Module 6: Evaluation & Production Ops · 8h
01 · UNDERSTAND
Day 40 Theory — A2A Interoperability and Learning New Frameworks from Docs
Why we need another protocol after MCP
Yesterday we looked at a high-level agent SDK. Earlier we learned MCP for connecting an AI host to capabilities such as tools and resources.
Today we address a different boundary:
How can one independently deployed agent service discover and communicate with another agent service using a shared protocol?
That is the problem addressed by Agent-to-Agent (A2A) interoperability.
MCP and A2A are not competitors that map to “passive” versus “active” systems. They solve different integration surfaces and can appear in the same architecture.
User
|
v
Service Desk Agent
| \
| MCP \ A2A
v v
Tool servers Remote specialist agent
The Service Desk might use MCP to call an internal ticketing capability and A2A to delegate a long-running certificate investigation to another agent service.
Protocol boundary versus implementation framework
A2A describes how compatible agent services communicate. It does not require both sides to use the same internal framework.
One agent might be implemented with LangGraph, another with the OpenAI Agents SDK, and another with custom Python.
Interoperability works only if the external protocol contract is respected.
This is an important production idea:
Internal implementation can change without forcing every external caller to change, as long as the published protocol contract remains compatible.
Agent discovery with the Agent Card
An A2A server can publish an Agent Card at the standard well-known location:
https://agent.example.com/.well-known/agent-card.json
The card is a self-describing manifest that helps a client learn things such as:
- agent name and description,
- supported interfaces,
- protocol versions,
- capabilities,
- input/output media types,
- skills,
- security requirements,
- optional signatures.
Discovery answers:
“What does this agent claim to support, and how can I contact it?”
It does not automatically answer:
“Should I trust this agent?”
Trust still requires application policy and, where used, cryptographic verification.
supportedInterfaces is the v1 communication contract
A2A v1 represents protocol endpoints through an ordered supportedInterfaces list.
A simplified interface entry looks like:
{
"url": "https://agent.example.com/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
The key fields mean:
url— where the selected interface is reachable,protocolBinding— how the abstract A2A operations are mapped onto a concrete binding,protocolVersion— which A2A protocol version the interface implements.
The first supported interface is the agent's preferred interface. A client should choose a binding it understands.
Do not use older card examples that put protocolVersion or a single primary url at the top level; v1 moved that information into supportedInterfaces.
Core Agent Card structure
A useful v1 mental model is:
AgentCard
├─ identity: name, description, version
├─ interfaces: supportedInterfaces[]
├─ runtime capabilities
├─ default input/output modes
├─ skills[]
├─ optional security schemes/requirements
└─ optional signatures[]
A skill is descriptive metadata about what the agent is likely to do successfully. It is not itself a permission grant.
A client still decides whether a discovered agent or skill is allowed for the current user/task.
Capabilities are not operation names
A common schema mistake is to write something such as:
{"capabilities": {"sendMessage": true, "getTask": true}}
That confuses core protocol operations with the AgentCapabilities structure.
Core operations exist as part of the protocol/binding. Capability fields describe optional behavior supported by the agent, such as streaming or other protocol-defined extensions/capabilities.
Always follow the current normative schema instead of inventing intuitive-looking JSON.
Messages carry communication content
A Message represents communication between roles.
A message has protocol-defined fields such as a unique message identifier and parts containing the actual content.
Conceptually:
Message
├─ messageId
├─ role
├─ parts[]
└─ optional task association / metadata
In A2A v1, Part discrimination is member-based. For a text part, think in terms of a part that contains text rather than inventing a generic type: "text" field unless the current schema for that binding says so.
This is exactly why protocol examples must be copied from the correct version of authoritative documentation.
Tasks represent work with lifecycle
Messages are communication. Tasks represent work that has state over time.
That distinction matters for operations that do not finish immediately.
Example:
SendMessage
|
v
Task accepted
|
+--> working
|
+--> input required
|
+--> completed / failed / cancelled
Use the protocol-defined TASK_STATE_* values where the schema exposes task state. Do not invent application statuses and label them protocol states.
SendMessage versus task retrieval
At the abstract protocol level, operations such as SendMessage let a client send a message that can begin or continue work.
If the interaction results in asynchronous task execution, task-oriented operations can retrieve or manage that work according to the selected binding and current specification.
Keep two layers separate:
Abstract A2A operation
|
v
Selected protocol binding representation
(JSON-RPC / gRPC / HTTP+JSON)
A JSON-RPC example and an HTTP+JSON example may look very different on the wire while representing the same protocol concept.
Agent Card signatures are optional trust evidence
Agent Cards may include signatures[].
A v1 AgentCardSignature follows JSON Web Signature (JWS)-style fields:
{
"protected": "base64url-jws-header",
"signature": "base64url-signature"
}
with optional unprotected header fields where applicable.
The important lessons are:
- signatures are optional,
- presence of a signature does not mean it was verified,
- verification needs an appropriate trusted key / trust policy,
- signed content must be canonicalized according to the protocol's signing rules.
Do not tell students “the card is trusted because it has a signature field.”
Why canonicalization matters
Digital signatures operate over bytes. Two JSON objects can represent the same logical data while having different whitespace or member ordering.
A2A's signing model therefore relies on deterministic canonicalization rules so signer and verifier can reproduce the same byte representation.
The conceptual flow is:
Agent Card data
|
v
canonical representation
|
v
JWS signing
|
v
signature published
Client:
card -> canonicalize -> verify signature -> apply trust policy
Cryptographic validity still does not automatically imply business authorization.
Security requirements belong in discovery, enforcement belongs in the client/application
An Agent Card may advertise security schemes and requirements.
That helps the client understand how to authenticate to the agent.
But the caller still needs to decide:
- whether this remote agent is approved,
- which credentials it may receive,
- which user data may be sent,
- which tenant the request belongs to,
- which returned artifacts can be trusted.
Interoperability without trust policy can simply make unsafe integration easier.
Streaming progress without leaking reasoning
A remote agent can expose useful progress through protocol-defined messages, status updates and artifacts.
Good operational progress might include:
Task accepted
Certificate inventory loaded
Approval required
Renewal artifact produced
Task completed
Do not stream private chain-of-thought as “progress.”
Users and systems need observable status, not hidden internal reasoning traces.
MCP and A2A together
A realistic architecture can use both:
A2A
Service Desk Agent ---------> Security Specialist Agent
| |
| MCP | MCP
v v
Ticket tools Certificate tools
Knowledge resources PKI resources
The boundaries are different:
- MCP: host/client ↔ capability server
- A2A: agent service ↔ agent service
Do not force every integration into one protocol simply because that protocol is familiar.
How to learn an evolving framework or protocol safely
This day is also about ecosystem literacy.
Agent frameworks and protocols change quickly. A strong engineer needs a repeatable method for learning from documentation.
Step 1 — Identify the exact version
Before copying any code, ask:
Which version/spec date is this page about?
Step 2 — Find the normative model
For protocols, distinguish:
- specification/schema,
- official guides,
- SDK examples,
- blogs,
- community tutorials.
Normative specification wins when examples disagree about protocol shape.
Step 3 — Build the smallest verified example
Do not begin with a full enterprise architecture.
Verify:
discover -> parse -> send one valid request -> inspect one valid response
Step 4 — Add security and failure cases
Then ask:
- What if discovery is unavailable?
- What if the card is malformed?
- What if the signature fails?
- What if no supported binding matches?
- What if the remote task fails or requires input?
- What if the caller is not authorized?
Step 5 — Check old examples for removed fields
Protocols frequently preserve old blog posts after the schema changes.
If a field appears in a tutorial but not in the current schema, do not “make both work” by inventing a hybrid object.
Service Desk connection
Today the Service Desk crosses its first agent-to-agent interoperability boundary.
Before
------
Service Desk owns all specialist logic locally
Today
-----
Service Desk
|
| discover Agent Card
| choose supported interface
| authenticate according to policy
v
Remote Security Specialist
|
| status / artifacts
v
Service Desk continues workflow
The principle is:
A2A standardizes communication between agent services, but discovery is not trust, schema validity is not authorization, and protocol version discipline is essential when the ecosystem evolves quickly.
02 · APPLY
Lesson goal
Today you will implement and inspect a small Agent-to-Agent (A2A) v1 interaction for the Service Desk.
By the end of the lesson you should be able to:
- explain when A2A is a better boundary than MCP,
- discover an agent through
/.well-known/agent-card.json, - read the v1
supportedInterfacesstructure, - distinguish core operations from capability flags,
- construct a valid message shape for a selected binding,
- explain task lifecycle and status updates,
- treat Agent Card signatures as optional trust evidence rather than automatic trust,
- identify which parts of the workflow still belong to application security.
What changed in the Service Desk today?
Until now, specialist behavior lived inside our own application/runtime.
Today we introduce a remote specialist boundary:
Service Desk Agent
|
| A2A
v
Remote Security Specialist
|
| may use its own tools/runtime internally
v
Task status + artifacts
The remote specialist does not need to use the same agent framework as the Service Desk. The shared contract is the A2A protocol.
Architecture walkthrough
graph LR
SD[Service Desk Agent]
Card[/.well-known/agent-card.json]
Remote[Remote Security Specialist]
Task[A2A Task]
Artifact[Result Artifact]
SD -->|1. Discover| Card
Card -->|2. Agent Card| SD
SD -->|3. Select supported interface| Remote
SD -->|4. SendMessage| Remote
Remote -->|5. Task status| Task
Task -->|6. Completed result| Artifact
Artifact --> SD
Notice the separation:
- Discovery tells us what the remote service claims to support.
- Interface selection chooses a compatible binding/version.
- Authentication/trust policy decides whether we should communicate with it.
- Message/task operations perform the actual interaction.
Step 1 — Read the Agent Card
A simplified A2A v1 Agent Card for our specialist can look like:
agent_card = {
"version": "1.0.0",
"name": "ServiceDeskSecuritySpecialist",
"description": "Remote IT security specialist for enterprise certificate incidents",
"supportedInterfaces": [
{
"url": "https://api.servicedesk.internal/a2a/v1",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0",
}
],
"capabilities": {
"streaming": True,
"pushNotifications": False,
"extensions": [],
"extendedAgentCard": False,
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain", "application/json"],
"skills": [
{
"id": "skill_renew_cert",
"name": "Renew certificate",
"description": "Handles approved SSL/SAML certificate-renewal workflows",
"tags": ["security", "certificates", "saml"],
}
],
}
What each important field means
version
: Version of the agent/service description, not a top-level A2A protocol version.
supportedInterfaces
: Ordered communication options. Each entry says where the interface lives, which binding it uses, and which A2A protocol version that interface implements.
capabilities
: Optional runtime capabilities defined by the protocol. Do not put operation names such as SendMessage or GetTask here.
defaultInputModes / defaultOutputModes
: Media types the agent accepts/produces by default.
skills
: Descriptive capability metadata. A skill description is not authorization.
Step 2 — Select a compatible interface
A client should choose a supported interface it understands.
def choose_jsonrpc_interface(card: dict) -> dict:
for interface in card["supportedInterfaces"]:
if (
interface["protocolBinding"] == "JSONRPC"
and interface["protocolVersion"].startswith("1.")
):
return interface
raise ValueError("No compatible A2A v1 JSON-RPC interface")
This is a useful place for deterministic validation.
The model should not invent an endpoint or protocol binding when the Agent Card already provides the contract.
Step 3 — Treat discovery as untrusted input
Before using the card, validate it and apply trust policy.
Questions include:
- Is the endpoint in an allowed domain/network?
- Does the declared binding/version match what our client supports?
- Does the card require a security scheme we can satisfy?
- Is this remote agent approved for the current tenant/use case?
- If signatures are present, do we require and successfully verify them?
A valid JSON document is not automatically a trusted remote agent.
Optional Agent Card signatures
A card may contain optional JWS-style signatures:
signed_card_fragment = {
"signatures": [
{
"protected": "<base64url protected JWS header>",
"signature": "<base64url signature>",
}
]
}
Correct handling is conceptually:
signature absent
-> apply policy for unsigned cards
signature present
-> canonicalize according to protocol rules
-> verify with trusted key material
-> apply trust policy
Do not write:
if card.get("signatures"):
trusted = True
Presence is not verification.
Step 4 — Construct a message
A2A messages have protocol-defined fields. For a v1 JSON-RPC interaction, a simplified request shape can be:
send_message_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg_cert_001",
"role": "ROLE_USER",
"parts": [
{"text": "Investigate renewal of the SAML certificate for auth.example.internal"}
],
}
},
}
Important details:
messageIdidentifies the message.roleuses the protocol-defined role representation.- a text Part uses member-presence form (
{"text": ...}) rather than an inventedtype: "text"discriminator. - do not invent root-level fields that are not in the selected binding's request schema.
If continuing work that is already associated with a task, use the task association exactly where the current message schema defines it.
Step 5 — Send through the selected binding
The Agent Card tells us which endpoint/binding to use.
import httpx
async def send_message(endpoint: str, payload: dict) -> dict:
async with httpx.AsyncClient(timeout=20.0) as client:
response = await client.post(endpoint, json=payload)
response.raise_for_status()
return response.json()
This HTTP call is transport/binding plumbing. Production code also needs:
- authentication required by the remote agent,
- TLS verification,
- retry policy appropriate to operation semantics,
- response schema validation,
- tracing and redaction,
- tenant/user policy checks.
Do not hide those responsibilities behind “A2A handles it.”
Step 6 — Understand task lifecycle
A remote agent may return or create task-oriented work rather than completing everything immediately.
A useful mental model is:
message sent
|
v
TASK_STATE_SUBMITTED / working state
|
+-- input required
|
+-- completed
|
+-- failed
|
+-- cancelled
Use the actual protocol-defined TASK_STATE_* values supported by the current schema/binding.
Application code should never turn arbitrary strings such as "almost_done" into pretend protocol states.
Step 7 — Expose progress without exposing private reasoning
A remote specialist can send useful operational progress such as:
Certificate inventory loaded
Approval required
CSR generated
Renewal completed
That is enough for UX and monitoring.
Do not expose private chain-of-thought in protocol messages or public progress events.
A2A versus MCP: apply the boundary correctly
Use this decision question:
Am I connecting my host/agent to a capability server, or am I communicating with another independently operated agent service?
For our Service Desk:
MCP
Service Desk -> ticket/search/certificate capability server
A2A
Service Desk Agent -> remote Security Specialist Agent
The remote Security Specialist may itself use MCP internally.
That is completely valid.
Failure walkthrough
Failure 1 — unsupported interface
Agent Card contains only a binding/version our client cannot speak.
Correct behavior:
- do not guess,
- return an explicit interoperability error,
- optionally select another declared compatible interface if one exists.
Failure 2 — signature verification fails
Correct behavior depends on trust policy, but for a card that must be signed:
- reject the card,
- record a security event,
- do not dispatch task data to that endpoint.
Failure 3 — task needs additional input
Do not invent a final answer. Surface the protocol-defined input-required state and obtain the missing user/application input through the correct workflow.
Failure 4 — remote specialist returns sensitive data
The Service Desk still owns output filtering/data policy before showing or persisting the result.
Remote interoperability does not create a trusted-data exemption.
Common mistakes
- Using an old Agent Card schema that puts
protocolVersionor primaryurlat the top level instead ofsupportedInterfaces. - Treating
capabilitiesas a list of core operation names. - Inventing Part fields from older versions/examples.
- Trusting every discovered endpoint. Discovery is not authorization.
- Treating signature presence as successful verification.
- Calling A2A “remote MCP.” They represent different integration boundaries.
- Streaming hidden model reasoning instead of safe operational status/artifacts.
Practical lab
Work in:
service-desk-day-40/
Task A — inspect the existing implementation
Locate the A2A-related implementation and identify:
- Agent Card representation,
- interface selection,
- request/message construction,
- task state handling,
- trust/security checks.
Write down which pieces are protocol logic and which pieces are Service Desk application policy.
Task B — validate interface selection
Add or inspect tests covering:
- valid JSON-RPC v1 interface,
- unsupported binding,
- incompatible protocol version,
- multiple interfaces where the first compatible option should be selected.
Task C — validate message shape
Ensure the request uses:
- a valid
messageId, - protocol-defined role,
- member-based text Part,
- task association only where the current schema defines it.
Task D — exercise trust failure
Simulate a card that violates your trust policy—for example an unapproved endpoint or failed required signature verification.
Expected behavior:
No remote task is dispatched.
A structured failure is returned/logged.
Task E — run the automated tests
cd service-desk-day-40
PYTHONPATH=. pytest
Do not stop at “pytest is green.” Be able to explain which protocol invariant each important test protects.
Knowledge check
1. Why did A2A v1 move protocol endpoint information into supportedInterfaces?
It allows an Agent Card to advertise ordered, versioned communication interfaces/bindings instead of assuming one top-level endpoint/transport.
2. Why is SendMessage not a boolean inside capabilities?
Because it is a core protocol operation; AgentCapabilities describes optional supported behavior defined by the protocol.
3. What does a valid Agent Card signature prove?
After correct verification against trusted key material and canonicalization rules, it can provide integrity/authenticity evidence for the card. It does not by itself grant authorization to use the agent.
4. When would MCP and A2A appear together?
A Service Desk agent may communicate with a remote specialist through A2A while either agent uses MCP to access tools/resources internally.
5. Why should task progress avoid chain-of-thought?
Operational status and artifacts are sufficient for users and systems; hidden model reasoning is not required for interoperability and can create privacy/security risks.
Scenario
Your Service Desk discovers an Agent Card for security-specialist.example.com. The JSON is structurally valid and the card advertises certificate-renewal skills, but the endpoint domain is not on the approved enterprise allowlist.
What should happen?
Answer: Do not dispatch the task. Schema validity only proves the data matches the expected structure. Application trust/authorization policy must still approve the remote agent and endpoint.
Key takeaways
- Be able to explain Why we need another protocol after MCP and point to its implementation/evidence in Day 40.
- Be able to explain Protocol boundary versus implementation framework and point to its implementation/evidence in Day 40.
- Be able to explain Agent discovery with the Agent Card and point to its implementation/evidence in Day 40.
Official references
- A2A Protocol Specification: https://a2a-protocol.org/latest/specification/
- A2A v1 changes: https://a2a-protocol.org/latest/whats-new-v1/
- RFC 7515 — JSON Web Signature (JWS): https://datatracker.ietf.org/doc/html/rfc7515
- RFC 8785 — JSON Canonicalization Scheme (JCS): https://datatracker.ietf.org/doc/html/rfc8785
03 · EXPLAIN
Interview checkpoint
Explain one design decision from this lesson, the alternative you rejected, and the failure mode or evidence that justified your choice.