Saltar al contenido principal
50% de descuento todos los planes, tiempo limitado. Desde $2.48/mo
15 min left
IA y machine learning

Cómo programar agentes de IA para que se ejecuten de noche en un VPS

S Por Sajjad 15 min de lectura
Schedule AI Agents Overnight: a dark terminal showing a 02:00 timestamp and a green exit code 0 line, next to a clock and a completed job card

A las 2 de la madrugada, una tarea programada se dispara en un VPS que nunca se durmió. Una ejecución headless de claude -p procesa una tarea en cola dentro de un repositorio clonado sin preguntar nada a nadie y luego termina. Cuando lo revisas por la mañana hay un commit esperando, o un informe, o un log que muestra exactamente dónde se detuvo y por qué. Nadie estuvo mirando.

Eso es muy distinto de dejar un terminal abierto toda la noche y confiar en que la conexión SSH aguante. Un punto de fallo habitual en una ejecución nocturna es el propio equipo: un portátil se suspende, se cierra la tapa, se cae la red o una actualización del sistema reinicia la máquina a mitad de tarea. Los fallos de autenticación, los errores de API y los bloqueos por permisos todavía pueden matar el trabajo, pero un host siempre encendido elimina el modo de fallo más fácil.

Esta guía cubre el mecanismo real: los flags headless que trae cada CLI importante de agente de código, las dos formas de disparar una ejecución programada y cuál elegir, qué necesita el host que hay debajo y los límites que evitan que una ejecución sin supervisión cueste o rompa más de lo que querrías explicar después.

La versión corta

  • Cada CLI importante de agente de código incluye un modo no interactivo documentado que ejecuta un prompt hasta el final y termina. Claude Code tiene claude -p, Codex CLI tiene codex exec, y Gemini CLI tiene gemini -p. No es un apaño, es una función oficial.
  • Claude Code también tiene su propia programación: Routines, tareas programadas de Desktop y /loop. Para algunos lectores eso basta de verdad, y hay menos que mantener que con un VPS.
  • Cron funciona bien para una tarea nocturna. Un temporizador systemd es la mejor opción por defecto en una máquina que puede reiniciarse, porque Persistent=true recupera una ejecución que cron se habría saltado en silencio.
  • La CLI en sí es ligera porque la inferencia ocurre en la API del proveedor. Dimensiona el VPS para los comandos que va a ejecutar (tests, builds, contenedores, trabajos en paralelo), no para el modelo.
  • Son los límites (herramientas acotadas, tope de turnos, ramificación por código de salida) los que hacen seguro dejar una programación a su aire. La programación por sí sola no es el mecanismo de seguridad.

Lo que necesitarás

Ten preparadas estas cinco cosas antes de escribir una sola línea de crontab o un archivo de unidad:

  • Un VPS al que puedas entrar por SSH, con una distribución de Linux basada en systemd.
  • La CLI del agente instalada en ese VPS: Claude Code, Codex CLI o Gemini CLI.
  • Una credencial no interactiva para la CLI que elijas. El modo bare de Claude Code no lee ningún inicio de sesión de cuenta, así que necesita ANTHROPIC_API_KEY en el entorno, o un apiKeyHelper en su configuración. Una ejecución normal en modo print, Codex y Gemini también pueden usar las credenciales de cuenta documentadas.
  • Un repositorio o directorio de tareas sobre el que trabajará el agente.
  • Acceso a shell con permiso para editar una crontab o escribir un archivo de unidad de systemd.

Ejecutar un agente sin ninguna sesión conectada

Comparación de modos headless: Claude Code ejecuta claude -p con salida text, json o stream-json, Codex CLI ejecuta codex exec con un flujo JSONL y una política de sandbox, y Gemini CLI ejecuta gemini -p sin TTY

Cada CLI importante de agente de código incluye un modo no interactivo pensado justo para esto. Claude Code acepta -p, que también se escribe --print. Codex CLI acepta codex exec. Gemini CLI acepta -p, que también se escribe --prompt. Cada uno acepta un prompt, lo ejecuta hasta el final y termina. Sin bucle de chat, sin terminal que mantener abierto, sin nada a lo que reconectarse.

¿Puede Claude Code ejecutarse sin una sesión activa? Sí. Al pasar -p ejecuta el prompt en modo no interactivo: Claude Code lo completa, imprime el resultado y termina. No hay bucle de chat ni nada que mantener vivo, y funciona sobre el mismo Agent SDK que da vida a la CLI interactiva, según la propia documentación de Anthropic sobre el modo headless.

CLIFlag no interactivoComportamientoSalida estructurada
Claude Code-p / --printEjecuta el prompt hasta el final, imprime el resultado y termina--output-format con valor text, json o stream-json
Codex CLIcodex execEnvía el progreso a stderr, escribe el mensaje final en stdout y termina--json para un flujo de eventos JSONL
Gemini CLI-p / --promptEjecuta el prompt de forma no interactiva y termina--output-format json

Aquí lo que más importa son los flags propios de Claude Code, porque son los que vas a usar de verdad en tus scripts. Dos de ellos permiten que una ejecución avance sin detenerse a pedir un permiso que nadie está despierto para conceder: --allowedTools, que preaprueba herramientas concretas, y --permission-mode, que fija la línea base para toda la ejecución. --max-turns limita cuántos turnos agénticos puede dar una ejecución antes de salir con error.

--bare se salta los hooks, las skills, los plugins, los servidores MCP y las instrucciones de proyecto como CLAUDE.md, para una ejecución en script más rápida y determinista. Eso también significa que cada instrucción de la que dependa el trabajo tiene que estar en el prompt o en el comando. El modo bare tampoco lee el inicio de sesión de tu cuenta, así que la documentación de Anthropic indica que definas una clave de API en el entorno antes de ejecutarlo. Claude Code rechaza --bg de plano cuando se combina con -p, y rechaza --cloud de la misma forma cuando le das una descripción de tarea. Nombra el conflicto y se detiene en lugar de hacer algo ambiguo.

Una invocación de ejemplo; adapta el prompt y la lista de herramientas a tu tarea:

claude --bare -p "Review open PRs in this repo and summarize any blockers in NOTES.md" \
  --allowedTools "Bash(gh pr list *),Bash(gh pr view *),Bash(gh pr diff *),Read,Edit" \
  --permission-mode dontAsk \
  --max-turns 8 \
  --max-budget-usd 5.00 \
  --output-format json

Ajusta el presupuesto y los patrones de comandos a la tarea; este ejemplo también da por hecho que la autenticación de GitHub CLI ya está configurada para la cuenta que lo ejecuta.

Si estás configurando Claude Code en un VPS recién creado y quieres el paso a paso para autenticarlo en una máquina sin navegador, eso se trata aparte en cómo autenticar Claude Code en un servidor headless; la versión corta de arriba basta para poner en marcha una ejecución programada.

El modo exec de Codex CLI, descrito en la documentación de OpenAI sobre el modo no interactivo, acepta --sandbox para elegir una política. read-only es el valor por defecto, workspace-write permite que el agente escriba dentro de su espacio de trabajo, y --json convierte stdout en un flujo de eventos analizable por máquina en lugar de texto plano. Evita danger-full-access en un trabajo sin supervisión salvo que el proceso esté aislado y ese riesgo sea deliberado.

El modo headless de Gemini CLI, documentado en la propia documentación headless del proyecto, se activa automáticamente en un entorno sin TTY, o de forma explícita con -p. Sale con un código distinto de cero específico según se trate de un error general, un error de entrada o haber alcanzado el límite de turnos, en lugar de un único código genérico de fallo.

Dónde debe vivir la programación

Antes de todo este trabajo de configuración: puede que el proveedor del agente ya te lo programe. Claude Code ofrece tres opciones integradas, y una de ellas puede encajar de verdad mejor que un VPS autogestionado.

Nube (Routines)Tarea programada de Desktop/loop
Se ejecuta enLa nube de AnthropicTu máquinaTu máquina
La máquina debe estar encendidaNo requeridoObligatorioObligatorio
Requiere una sesión abiertaNo requeridoNo requeridoObligatorio
Intervalo mínimo1 hora1 minuto1 minuto
Acceso a archivos localesNinguno, parte de un clon nuevoAcceso completoAcceso completo

La propia documentación de Anthropic sobre tareas programadas plantea esto como una elección real entre tres, no como una jerarquía con el VPS arriba. Si tu tarea no necesita estado local, tolera un mínimo de una hora y solo usas Claude Code, Routines requiere menos mantenimiento que lo que viene a continuación: Anthropic lo ejecuta en la nube desde un clon nuevo mientras tu máquina está apagada.

/loop merece la pena conocerlo, pero no encaja en este caso de uso, porque exige una sesión abierta e inactiva, que es justo la restricción que intentas eliminar. La misma documentación menciona además GitHub Actions como cuarta opción, para equipos cuyo disparador ya vive en la CI y no en una programación atada a una máquina concreta.

El VPS autogestionado se gana su sitio cuando el trabajo necesita acceso completo al sistema de archivos local y a las herramientas, cuando quieres el mismo mecanismo funcionando igual en Claude Code, Codex CLI y Gemini CLI, o cuando el intervalo que permite Routines resulta demasiado grueso. Una función serverless convencional suele quedar incómoda aquí porque tiene que restaurar credenciales, clonar el repositorio y terminar dentro de los límites de ejecución de la plataforma. Un runner de CI efímero como GitHub Actions sigue siendo una tercera vía válida cuando aceptas un checkout nuevo en cada ejecución. Si ya tienes hardware encendido sin usar, una máquina de homelab también sirve; a cambio dependes de la fiabilidad de tu red doméstica y de tu acceso remoto en lugar de los de un proveedor.

¿Cron o un temporizador systemd?

Cron frente a un temporizador systemd: a la izquierda, una línea de crontab y una ejecución perdida que simplemente se salta; a la derecha, un par .service y .timer con recuperación mediante Persistent=true, registro en journald y control de solapamiento por instancia única

Ambas herramientas pueden lanzar el mismo comando con el mismo horario, pero difieren en qué ocurre cuando la máquina se reinicia y en cuánta configuración cuesta cada una:

crontemporizador systemd
Coste de configuraciónUna línea de crontabUn archivo .timer y un archivo .service
Recuperación de ejecuciones perdidasNinguna, una ejecución saltada simplemente se pierdePersistent=true la ejecuta en cuanto el sistema vuelve a estar en marcha
Registro de eventosManual, tú mismo rediriges la salidaAutomático, lo captura journald
Orden de dependenciasNingunaOrden completo de systemd con After= y Requires=

El cron sencillo va bien para una tarea nocturna en una máquina que rara vez se reinicia. La trampa es el entorno: cron arranca con un PATHmínimo, no entra en tu repositorio por ti y arrancará tan tranquilo una segunda copia mientras la primera sigue corriendo. Pon la ruta del repositorio, el comando acotado del agente y la carga de credenciales en un script envoltorio protegido, y luego usa flock para evitar ejecuciones solapadas.

# /usr/local/bin/agent-nightly
#!/usr/bin/env bash
set -euo pipefail
export PATH=/usr/local/bin:/usr/bin:/bin
export ANTHROPIC_API_KEY="$(
  cat "$HOME/.config/agent-nightly/anthropic_api_key"
)"
cd /srv/myrepo
exec /usr/local/bin/claude --bare -p \
  "Run the nightly dependency audit and write the findings to NOTES.md" \
  --allowedTools "Bash(npm audit *),Read,Edit" \
  --permission-mode dontAsk \
  --max-turns 8 \
  --max-budget-usd 5.00 \
  --output-format json
# crontab -e
0 2 * * * /usr/bin/flock -n "$HOME/.local/state/agent-runs/nightly.lock" /usr/local/bin/agent-nightly >> "$HOME/.local/state/agent-runs/nightly.log" 2>&1

Crea una sola vez los directorios de credenciales y de logs, y luego haz ejecutable el script envoltorio:

install -d -m 700 \
  "$HOME/.config/agent-nightly" \
  "$HOME/.local/state/agent-runs"
touch "$HOME/.config/agent-nightly/anthropic_api_key"
chmod 600 "$HOME/.config/agent-nightly/anthropic_api_key"
"${EDITOR:-nano}" \
  "$HOME/.config/agent-nightly/anthropic_api_key"
sudo chmod 755 /usr/local/bin/agent-nightly

Pega solo la clave de API en el archivo de credenciales. No la pongas directamente en la crontab.

Un temporizador systemd exige más configuración y te da dos cosas que cron no tiene: registro en journald sin redirecciones hechas a mano, y Persistent=true. El ejemplo de abajo supone que una cuenta agent-runner dedicada es la propietaria de /srv/myrepo. Guarda la clave de API en un archivo de credenciales accesible solo por root en lugar de incrustarla en la unidad.

Según el manual de systemd.timer, establecer Persistent=true significa que "la unidad de servicio se dispara de inmediato si se habría disparado al menos una vez durante el tiempo en que el temporizador estuvo inactivo". Así que una ejecución que habría saltado mientras tu VPS se reiniciaba por una actualización del kernel se lanza en cuanto vuelve, en lugar de desvanecerse en silencio hasta la siguiente franja programada.

Crea el archivo de credenciales accesible solo por root que usará el servicio:

sudo install -d -m 700 /etc/agent-nightly
sudo touch /etc/agent-nightly/anthropic_api_key
sudo chmod 600 /etc/agent-nightly/anthropic_api_key
sudoedit /etc/agent-nightly/anthropic_api_key

Pega solo la clave de API en el archivo.

# /etc/systemd/system/agent-nightly.service
[Unit]
Description=Nightly scoped agent run
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=agent-runner
Group=agent-runner
WorkingDirectory=/srv/myrepo
Environment=HOME=/home/agent-runner
Environment=PATH=/usr/local/bin:/usr/bin:/bin
LoadCredential=anthropic_api_key:/etc/agent-nightly/anthropic_api_key
ExecStart=/bin/sh -c 'export ANTHROPIC_API_KEY="$(cat "$CREDENTIALS_DIRECTORY/anthropic_api_key")"; exec /usr/local/bin/claude --bare -p "Run the nightly dependency audit and write the findings to NOTES.md" --allowedTools "Bash(npm audit *),Read,Edit" --permission-mode dontAsk --max-turns 8 --max-budget-usd 5.00 --output-format json'
StandardOutput=journal
StandardError=journal
UMask=0077
# /etc/systemd/system/agent-nightly.timer
[Unit]
Description=Run agent-nightly.service at 2am daily, catching up missed runs

[Timer]
# Uses the VPS's configured local timezone
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=agent-nightly.service

[Install]
WantedBy=timers.target

Recarga systemd, habilita el temporizador y ejecuta el servicio una vez de inmediato, para que los problemas de credenciales, permisos y rutas salgan ahora y no a las 2 de la madrugada:

sudo systemctl daemon-reload
sudo systemctl enable --now agent-nightly.timer
sudo systemctl start agent-nightly.service
systemctl list-timers agent-nightly.timer
sudo journalctl \
  -u agent-nightly.service \
  -n 100 \
  --no-pager

Persistent=true es la diferencia decisiva: el temporizador recuerda una ejecución de calendario perdida en lugar de descartarla en silencio.

Lo que el VPS necesita de verdad

Esta es la parte que sorprende a quien dimensiona esto por primera vez: la CLI en sí es ligera porque la inferencia ocurre en la API del proveedor. Pero el agente puede lanzar localmente builds, tests, gestores de paquetes, servidores de lenguaje y contenedores, así que la carga del repositorio marca el suelo real.

Toma 1 o 2 vCPU y 2 o 4 GB de RAM con almacenamiento NVMe como punto de partida para una tarea programada ligera. Los repositorios grandes, los compiladores, los builds de Docker, las suites de tests o las ejecuciones simultáneas pueden necesitar bastante más. Lo que obliga a subir es el comando local más pesado que ejecutará el agente, no el modelo detrás de la API. Si ya ejecutas cargas de Docker en este VPS y quieres una idea más completa de lo que presupuestar, dimensionar y proteger una máquina de builds recorre el mismo compromiso para otra carga de trabajo sin supervisión.

Una cosa más que conviene prever: una ejecución sin supervisión genera logs cada noche, haya salido mal algo o no. Añade logrotate si cron escribe en un archivo, y revisa los límites de retención de journald en lugar de dar por hecho que sus valores por defecto encajan en el disco del VPS.

Todo el enfoque depende de un host que esté despierto a las 2 de la madrugada y siga así, haga lo que haga tu portátil. Ese es justamente el trabajo de un Linux VPS con acceso root. Nada lo duerme, y no lo compartes con los cron de nadie más.

Ver planes Linux

Construye sobre un VPS Linux con acceso root, NVMe y la potencia de AMD EPYC.

Ver planes Linux

Evitar que una ejecución sin supervisión se tuerza

La mayor diferencia entre una ejecución programada que funciona y una que no es si la tarea está lo bastante acotada como para terminar sin que un humano responda una pregunta a mitad. Los prompts ambiciosos se quedan esperando una decisión que nadie está ahí para tomar; las tareas estrechas y autocontenidas terminan y salen limpiamente.

Los dos flags de permisos existen para que una ejecución no se atasque en una petición a las 2 de la madrugada, pero un acceso Bash abierto no es una barrera estrecha: puede hacer casi todo lo que puede hacer la cuenta de servicio. Prefiere reglas específicas por comando, como Bash(git status *), combínalas con --permission-mode dontAsk, y ejecuta el servicio bajo una cuenta dedicada que no sea root. El número de turnos y el gasto tienen sus propios topes: --max-turns limita cuánto puede divagar el agente, y --max-budget-usd pone un tope a lo que una sola ejecución puede gastar en llamadas a la API.

Consejo: ejecuta con --output-format json y registra el campo total_cost_usd de cada invocación. Es el enganche más limpio para saber lo que cuesta de verdad una ejecución programada cada noche y para avisarte cuando una ejecución cuesta bastante más que las demás. Vale los cinco minutos que cuesta montarlo, porque lo que estás siguiendo es tu factura, no una abstracción.

Los sobrecostes sin supervisión no son hipotéticos. En una publicación de Hacker News, un usuario informó de una factura bruta de AWS Bedrock de 37.901,73 $ generada por un flujo de trabajo diario de agente de código en el que el almacenamiento en caché de prompts solo funcionaba en parte, dejando unos 6.470 millones de tokens de entrada sin cachear. Ocurrió en otra pila, no en el modo headless de Claude Code, pero muestra por qué el registro de costes y un presupuesto estricto por ejecución deben formar parte de la programación.

Consejo: Claude Code sale con código 0 si todo va bien y con un código distinto de cero si falla. Un script envoltorio que compruebe el estado de salida puede enviarte una notificación cuando algo falla, así una mala noche se detecta a la mañana siguiente y no tres días después, cuando por casualidad te da por mirar.

Como mínimo, ejecuta cada trabajo en una rama dedicada o un worktree desechable y exige revisión humana antes de fusionar. Las credenciales acotadas, el aislamiento del sistema de archivos y el control del radio de impacto a nivel de servidor son un tema mayor que merece su propio tratamiento, no un párrafo pegado al final de una guía de programación.

Son los límites los que hacen seguro dejar la programación a su aire: la programación en sí no es el mecanismo de seguridad.

Cuándo cron deja de bastar

Un solo prompt sobre un temporizador no necesita nada más de lo que ya se ha visto aquí. Tres pasos encadenados con una condición, un reintento y una notificación de Slack necesitan otra cosa.

Merece la pena conocer tres opciones, cada una un escalón más arriba por una razón distinta:

  • Dagu es el salto más ligero: trabajos autocontenidos definidos en YAML, con dependencias en DAG, reintentos y una interfaz web para ver qué se ejecutó.
  • n8n encaja mejor cuando la ejecución del agente es un nodo más entre varias integraciones y notificaciones, y no todo el flujo de trabajo.
  • Kestra es el más pesado de los tres, pensado para orquestar pipelines de datos e infraestructura, y es la respuesta correcta cuando programar el agente forma parte de un pipeline mayor en lugar de ser el objetivo.

Para quien ejecuta un único prompt cada noche, los tres son excesivos, y conviene decirlo con claridad en lugar de empujarte a un montaje más pesado del que necesitas. Si con el tiempo una cadena de pasos llega a justificar uno, Dagu, n8n, y Kestra se despliegan todos con un clic, lo cual es una comodidad real justo en el momento en que estás decidiendo si el coste de montarlo compensa.

Los frameworks de orquestación multiagente como LangChain o CrewAI son un tema completamente distinto: construir sistemas de agentes, no programar una CLI que ya existe.

Preguntas frecuentes

¿Puede Claude Code funcionar sin una sesión activa?

Sí. Al pasar -p ejecuta el prompt en modo no interactivo: Claude Code lo completa, imprime el resultado y termina, sin bucle de chat ni sesión que mantener abierta.

¿Necesito un VPS si Claude Code ya tiene Routines?

No siempre. Routines se ejecuta en la nube de Anthropic con la máquina apagada y parte de un clon nuevo, pero no puede acceder a archivos que solo existen en tu equipo y tiene un intervalo mínimo de una hora. Un VPS autogestionado se gana su sitio cuando la tarea necesita archivos locales, intervalos arbitrarios o un mecanismo que funcione igual con las CLI de más de un proveedor.

¿Debo usar cron o un temporizador systemd para un agente programado?

Un temporizador systemd, si el VPS se reinicia alguna vez por mantenimiento. Persistent=true ejecuta un trabajo que se habría disparado durante la caída en cuanto el sistema vuelve, algo para lo que cron no tiene equivalente. Cron va bien para una tarea nocturna en una máquina que no se apaga.

¿Cuánta RAM necesita un agente de IA programado en un VPS?

Empieza con 1 o 2 vCPU y 2 o 4 GB de RAM para una tarea programada ligera, y luego dimensiona según el comando local más pesado que ejecutará el agente. Los builds, los tests, Docker, los repositorios grandes y las ejecuciones simultáneas importan mucho más que la inferencia remota del modelo.

¿Ejecutar un agente de forma programada cambia cómo se factura?

La programación no crea un modo de facturación aparte. Claude Code -p puede usar credenciales de suscripción o una clave de API, pero --bare ignora el inicio de sesión de la suscripción, así que necesita ANTHROPIC_API_KEY en el entorno, o un apiKeyHelper en su configuración. Codex y Gemini siguen el método de autenticación que hayas configurado para su CLI. Como los precios y las condiciones de uso cambian rápido, consulta los precios actuales del proveedor y tus propios datos de uso cuando montes esto. Para las ejecuciones de Claude Code vía API, también puedes registrar el campo total_cost_usd de la salida JSON.

Compartir

Debate

Comentarios

Inicia sesión para unirte al debate.

Más del blog

Sigue leyendo.

¿Listo para desplegar? Desde $2,48/mes.

Cloud independiente desde 2008. AMD EPYC, NVMe, 40 Gbps. Reembolso en 14 días.