Worker persistenti¶
Un terminale interattivo o una chat non restano vivi per sempre. Un worker persistente gira sotto il gestore dei servizi dell'host, interroga Griglia e avvia una nuova sessione non interattiva dell'agente ogni volta che c'è del lavoro assegnato. Chiudere il terminale, il browser o la sessione originale dell'agente non lo ferma.
Griglia distribuisce il worker e un template di servizio utente systemd insieme agli altri script per l'host:
Il worker è neutro rispetto al fornitore, tutto attorno al contratto della board: ogni istanza usa la propria
chiave d'agente con griglia:check --agent=<chiave>, il proprio lock e gli stessi stati dei task. Ci sono
driver di lancio integrati per Codex CLI e Claude Code; un template di argv in JSON collega un'altra
CLI senza passare da una shell.
Installare il servizio utente systemd¶
Copia l'esempio e sostituisci /absolute/path/to/project in entrambe le righe con il percorso assoluto vero
del progetto:
mkdir -p ~/.config/systemd/user
cp scripts/systemd/griglia-agent-worker@.service.example \
~/.config/systemd/user/griglia-agent-worker@.service
sed -i 's#/absolute/path/to/project#/srv/my-project#g' \
~/.config/systemd/user/griglia-agent-worker@.service
systemctl --user daemon-reload
Abilita un'istanza per ogni agente configurato. Il nome dell'istanza è la chiave dell'agente in Griglia:
systemctl --user enable --now griglia-agent-worker@codex.service
systemctl --user enable --now griglia-agent-worker@claude.service
Più applicazioni sullo stesso PC¶
Serve un worker per applicazione e per agente: ogni worker interroga una sola board e avvia l'agente nella
directory del relativo progetto. Il lock include automaticamente repository e chiave agente, quindi due
applicazioni possono usare entrambe codex senza bloccarsi tra loro.
Per ogni applicazione copia il template con un prefisso unità univoco e sostituisci il percorso. Per esempio:
cp app-one/scripts/systemd/griglia-agent-worker@.service.example \
~/.config/systemd/user/griglia-agent-worker-app-one@.service
sed -i 's#/absolute/path/to/project#/srv/app-one#g' \
~/.config/systemd/user/griglia-agent-worker-app-one@.service
cp app-two/scripts/systemd/griglia-agent-worker@.service.example \
~/.config/systemd/user/griglia-agent-worker-app-two@.service
sed -i 's#/absolute/path/to/project#/srv/app-two#g' \
~/.config/systemd/user/griglia-agent-worker-app-two@.service
systemctl --user daemon-reload
systemctl --user enable --now griglia-agent-worker-app-one@codex.service
systemctl --user enable --now griglia-agent-worker-app-two@codex.service
La configurazione comune dell'agente resta in ~/.config/griglia-worker/codex.env. Le impostazioni specifiche
del progetto possono sovrascriverla in
~/.config/griglia-worker/griglia-agent-worker-app-one-codex.env (schema %p-%i.env della unità). In
particolare ogni applicazione Docker deve indicare un GRIGLIA_WORKER_CONTAINER diverso; per il trasporto
locale imposta invece GRIGLIA_WORKER_REPO e, se serve, GRIGLIA_WORKER_PHP.
codex invoca codex exec --approve-for-me; claude invoca claude -p --permission-mode bypassPermissions.
L'unit aggiunge %h/.local/bin al PATH, il posto solito dei launcher installati dall'utente. Se
command -v codex o command -v claude indicano un'altra cartella, metti una riga PATH=... completa in
~/.config/griglia-worker/<chiave-agente>.env.
Per guardare il servizio e seguirne l'output:
systemctl --user status griglia-agent-worker@codex.service
journalctl --user -u griglia-agent-worker@codex.service -f
Per tenere in piedi i servizi utente dopo il logout e farli partire all'avvio, abilita una volta il lingering:
Configurazione¶
Ogni istanza legge, se c'è, ~/.config/griglia-worker/<chiave-agente>.env:
GRIGLIA_WORKER_DRIVER=codex
GRIGLIA_WORKER_INTERVAL=10
GRIGLIA_WORKER_RETRY_DELAY=30
GRIGLIA_WORKER_MAX_PARALLEL=2
GRIGLIA_WORKER_TRANSPORT=auto
GRIGLIA_WORKER_CONTAINER=laravel-dev-app
GRIGLIA_WORKER_REPO=/srv/my-project
Il trasporto di default è auto: all'avvio il worker verifica <container>, esegue docker exec <container>
php artisan se risponde e altrimenti php artisan dentro il repository, stampando quale ha scelto. Fissalo su
local dove Laravel gira direttamente sull'host del worker — così Docker non compare da nessuna parte nel giro:
GRIGLIA_WORKER_TRANSPORT=local
GRIGLIA_WORKER_PHP=/usr/bin/php8.4
GRIGLIA_WORKER_REPO=/srv/my-project
I nomi GRIGLIA_WORKER_* configurano una singola istanza. Quando mancano, il worker ripiega sulle variabili
che leggono gli altri script sull'host — GRIGLIA_TRANSPORT, GRIGLIA_PHP, GRIGLIA_CONTAINER —
così una scelta sola, esportata una volta per la macchina, copre sia il worker sia gli script che l'agente
lancia da sé (conteggio dei token, sincronizzazione di contesto e skill). Ogni impostazione ha anche il suo
flag, comodo per un lancio una tantum:
| Flag | Variabile d'ambiente | Default |
|---|---|---|
--transport auto\|docker\|local |
GRIGLIA_WORKER_TRANSPORT, GRIGLIA_TRANSPORT |
auto |
--container |
GRIGLIA_WORKER_CONTAINER, GRIGLIA_CONTAINER |
laravel-dev-app |
--php |
GRIGLIA_WORKER_PHP, GRIGLIA_PHP |
php |
--repo |
GRIGLIA_WORKER_REPO |
cartella corrente |
--driver codex\|claude\|custom |
GRIGLIA_WORKER_DRIVER |
la chiave dell'agente |
--interval, --retry-delay |
GRIGLIA_WORKER_INTERVAL, GRIGLIA_WORKER_RETRY_DELAY |
10, 30 |
--max-parallel |
GRIGLIA_WORKER_MAX_PARALLEL |
2 |
--model |
GRIGLIA_WORKER_MODEL |
il default della CLI dell'agente |
--effort |
GRIGLIA_WORKER_EFFORT |
il default della CLI dell'agente |
Il driver di default è la chiave dell'agente, quindi le chiavi che si chiamano codex e claude non hanno
bisogno di alcun file env. Se la chiave è diversa, indica il driver in modo esplicito.
Modello e livello di ragionamento¶
Senza altra configurazione ogni sessione usa il modello con cui è configurata la CLI dell'agente.
GRIGLIA_WORKER_MODEL e GRIGLIA_WORKER_EFFORT li scelgono per singolo worker, così l'agente della board può
girare su un modello diverso da quello delle sessioni interattive della stessa CLI:
Il driver claude li passa come --model e --effort (low, medium, high, xhigh, max); il driver
codex come --model e -c model_reasoning_effort="<effort>". Il driver custom li riceve come segnaposto
{model} e {effort} (stringa vuota se non impostati), quindi è il template argv a decidere dove finiscono.
Il worker non valida i valori: un modello o un livello sconosciuto fallisce dentro la CLI dell'agente.
Sceglierli dalla board¶
I valori del worker sono il default: la board può cambiarli per lista e per task. Dichiara quali modelli ed effort offre ogni agente e compaiono due tendine — una nella barra della lista, una nella targhetta sotto il titolo del task (e fra i comandi del modale):
GRIGLIA_AGENT_MODELS="claude:opus=Opus,sonnet=Sonnet;codex:gpt-5,gpt-5-codex"
GRIGLIA_AGENT_EFFORTS="low,medium,high,xhigh,max"
Un gruppo per agente (chiave:valori), separati da ;; un elenco senza agente vale per tutti; valore=Etichetta
rinomina una voce nell'interfaccia. Senza queste variabili non cambia niente: nessuna tendina, e ogni sessione
usa il default del worker.
Ogni tendina ha la sua etichetta — Agente, Modello, Effort — nella barra della lista, sotto il titolo del task e fra i comandi del modale.
Dichiara alla board con quale valore parte già la CLI e la board lo nomina, invece di scrivere un generico Predefinito CLI:
Stessa forma del catalogo. Dove non si è scelto niente la board legge allora Predefinito (opus) nelle
tendine e (opus) sulla targhetta: le parentesi dicono «qui non l'ha scelto nessuno, e girerà così». Le due
variabili sono solo una didascalia: la board non le invia mai, quindi ogni worker tiene il suo
GRIGLIA_WORKER_MODEL (mettici gli stessi valori perché la didascalia resti vera).
Un task usa il proprio valore, altrimenti quello della lista, altrimenti quello del worker. La targhetta mostra
il valore effettivo, il modale lo ripete fra i comandi, griglia:check lo stampa accanto al titolo
({agent: claude, model: opus, effort: high}, oppure model: (opus) per un default dichiarato che nessuno ha
cambiato) e il worker lo legge da --worker-json quando lancia la sessione.
I valori che l'agente non offre vengono ignorati: riassegnare un task a un altro agente lascia cadere un modello
che quell'agente non conosce, invece di far fallire la sua CLI. Mentre il task è in lavorazione le tendine sono
bloccate: la sessione è già partita.
Le variabili si leggono all'avvio del worker. Per applicare una modifica senza interrompere le sessioni in corso, svuota il worker invece di riavviarlo — vedi Aggiornare un worker in esecuzione.
Per Gemini CLI, Aider o un altro agente, usa il driver custom. L'array JSON viene eseguito direttamente (mai
attraverso una shell); {prompt}, {repo}, {agent}, {model} e {effort} vengono sostituiti dentro i singoli argomenti:
GRIGLIA_WORKER_DRIVER=custom
GRIGLIA_WORKER_COMMAND_JSON=["agent-cli","--cwd","{repo}","--prompt","{prompt}"]
Trasporto e driver sono indipendenti, quindi Codex, Claude e i driver personalizzati funzionano in tutte e due le modalità. L'utente del servizio deve poter usare Docker o l'eseguibile PHP locale configurato, e la CLI dell'agente scelto in modo non interattivo. Non usare flag che disattivano del tutto sandbox e approvazioni: concedi solo i permessi sul progetto che servono al flusso di lavoro.
Comportamento e prove¶
Il worker interroga lo stato corrente della board, quindi trova anche il lavoro già aperto prima di un riavvio.
In modalità ordered esegue esattamente una sessione. In modalità multitasking esegue fino a
--max-parallel sessioni (2 per default), una per task idoneo; riduci il limite quando i task possono modificare
gli stessi file. Un flock per coppia repository/agente impedisce worker duplicati, mentre il worker tiene
traccia di ogni processo figlio per task. Prima di avviare la CLI per un task aperto, il worker lo prende con
griglia:check --take: così ripete il controllo di assegnazione corrente della board. Se l'utente ha cambiato
l'agente del task o il predefinito della lista dopo lo snapshot, la board rifiuta la presa obsoleta e
l'agente sbagliato non parte.
Uno Stop termina solo il processo di quel task; quando un figlio esce,
il suo slot torna disponibile e nel journal compare task <id>: agent session ended with status <codice>. Il
worker legge solo il documento JSON di --worker-json: un avviso che la board stampa dopo non ferma il ciclo.
Aggiornare un worker in esecuzione¶
Un semplice systemctl --user restart uccide le sessioni dell'agente avviate dal worker. Per questo il worker
si tiene aggiornato da solo, senza riavvio:
- Nuovo script su disco — un rilascio del package,
vendor:publish --tag=griglia-scripts, ungit pull: entro un intervallo il worker si ri-esegue sul posto. Stesso PID, stesso lock, e ogni sessione in corso passa al nuovo codice (--adopt), quindi nessuno viene interrotto; nel journal comparereloading worker from <percorso> (<n> running session(s) carried over)e poicarried over after reload: …. Un file che non compila viene ignorato finché non cambia di nuovo. -
Nuovo ambiente —
~/.config/griglia-worker/<chiave-agente>.env, per esempioGRIGLIA_WORKER_MAX_PARALLEL: lo legge il service manager all'avvio, quindi il worker deve ripartire, ma alle sue condizioni. MandagliSIGHUP:Il worker non avvia nuove sessioni, lascia finire quelle in corso e poi esce;
Restart=alwaysnella unit lo fa ripartire con ambiente e script correnti. Nel journal compaionoSIGHUP received: draining …edrained: exiting so the service manager restarts the worker. Il lavoro aperto nel frattempo aspetta il riavvio — pochi secondi dopo la fine dell'ultima sessione.
Usa systemctl --user restart solo quando interrompere le sessioni in corso è accettabile.
Per controllare la configurazione senza lanciare un agente:
python3 scripts/griglia-agent-worker.py --agent=codex --driver=codex --once --dry-run
python3 scripts/griglia-agent-worker.py --agent=codex --transport=local --php=/usr/bin/php8.4 \
--repo=/srv/my-project --once --dry-run
Il comando legge la board attraverso il trasporto scelto e stampa l'argv che eseguirebbe, così un errore qui è un problema di trasporto o di permessi, non dell'agente.
Per una prova da capo a fondo, abilita il servizio, crea un task innocuo assegnato a quell'agente e mettilo
open to work. Nel journal dovresti vedere dispatching task <id> to <agent> e sulla board il task dovrebbe
passare da aperto a working a fatto. Chiudere il terminale da cui hai fatto la prova non tocca il servizio
systemd.
Per disabilitare un'istanza:
Vedi anche¶
- Il lato agente — comandi, stati e ambito con più agenti.
- Due agenti insieme — cosa condividono due worker, e come si evitano.
- Script sull'host — tutti gli script pubblicati da
griglia-scripts. - Comandi artisan — il reference dei comandi, generato.