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.

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:
- Cortar el canal (Telegram
deleteWebhook/ Slack uninstall). - Flag de proceso (
AGENT_DISABLED=1→ 503, cero tools). - 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

| Capa | Acción | Reversible | Cuándo |
|---|---|---|---|
| Canal | deleteWebhook (+ drop_pending_updates) | setWebhook | spam / alucinación ya |
| Flag | AGENT_DISABLED + restart/redeploy | quita la env | loop de tools, 429 |
| Réplicas | scale 0 | scale 1 (Fly: no dejes autodeploy) | proceso hung, CPU 100% |
| Rollback | Instant Rollback / release viejo | re-deploy | bug de código |
Errores comunes

| Síntoma | Causa | Fix |
|---|---|---|
| Sigue contestando | solo scale 0, webhook vivo | deleteWebhook + getWebhookInfo vacío |
| Preview mudo, prod no | flag en All envs | Production only |
| Vercel no apaga | env sin redeploy | canal primero, luego env + deploy nuevo |
| Cron sigue | Workers cron en prod | quita el trigger / --env |
| “Apagué” y Telegram acumula | 502 en cadena (no-2XY) | deleteWebhook, no 502 eterno |
| Fly vuelve solo | scale count 0 + fly deploy reseeds | pausa autodeploy antes de bajar a cero |
| Rearme come el spam | no pasaste drop_pending_updates | descarta 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
deleteWebhookdocumentado (token de prod no en el chat) -
drop_pending_updates=truesi no quieres el backlog - Tras el corte,
getWebhookInfoconurlvacío -
AGENT_DISABLEDleí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.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



