Protocolo A2A Google Open Protocol v0.3.0

Pipeline Multi-Agente
para CULTIVA IA

Implementación del protocolo Agent-to-Agent de Google para orquestar agentes especializados en la generación de contenido de marketing. JSON-RPC 2.0 · SSE Streaming · Agent Discovery

🎯 Caso de uso: PeluquIA — app de reservas con IA para peluquerías · Brief de lanzamiento → Contenido multicanal completo
1
Arquitectura del Sistema
4 agentes A2A independientes · 1 orchestrator · comunicación estandarizada via JSON-RPC 2.0
🏢
CULTIVA IA
Cliente / Usuario
brief: "Lanzamiento
PeluquIA"
HTTP POST
/tasks/send
🧠
orchestrator-agent
A2A Server · Puerto 8000
Coordina el pipeline
/.well-known/agent.json
A2A Client
Delegation
🔍
research-agent
Puerto 8001 · web-search, competitor-analysis
✍️
writer-agent
Puerto 8002 · copy-web, email, social-posts
🎯
reviewer-agent
Puerto 8003 · brand-check, quality-review
📡 SSE Streaming
🔔 Push Notifications
🔎 Agent Discovery
📋 Task Lifecycle
🔐 Auth: Bearer JWT
2
Agent Cards — Ficheros de Descubrimiento
Servidos automáticamente en /.well-known/agent.json de cada agente
🔍
research-agent
research.cultivaia.app/.well-known/agent.json
version 1.0.0
inputModes text/plain, application/json
outputModes application/json, text/plain
auth Bearer JWT (ApiKey)
web-search competitor-analysis trend-detection streaming ✓ pushNotifications ✓
SSE push stateHistory
✍️
writer-agent
writer.cultivaia.app/.well-known/agent.json
version 1.0.0
inputModes application/json, text/plain
outputModes application/json
auth Bearer JWT (ApiKey)
copy-web email-copy social-posts ad-copy streaming ✓
SSE multiTurn
🎯
reviewer-agent
reviewer.cultivaia.app/.well-known/agent.json
version 1.0.0
inputModes application/json
outputModes application/json
auth Bearer JWT (ApiKey)
brand-check quality-review tone-analysis humanInLoop ✓
SSE input-required
3
Código de Implementación
Python · a2a-sdk · FastAPI/Starlette · Orquestación secuencial con fan-out
research_agent.py Python
# research_agent.py — A2A Server
from a2a.types import AgentCard, AgentSkill, AgentCapabilities
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.apps.starlette import A2AStarletteApplication
from a2a.types import Message, TextPart, DataPart
from a2a.types import TaskState, TaskStatus
import uvicorn, asyncio

agent_card = AgentCard(
    name="research-agent",
    description="Investiga mercado y competencia para campañas",
    url="https://research.cultivaia.app",
    version="1.0.0",
    capabilities=AgentCapabilities(
        streaming=True,
        pushNotifications=True,
    ),
    skills=[
        AgentSkill(
            id="web-search",
            name="Búsqueda Web",
            description="Investiga tendencias, competidores y público",
            tags=["research", "market", "competitors"],
            examples=["Analiza el mercado de apps de reserva para peluquerías"],
        ),
    ],
    defaultInputModes=["text/plain", "application/json"],
    defaultOutputModes=["application/json"],
)

class ResearchExecutor(AgentExecutor):
    async def execute(self, ctx: RequestContext,
                      eq: EventQueue):
        query = ctx.get_user_message().parts[0].text

        # Emitir estado working con SSE
        await eq.enqueue_event(TaskStatus(
            state=TaskState.working,
            message=Message(role="agent",
                parts=[TextPart(text="🔍 Analizando mercado...")])
        ))

        results = await self._research(query)

        # Emitir resultado como DataPart (JSON)
        await eq.enqueue_event(TaskStatus(
            state=TaskState.completed,
            message=Message(role="agent",
                parts=[DataPart(data=results)])
        ))

    async def _research(self, query: str) -> dict:
        await asyncio.sleep(2)  # LLM call
        return {
            "target_audience": "Dueños peluquería 35-55",
            "pain_points": ["citas por teléfono", "no-shows"],
            "competitors": ["Planfy", "Setmore", "SimplyBook"],
            "differentiators": ["IA predictiva de hueco", "sin comisión"],
        }

    async def cancel(self, ctx: RequestContext,
                    eq: EventQueue):
        await eq.enqueue_event(
            TaskStatus(state=TaskState.canceled))

handler = DefaultRequestHandler(
    agent_executor=ResearchExecutor(),
    task_store=InMemoryTaskStore(),
)
app = A2AStarletteApplication(
    agent_card=agent_card,
    http_handler=handler,
)
uvicorn.run(app.build(), host="0.0.0.0", port=8001)
orchestrator.py Python
# orchestrator.py — Pipeline A2A secuencial
from a2a.client import A2AClient
from a2a.types import MessageSendParams, SendMessageRequest
from a2a.types import Message, TextPart, DataPart
import asyncio, json

async def build_content_pipeline(brief: str):
    """Pipeline: research → write → review"""

    # 1. Descubrir agentes via Agent Cards
    research = await A2AClient.get_client_from_agent_card_url(
        "https://research.cultivaia.app/.well-known/agent.json"
    )
    writer = await A2AClient.get_client_from_agent_card_url(
        "https://writer.cultivaia.app/.well-known/agent.json"
    )
    reviewer = await A2AClient.get_client_from_agent_card_url(
        "https://reviewer.cultivaia.app/.well-known/agent.json"
    )

    # 2. STEP 1 — Investigar mercado
    research_resp = await research.send_message(
        SendMessageRequest(params=MessageSendParams(
            message=Message(role="user",
                parts=[TextPart(text=f"Investiga: {brief}")])
        ))
    )
    market_data = research_resp.status.message.parts[0].data
    print(f"✅ Research completado: {market_data['target_audience']}")

    # 3. STEP 2 — Generar copies (con streaming)
    async for event in writer.send_message_streaming(
        SendMessageRequest(params=MessageSendParams(
            message=Message(role="user",
                parts=[DataPart(data={
                    "brief": brief,
                    "market_data": market_data,
                    "channels": ["web", "email", "instagram"],
                })])
        ))
    ):
        if hasattr(event, "status") and event.status.message:
            part = event.status.message.parts[0]
            if hasattr(part, "text"):
                print(part.text, end="", flush=True)
            elif hasattr(part, "data"):
                content_package = part.data

    # 4. STEP 3 — Revisar con human-in-the-loop
    review_resp = await reviewer.send_message(
        SendMessageRequest(params=MessageSendParams(
            message=Message(role="user",
                parts=[DataPart(data=content_package)])
        ))
    )

    # Manejar input-required (aprobación humana)
    if review_resp.status.state.value == "input-required":
        print("\n⚠️ Revisión manual requerida")
        return review_resp

    return content_package

# Ejecutar pipeline
brief = """Lanzamiento PeluquIA: app de reservas con IA
para peluquerías. Target: dueños 35-55 años.
Tono: cercano, moderno, sin tecnicismos."""

result = asyncio.run(build_content_pipeline(brief))
4
Ciclo de Vida de una Tarea A2A
Estados del task para el brief "PeluquIA" a través del pipeline
PASO 1
Cliente → Orchestrator
Brief enviado via HTTP POST /tasks/send
POST /tasks/send
role: "user"
text: "Lanzamiento PeluquIA..."
PASO 2
working
Orchestrator → Research
SSE streaming con actualizaciones en tiempo real
data: {"status": "working"}
data: "🔍 Analizando mercado..."
data: {"target": "35-55 años"}
PASO 3
streaming
Research → Writer
DataPart JSON con datos de mercado como input
data: {"pain_points": [...]}
data: "✍️ Generando copies..."
data: {"channels": ["web","ig"]}
PASO 4
completed
Reviewer → Cliente
Paquete de contenido validado y entregado
data: {"state": "completed"}
artifacts: [web_copy, email,
instagram_posts, ad_copy]
5
Output del Pipeline — Paquete de Contenido PeluquIA
Artefactos JSON generados por writer-agent y validados por reviewer-agent
web_copy
email
instagram
ad_copy
writer-agent · artifacts[0] · web_copy
"web_copy": {
  "hero_headline": "Tu peluquería llena,\nsin colgar el teléfono",
  "hero_sub": "PeluquIA gestiona tus citas\nautomáticamente. Sin comisión.",
  "cta": "Prueba gratis 30 días",
  "features": [
    "IA predice huecos libres",
    "Recuerda citas por WhatsApp",
    "Sin instalación, desde móvil"
  ],
  "social_proof": "+1.200 peluquerías\nya usan PeluquIA",
  "tone_score": 9.2,
  "reading_level": "B1",
  "technicism_count": 0
}
reviewer-agent · review_report
"review_report": {
  "status": "approved",
  "brand_alignment": 9.4,
  "tone_check": "cercano ✓ moderno ✓",
  "technicism_check": "sin tecnicismos ✓",
  "clarity_score": 9.1,
  "suggestions": [
    "Añadir número concreto de tiempo\nahorrado por semana"
  ],
  "channels_reviewed": [
    "web", "email",
    "instagram", "ads"
  ],
  "approved_by": "reviewer-agent v1.0.0",
  "timestamp": "2026-06-18T09:41:22Z"
}
6
A2A vs MCP — Cuándo usar cada protocolo
Referencia rápida para arquitecturas de agentes CULTIVA IA
Dimensión Protocolo A2A Protocolo MCP
Propósito Agente ↔ Agente Agente ↔ Herramienta/Datos
Actores Dos agentes autónomos con lógica propia Agente + función/API stateless
Tareas Stateful, long-running, cancelables, async Llamadas de función stateless
Descubrimiento ✓ Agent Cards automáticas ✗ Configuración manual
Streaming ✓ SSE nativo Parcial (depende del servidor)
Human-in-the-loop ✓ Estado input-required ✗ No nativo
Uso en CULTIVA IA Delegar subtareas a agentes especializados (research, writer, reviewer) Integrar herramientas dentro de un agente (buscar en web, escribir en Notion, enviar email)