Cloudflare Workflows: il vero costo della migrazione per linea

Come migrare routine editoriali attivate da agenti ai Dynamic Workflows per linea, senza perdere l'idempotenza, tra costi e limiti di Workflows V2.

Friday, September 4, 2026Omid Saffari
Cloudflare Workflows: il vero costo della migrazione per linea

Ogni giorno si attivano sei routine di publishing lato Anthropic; ciascuna invia, tramite Cloudflare Workflows, una richiesta POST a un unico PublishWorkflow statico, identificato da publish-{brief_id}. Il 1° maggio Cloudflare ha rilasciato @cloudflare/dynamic-workflows e, pochi giorni dopo, Workflows V2 con un control plane riprogettato per i carichi attivati dagli agenti. La parte interessante non è il lancio in sé. È che la scelta di un solo workflow statico, fatta per garantire l'idempotenza, oggi non è più un vincolo ereditato: è una decisione architetturale che devo saper difendere.

Cloudflare Workflows: che cosa mi ha sorpreso davvero

Mi aspettavo un annuncio del tipo «Workflows ora scala di più»: limiti più alti, code più capienti, il consueto incremento annuale. Era solo la metà meno importante della novità.

Il cambiamento sostanziale è un altro: ora il codice del workflow può variare a runtime per ogni tenant. @cloudflare/dynamic-workflows, rilasciato il 1° maggio con licenza MIT e basato su Dynamic Workers, consente di registrare un solo WorkflowEntrypoint il cui corpo viene risolto da un Dynamic Worker al momento della creazione dell'istanza. In questo modo è il grafo degli step ad adattarsi al tenant, invece di restare incorporato nel deployment. Cloudflare lo descrive come «durable execution che segue il tenant»: una formula molto precisa per chiunque abbia provato ad aggiungere logiche specifiche per cliente a un unico workflow tramite feature flag.

Le mie sei routine editoriali — news, dev, build, design, marketing, founders e business — funzionano, di fatto, come sei tenant che condividono un solo PublishWorkflow. Non sono clienti, ma routine AI lato Anthropic, ognuna dedicata a una linea di contenuti diversa. Il modello multi-tenant, però, calza perfettamente: stessa struttura di orchestrazione, piccole variazioni per linea e un unico account Cloudflare come destinazione.

Pochi giorni più tardi è arrivato Workflows V2, e l'impostazione di quell'annuncio conta più dei numeri. V2 è stato riprogettato esplicitamente per istanze create da agenti alla velocità delle macchine, non da persone che fanno clic su un pulsante. È esattamente il mio schema di attivazione: quando una routine editoriale decide di pubblicare un articolo, un evento non umano crea un'istanza di esecuzione durevole. Il control plane di V1 era pensato per un profilo di carico diverso.

Che cosa devono sapere i founder non tecnici

«Esecuzione durevole» sembra un'espressione congegnata per tenere lontani i non addetti ai lavori. In parole semplici, è un processo in più passaggi che sopravvive a un arresto anomalo e riparte dall'ultimo step completato, invece di ricominciare da zero. Se lo step 5 di 8 fallisce perché un'API non è disponibile, il sistema riprova lo step 5: non esegue di nuovo gli step da 1 a 4 e non li addebita nuovamente.

È questo il punto che dovrebbe interessare a un founder: il confine del retry per ogni step coincide anche con il confine del costo. Se la pipeline chiama un modello AI a pagamento nello step 3 e un altro nello step 6, mentre lo step 7 va in errore, deve essere ripetuto soltanto lo step 7, non l'intero processo. In una pipeline a pagamento, una riesecuzione non idempotente non produce solo una riga duplicata nel database: produce una fattura duplicata.

La scelta tra costruire subito e rimandare si può riassumere per un consulente in una frase: separa i workflow per linea di prodotto soltanto quando la loro logica diverge davvero, non perché oggi la piattaforma lo permette. Cloudflare ha appena introdotto un'astrazione potente, e davanti a novità simili la tentazione è riprogettare tutto attorno ad esse. N definizioni di workflow comportano N volte la manutenzione, mentre il vantaggio emerge solo se le linee svolgono davvero attività diverse. Quando qualcuno propone di separare ogni linea di prodotto in un workflow dedicato solo perché ora è possibile, la domanda corretta è che cosa sia cambiato nel lavoro, non nella piattaforma.

L'architettura che uso davvero

La struttura reale è abbastanza compatta da poterla tenere a mente. Ogni routine editoriale lato Anthropic completa la ricerca, prepara un brief e invia una richiesta POST a /api/admin/publish sul sito. L'endpoint valida il payload, genera un ID istanza e chiama instances.create su un unico WorkflowEntrypoint chiamato PublishWorkflow.

Il workflow è composto da otto fasi idempotenti step.do:

  1. validate (schema del brief, unicità dello slug)
  2. ground (recupero delle citazioni, risoluzione dei link)
  3. generate (chiamata a Claude per il corpo dell'articolo)
  4. clean (validazione delle direttive markdown)
  5. persist (inserimento in Postgres, versionamento)
  6. index (embedding, aggiornamento della ricerca)
  7. cover (generazione dell'immagine + upload su R2)
  8. publish (cambio di stato, ping della sitemap)
TypeScript
export class PublishWorkflow extends WorkflowEntrypoint<Env, PublishParams> {
  async run(event: WorkflowEvent<PublishParams>, step: WorkflowStep) {
    const brief = await step.do("validate", () => validateBrief(event.payload));
    const grounded = await step.do("ground", () => groundCitations(brief));
    const draft = await step.do("generate", () => generateBody(grounded));
    const cleaned = await step.do("clean", () => validateDirectives(draft));
    const row = await step.do("persist", () => persistArticle(cleaned));
    await step.do("index", () => reindex(row.id));
    await step.do("cover", () => generateCover(row.id));
    await step.do("publish", () => flipStatus(row.id));
  }
}

Il confine di dispatch si presenta così:

TypeScript
const id = `publish-${brief.brief_id}`;
try {
  await env.PUBLISH.create({ id, params: brief });
} catch (e) {
  if (isDuplicateIdError(e)) return new Response("already queued", { status: 200 });
  throw e;
}

instances.create genera un errore quando riceve un ID duplicato. Quella singola riga regge una parte essenziale dell'architettura: è l'unico elemento che impedisce a una routine Anthropic riattivata di pubblicare due volte lo stesso articolo e di farmi pagare due volte le chiamate AI al suo interno.

Perché una sola definizione per sei linee? Perché tutte e sei usano gli stessi otto step. Cambia soltanto il pacchetto editoriale: specifiche della voce e del pubblico, elenco degli anti-pattern e indicazioni per il brief. Questi elementi viaggiano come dati a runtime nel payload inviato al dispatch, non come codice. Il nome della linea seleziona il pacchetto inviato allo step 3 (generate); tutti gli altri step restano indipendenti dalla linea.

Il punto di pressione — e l'unico motivo concreto per valutare i Dynamic Workflows — è che una linea comincia ad avere bisogno di un grafo degli step diverso, non solo di un pacchetto diverso. La linea ad alta intensità di ricerca richiede un ulteriore passaggio di grounding prima della generazione e, forse, uno step di fact-checking successivo. È una differenza strutturale, non una differenza nei dati. Al momento viene gestita con un ramo condizionale dentro ground: funziona, ma è il genere di soluzione che si deteriora rapidamente se altre tre linee aggiungono eccezioni proprie.

Quanto costa migrare a Dynamic Workflows per linea

È qui che entra in gioco createDynamicWorkflowEntrypoint. Il codice specifico di ogni linea viene caricato a runtime da un Dynamic Worker; mantenere pronte le linee che oggi non sono state attivate costa quindi quasi nulla. Non si paga più il costo di manutenzione di «tutte le linee sempre distribuite nello stesso bundle», e il codice di ciascuna può evolvere in autonomia senza un nuovo deployment della struttura condivisa.

Diagramma in cui il confine HTTP /publish gestisce la chiave di idempotenza publish-{brief_id} come gate prima del fan-out verso workflow distinti per linea
La chiave di idempotenza appartiene al confine di dispatch, mai al codice delle singole linee.

Il costo reale è quello su cui l'annuncio si sofferma meno. Si scambia un unico WorkflowEntrypoint verificato staticamente — dove il compilatore segnala una variazione nel contratto di input di uno step — con N definizioni caricate a runtime, prive della stessa protezione. Le divergenze nei contratti degli step tra linee diventano errori di runtime anziché errori di build. Con sei linee, se dopo un refactoring lo step persist si aspetta una struttura della riga leggermente diversa in due di esse perché metà delle definizioni non è stata aggiornata, il problema emerge quando si tenta di pubblicare un articolo reale, non durante la CI.

La migrazione non deve essere totale, e presentarla in questi termini porta dritto all'over-engineering. Questo è il percorso che sto pianificando:

  1. Mantenere la struttura condivisa in 8 fasi come WorkflowEntrypoint statico. È l'hot path e cinque linee su sei lo usano senza modifiche.
  2. Costruire la linea ad alta intensità di ricerca come Dynamic Workflow, con un grafo dedicato (passaggio di grounding aggiuntivo e step di fact-checking).
  3. Eseguire il dispatch in base alla linea al confine /publish: un semplice switch di una riga su brief.lane sceglie su quale binding chiamare create.

La regola decisionale, con una soglia precisa, è questa: migrare una linea su un proprio Dynamic Workflow solo quando il suo grafo differisce dalla struttura condivisa per più di una fase. Sotto questa soglia, mantenere un ramo condizionale nello step esistente costa meno di una seconda definizione di workflow. Sopra la soglia, il ramo comincia a nascondere ciò che la linea fa davvero e una definizione separata ripaga il proprio costo.

Una linea è sopra la soglia, cinque sono sotto. Il piano di migrazione si riduce a queste due frasi. Se ricevessi lo stesso brief da un consulente che propone di separare tutte e sei in Dynamic Workflows fin dal primo giorno, non lo approverei: il costo di sviluppo è reale e il vantaggio è concentrato esattamente in una linea.

Che cosa cambia in Cloudflare Workflows V2 per i trigger a velocità macchina

I numeri principali di V2 sono 50,000 istanze simultanee e 2,000,000 di istanze in coda per workflow, rispetto al limite di 1,000,000 in coda di V1. Non sono cifre puramente celebrative, ma sono anche lontanissime dal mio caso d'uso. Con sei routine attivate una o due volte al giorno, resto circa nove ordini di grandezza sotto il nuovo limite.

Per il mio stack, però, il punto decisivo non sono i numeri. È il fatto che il control plane di V2 sia stato ricostruito assumendo come scenario principale la creazione di istanze da parte degli agenti. V1 era ottimizzato per l'attivazione umana: una persona fa clic su un pulsante, parte un'istanza e il sistema assorbe un carico a picchi ma circoscritto. V2 presuppone che il trigger sia un processo, non una persona, e che la frequenza dipenda dalla capacità degli agenti a monte anziché dai clic nell'interfaccia.

Vale la pena spiegare il replay deterministico di V2 senza gergo. Ogni step è isolato, rieseguibile e idempotente; in caso di retry, il workflow riparte dall'ultimo step completato con successo. È esattamente la proprietà che stavo proteggendo manualmente al confine di dispatch con l'ID istanza publish-{brief_id}. V1 offriva già la garanzia di retry per singolo step; V2 consolida la semantica del replay, quindi la proprietà strutturale che difendevo viene ora fatta rispettare anche dalla piattaforma sottostante.

La mossa pratica per questa settimana è non toccare l'hot path. Il modello V2 richiede un nuovo deployment per essere adottato, e la scelta peggiore per una pipeline idempotente che funziona è spostarla in fretta su un nuovo control plane per inseguire proprietà che possiede già. Porterò su V2 la linea ad alta intensità di ricerca quando la costruirò come Dynamic Workflow, perché sviluppare codice nuovo sul nuovo modello costa poco. Le cinque linee statiche resteranno dove sono finché non ci sarà un motivo per intervenire diverso da «la piattaforma ha rilasciato una nuova versione».

L'unico invariante a cui non rinuncerò

Usare publish-{brief_id} come ID istanza è una scelta strutturale. Senza questa chiave, la pipeline riparte quando una routine Anthropic esegue un retry, pubblica due volte lo stesso articolo e fa pagare due volte ogni chiamata API a pagamento nel corpo del workflow.

Dynamic Workflows non mette direttamente a rischio questo invariante: il contratto dell'ID in instances.create non cambia. Il pericolo nasce invece da un refactoring poco rigoroso per linea, nel quale il codice ricalcola localmente l'ID. Una linea potrebbe, per esempio, decidere di includere un timestamp nell'ID «per sicurezza», rompendo silenziosamente la deduplicazione perché ogni retry produrrebbe un ID univoco.

La regola che sopravvive a ogni cambio di versione, e che scriverei sul muro prima di lasciare intervenire chiunque altro sul codice, è questa:

Un invariante di questo tipo non compare negli annunci o nelle guide alla migrazione, perché esiste soltanto quando si è già incontrato in produzione il guasto che previene. Il controllo dei costi — impedire che una singola esecuzione del workflow generi una spesa incontrollata anche se la deduplicazione fallisce — costituisce un livello separato e va applicato alle chiamate ai modelli, non alle istanze. Entrambe le difese rispondono allo stesso principio: in una pipeline dove ogni step costa denaro, la correttezza strutturale è l'assicurazione più economica che si possa acquistare.

Che cosa farei diversamente ripartendo da zero a maggio 2026

Se costruissi oggi questo stack invece di ereditare decisioni prese un anno fa, cambierei tre cose.

Per prima cosa, partirei con la struttura statica condivisa e un solo Dynamic Workflow per l'eventuale linea davvero divergente, non con sei definizioni separate. Quando arriva una nuova astrazione, la tentazione è applicarla ovunque; il costo di manutenzione di N workflow, però, arriva prima del vantaggio di scala. Sei definizioni significano sei punti in cui correggere un bug, sei punti in cui aggiornare una dipendenza e sei punti in cui il contratto di uno step può divergere. Una struttura più una sola eccezione è la separazione minima praticabile.

In secondo luogo, metterei la chiave di idempotenza al confine HTTP fin dal primo giorno. Aggiungere publish-{brief_id} dopo un caso di doppia pubblicazione è la strada più costosa: bisogna riconciliare le righe duplicate, rimborsare i costi interessati e strumentare il livello di dispatch mentre il sistema è già in produzione. Introdurre il pattern try { create({id}) } catch (dup) {} prima ancora di rilasciare una pipeline richiede dieci minuti ed elimina un'intera categoria di guasti.

Infine, tratterei il determinismo di Workflows V2 come il requisito minimo del progetto, non come una funzionalità da adottare in seguito. Ogni step dovrebbe essere progettato per tollerare il replay anche se il limite di 50k istanze simultanee non verrà mai raggiunto. La tolleranza al replay non riguarda la scala, ma la correttezza. Uno step che non tollera il replay si rompe durante un retry, e i retry avvengono a qualsiasi scala.

Quest'ultimo punto è una scelta da senior mascherata da decisione tecnica. La trappola consiste nel considerare le nuove capacità della piattaforma come funzionalità da adottare. La disciplina sta nel trattarle come vincoli di progettazione, che si sfrutti o meno il margine disponibile.

Servono i Dynamic Workflows se tutti i tenant eseguono la stessa logica?

No. Se il grafo degli step è identico e cambiano soltanto i dati, inserisci questi ultimi nel payload di dispatch e mantieni un solo workflow statico. Dynamic Workflows giustifica il proprio costo quando è il codice a divergere per tenant, cioè quando cambia il grafo degli step e non soltanto i parametri.

Cloudflare Workflows V2 rende incompatibili i workflow V1 esistenti?

V2 è un control plane riprogettato per l'esecuzione deterministica attivata dagli agenti. Considera la migrazione come opt-in, verifica l'idempotenza prima di spostare un hot path e non migrare codice funzionante soltanto perché esiste una nuova versione.

Qual è ora il limite effettivo di concorrenza?

50,000 istanze simultanee e 2,000,000 di istanze in coda per workflow, rispetto a 1,000,000 in coda in precedenza. Per la maggior parte degli operatori sono numeri molto superiori alle esigenze reali; la proprietà più importante di V2 è il replay deterministico, non il nuovo limite.

@cloudflare/dynamic-workflows è pronto per la produzione o è ancora in anteprima?

È stato rilasciato il 1° maggio 2026 come libreria con licenza MIT basata su Dynamic Workers. La maturità della libreria e il contratto di idempotenza devono guidare la valutazione del rischio, non la licenza. Con un livello di dispatch solido, la libreria è pronta prima del piano di migrazione.

Quando conviene a un founder pagare un ingegnere per questa migrazione?

Quando la logica di automazione di una linea di prodotto diverge davvero dalle altre: un grafo degli step diverso, non parametri diversi. Separare solo per scalare è prematuro; una logica divergente è il vero segnale. Se il team ha bisogno di aiuto per valutare una migrazione di questo tipo, è esattamente il genere di revisione architetturale che svolgo tramite DVNC.dev.

Ultimo aggiornamento

4 set 2026

CategoriaBuild

Preferisca questo sito su Google

Aggiungi omidsaffari.com come fonte preferita nella Ricerca Google

Segni omidsaffari.com come fonte preferita e Google lo mette in evidenza per lei in Top Stories, AI Overviews e AI Mode.

Newsletter

Una lettera, ogni domenica. Sistemi che funzionano, non hot take.

Build log, sistemi in produzione e note dal campo da un portafoglio di venture AI.

Settimanale. Niente spam. Si cancella quando vuole.