A practical guide to connecting specialized AI agents across security boundaries and SDK frameworks using Gemini Agent Platform.
Authors: Pedro Melendez & Sergio Villani
Moving Beyond the Monolithic Agent
As enterprise teams move from single-purpose AI assistants to production workflows, a common anti-pattern emerges: the monolithic “do-everything” agent. Developers keep bolting more SQL connections, internal REST APIs, and domain instructions onto a single agent until its context window bloats, tool selection degrades, and security boundaries blur.
In any real organization, data, domain expertise, and technology choices are distributed across independent teams and trust boundaries:
- Domain & Data Ownership: Different business units (such as Finance, Supply Chain, Analytics, or HR) own their own data warehouses, schemas, and domain logic, and cannot hand raw database credentials to another team’s agent.
- Security & Governance Boundaries: Sensitive internal systems sit behind strict Zero-Trust controls, requiring centralized service discovery, identity verification, and short-lived access tokens.
- Heterogeneous Agent Frameworks: Across internal groups, newly acquired teams, and external partners, agents are built on different SDKs (such as Google ADK, OpenAI Agents SDK, LangGraph, or CrewAI) that need to collaborate without forcing a full stack rewrite.
Instead of forcing one agent to hold every database credential and schema, teams split responsibilities across specialized agents. However, without a shared communication standard, connecting those agents quickly creates a new bottleneck: chaotic point-to-point integrations. Every pair of agents requires custom glue code across incompatible transports and message formats (REST, gRPC, RabbitMQ, NATS, MQTT, or legacy agent languages like FIPA-ACL and KQML).

The Agent2Agent (A2A) Protocol eliminates this spaghetti wiring by introducing a single, open, standardized protocol for agent collaboration. Instead of building custom adapters for every new agent pair, each agent exposes a standard A2A interface over HTTP, discovering capabilities dynamically and delegating tasks without exposing its private prompts, tools, or underlying databases.

In this post, we walk through a practical, four-agent enterprise architecture that combines Model Context Protocol (MCP) for local tools, Agent2Agent (A2A) for cross-service collaboration, and cross-framework interoperability between Google ADK and the OpenAI Agents SDK, all powered by Gemini Agent Platform.
MCP vs. A2A
A common architectural question is: “If I already use the Model Context Protocol (MCP), why do I need A2A?”
MCP and A2A are complementary layers of the modern agentic stack:
- MCP (Vertical Integration / Agent-to-Tool): Standardized “USB-C” ports that connect an individual agent to its private, deterministic tools, local files, and database connectors. An MCP tool is stateless and executes a predefined function with structured inputs.
- A2A (Horizontal Integration / Agent-to-Agent): Standardized networking that connects independent agents across teams, clouds, and frameworks. An A2A target is an autonomous peer that can reason, plan, enforce its own security policies, and converse over multiple turns.
Is your service a tool or an agent, or both? Often both, if you’re building agents. A good mental model is to ask yourself if your service returns an answer or takes responsibility for an outcome.

The Enterprise Scenario
To see how MCP and A2A work together in practice, consider a multi-team analytics workflow where an organization wants to compare internal workforce demographics against public US Census name trends and archive executive reports in a local knowledge base.
Rather than giving a single monolithic agent direct access to every database and credential, four teams each own a specialized agent powered by Gemini models:
- core_analytics_agent — Core Analytics Team (Google ADK on :8003): Queries public US Social Security Administration name records (bigquery-public-data.usa_names.usa_1910_current) via BigQuery SQL (analyze_name_in_bigquery) and manages saved reports in a local JSON knowledge base (local_kb.json) via MCP (query_local_mcp_kb and write_local_mcp_kb).
- partner_openai_agent — Partner / Acquired Team (OpenAI Agents SDK): Runs an existing assistant built on the OpenAI Agents SDK (agents.Agent) that needs to run cross-domain analytics without duplicating the Core Analytics Team’s BigQuery SQL queries, MCP storage tools, or security logic.
- gateway_agent — Platform & Security Team (Service Registry & Token Broker on :8001): Acts as the central control plane, maintaining the live service registry of internal agents and brokering short-lived, cryptographically signed HS256 JWTs (X-Gateway-Token).
- corporate_names_analytics_agent — HR & Corporate Systems Team (Protected Corporate Directory on :8002): Guards a private 5,000-Employee Corporate Directory (corp_employees) in SQLite behind Zero-Trust HTTP middleware that rejects any request lacking a valid X-Gateway-Token JWT (401 Unauthorized).

REST vs. A2A Discovery
How should core_analytics_agent discover corporate_names_analytics_agent and obtain an X-Gateway-Token from gateway_agent? Let’s first see what happens if we wire them together using traditional REST tools (/api/v1/lookup and /api/v1/token) with hardcoded service identifiers:
# Fragile REST approach: hardcoded service_key parameter
def lookup_service_via_rest(service_key: str) -> str:
"""Calls GET /api/v1/lookup?service_key=<service_key>.
Known service keys: 'corporate_names_analytics_agent'.
"""
The moment the Platform Team migrates the internal registry key from corporate_names_analytics_agent to corp_analytics_registry (corporate_names_analytics_v2) during a service upgrade, the REST integration breaks with a 400 Bad Request error (curl -i “http://localhost:8001/api/v1/lookup?service_key=corporate_names_analytics_agent"). Because a REST endpoint only matches exact string keys, the caller is blocked and cannot reach the Corporate Names Agent.

Semantic A2A Discovery & Token Exchange
When we replace those rigid REST calls with an A2A sub-agent (RemoteA2aAgent), core_analytics_agent no longer guesses internal database keys or passes static API keys through prompts:
- Out-of-Band OIDC Identity: Its httpx client (GoogleCloudOIDCAuth) automatically attaches the caller’s Google Cloud OIDC Bearer token in the HTTP Authorization header while fetching the Gateway’s machine-readable AgentCard (/.well-known/agent-card.json).
- Semantic Resolution & JWT Minting: It asks gateway_agent in natural language for the corporate employee directory service and an access token. The Gateway’s LLM semantically maps the request to corporate_names_analytics_v2, verifies the caller’s Google OIDC identity from the HTTP header, and mints a short-lived (5-minute TTL), signed HS256 JWT (X-Gateway-Token) scoped to target audience “corporate_names_analytics_v2”. (In the companion repo, gateway_agent and corporate_names_analytics_agent share a default demo GATEWAY_JWT_SECRET in .env.sample so local testing works out of the box; in production, inject this secret via Secret Manager or use asymmetric RSA/ECDSA keys with a public JWKS endpoint.)
- End-to-End Analytics & MCP Persistence: Using contact_discovered_a2a_agent, core_analytics_agent attaches the signed X-Gateway-Token header to query corporate_names_analytics_agent (:8002), compares the returned corporate names (such as Alex, Michael, and David) against historical US birth totals in BigQuery (analyze_name_in_bigquery), and saves the executive comparison report into local_kb.json via MCP (write_local_mcp_kb).
Cross-Framework Collaboration
Now how does the Partner Team (partner_openai_agent, built on the OpenAI Agents SDK) tap into this workflow without copying the Core Analytics Team’s BigQuery SQL queries, Local MCP tools, and Gateway token-brokering code into a second repository?
Because A2A standardizes the wire protocol over HTTP, JSON-RPC 2.0, and /.well-known/agent-card.json:
- The Core Analytics Team exposes their existing Google ADK orchestrator (core_analytics_agent) as a standards-compliant A2A Server on port 8003 in just two lines of code (to_a2a(root_agent, port=8003)).
- The Partner Team keeps their OpenAI Agents SDK agent (partner_openai_agent/openai_agent.py, powered by a Gemini model via OpenAIChatCompletionsModel) and delegates cross-domain analytics tasks to http://localhost:8003 over A2A.

1. Core Analytics Team (Google ADK)
from google.adk.agents import Agent
from google.adk.agents.remote_a2a_agent import AGENT_CARD_WELL_KNOWN_PATH, RemoteA2aAgent
from google.adk.tools.agent_tool import AgentTool
from .instructions import agent_instructions
from .tools import (
analyze_name_in_bigquery,
contact_discovered_a2a_agent,
query_local_mcp_kb,
write_local_mcp_kb,
)
from .utils import GATEWAY_URL, get_gateway_httpx_client, log_after_tool, log_before_tool
# --- A2A Sub-Agent: Gateway Service Registry & OIDC-to-JWT Token Broker ---
gateway_agent = RemoteA2aAgent(
name="gateway_agent",
description=(
"Remote Gateway Agent for service discovery and OIDC-to-JWT token"
" brokering."
),
agent_card=f"{GATEWAY_URL}{AGENT_CARD_WELL_KNOWN_PATH}",
httpx_client=get_gateway_httpx_client(),
)
# --- Main Orchestrator Agent ---
root_agent = Agent(
name="core_analytics_agent",
model=os.getenv("MODEL_NAME"),
description=(
"US and Corporate Name Analytics Orchestrator powered by BigQuery,"
" Local MCP, and the Agent2Agent (A2A) protocol on Gemini Agent Platform."
),
instruction=agent_instructions,
tools=[
analyze_name_in_bigquery,
query_local_mcp_kb,
write_local_mcp_kb,
AgentTool(agent=gateway_agent),
contact_discovered_a2a_agent,
],
before_tool_callback=log_before_tool,
after_tool_callback=log_after_tool,
)
2. Partner Team (OpenAI Agents SDK)
Using the Gemini OpenAI-compatible Chat Completions endpoint (OpenAIChatCompletionsModel in openai_utils.py with Google Cloud ADC auth), the Partner Team’s OpenAI agent simply delegates cross-domain analytics tasks to the Core Analytics Team’s ADK A2A Server (http://localhost:8003):
from agents import Agent
from .openai_tools import call_core_analytics_a2a_agent, contact_discovered_a2a_agent
from .openai_utils import get_gemini_openai_model
root_agent = Agent(
name="partner_openai_agent",
model=get_gemini_openai_model(os.getenv("MODEL_NAME")),
instructions=PARTNER_AGENT_INSTRUCTIONS,
tools=[
call_core_analytics_a2a_agent,
contact_discovered_a2a_agent,
],
)
Stateful A2A Task Lifecycle
A key distinction between an MCP tool call and an A2A collaboration is stateful task management. While an MCP tool executes a single stateless function and returns immediately, an enterprise A2A workflow often spans multiple downstream agents, crosses security boundaries, and requires human-in-the-loop (HITL) authorization before accessing restricted data or mutating persistent storage.
The A2A Protocol models these long-running, multi-turn interactions as a first-class Task object tracked by a persistent taskId and contextId across standardized JSON-RPC 2.0 methods (message/stream, message/send, and tasks/get).

Step-by-Step Lifecycle
When the Partner Team’s OpenAI Agents SDK client (partner_openai_agent) delegates a cross-domain request (for example, analyzing the name ‘Ebba’ across public US Census records and the internal Corporate HR Directory, then archiving the report in local_kb.json) to the Core Analytics Team’s Google ADK A2A Server (core_analytics_agent on :8003), the four agents coordinate through five stateful lifecycle phases:
🔄 Phase 1: Discovery & Streaming Submission
- Method & Event: GET /.well-known/agent-card.json | POST message/stream (SSE)
- TaskState: submitted ➔ working
- What Happens: The Partner Team’s OpenAI Agent discovers the Google ADK A2A Server (:8003) and opens a Server-Sent Events (SSE) stream. The ADK server allocates a persistent taskId and contextId and begins processing.
🔐 Phase 2: Human-in-the-Loop Gate 1/2 (Corporate Auth)
- Method & Event: TaskStatusUpdateEvent | POST tasks/get
- TaskState: input-required
- What Happens: Because querying the Corporate HR Directory requires brokering a privileged X-Gateway-Token JWT on the caller's behalf, the ADK server pauses the task. The OpenAI client inspects the paused task via tasks/get and prompts the user:
- [Confirm A2A Task (1/2)] Allow core_analytics_agent to broker an X-Gateway-Token and query corporate_names_analytics_agent? [Y/n]
🚀 Phase 3: Multi-Agent Fan-Out (Resume taskId)
- Method & Event: POST message/stream (same taskId & contextId)
- TaskState: working
- What Happens: Once the user approves, the OpenAI client resumes. The Google ADK server parallelizes the work:
- BigQuery: Queries usa_names for national historical stats.
- Gateway Agent (:8001): Calls gateway over A2A with its Google Cloud OIDC token to discover corporate_names_analytics_v2 and mints a signed HS256 token.
- Analytics Agent (:8002): Calls corporate analytics over A2A with the gateway token to fetch internal employee counts.
🛑 Phase 4: Human-in-the-Loop Gate 2/2 (MCP Write)
- Method & Event: TaskStatusUpdateEvent | POST tasks/get
- TaskState: input-required
- What Happens: With public and corporate data collected, the ADK server pauses a second time before modifying local_kb.json.
- [Confirm A2A Task (2/2)] Allow core_analytics_agent to write to local_kb.json? [Y/n]
(If the user declines, the task is cleanly aborted without mutating local storage.)
🏁 Phase 5: Artifact Delivery & Verification
- Method & Event: TaskArtifactUpdateEvent | TaskStatusUpdateEvent | POST tasks/get
- TaskState: completed (final=True)
- What Happens: Upon final approval, the ADK server resumes, writes ebba_a2a_report into local_kb.json via MCP (write_local_mcp_kb), streams back the structured analytics_report Artifact, and transitions to completed. The OpenAI client calls tasks/get to verify the persisted history.
Live A2A Lifecycle Trace
When running this cross-framework delegation in the companion repository, the OpenAI Agents SDK client logs every SSE state transition, tasks/get inspection, and [Y/n] human-in-the-loop gate in real time:
🌐 [A2A Discovery] http://localhost:8003/.well-known/agent-card.json -> 'core_analytics_agent'
📤 [A2A Request -> http://localhost:8003] Analyze the name 'Ebba' in BigQuery, check 'Ebba' in the corporate directory via its Gateway Agent, and save a summary note under 'ebba_a2a_report' in its local knowledge base!
🔄 [A2A Task Lifecycle (message/stream SSE)] taskId=task-9f4a1c2e | contextId=ctx-3b8d7e10
├─ State: submitted (Task accepted & queued by A2A server)
├─ State: working (Remote agent processing request...)
└─ State: input-required (Task paused: server requested caller confirmation)
🔍 [A2A tasks/get] Inspected persisted Task taskId=task-9f4a1c2e -> state='input-required' (history=2 messages, artifacts=0)
⏸️ [A2A Human-in-the-Loop (Step 1/2)] Server paused Task taskId=task-9f4a1c2e in state='input-required' (brokering X-Gateway-Token for Corporate Directory requires caller authorization).
❓ [Confirm A2A Task task-9f4a1c2e (1/2)] Allow 'core_analytics_agent' to broker an X-Gateway-Token and query 'corporate_names_analytics_agent'? [Y/n] > y
🔁 [A2A Task Continuation (1/2)] Resuming paused taskId=task-9f4a1c2e | contextId=ctx-3b8d7e10
🔄 [A2A Task Lifecycle (Resume taskId=task-9f4a1c2e)] taskId=task-9f4a1c2e | contextId=ctx-3b8d7e10
├─ State: working (Running BigQuery SQL & brokering X-Gateway-Token via Gateway Agent...)
├─ Progress: Querying 'corporate_names_analytics_agent' with X-Gateway-Token...
└─ State: input-required (Task paused: awaiting caller confirmation before writing to local_kb.json)
🔍 [A2A tasks/get] Inspected persisted Task taskId=task-9f4a1c2e -> state='input-required' (history=4 messages, artifacts=0)
⏸️ [A2A Human-in-the-Loop (Step 2/2)] Corporate Directory analytics ready. Server paused Task taskId=task-9f4a1c2e in state='input-required' (writing to local_kb.json requires caller confirmation).
❓ [Confirm A2A Task task-9f4a1c2e (2/2)] Allow 'core_analytics_agent' to write to local_kb.json? [Y/n] > y
🔁 [A2A Task Continuation (2/2)] Resuming paused taskId=task-9f4a1c2e | contextId=ctx-3b8d7e10
🔄 [A2A Task Lifecycle (Resume taskId=task-9f4a1c2e)] taskId=task-9f4a1c2e | contextId=ctx-3b8d7e10
├─ State: working (Saving summary note to local_kb.json & finalizing report...)
├─ Artifact: Received artifact 'analytics_report' (1 part(s))
└─ State: completed (Task finished!)
🔍 [A2A tasks/get] Inspected persisted Task taskId=task-9f4a1c2e -> state='completed' (history=6 messages, artifacts=1)
Key Takeaways
- Keep Agents Focused and Specialized: Avoid monolithic agents that accumulate every tool and credential in the company. Let each domain team own a focused agent behind a clean A2A boundary.
- Pair MCP with A2A: Use MCP vertically to connect an agent to its local databases, files, and deterministic functions. Use A2A horizontally so autonomous agents can discover and collaborate with one another.
- Use Semantic Discovery Instead of Hardcoded Schemas: Replacing rigid REST lookup parameters with /.well-known/agent-card.json and natural-language delegation makes multi-agent systems resilient to backend version changes.
- Enforce Zero-Trust Security & Stateful Human-in-the-Loop Gates: Never share raw database credentials across agents. Broker short-lived tokens (X-Gateway-Token) through a dedicated Gateway Agent, validate them in HTTP middleware, and use A2A’s input-required state to pause multi-agent tasks whenever privileged actions require human approval.
- Standardize the Wire Protocol, Not the SDK: With A2A and Gemini Agent Platform, teams using Google ADK and the OpenAI Agents SDK can interoperate effortlessly without rewriting their existing agents.
Explore the Code on GitHub
Ready to try this architecture yourself? Check out the companion GitHub repository for the complete source code of all four agents (core_analytics_agent in Google ADK, partner_openai_agent in the OpenAI Agents SDK, gateway_agent, and corporate_names_analytics_agent), along with step-by-step instructions to run and test the entire multi-agent A2A workflow locally.
From Solo Agents to Agent Ecosystems with A2A was originally published in Google Cloud – Community on Medium, where people are continuing the conversation by highlighting and responding to this story.
Source Credit: https://medium.com/google-cloud/from-solo-agents-to-agent-ecosystems-with-a2a-ac460584b23a?source=rss—-e52cf94d98af—4
