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"
PeluquIA"
▶
HTTP POST
/tasks/send
/tasks/send
🧠
orchestrator-agent
A2A Server · Puerto 8000
Coordina el pipeline
/.well-known/agent.json
▶
A2A Client
Delegation
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)
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)
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)
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
submitted
Cliente → Orchestrator
Brief enviado via HTTP POST /tasks/send
POST /tasks/send
role: "user"
text: "Lanzamiento PeluquIA..."
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"}
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"]}
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]
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) |