Agentes programados: cron, colas y no duplicar el trabajo
Resumen
Cómo disparar un agente cada N minutos sin correrlo dos veces: Cron Triggers de Cloudflare (scheduled(), UTC, 5/250 por cuenta), Cron Jobs de Vercel (hasta 100 por proyecto) y el patrón lock + cola. Qué no cabe en un cron serverless y checklist de idempotencia.

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.
Un agente que “revisa el inbox cada 5 minutos” no es un webhook: es un job periódico. Si el cron se dispara dos veces, mandas dos resúmenes. Si el job dura más que el intervalo, se pisan. Esta guía cubre los dos cron serverless que ya usa el resto del sitio (Cloudflare Workers y Vercel) y el mínimo de locking para que el agente corra una vez.
El techo de tiempo del runtime sigue siendo el de dónde desplegar. Aquí el foco es el disparo, no el hosting.
Cloudflare: scheduled() en UTC
Cloudflare documenta Cron Triggers como un cron expression mapeado a un handler scheduled() del Worker. Ideal para mantenimiento o llamar APIs periódicas. Detalles oficiales:
- La expresión corre en UTC, no en America/Guatemala.
0 9 * * *es 9:00 UTC = 3:00 CST. - El handler:
export default {
async scheduled(controller, env, ctx) {
await runAgentJob(env);
},
};
- Tras añadir el trigger, Cloudflare indica que puede tardar hasta 15 minutos en propagarse a la red global. No asumas el primer tick al segundo de hacer deploy.
- Límites por cuenta (docs de limits): 5 Cron Triggers en el plan Free, 250 en Paid.
- CPU time del Worker: 10 ms Free, 5 min Paid. Un agente que llama un modelo se come CPU de espera de red de otra forma (I/O), pero un loop denso no cabe en Free.
Wrangler declara los crons en la config del proyecto. Workflows de Cloudflare también tienen schedules en el binding; es otro producto. Si solo necesitas “cada hora llama esta función”, Cron Triggers alcanza.

Vercel: Cron Jobs sobre Functions
Vercel documenta Cron Jobs como programación por cron expression, configurados en vercel.json (o Build Output API). Changelog oficial: hasta 100 cron jobs por proyecto en todos los planes.
El cron de Vercel pega a una Function. Hereda el maxDuration de esa función, no un techo mágico aparte. Si el agente tarda más que la función, el cron “funcionó” y el trabajo murió a mitad.
Patrón mínimo:
{
"crons": [{ "path": "/api/cron/inbox", "schedule": "0 * * * *" }]
}
Protege /api/cron/inbox con el header de cron de Vercel o un secreto. Un GET público es un botón de “corre el agente” para internet.
La doc de Vercel apunta a Managing Cron Jobs para duration, errores, deploys, concurrencia y ejecución local: léela antes de asumir overlap. Default seguro: un job a la vez (lock).
El patrón que no duplica
El handler del cron debe ser tonto y corto:
- Verificar secreto / que el invocador es el cron.
- Intentar un lock (
SET job:inbox NX EX 600en Redis, o una filarunning_sinceen SQLite). - Si el lock existe, salir 200. No reencolar a ciegas.
- Meter un mensaje en la cola (id estable:
inbox-2026-09-03T15:00Z). - Liberar el lock al terminar o al expirar.
La inferencia vive en el worker de la cola, igual que en arquitectura de webhooks y colas. El cron no llama al modelo. Si llama al modelo, el timeout del cron es el timeout del agente, y el overlap es un incidente.
Idempotencia: el id del job es la ventana (hora redondeada), no un UUID. Dos ticks de la misma hora = el mismo job.

Qué no pongas en un cron serverless
- Jobs > techo de CPU/duración del plan. Muévelos a un VPS o a una cola con workers largos.
- “Cada minuto” para un agente caro. RPM y factura. Empieza cada hora.
- Tiempo local sin zona. Cloudflare es UTC; Guatemala es UTC−6 (CST, sin DST).
- Cron que escribe en disco efímero y espera encontrarlo en el siguiente tick.
Checklist
- Cron en UTC. Comentario en el repo con la hora local.
- Handler autenticado. No hay GET público.
- Lock por ventana + id de job estable.
- Inferencia fuera del handler (cola).
- Alerta si el cron no corre (un tick de watchdog, no “ya veremos”).
- Límite de triggers del plan (5 Free en CF; 100/proyecto en Vercel) no saturado con crons de prueba.
- Un test: disparar dos veces la misma ventana → un solo side effect.
- Logs de
scheduledTime/ invocación, para observabilidad.
FAQ
¿GitHub Actions schedule? Sirve para CI, no para un agente de producto: el cron de GHA se retrasa cuando GitHub está cargado y el repo tiene que ser el lugar del secreto. Workers/Vercel están más cerca del runtime.
¿Cada 1 minuto? Solo si el job es < 10 s y barato. Si no, estás pagando RPM para mirar que no hay nada.
¿El cron sustituye al webhook? No. El webhook reacciona al evento del usuario; el cron barre lo que no tiene evento (reportes, sync, “¿hay tickets viejos?”).
Empieza con un cron horario, lock de 10 minutos y un job idempotente. Cuando eso sea aburrido, recién bajas a 15 minutos.
Lecturas relacionadas
Sigue explorando Deploy y otras piezas para builders.



