Módulo 1: Por qué operar es distinto de construir

Operar vs. construir: la frontera

Descripción

Con las cuatro señales de las lecciones 04 y 05 ya calculadas sobre runs reales, esta lección se detiene a trazar, con precisión, dos líneas que separan lo que esta guía hace de lo que hacen sus dos guías vecinas más cercanas. La primera línea separa construir (agent-fundamentals-and-tool-calling-guide, ya hecho) de operar (esta guía). La segunda separa operar el agente (esta guía, a nivel de aplicación) de operar la infraestructura (sre-and-incident-response-guide, otro ecosistema). Ninguna de las dos líneas es un tecnicismo — cada una decide, con precisión, a qué guía acudir cuando tu agente choque con un problema real.

Conexión con el módulo

Esta lección no agrega ninguna señal nueva ni ningún artefacto de ingeniería — es, deliberadamente, la lección más conceptual del módulo. Su función es evitar el error más caro que se puede cometer al empezar esta guía: reconstruir algo que ya existe, o confundir el alcance de esta guía con el de una guía vecina. Las dos líneas que traza aquí se repiten, con más detalle técnico, en los Módulos 2, 6 y 8.


Analogía: quien construye el auto, y quien lo maneja todos los días

La fábrica que construyó tu auto ya resolvió un problema específico: qué pasa si una bujía falla una vez, en medio de un viaje. La computadora del motor lo detecta, ajusta la inyección de combustible sobre la marcha, y el auto sigue andando sin que el conductor note nada — quizás, como mucho, una luz de advertencia se enciende un instante y se apaga. Eso es robustez de fábrica: una respuesta automática, dentro del mismo viaje, a una falla puntual. Nadie que compra el auto necesita rediseñar ese mecanismo — ya viene resuelto.

Pero hay una pregunta que la fábrica no responde, porque no le corresponde: ¿qué hace el dueño del auto cuando esa misma luz de advertencia se enciende, se apaga, y vuelve a encenderse, viaje tras viaje, durante dos semanas? La fábrica resolvió el fallo individual; el dueño tiene que decidir, con la información acumulada de muchos viajes, cuándo dejar de confiar en ese componente y llevarlo al taller. Esa decisión —tomada con datos de muchos viajes, no de uno— es, con precisión, el trabajo de operar. Y hay una tercera pregunta que ni la fábrica ni el dueño responden: si el auto se queda varado porque la autopista misma está cerrada por una obra, eso no es un problema del auto — es un problema de la infraestructura vial, y lo resuelve un sistema completamente distinto.


Frontera 1: agent-fundamentals (construir) vs. esta guía (operar)

Lo que ya está resuelto, dentro de UN run

agent-fundamentals M7 ya construyó, y ya probó, la respuesta a "¿qué hace el agente cuando ESTA llamada a ESTA tool falla, ahora mismo, dentro de este run?" — dispatch_robust, reutilizado sin cambios desde la lección 01 de este módulo, ya reintenta fallos transitorios con un tope:

import reservo_tools as rt
import reservo_robust as rr

_flaky_calls = {"count": 0}


def flaky_list_rooms():
    _flaky_calls["count"] += 1
    if _flaky_calls["count"] <= 2:
        raise ConnectionError(f"timeout de red simulado (intento {_flaky_calls['count']})")
    return rt.list_rooms()


rr.TOOLS["flaky_list_rooms"] = flaky_list_rooms
rr.SCHEMAS["flaky_list_rooms"] = {"type": "object", "properties": {}}

block = {"id": "toolu_06", "name": "flaky_list_rooms", "input": {}}
print(rr.dispatch_robust(block, max_retries=3))

Qué esperar:

{'type': 'tool_result', 'tool_use_id': 'toolu_06', 'content': '[{"room": "Focus", "rate_cents": 2500}, {"room": "Studio", "rate_cents": 4000}, {"room": "Boardroom", "rate_cents": 8000}]'}

flaky_list_rooms falla dos veces y se recupera a la tercera, dentro de esta única llamada a dispatch_robust — exactamente el mecanismo que ya construiste, ejecutado, en agent-fundamentals M7 y M8. Esta guía no vuelve a construir esto. No lo mejora, no lo reescribe, no le agrega un caso nuevo. Lo usa, tal cual, cada vez que run_reservo_agent despacha una tool.

Lo que esta guía sí construye, entre MUCHOS runs

La pregunta que dispatch_robust no responde —y no tiene por qué responder, porque no es su trabajo— es distinta: ¿qué pasa cuando book_room no falla una vez dentro de un run, sino que falla de forma consistente, run tras run, durante los últimos veinte intentos? Reintentar cada fallo individual con dispatch_robust seguiría "funcionando" en el sentido de que nunca lanza una excepción sin control — pero seguiría gastando reintentos, tiempo y costo en una tool que, con la evidencia acumulada, probablemente sigue caída. Esa decisión —dejar de intentarlo, temporalmente, con base en el historial de muchos runs— es del Módulo 6 de esta guía, un CircuitBreaker con estado que persiste entre llamadas (CLOSED/OPEN/HALF_OPEN, el mismo vocabulario de resilience-and-reliability-patterns-guide, aplicado aquí específicamente a la capa de tool-calls de un agente). No se construye en este módulo — se nombra aquí, con precisión, para que quede claro que es una capa encima de dispatch_robust, no un reemplazo.

agent-fundamentals M7        ->  dispatch_robust: "esta tool falló AHORA, ¿reintento?"
                                  (decisión DENTRO de un run, sin memoria de runs previos)

esta guía, Módulo 6           ->  CircuitBreaker: "esta tool lleva fallando MUCHOS runs,
                                  ¿dejo de intentarla por un tiempo?"
                                  (decisión ENTRE runs, con memoria de todos los anteriores)

La misma distinción aplica a la validación de argumentos. check_input_v2 (M7) protege contra un tier mal escrito, un hours menor al mínimo — ingeniería básica de robustez, dentro de un run, ya construida. Esta guía nunca revalida argumentos ni le agrega una capa nueva de validación — lo que hace, en el Módulo 5, es correr un harness de regresión que confirma, de forma determinista, que esa validación (y el resto del comportamiento del agente) sigue funcionando igual después de un cambio, comparado contra un caso guionado con un resultado exacto esperado.


Frontera 2: el agente (esta guía) vs. la infraestructura (sre-and-incident-response-guide)

La segunda frontera es de capa, no de tiempo. Esta guía opera el agente: sus runs, sus tool calls, sus tokens, sus prompts — a nivel de aplicación, con Python puro, sin Docker, sin AWS, costo cero. sre-and-incident-response-guide opera la infraestructura que hipotéticamente correría detrás de un agente en un despliegue real: el servicio que lo expone, la base de datos que usan sus tools, el balanceador que reparte tráfico entre instancias.

Esta guía (el agente)sre-and-incident-response-guide (la infraestructura)
Qué mideTasa de error de un run, tasa de fallo por tool call, costo por run, latencia por runSLI/SLO de un servicio (disponibilidad, latencia de un endpoint), métricas de un Lambda, una API
Con quéPython 3.14 stdlib (logging, json, dataclasses, statistics)Prometheus, Grafana, CloudWatch
La unidad que observaUn run del agente: pregunta → pasos → respuestaUn servicio: peticiones, réplicas, nodos
Cuando algo falla malUn circuit breaker por tool (Módulo 6), un rollback de versión (Módulo 7)El ciclo de vida completo de un incidente: roles, severidades, un postmortem sin culpa
Costo de operar$0, corre en tu máquinaRequiere infraestructura real desplegada (AWS, contenedores)

Ninguna de las dos guías reemplaza a la otra — en un sistema real, ambas aplican al mismo tiempo, en capas distintas: el agente podría estar operando perfectamente (baja tasa de error, costo estable) mientras el servicio que lo expone sufre un incidente de infraestructura completamente ajeno al agente (una base de datos caída, una región de AWS con problemas). Y al revés: la infraestructura podría estar sana —el servicio responde, el balanceador reparte tráfico sin problemas— mientras el agente, a nivel de aplicación, tiene una tasa de fallo por herramienta anormalmente alta porque un cambio de prompt lo hizo pedir un tier inválido con más frecuencia. Cada guía responde a una pregunta que la otra no puede responder.


Cómo usar las dos fronteras al leer el resto de esta guía

Una regla práctica para cuando, en cualquier módulo posterior, no estés seguro de si algo "ya se cubrió en otro lado":

  • Si la pregunta es "¿qué hace el agente cuando ESTO falla, AHORA, dentro de este run?" → ya está resuelto, en agent-fundamentals M7. Esta guía lo usa, no lo repite.
  • Si la pregunta es "¿qué le pasa a este run si lo mido, lo comparo con otros, o decido actuar según su historial?" → es el terreno de esta guía.
  • Si la pregunta es "¿el servicio que expone al agente está disponible, y cómo reacciono a un incidente de infraestructura?" → es sre-and-incident-response-guide.

Errores comunes

  1. Pensar que el circuit breaker del Módulo 6 "arregla" lo que dispatch_robust ya maneja. No lo arregla — lo complementa, en una capa distinta. dispatch_robust sigue siendo el único código que decide qué hacer con un fallo dentro de un run específico; el circuit breaker decide, por fuera, si vale la pena intentarlo del todo.

  2. Confundir "medir la latencia de un run" con "medir la latencia de un servicio". Son números distintos, con fuentes distintas. Esta guía modela la latencia de los pasos internos de un run (cuánto tarda cada tool call). sre-and-incident-response-guide mide la latencia de un endpoint HTTP completo, con reintentos de red, balanceo de carga y todo lo que hay entre el cliente y el servicio.

  3. Creer que, como el agente corre en Python puro sin infraestructura, "no necesita" SRE nunca. Sí lo necesita — en el momento en que ese mismo agente se despliegue detrás de un servicio real, con usuarios de verdad conectándose por HTTP. Lo que esta guía deja claro es que ese despliegue es un problema distinto, que no resuelve aquí — no que no exista.

  4. Reescribir check_input_v2 o dispatch_robust "para hacerlos más completos". Ya cumplen su función, ya se probaron a fondo en agent-fundamentals. Cualquier mejora a esas funciones es una nota para esa guía, no una reescritura silenciosa en esta.

  5. Pensar que esta lección ya construyó el circuit breaker. No — solo lo nombró, con precisión, para trazar la frontera. El código real —la máquina de estados CLOSED/OPEN/HALF_OPEN— es contenido íntegro del Módulo 6.


Ejercicios

Ejercicio 1: Clasifica cinco preguntas por guía (Fácil)

Para cada pregunta, indica si la resuelve agent-fundamentals (ya construido), esta guía, o sre-and-incident-response-guide: (a) "¿get_quote reintentó cuando ConnectionError ocurrió una vez?"; (b) "¿cuánto costó, en total, el lote de runs de esta mañana?"; (c) "¿el balanceador de carga sigue repartiendo tráfico entre las tres réplicas del servicio?"; (d) "¿book_room lleva fallando los últimos quince runs seguidos?"; (e) "¿el input_schema de cancel_booking rechaza un id que no es un entero?"

Ver solución

(a) agent-fundamentals M7 — reintento dentro de un run, ya construido en dispatch_robust.

(b) Esta guía (Módulo 3, con lo que empezaste en la lección 05) — costo agregado sobre un lote de runs.

(c) sre-and-incident-response-guide — infraestructura, balanceo de carga entre réplicas de un servicio.

(d) Esta guía (Módulo 6) — un patrón de fallo a través de muchos runs, el terreno del circuit breaker.

(e) agent-fundamentals M2/M7 — validación de un input_schema, ya construida en check_input/check_input_v2.

Ejercicio 2: Explica en una frase por qué (b) y (d) no son la misma guía que (a) y (e) (Medio)

Usando tus respuestas del Ejercicio 1, explica con precisión qué tienen en común (b) y (d) que las distingue de (a) y (e).

Ver solución

(a) y (e) son preguntas sobre un evento puntual, dentro de un solo run: si un reintento ocurrió esta vez, si un input_schema rechaza este valor específico ahora mismo. Ninguna de las dos necesita información de ningún otro run para responderse — la respuesta está completa dentro del alcance de una sola ejecución. (b) y (d), en cambio, solo tienen sentido mirando varios runs juntos: el costo total de un lote no existe sin sumar el costo de cada run que lo compone, y "lleva fallando los últimos quince runs" es, por definición, una afirmación sobre una secuencia de eventos a través del tiempo, no sobre uno solo. Esa es, con precisión, la distinción entre "construir" (resolver algo dentro de una ejecución) y "operar" (medir y decidir con base en el patrón de muchas ejecuciones).

Ejercicio 3: Diseña un escenario donde el agente esté sano pero la infraestructura no, y viceversa (Difícil)

Describe, en un párrafo cada uno, dos escenarios: (a) uno donde las cuatro señales de la lección 04 del agente son excelentes (baja tasa de error, bajo costo, latencia estable) pero un ingeniero de SRE igual reportaría un incidente; (b) uno donde el servicio de infraestructura está perfectamente sano (disponible, sin errores 5xx, latencia de red normal) pero las señales del agente mostrarían un problema real.

Ver solución

(a) El agente de Reservo procesa sus runs con una tasa de error de 0%, costo estable, latencia modelada dentro de lo esperado — todas las señales de esta guía en verde. Pero la base de datos real detrás de book_room (en un despliegue real, no en esta guía de $0) está a punto de quedarse sin espacio en disco, o la región de AWS donde corre el servicio tiene una degradación de red que todavía no afecta las respuestas pero sí aumenta el riesgo. Ninguna de esas dos condiciones aparece en las señales del agente —el agente no sabe nada sobre espacio en disco ni sobre la salud de una región de AWS— pero un ingeniero de SRE, mirando dashboards de infraestructura, sí las vería y reportaría un incidente antes de que el agente se viera afectado.

(b) El servicio que expone al agente está perfectamente disponible: cero errores 5xx, latencia de red normal, todas las réplicas saludables según cualquier dashboard de infraestructura. Pero un cambio reciente en el prompt del sistema (algo fuera del alcance de SRE por completo) hizo que el modelo (concepto) empezara a pedir tier="premium" con mucha más frecuencia que antes — la tasa de fallo por herramienta de get_quote, medida con las herramientas de esta guía, se disparó del 10% habitual al 60%. Ningún dashboard de infraestructura mostraría nada raro, porque desde su perspectiva cada petición HTTP se respondió correctamente (con un is_error en el tool_result, que sigue siendo una respuesta HTTP 200 perfectamente válida) — el problema vive enteramente en la capa de aplicación que esta guía opera, invisible para SRE.


Resumen y siguiente paso

  • Trazamos la primera frontera: agent-fundamentals M7 (dispatch_robust, ya construido) resuelve fallos dentro de un run; el Módulo 6 de esta guía resuelve patrones de fallo entre muchos runs, con un circuit breaker — nombrado aquí, construido más adelante.
  • Trazamos la segunda frontera: esta guía opera el agente (runs, tool calls, tokens, prompts, a nivel de aplicación, $0); sre-and-incident-response-guide opera la infraestructura (SLI/SLO de un servicio, Prometheus, el ciclo de vida de un incidente).
  • Confirmamos, con dos escenarios concretos, que el agente puede estar sano mientras la infraestructura no, y viceversa — las dos capas son independientes, y cada guía responde exactamente a una de las dos.

Siguiente lección: 07 — El encargo: opera el agente de Reservo para usuarios reales. Con las dos fronteras ya claras, planteamos el caso de esta guía como un encargo de negocio concreto: cuatro preguntas que hoy nadie puede responder, y qué módulo responde cada una.


Recursos adicionales

  1. Anthropic — Tool use (function calling) overview — El protocolo completo sobre el que dispatch_robust opera, sin cambios, dentro de cada run.
  2. Anthropic — Building effective agents — Sobre la separación entre la ingeniería que hace confiable un mecanismo y la disciplina que lo mantiene confiable en operación.
  3. Python — concurrent.futures — La base de call_with_timeout, el mecanismo de dispatch_robust reutilizado en esta lección.
  4. Python 3.14 — What's New — La versión con la que se ejecutó el ejemplo de esta lección.