Vai al contenuto principale
50% di sconto tutti i piani, tempo limitato. A partire da $2.48/mo
15 min left
AI e machine learning

Come pianificare agenti IA per l'esecuzione notturna su un VPS

S Di Sajjad 15 min di lettura
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

Alle 2 del mattino, un job pianificato parte su un VPS che non si è mai addormentato. Un'esecuzione headless di claude -p elabora un'attività in coda in un repository clonato senza chiedere nulla a nessuno, poi termina. Quando controlli al mattino trovi un commit che ti aspetta, oppure un report, oppure un log che mostra esattamente dove si è fermata e perché. Nessuno ha guardato.

È una configurazione diversa dal lasciare un terminale aperto tutta la notte sperando che la connessione SSH regga. Un punto di guasto frequente in un'esecuzione notturna è l'host stesso: un portatile va in sospensione, si chiude il coperchio, cade la rete, oppure un aggiornamento del sistema riavvia la macchina a metà attività. Errori di autenticazione, errori API e blocchi sui permessi possono ancora uccidere il job, ma un host sempre acceso elimina la modalità di guasto più banale.

Questa guida copre il meccanismo vero e proprio: i flag headless che ogni CLI importante di agente di coding mette a disposizione, i due modi per avviare un'esecuzione pianificata e quale scegliere, ciò di cui ha bisogno l'host sottostante e le protezioni che impediscono a un'esecuzione non presidiata di costare o rompere più di quanto vorresti dover spiegare in seguito.

La versione breve

  • Ogni CLI importante di agente di coding include una modalità non interattiva documentata che esegue un prompt fino alla fine e poi termina. Claude Code ha claude -p, Codex CLI ha codex exec, e Gemini CLI ha gemini -p. Non è un espediente, è una funzionalità ufficiale.
  • Claude Code ha anche una pianificazione propria: Routines, attività pianificate Desktop e /loop. Per alcuni lettori è davvero sufficiente, e c'è meno da mantenere rispetto a un VPS.
  • Cron va benissimo per un job notturno. Un timer systemd è il default migliore su una macchina che potrebbe riavviarsi, perché Persistent=true recupera un'esecuzione che cron avrebbe saltato in silenzio.
  • La CLI in sé è leggera perché l'inferenza avviene sull'API del provider. Dimensiona il VPS per i comandi che eseguirà (test, build, container, job paralleli), non per il modello.
  • Sono le protezioni (strumenti con ambito ristretto, un tetto ai turni, ramificazione sul codice di uscita) a rendere sicuro lasciare una pianificazione da sola. La pianificazione in sé non è il meccanismo di sicurezza.

Cosa ti servirà

Prepara queste cinque cose prima di scrivere una singola riga di crontab o un file unit:

  • Un VPS accessibile via SSH, con una distribuzione Linux basata su systemd.
  • La CLI dell'agente installata su quel VPS: Claude Code, Codex CLI o Gemini CLI.
  • Una credenziale non interattiva per la CLI che scegli. La modalità bare di Claude Code non legge alcun accesso all'account, quindi richiede ANTHROPIC_API_KEY nell'ambiente, oppure un apiKeyHelper nelle sue impostazioni. Un'esecuzione normale in modalità print, Codex e Gemini possono usare anche le credenziali di accesso all'account documentate.
  • Un repository o una directory di lavoro su cui l'agente opererà.
  • Accesso alla shell con il permesso di modificare una crontab o scrivere un file unit di systemd.

Eseguire un agente senza una sessione collegata

Confronto delle modalità headless: Claude Code esegue claude -p con output text, json o stream-json, Codex CLI esegue codex exec con uno stream JSONL e una policy di sandbox, e Gemini CLI esegue gemini -p senza TTY

Ogni CLI importante di agente di coding include una modalità non interattiva pensata esattamente per questo. Claude Code accetta -p, scritto anche --print. Codex CLI accetta codex exec. Gemini CLI accetta -p, scritto anche --prompt. Ognuno accetta un prompt, lo esegue fino alla fine e termina. Nessun ciclo di chat, nessun terminale da tenere aperto, niente a cui riagganciarsi.

Claude Code può funzionare senza una sessione attiva? Sì. Passando -p esegue il prompt in modalità non interattiva: Claude Code lo porta a termine, stampa il risultato e termina. Non c'è alcun ciclo di chat né nulla da tenere in vita, e gira sullo stesso Agent SDK che alimenta la CLI interattiva, secondo la documentazione ufficiale di Anthropic sulla modalità headless.

CLIFlag non interattivoComportamentoOutput strutturato
Claude Code-p / --printEsegue il prompt fino alla fine, stampa il risultato, termina--output-format impostato su text, json o stream-json
Codex CLIcodex execTrasmette l'avanzamento su stderr, scrive il messaggio finale su stdout, termina--json per uno stream di eventi JSONL
Gemini CLI-p / --promptEsegue il prompt in modo non interattivo, termina--output-format json

Qui contano soprattutto i flag di Claude Code stesso, perché sono quelli su cui scriverai davvero i tuoi script. Due di essi consentono a un'esecuzione di procedere senza fermarsi a chiedere un permesso che nessuno è sveglio per concedere: --allowedTools, che pre-approva strumenti specifici, e --permission-mode, che stabilisce la linea di base per l'intera esecuzione. --max-turns limita quanti turni agentici può compiere un'esecuzione prima di terminare con un errore.

--bare salta hook, skill, plugin, server MCP e istruzioni di progetto come CLAUDE.md, per un'esecuzione da script più rapida e più deterministica. Significa anche che ogni istruzione da cui dipende il job deve essere presente nel prompt o nel comando. La modalità bare non legge nemmeno l'accesso al tuo account, quindi la documentazione di Anthropic dice di impostare una chiave API nell'ambiente prima di eseguirlo. Claude Code rifiuta --bg categoricamente quando è combinato con -p, e rifiuta --cloud allo stesso modo quando gli passi una descrizione dell'attività. Nomina il conflitto e si ferma invece di fare qualcosa di ambiguo.

Un'invocazione completa, adatta il prompt e l'elenco degli strumenti alla tua attività:

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

Adatta il budget e i pattern dei comandi all'attività; questo esempio presuppone anche che l'autenticazione della GitHub CLI sia già configurata per l'account che lo esegue.

Se stai configurando Claude Code su un VPS appena creato e vuoi la procedura per autenticarlo su una macchina senza browser, l'argomento è trattato a parte in come autenticare Claude Code su un server headless; la versione breve qui sopra basta per far funzionare un'esecuzione pianificata.

La modalità exec di Codex CLI, descritta in la documentazione di OpenAI sulla modalità non interattiva, accetta --sandbox per scegliere una policy. read-only è il valore predefinito, workspace-write permette all'agente di scrivere nel proprio workspace, e --json trasforma stdout in uno stream di eventi analizzabile dalla macchina anziché testo semplice. Evita danger-full-access in un job non presidiato, a meno che il processo non sia isolato e quel rischio sia deliberato.

La modalità headless di Gemini CLI, documentata in la documentazione headless del progetto stesso, si attiva automaticamente in un ambiente senza TTY, oppure esplicitamente con -p. Termina con un codice diverso da zero specifico per un errore generale, un errore di input o il raggiungimento del limite di turni, invece di un unico codice di errore generico.

Dove dovrebbe vivere la pianificazione

Prima di tutto questo lavoro di configurazione: il fornitore dell'agente potrebbe già pianificarlo per te. Claude Code offre tre opzioni integrate, e una di esse potrebbe davvero adattarsi meglio di un VPS autogestito.

Cloud (Routines)Attività pianificata Desktop/loop
Viene eseguito suIl cloud di AnthropicLa tua macchinaLa tua macchina
La macchina deve essere accesaNon richiestoRichiestoRichiesto
Richiede una sessione apertaNon richiestoNon richiestoRichiesto
Intervallo minimo1 ora1 minuto1 minuto
Accesso ai file localiNessuno, parte da un clone nuovoAccesso completoAccesso completo

La documentazione ufficiale di Anthropic sulle attività pianificate presenta la questione come una vera scelta a tre, non come una gerarchia con il VPS in cima. Se la tua attività non richiede stato locale, tollera un limite minimo di un'ora e usi solo Claude Code, Routines richiede meno manutenzione di quanto segue: Anthropic lo esegue nel cloud da un clone nuovo mentre la tua macchina è spenta.

/loop vale la pena conoscerlo ma non si adatta a questo caso d'uso, perché richiede una sessione aperta e inattiva, cioè esattamente il vincolo che stai cercando di eliminare. La stessa documentazione indica anche GitHub Actions come quarta opzione, per i team il cui trigger vive già nella CI invece che in una pianificazione legata a una macchina specifica.

Il VPS autogestito si guadagna il suo posto quando il job richiede pieno accesso al filesystem locale e agli strumenti, quando vuoi lo stesso meccanismo che funziona in modo identico su Claude Code, Codex CLI e Gemini CLI, oppure quando l'intervallo consentito da Routines è troppo grossolano. Una classica funzione serverless qui è di solito scomoda, perché deve ripristinare le credenziali, clonare il repository e concludere entro i limiti di runtime della piattaforma. Un runner CI effimero come GitHub Actions resta una terza via valida quando un checkout nuovo a ogni esecuzione è accettabile. Se hai già hardware sempre acceso che non fa nulla, va bene anche una macchina da homelab; in cambio dipendi dall'affidabilità della tua rete domestica e del tuo accesso remoto invece che da quelle di un provider.

Cron o un timer systemd?

Cron a confronto con un timer systemd: a sinistra una riga di crontab e un'esecuzione mancata che viene semplicemente saltata, a destra una coppia .service e .timer con recupero tramite Persistent=true, logging con journald e controllo delle sovrapposizioni con istanza singola

Entrambi gli strumenti possono lanciare lo stesso comando con la stessa pianificazione, ma divergono su cosa succede quando la macchina si riavvia e su quanta configurazione richiede ciascuno:

crontimer systemd
Peso della configurazioneUna riga di crontabUn file .timer e un file .service
Recupero delle esecuzioni mancateNessuno, un'esecuzione saltata è semplicemente persaPersistent=true la esegue non appena il sistema è di nuovo attivo
AccessoManuale, reindirizzi tu stesso l'outputAutomatico, catturato da journald
Ordinamento delle dipendenzeNessunoOrdinamento systemd completo con After= e Requires=

Il cron semplice va bene per un job notturno su una macchina che si riavvia di rado. L'inghippo è l'ambiente: cron parte con un PATHPATH minimale, non entra nel tuo repository al posto tuo e avvierà tranquillamente una seconda copia mentre la prima è ancora in esecuzione. Metti il percorso del repository, il comando ristretto dell'agente e il caricamento delle credenziali in uno script wrapper protetto, poi usa flock per impedire esecuzioni sovrapposte.

# /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 volta sola le directory per le credenziali e i log, poi rendi eseguibile lo script wrapper:

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

Incolla solo la chiave API nel file delle credenziali. Non metterla direttamente nella crontab.

Un timer systemd richiede più configurazione e ti dà due cose che cron non ha: il logging tramite journald senza redirezioni fatte a mano, e Persistent=true. L'esempio qui sotto presuppone che un account dedicato agent-runner sia proprietario di /srv/myrepo. Conserva la chiave API in un file di credenziali leggibile solo da root anziché incorporarla nella unit.

Secondo il manuale di systemd.timer, impostare Persistent=true significa che "la unit di servizio viene attivata immediatamente se sarebbe stata attivata almeno una volta durante il periodo in cui il timer era inattivo." Così un'esecuzione che sarebbe partita mentre il tuo VPS si riavviava per un aggiornamento del kernel parte nel momento in cui torna disponibile, invece di svanire in silenzio fino allo slot successivo.

Crea il file di credenziali leggibile solo da root usato dal servizio:

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

Incolla solo la chiave API nel file.

# /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

Ricarica systemd, abilita il timer ed esegui il servizio una volta subito, così i problemi di credenziali, permessi e percorsi emergono adesso e non alle 2 del mattino:

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 è la differenza decisiva: il timer ricorda un'esecuzione di calendario mancata invece di scartarla in silenzio.

Di cosa ha davvero bisogno il VPS

Ecco la parte che sorprende chi dimensiona tutto questo per la prima volta: la CLI in sé è leggera perché l'inferenza avviene sull'API del provider. Ma l'agente può comunque avviare build, test, gestori di pacchetti, language server e container in locale, quindi è il carico di lavoro del repository a fissare il vero limite minimo.

Prendi 1-2 vCPU e 2-4 GB di RAM con storage NVMe come punto di partenza per un singolo job pianificato leggero. Repository di grandi dimensioni, compilatori, build Docker, suite di test o esecuzioni concorrenti possono richiedere molto di più. Ciò che spinge ad aumentare le risorse è il comando locale più pesante che l'agente eseguirà, non il modello dietro l'API. Se su questo VPS esegui già carichi Docker e vuoi un quadro più completo di ciò che serve mettere a budget, dimensionare e mettere in sicurezza una macchina di build affronta lo stesso compromesso per un carico di lavoro non presidiato diverso.

Un'altra cosa da mettere in conto: un'esecuzione non presidiata produce log ogni notte, che sia andato storto qualcosa oppure no. Aggiungi logrotate se cron scrive su un file, e controlla i limiti di conservazione di journald invece di dare per scontato che i valori predefiniti stiano nel disco del VPS.

Tutto l'approccio dipende da un host sveglio alle 2 del mattino che resti tale, qualunque cosa faccia il tuo portatile. È esattamente il compito per cui esiste un Linux VPS con accesso root. Niente lo manda in sospensione, e non lo condividi con i cron job di nessun altro.

Vedi piani Linux

Costruisci su un VPS Linux con accesso root, NVMe e la potenza di AMD EPYC.

Vedi piani Linux

Come evitare che un'esecuzione non presidiata vada storta

La differenza più grande tra un'esecuzione pianificata che funziona e una che no sta nel fatto che l'attività sia abbastanza circoscritta da concludersi senza che un umano risponda a una domanda a metà strada. I prompt ambiziosi si bloccano in attesa di una decisione che nessuno è lì a prendere; le attività ristrette e autonome arrivano in fondo e terminano in modo pulito.

I due flag dei permessi esistono perché un'esecuzione non si blocchi su una richiesta alle 2 del mattino, ma l'accesso Bash nudo non è una protezione stretta: può fare quasi tutto ciò che può fare l'account di servizio. Preferisci regole specifiche per comando come Bash(git status *), abbinale a --permission-mode dontAsk, ed esegui il servizio con un account dedicato non root. Numero di turni e spesa hanno ciascuno il proprio tetto: --max-turns limita quanto a lungo l'agente può vagare, e --max-budget-usd pone un tetto a quanto una singola esecuzione può spendere in chiamate API.

Suggerimento: esegui con --output-format json e registra il campo total_cost_usd di ogni invocazione. È l'aggancio più pulito per monitorare quanto costa davvero un'esecuzione pianificata ogni notte e per ricevere un avviso quando una singola esecuzione costa sensibilmente più delle altre. Vale i cinque minuti che servono a metterlo in piedi, perché ciò che monitora è la tua fattura, non un'astrazione.

Gli sforamenti di spesa nelle esecuzioni non presidiate non sono ipotetici. In un post su Hacker News, un utente ha riferito una fattura lorda AWS Bedrock da 37.901,73 dollari generata da un flusso di lavoro quotidiano con agente di coding in cui la cache dei prompt funzionava solo in parte, lasciando circa 6,47 miliardi di token di input non memorizzati in cache. È successo su uno stack diverso, non in modalità headless di Claude Code, ma mostra perché il logging dei costi e un budget rigido per esecuzione appartengono alla pianificazione.

Suggerimento: Claude Code termina con codice 0 in caso di successo e con un codice diverso da zero in caso di errore. Uno script wrapper che controlla lo stato di uscita può inviarti una notifica in caso di errore, così una nottata storta emerge il mattino dopo invece che tre giorni più tardi, quando ti capita di guardare.

Come minimo, esegui ogni job su un branch dedicato o un worktree usa e getta, e richiedi una revisione umana prima del merge. Credenziali con ambito ristretto, isolamento del filesystem e controllo del raggio d'impatto a livello di server sono un argomento più ampio, che merita una trattazione propria invece di un paragrafo appiccicato in fondo a una guida sulla pianificazione.

Sono le protezioni a rendere sicuro lasciare in pace la pianificazione: la pianificazione in sé non è il meccanismo di sicurezza.

Quando cron non basta più

Un singolo prompt su un timer non richiede nulla più di quanto già visto qui. Tre passaggi concatenati con una condizione, un retry e una notifica su Slack richiedono altro.

Vale la pena conoscere tre opzioni, ciascuna un gradino più su per un motivo diverso:

  • Dagu è il gradino più leggero: job autonomi definiti in YAML, con dipendenze a DAG, retry e una web UI per vedere cosa è stato eseguito.
  • n8n si adatta meglio quando l'esecuzione dell'agente è un nodo tra varie integrazioni e notifiche, non l'intero flusso di lavoro.
  • Kestra è il più pesante dei tre, costruito per orchestrare pipeline di dati e infrastruttura, ed è la risposta giusta quando pianificare l'agente è parte di una pipeline più grande invece che il suo scopo.

Per chi esegue un solo prompt a notte, tutti e tre sono sovradimensionati, e vale la pena dirlo chiaramente invece di convincerti a un impianto più pesante di quanto ti serva. Se una catena di passaggi finirà davvero per giustificarne uno, Dagu, n8n, e Kestra si distribuiscono tutti con un clic, il che è una comodità concreta proprio nel momento in cui stai valutando se il costo della configurazione ne valga la pena.

I framework di orchestrazione multi-agente come LangChain o CrewAI sono un argomento del tutto diverso: costruire sistemi di agenti anziché pianificare una CLI che esiste già.

Domande frequenti

Claude Code può funzionare senza una sessione attiva?

Sì. Passando -p esegue il prompt in modalità non interattiva: Claude Code lo porta a termine, stampa il risultato e termina, senza ciclo di chat e senza una sessione da tenere aperta.

Mi serve un VPS se Claude Code ha già Routines?

Non sempre. Routines gira nel cloud di Anthropic anche a macchina spenta e parte da un clone nuovo, ma non può accedere a file che esistono solo sulla tua macchina e ha un intervallo minimo di un'ora. Un VPS autogestito si guadagna il suo posto quando l'attività richiede file locali, intervalli arbitrari o un meccanismo che funzioni allo stesso modo sulle CLI di più fornitori.

Per un agente pianificato conviene cron o un timer systemd?

Un timer systemd, se il VPS si riavvia mai per manutenzione. Persistent=true esegue un job che sarebbe partito durante il fermo non appena il sistema torna disponibile, e cron non ha un equivalente. Cron va bene per un job notturno su una macchina che resta accesa.

Quanta RAM serve a un agente IA pianificato su un VPS?

Parti da circa 1-2 vCPU e 2-4 GB di RAM per un singolo job pianificato leggero, poi dimensiona in base al comando locale più pesante che l'agente eseguirà. Build, test, Docker, repository di grandi dimensioni ed esecuzioni concorrenti contano molto più dell'inferenza remota del modello.

Eseguire un agente su pianificazione cambia la fatturazione?

La pianificazione non crea una modalità di fatturazione separata. Claude Code -p può usare credenziali dell'abbonamento o una chiave API, ma --bare ignora l'accesso dell'abbonamento, quindi richiede ANTHROPIC_API_KEY nell'ambiente, oppure un apiKeyHelper nelle sue impostazioni. Codex e Gemini seguono il metodo di autenticazione che hai configurato per la rispettiva CLI. Poiché prezzi e condizioni d'uso cambiano rapidamente, verifica i prezzi attuali del provider e i tuoi dati di utilizzo quando imposti tutto questo. Per le esecuzioni di Claude Code via API puoi anche registrare il campo total_cost_usd dall'output JSON.

Condividi

Discussione

Commenti

Accedi per partecipare alla discussione.

Altro dal blog

Continua a leggere.

Pronto a distribuire? Da 2,48 $/mese.

Cloud indipendente, dal 2008. AMD EPYC, NVMe, 40 Gbps. Rimborso entro 14 giorni.