GUION v3 — 7 Agentes ADK en PRODUCCIÓN: Cloud Run + Protocolo A2A
GUION v3 — 7 Agentes ADK en PRODUCCIÓN: Cloud Run + Protocolo A2A
Sección titulada «GUION v3 — 7 Agentes ADK en PRODUCCIÓN: Cloud Run + Protocolo A2A»“Del local a producción real: 7 agentes ADK como servidores A2A independientes en Cloud Run”
Duración objetivo: 45-50 minutos Patrón: Resultado primero → contexto → concepto → setup GCP → deploy en vivo → momento wow → cierre Fecha: 2026-04-04 (actualizado con dry run real)
HOOK — RESULTADO PRIMERO (0:00 - 1:30)
Sección titulada «HOOK — RESULTADO PRIMERO (0:00 - 1:30)»[PANTALLA: Terminal — orchestrator_a2a.py corriendo, logs de los 7 agentes respondiendo desde Cloud Run]
TÚ: “Lo que ves ahí son 7 agentes de IA respondiendo en cadena. No en local. No en Docker. Desde Google Cloud.”
[CORTE A: Terminal mostrando las 7 URLs activas]
platform-architect → https://platform-architect-r7xydrdtta-uc.a.run.appinfrastructure → https://infrastructure-r7xydrdtta-uc.a.run.appsecurity → https://security-r7xydrdtta-uc.a.run.appcicd → https://cicd-r7xydrdtta-uc.a.run.appobservability → https://observability-r7xydrdtta-uc.a.run.appdevex → https://devex-r7xydrdtta-uc.a.run.appweb-portal → https://web-portal-r7xydrdtta-uc.a.run.appTÚ: “Siete URLs. Siete servicios independientes en Cloud Run. Cada uno es un agente ADK corriendo el protocolo A2A de Google.”
[TÍTULO: “7 Agentes ADK en PRODUCCIÓN — Cloud Run + Protocolo A2A”]
OPEN LOOPS (1:30 - 2:30)
Sección titulada «OPEN LOOPS (1:30 - 2:30)»TÚ: “Tres cosas que vas a ver en este video:”
“Primero: por qué el sistema del video anterior no podía ir a producción y qué cambié en el código para que funcionara.”
“Segundo: el setup completo de GCP — proyecto, APIs, permisos, secrets — sin saltar nada. Esto es lo que la mayoría de tutoriales omite.”
“Tercero: los siete deploys en vivo, uno por uno, con los tiempos reales. Y al final la cadena completa corriendo desde Cloud Run.”
CONTEXTO — EL VIDEO ANTERIOR (2:30 - 6:00)
Sección titulada «CONTEXTO — EL VIDEO ANTERIOR (2:30 - 6:00)»[MOSTRAR: Screenshot del video ADK anterior]
TÚ: “Contexto rápido. En el video anterior construí un IDP completo con 7 agentes ADK: Platform Architect, Infrastructure, Security, CI/CD, Observability, DevEx, Web Portal. Todo en local, todo en Docker.”
[MOSTRAR: Diagrama — un solo contenedor, filesystem compartido]
“Funcionaba así: un solo proceso Docker, los 7 agentes en secuencia, compartiendo un filesystem. El Platform Architect escribía un YAML al disco. El Infrastructure lo leía. Y así en cascada.”
“Funcionó. El video anduvo bien. Pero había un problema que no mencioné.”
[PAUSA]
“Todo eso es desarrollo local. En Cloud Run eso no existe.”
CONCEPTO — POR QUÉ EL SISTEMA LOCAL NO ESCALA (6:00 - 18:00)
Sección titulada «CONCEPTO — POR QUÉ EL SISTEMA LOCAL NO ESCALA (6:00 - 18:00)»El problema del filesystem compartido (6:00 - 9:00)
Sección titulada «El problema del filesystem compartido (6:00 - 9:00)»TÚ: “El problema central es este.”
[MOSTRAR: Código — OUTPUT_DIR = os.getenv(‘ADK_OUTPUT_DIR’, ‘/app/outputs’)]
“Cada agente leía y escribía en este directorio. Funcionaba porque todos corrían en el mismo contenedor, compartiendo el mismo filesystem.”
[MOSTRAR: Diagrama — 7 contenedores independientes en Cloud Run, sin filesystem compartido]
“En Cloud Run, cada servicio es un contenedor independiente. Si el Platform Architect escribe en /app/outputs, el Infrastructure no puede leer ese archivo — está en otro contenedor, en otra máquina, posiblemente en otra región. El filesystem compartido no existe.”
“El modelo mental cambia completamente. De centralizado a distribuido.”
La solución — protocolo A2A (9:00 - 14:00)
Sección titulada «La solución — protocolo A2A (9:00 - 14:00)»TÚ: “La solución es A2A — Agent-to-Agent. Un protocolo abierto que Google donó a la Linux Foundation.”
[MOSTRAR: a2a-protocol.org]
“En lugar de compartir estado via filesystem, el estado viaja en los mensajes. El orquestador toma el output de cada agente y lo inyecta como contexto en el siguiente.”
[DIAGRAMA — flujo A2A]
Orchestrator → POST /run → Platform ArchitectPlatform Architect responde → "Python, FastAPI, PostgreSQL, Redis"
Orchestrator toma esa respuesta → POST /run → Infrastructure con el contexto: "el Platform Architect decidió este stack..."
Infrastructure responde → docker-compose generado
Y así con los 7 en cadena.“JSON sobre HTTPS. Stateless. Cada agente recibe su tarea con el contexto necesario, procesa, responde. Sin estado compartido.”
El refactor (14:00 - 18:00)
Sección titulada «El refactor (14:00 - 18:00)»TÚ: “Para que esto funcionara en Cloud Run, hice tres cambios al proyecto del video anterior.”
[MOSTRAR: Estructura de carpetas — antes vs después]
“Primero: cada agente necesitaba su propio servidor HTTP. Agregué un __main__.py que inicia adk api_server — el servidor HTTP que viene incluido en el paquete de Google ADK.”
[MOSTRAR: main.py]
ENTRYPOINT ["uv", "run", "adk", "api_server", "--host", "0.0.0.0", "--port", "8080"]“Segundo: cada agente necesitaba su propio Dockerfile.”
[MOSTRAR: Dockerfile]
FROM python:3.13-slimCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/EXPOSE 8080WORKDIR /appCOPY pyproject.toml uv.lock ./RUN uv sync --no-dev --frozenCOPY . /app/platform_architect/ENTRYPOINT ["uv", "run", "adk", "api_server", "--host", "0.0.0.0", "--port", "8080"]“Noten: python:3.13-slim. No Alpine. Alpine rompe con grpcio — una dependencia de ADK — porque usa musl libc en lugar de glibc. Ese detalle no está documentado claramente en ningún lado.”
“Tercero: los tools que leían del filesystem cambiaron para aceptar contexto via mensaje, con fallback al disco para modo local.”
[MOSTRAR: cambio en get_platform_config()]
# Antes — solo lee del discodef get_platform_config() -> dict: config_path = Path(OUTPUT_DIR) / "platform-config.yaml" with open(config_path, 'r') as f: return yaml.safe_load(f)
# Después — acepta contexto A2A, fallback al disco para localdef get_platform_config(context_json: str = "") -> dict: if context_json: data = json.loads(context_json) return data.get("platform_config") or data config_path = Path(OUTPUT_DIR) / "platform-config.yaml" with open(config_path, 'r') as f: return yaml.safe_load(f)“Este cambio mantiene compatibilidad. En local sigue funcionando igual. En Cloud Run el contexto llega via mensaje.”
SETUP GCP (18:00 - 26:00)
Sección titulada «SETUP GCP (18:00 - 26:00)»TÚ: “Antes del deploy, hay que preparar GCP. Voy a mostrar cada paso — esto es lo que la mayoría de tutoriales omite o hace en background.”
Autenticación y proyecto (18:00 - 19:30)
Sección titulada «Autenticación y proyecto (18:00 - 19:30)»[MOSTRAR: terminal]
gcloud auth logingcloud config set project TU_PROJECT_IDgcloud config get-value project“Me autentico con mi cuenta de Google, seteo el proyecto, verifico. Tres comandos.”
Habilitar APIs (19:30 - 20:30)
Sección titulada «Habilitar APIs (19:30 - 20:30)»TÚ: “GCP tiene todo deshabilitado por defecto. Necesito habilitar cuatro APIs.”
gcloud services enable \ run.googleapis.com \ cloudbuild.googleapis.com \ artifactregistry.googleapis.com \ secretmanager.googleapis.com“Cloud Run para correr los servicios. Cloud Build para construir las imágenes Docker. Artifact Registry para guardarlas. Secret Manager para las credenciales — ahora explico por qué.”
Permisos del Service Account (20:30 - 22:30)
Sección titulada «Permisos del Service Account (20:30 - 22:30)»TÚ: “En GCP, cuando deploys un servicio, Cloud Run usa un service account para ejecutar. El nombre de ese service account sigue siempre el mismo patrón.”
[MOSTRAR en pantalla]
{PROJECT_NUMBER}-compute@developer.gserviceaccount.com“Noten: PROJECT_NUMBER, no PROJECT_ID. Son distintos. El PROJECT_ID es el nombre que tú le pusiste. El PROJECT_NUMBER lo asigna Google automáticamente. Para obtenerlo:”
PROJECT_NUMBER=$(gcloud projects describe TU_PROJECT_ID --format="value(projectNumber)")echo $PROJECT_NUMBER“Con ese número, doy permisos al service account para que Cloud Build pueda construir las imágenes:“
gcloud projects add-iam-policy-binding TU_PROJECT_ID \ --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \ --role="roles/storage.objectAdmin"
gcloud projects add-iam-policy-binding TU_PROJECT_ID \ --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \ --role="roles/cloudbuild.builds.builder"Gemini API Key en Secret Manager (22:30 - 26:00)
Sección titulada «Gemini API Key en Secret Manager (22:30 - 26:00)»TÚ: “Los agentes usan Gemini 2.5 Flash como modelo. Necesitan una API key. Y aquí viene algo importante:”
[PAUSA]
“Nunca pongas una API key en un Dockerfile. Nunca en el código. Nunca en una variable de entorno hardcodeada. Cloud Run tiene integración nativa con Secret Manager — úsala.”
“Primero creo el secret:”
echo -n "TU_GEMINI_API_KEY" | gcloud secrets create GEMINI_API_KEY --data-file=-“Luego doy acceso al service account de Cloud Run al secret:”
gcloud secrets add-iam-policy-binding GEMINI_API_KEY \ --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \ --role="roles/secretmanager.secretAccessor"“En el deploy voy a usar --set-secrets para inyectarla como variable de entorno. El contenedor nunca toca la key directamente — la lee de Secret Manager en runtime.”
DEPLOY EN VIVO (26:00 - 42:00)
Sección titulada «DEPLOY EN VIVO (26:00 - 42:00)»El comando (26:00 - 28:00)
Sección titulada «El comando (26:00 - 28:00)»TÚ: “El comando de deploy. Lo explico antes de correrlo para que entiendas cada flag.”
gcloud run deploy platform-architect \ --source=agents_adk/platform_architect/ \ --region=us-central1 \ --no-allow-unauthenticated \ --memory=1Gi \ --cpu=1 \ --min-instances=0 \ --max-instances=1 \ --cpu-throttling \ --timeout=300 \ --set-secrets="GEMINI_API_KEY=GEMINI_API_KEY:latest" \ --port=8080“--source apunta a la carpeta del agente — Cloud Build construye la imagen automáticamente desde el Dockerfile.”
“--no-allow-unauthenticated — el servicio requiere un IAM identity token para recibir requests. Seguridad básica.”
“--min-instances=0 — escala a cero cuando no hay tráfico. No pagas nada en idle.”
“--cpu-throttling — la CPU solo se asigna durante requests, no en espera. Más ahorro.”
“--set-secrets — inyecta la API key desde Secret Manager. El contenedor la recibe como variable de entorno en runtime, sin que esté hardcodeada en ningún lado.”
“Una cosa más — los nombres de servicios en Cloud Run solo aceptan letras minúsculas, números y guiones. Sin underscores. Por eso el agente web_portal se deploya como web-portal.”
Deploy ×7 (28:00 - 40:00)
Sección titulada «Deploy ×7 (28:00 - 40:00)»[DEPLOY 1 — platform-architect]
TÚ: “Platform Architect. El que decide el stack. Cloud Build va a construir la imagen desde el Dockerfile, subirla a Artifact Registry, y deployar el servicio. Voy a narrar mientras corre.”
[narrar el proceso: validación → build → push → deploy → routing]
[URL aparece]
TÚ:
“Ahí está la URL. Noten el patrón: {nombre-servicio}-{hash}-uc.a.run.app. Google genera este hash automáticamente — es único por proyecto y región. Todos sus agentes van a compartir el mismo hash r7xydrdtta, lo que hace fácil identificarlos.”
https://platform-architect-r7xydrdtta-uc.a.run.app[DEPLOY 2-7 — narrar cada uno brevemente]
TÚ para infrastructure: “Infrastructure. Genera el docker-compose basado en lo que decida el Platform Architect.”
TÚ para security: “Security. Escanea el código y genera las configuraciones de seguridad.”
TÚ para cicd: “CI/CD. Los pipelines de build, test y deploy.”
TÚ para observability: “Observability. Prometheus, Grafana, alertas.”
TÚ para devex: “DevEx. El CLI tool para los desarrolladores.”
TÚ para web-portal: “Web Portal. El último. Y ojo — el nombre tiene underscore en el código pero Cloud Run no acepta underscores, así que se deploya como web-portal con guión.”
[7 URLs en pantalla]
TÚ: “Siete de siete. Todos online.”
Verificación (40:00 - 42:00)
Sección titulada «Verificación (40:00 - 42:00)»TÚ: “Verifico que responden antes de correr la cadena.”
TOKEN=$(gcloud auth print-identity-token)
curl -s https://platform-architect-r7xydrdtta-uc.a.run.app/list-apps \ -H "Authorization: Bearer $TOKEN"# Responde: ["platform_architect"]“El endpoint /list-apps confirma que el agente está activo. El Bearer token es el IAM identity token — sin él el servicio rechaza la conexión.”
SETUP SPLIT SCREEN (42:00 - 44:00)
Sección titulada «SETUP SPLIT SCREEN (42:00 - 44:00)»TÚ: “Antes de correr el orchestrator, abro los logs de cada agente en tiempo real. Así pueden ver exactamente lo que pasa en cada servicio de Cloud Run.”
[ABRIR 7 TERMINALES en split screen]
gcloud run services logs tail platform-architect --region=us-central1gcloud run services logs tail infrastructure --region=us-central1gcloud run services logs tail security --region=us-central1gcloud run services logs tail cicd --region=us-central1gcloud run services logs tail observability --region=us-central1gcloud run services logs tail devex --region=us-central1gcloud run services logs tail web-portal --region=us-central1TÚ: “Por ahora silencio. Los agentes están en cero — no hay ningún contenedor corriendo. Escalan a cero cuando no los usan. Eso es Cloud Run.”
MOMENTO WOW — LA CADENA COMPLETA (44:00 - 52:00)
Sección titulada «MOMENTO WOW — LA CADENA COMPLETA (44:00 - 52:00)»TÚ:
“Ahora el momento que importa. Este es el orchestrator — pero no es un script HTTP manual. Es un SequentialAgent de ADK con 7 RemoteA2aAgent como sub-agentes. ADK maneja todo el protocolo A2A por nosotros.”
[MOSTRAR: orchestrator_sequential_a2a.py — la parte del SequentialAgent]
pipeline = SequentialAgent( name="idp_orchestrator", sub_agents=[ RemoteA2aAgent(name="platform_architect", agent_card=PA_CARD_URL), RemoteA2aAgent(name="infrastructure", agent_card=INFRA_CARD_URL), RemoteA2aAgent(name="security", agent_card=SEC_CARD_URL), RemoteA2aAgent(name="cicd", agent_card=CICD_CARD_URL), RemoteA2aAgent(name="observability", agent_card=OBS_CARD_URL), RemoteA2aAgent(name="devex", agent_card=DEV_CARD_URL), RemoteA2aAgent(name="web_portal", agent_card=WP_CARD_URL), ])TÚ:
“Cada agente tiene un agent-card.json — su tarjeta de presentación A2A. El RemoteA2aAgent la lee y sabe exactamente cómo comunicarse con él. Y el SequentialAgent garantiza el orden de ejecución.”
[MOSTRAR: agent-card.json de uno de los agentes]
TÚ: “Corro el orchestrator.”
cd agents_adk/platform_architectuv run python ../../orchestrator_sequential_a2a.py \ "Build IDP for a Python FastAPI application"[MOSTRAR: logs en tiempo real — cada agente respondiendo desde Cloud Run]
TÚ (narrando): “Platform Architect… decidió el stack…” “Infrastructure… generó el docker-compose…” “Security, CI/CD, Observability, DevEx, Web Portal…”
[RESPUESTA FINAL]
TÚ: “Siete de siete. La cadena completa. Cada agente corrió en Cloud Run, respondió via A2A, y el contexto fluyó de uno al siguiente.”
[PAUSA]
“Pero hay algo más.”
BONUS — EL PORTAL EN PRODUCCIÓN (50:00 - 55:00)
Sección titulada «BONUS — EL PORTAL EN PRODUCCIÓN (50:00 - 55:00)»TÚ: “El web portal agent generó el código de un dashboard. Lo deployamos como un servicio más en Cloud Run.”
gcloud run deploy idp-portal \ --source=outputs/portal/ \ --region=us-central1 \ --allow-unauthenticatedTÚ: “Y ahora lo exponemos en un dominio real.”
gcloud beta run domain-mappings create \ --service=idp-portal \ --domain=idp.nicolasneira.dev \ --region=us-central1[MOSTRAR: CNAME record en Cloudflare]
idp CNAME ghs.googlehosted.com.TÚ: “Google provisiona el certificado SSL automáticamente. Y ahora…”
[ABRIR BROWSER: https://idp.nicolasneira.dev]
TÚ: “Este portal está mostrando el estado real de los 7 agentes en Cloud Run. Los health checks los hace el backend del portal con IAM auth — el browser nunca toca los agentes directamente. Todo privado, todo en producción.”
[PAUSA]
“Esto no era posible con el sistema del video anterior. El filesystem compartido no existe en Cloud Run. El protocolo A2A lo resuelve — y el resultado es un sistema real, con URL, con SSL, accesible desde cualquier lugar.”
LECCIONES Y CIERRE (48:00 - 52:00)
Sección titulada «LECCIONES Y CIERRE (48:00 - 52:00)»Lecciones
Sección titulada «Lecciones»TÚ: “Seis cosas que me llevé:”
[LECCIÓN 1 — python:3.13-slim] “No Alpine. grpcio — que es una dependencia de ADK — no compila en Alpine porque usa musl libc. python:3.13-slim usa glibc. Ese detalle no está claro en ninguna documentación.”
[LECCIÓN 2 — Secret Manager]
“Nunca pongas credenciales en el Dockerfile ni en variables hardcodeadas. --set-secrets en Cloud Run inyecta el secret en runtime. Es una línea más en el deploy y elimina un riesgo real.”
[LECCIÓN 3 — PROJECT_NUMBER vs PROJECT_ID]
“Son distintos. El PROJECT_ID es el nombre que tú le pones. El PROJECT_NUMBER lo asigna Google. Para los service accounts necesitas el número, no el ID. Obtenlo dinámicamente con gcloud projects describe.”
[LECCIÓN 4 — Cloud Run y underscores]
“Los nombres de servicios en Cloud Run solo aceptan letras minúsculas, números y guiones. Sin underscores. web_portal → web-portal. Ese error lo encuentras en el primer deploy.”
[LECCIÓN 5 — A2A requiere —a2a + agent.json]
“adk api_server sin --a2a no expone el protocolo A2A — solo la REST API propia de ADK. Para A2A real necesitas dos cosas: el flag --a2a en el Dockerfile y un archivo agent.json en la carpeta del agente. Sin el agent.json el servidor ignora el agente para A2A aunque el flag esté activo.”
[LECCIÓN 6 — IAM auth en cadena de servicios]
“El portal es público pero los agentes son privados. El portal hace los health checks desde su backend usando el metadata server de GCP — obtiene el IAM token automáticamente sin credenciales hardcodeadas. roles/run.invoker en la service account del portal es todo lo que necesitas.”
TÚ: “El código completo está en GitHub — los 7 Dockerfiles, el orchestrator, todo el refactor. Link en la descripción.”
[PAUSA]
“Estos 7 agentes están en producción, tienen URLs, responden al protocolo A2A. Cualquier otro agente — de cualquier framework, de cualquier empresa — puede llamarlos. Eso es exactamente para lo que fue diseñado A2A.”
“En el próximo video voy a construir la interfaz web para interactuar con estos agentes. Nada de terminal — una UI real conectada al sistema en producción.”
[FADE OUT]
NOTAS DE PRODUCCIÓN
Sección titulada «NOTAS DE PRODUCCIÓN»Lo que NO se edita
Sección titulada «Lo que NO se edita»- Tiempos reales de cada
gcloud run deploy(~3-4 min por agente) - Si hay un error — resolverlo en vivo (ej: el underscore en web_portal)
- Los tiempos de respuesta de la cadena completa
URLs reales (ya deployadas — usar estas en la grabación)
Sección titulada «URLs reales (ya deployadas — usar estas en la grabación)»platform-architect → https://platform-architect-r7xydrdtta-uc.a.run.appinfrastructure → https://infrastructure-r7xydrdtta-uc.a.run.appsecurity → https://security-r7xydrdtta-uc.a.run.appcicd → https://cicd-r7xydrdtta-uc.a.run.appobservability → https://observability-r7xydrdtta-uc.a.run.appdevex → https://devex-r7xydrdtta-uc.a.run.appweb-portal → https://web-portal-r7xydrdtta-uc.a.run.appB-roll clave
Sección titulada «B-roll clave»- Las 7 URLs apareciendo una por una
/list-appsrespondiendo con el nombre del agente- El orchestrator corriendo con logs de cada agente
- Diagrama local (filesystem) vs producción (A2A messages)
Errores conocidos del dry run (mostrarlos en vivo o mencionar)
Sección titulada «Errores conocidos del dry run (mostrarlos en vivo o mencionar)»- Permiso storage.objectAdmin faltaba → se resuelve con
gcloud projects add-iam-policy-binding - Secret Manager API deshabilitada →
gcloud services enable secretmanager.googleapis.com - web_portal con underscore → renombrar a web-portal
Pantallas preparadas antes de grabar
Sección titulada «Pantallas preparadas antes de grabar»gcloud config get-value project— proyecto correcto- Variables de entorno con las 7 URLs seteadas
- orchestrator_a2a.py listo para correr
- v1 — descartado (tenía Claude Code Agent Teams)
- v2 — estructura base
- v3 — actualizado con dry run real (2026-04-04)
- Dry run completado — 7 agentes deployados y funcionando
- Grabación