Guía10 min

Kill switch de un agente IA en producción: apagar sin borrar

Resumen

Cómo silenciar el bot ya: deleteWebhook con drop_pending_updates, flag AGENT_DISABLED, fly scale count 0 (ojo: deploy desde cero reseeds) y réplicas de Railway. No es rollback del código. Corta el webhook y las llamadas al modelo en minutos, con un ensayo a las 2 AM.

TelegramVercelRailway
Interruptor de corte de un agente frente a un webhook activo

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

El agente alucinó, está spammeando el grupo o la factura del modelo se fue. Rollback cambia el binario. El kill switch deja de hablar. Son dos palancas. Fuentes oficiales consultadas el 3 de septiembre de 2026.

Rollback cubre el deploy malo. Preview cubre no mezclar tokens. Esta guía cubre apagar prod ahora.

La regla

Tres capas, de más rápida a más lenta:

  1. Cortar el canal (Telegram deleteWebhook / Slack uninstall).
  2. Flag de proceso (AGENT_DISABLED=1 → 503, cero tools).
  3. Bajar réplicas (fly scale count 0 / Railway replicas 0).

Si solo haces (3) y el webhook sigue registrado, Telegram reintenta contra un muerto y luego contra el siguiente deploy. Corta (1) primero.

Canal: Telegram

deleteWebhook quita la integración HTTP. El bot deja de recibir updates por POST; si quieres polling, pasas a getUpdates. La llamada devuelve verdadero si Telegram aceptó el corte. Guarda el comando y el token de prod en un sitio que no sea el laptop del intern:

curl -sS "https://api.telegram.org/bot${TOKEN}/deleteWebhook?drop_pending_updates=true"

El parámetro opcional drop_pending_updates descarta la cola que ya estaba en tránsito. Sin él, getWebhookInfo sigue reportando pending_update_count distinto de cero: al rearmar, el bot traga el backlog del incendio. Tras el curl, llama getWebhookInfo: el campo url tiene que quedar vacío. Si ves last_error_message de 502 en cadena, el webhook sigue vivo o Telegram aún está reintentando.

La API de setWebhook documenta el reintento: si tu servidor no responde con un código 2XY, Telegram repite y se rinde después de un número razonable de intentos. Un scale a cero sin borrar el webhook es exactamente ese bucle: 502 eterno, cola creciente, primer deploy nuevo que se come el spam. Por eso el canal va primero.

max_connections en setWebhook admite 1–100 y por defecto es 40. No es un kill switch: solo limita cuántos POST simultáneos te mandan. Si el incendio es semántico (el modelo alucina), bajar conexiones no silencia nada.

Vuelve a setWebhook cuando el incendio pase, al hostname estable (TLS). Slack/WhatsApp: quita el endpoint o rota el secret; el criterio es el mismo.

Flag de proceso

Una env solo Production: AGENT_DISABLED. En el handler:

  • si está set → 200/503 sin llamar al modelo;
  • log “kill switch”;
  • no encoles work.

En Vercel, un cambio de variable no entra en deploys viejos. Solo aplica a deploys nuevos: hay que redeployar el proyecto para que las Functions vean el valor. No es instantáneo. Por eso el canal (1) va primero. Railway/Fly/Docker recogen env en el siguiente restart; un restart es más rápido que un rollback de git.

No pongas el flag en Preview “All environments”: apagas el PR y crees que prod está muerto. En el dashboard de Vercel eliges Production / Preview / Development al crear la variable; elige Production y redeploya.

Réplicas a cero

Fly: fly scale count 0 destruye Machines hasta el objetivo. Arrancar y parar Machines existentes es más rápido que crearlas o destruirlas; las paradas sueltan CPU/RAM y reconstruyen rootfs desde la imagen. El recuento lo preserva fly deploy salvo que bajes a cero: sin Machines, el siguiente fly deploy siembra de nuevo en primary_region según [processes] de fly.toml. El kill switch de Fly no es “apagado permanente”: es un hueco hasta el próximo deploy. Tras scale count 0, no dejes un autodeploy de GitHub encendido.

Railway: las réplicas se cambian en settings del servicio. Crear, borrar o reasignar dispara un staged change; al aplicarlo, Railway escala sin redeploy completo. Las réplicas nuevas usan la imagen del deploy actual; las que quitas se drenan. No hay sticky sessions: el tráfico público se reparte al azar (o a la región más cercana si hay multi-región). Bajar a 0 réplicas apaga el proceso; el volumen no se borra. Pro documenta hasta 24 vCPU y 24 GB por réplica — irrelevante para el corte, útil para no confundir “apagar” con “achicar vertical”.

Compose: docker compose stop agent. El sqlite no se borra; el proceso no habla. Workers: no hay count 0 — desactiva el Custom Domain o el cron, o despliega un Worker que solo responde 503.

Bajar réplicas sin deleteWebhook deja a Telegram pegando a un 502. Haz las dos.

Qué no es

No borres el proyecto en Vercel. No revokes la API key del modelo salvo leak (eso es secretos). No hagas fly destroy. El kill switch es reversible en minutos.

Tabla

Tres capas para silenciar un agente en producción

CapaAcciónReversibleCuándo
CanaldeleteWebhook (+ drop_pending_updates)setWebhookspam / alucinación ya
FlagAGENT_DISABLED + restart/redeployquita la envloop de tools, 429
Réplicasscale 0scale 1 (Fly: no dejes autodeploy)proceso hung, CPU 100%
RollbackInstant Rollback / release viejore-deploybug de código

Errores comunes

Bot que sigue hablando porque solo se bajó una réplica

SíntomaCausaFix
Sigue contestandosolo scale 0, webhook vivodeleteWebhook + getWebhookInfo vacío
Preview mudo, prod noflag en All envsProduction only
Vercel no apagaenv sin redeploycanal primero, luego env + deploy nuevo
Cron sigueWorkers cron en prodquita el trigger / --env
“Apagué” y Telegram acumula502 en cadena (no-2XY)deleteWebhook, no 502 eterno
Fly vuelve soloscale count 0 + fly deploy reseedspausa autodeploy antes de bajar a cero
Rearme come el spamno pasaste drop_pending_updatesdescarta cola o procesa a mano

Relación con el resto

  • Código malo: rollback.
  • Apagado ordenado de un turno: SIGTERM.
  • Quién puede tocar prod: CI/CD.
  • Ver el spam en logs: logs.

Checklist

  • Comando deleteWebhook documentado (token de prod no en el chat)
  • drop_pending_updates=true si no quieres el backlog
  • Tras el corte, getWebhookInfo con url vacío
  • AGENT_DISABLED leído antes del primer tool
  • Cómo scale 0 en Fly/Railway/Compose, una línea cada uno
  • Fly: autodeploy pausado antes de bajar a cero Machines
  • Cómo rearmar: setWebhook + quitar flag + scale 1
  • Ensayo en el bot de Preview, no en el de clientes
  • Cron de prod también tiene off switch

FAQ

¿Basta con pausar el cron? No, si el webhook HTTP sigue vivo.

¿Revocar la API key? Solo si se filtró. Si no, el flag ahorra el redeploy de secretos.

¿Workers? Un deploy de 10 líneas que responde 503 + quitar Custom Domain. No hay scale 0.

¿setWebhook con URL vacía? Equivale a quitar la integración. Prefiere deleteWebhook y verifica con getWebhookInfo.

El ensayo (Preview): activa el flag, pega un mensaje, cero respuestas del modelo. Quita el flag, una respuesta. Luego el mismo ensayo con deleteWebhook y confirma url vacío.

Guarda las tres capas en un runbook de 10 líneas, no en un wiki de 8 páginas. A las 2 AM solo pegas el curl. Si el token de prod vive solo en el dashboard del proveedor, el runbook dice “copia TOKEN de Production y corre esto”, no pega el secret en Git.

Siguiente paso: si apagas y al rearmar está igual de roto, rollback. Sin runtime: curso.