Les versions bêta sont utiles pour les retours d'expérience et l'itération, mais de compte, tous les utilisateurs n'ont pas envie de servir de cobaye ou ne tolèrent pas forcément les nouveautés radicales qui caractérisent parfois les logiciels en version bêta. Parfois, vous avez besoin de la grande promotion « En disponibilité générale » (ou d'un article de blog), et maintenant, c'est au tour de Workflows.
La solution Workflows, notre moteur d'exécution durable et serverless qui vous permet de développer des applications de longue durée et multi-étapes (certains les appellent « fonctions d'étapes ») sur Workers, est désormais accessible en disponibilité générale.
En résumé, cela signifie que la solution est prête pour la production, mais que Workflows n'est pas forcément fixe. Nous continuons à faire évoluer Workflows (avec notamment plus d'instances simultanées), à proposer de nouvelles fonctionnalités (comme la nouvelle API waitForEvent )
et à faciliter le développement d'agents IA grâce à notre SDK pour agents et à Workflows.
Si vous préférez le code à la prose, vous pouvez rapidement installer le projet de démarrage Workflows et commencer à explorer le code et l'API à l'aide d'une simple commande :
npm create cloudflare@latest workflows-starter --
--template="cloudflare/workflows-starter"
Comment fonctionne Workflows ? Que puis-je construire avec Workflows ? Comment penser le développement d'agents IA avec Workflows et le SDK d'agents ? Poursuivez la lecture.
Développer avec Workflows
Workflows est un moteur d'exécution durable basé sur Cloudflare Workers qui vous permet de développer des applications résistantes et multi-étapes.
Fondamentalement, la solution Workflows met en œuvre une architecture reposant sur des étapes, au sein de laquelle chaque étape de votre application peut être réutilisée indépendamment, avec la possibilité de conserver automatiquement l'état entre les étapes. Cela signifie que même si une étape échoue en raison d'une erreur transitoire ou d'un problème réseau, Workflows peut réessayer cette étape uniquement, sans avoir à redémarrer l'ensemble de l'application depuis le début.
Lorsque vous définissez un flux de travail, vous décomposez votre application en étapes logiques.
Chaque étape peut soit exécuter un code (
step.do
), soit mettre votre flux de travail en sommeil (étape.sleep
ouétape.sleepUntil
), ou encore, attendre un événement (étapes.waitForEvent
).Tandis que votre flux de travail s'exécute, il conserve automatiquement l'état renvoyé à chaque étape, afin de garantir que votre application peut reprendre exactement là où elle avait été interrompue, même après des défaillances ou des périodes d'hibernation.
Ce modèle d'exécution durable est particulièrement puissant pour les applications qui coordonnent plusieurs systèmes, traitent les données dans l'ordre ou doivent gérer des tâches de longue durée qui peuvent durer des minutes, des heures, voire des jours.
Les flux de travail sont particulièrement utiles pour gérer les processus métier complexes, qui donnent du fil à retordre aux fonctions traditionnelles sans état.
Par exemple, un flux de travail de traitement d'une commande e-commerce peut vérifier les stocks, facturer un mode de paiement, envoyer une confirmation par e-mail et mettre à jour une base de données ; tout cela, selon des étapes distinctes. Si l'étape de traitement des paiements échoue en raison d'une panne temporaire, Workflows réessaie automatiquement cette étape lorsque le service de paiement est à nouveau disponible, sans qu'il soit nécessaire de vérifier les stocks ou de recommencer l'ensemble du processus.
Vous pouvez observer le fonctionnement ci-dessous : chaque appel à un service peut être modélisé comme une étape, permettant un nouvel essai indépendamment et, si nécessaire, une récupération à partir de cette étape :
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers';
// The params we expect when triggering this Workflow
type OrderParams = {
orderId: string;
customerId: string;
items: Array<{ productId: string; quantity: number }>;
paymentMethod: {
type: string;
id: string;
};
};
// Our Workflow definition
export class OrderProcessingWorkflow extends WorkflowEntrypoint<Env, OrderParams> {
async run(event: WorkflowEvent<OrderParams>, step: WorkflowStep) {
// Step 1: Check inventory
const inventoryResult = await step.do('check-inventory', async () => {
console.log(`Checking inventory for order ${event.payload.orderId}`);
// Mock: In a real workflow, you'd query your inventory system
const inventoryCheck = await this.env.INVENTORY_SERVICE.checkAvailability(event.payload.items);
// Return inventory status as state for the next step
return {
inStock: true,
reservationId: 'inv-123456',
itemsChecked: event.payload.items.length,
};
});
// Exit workflow if items aren't in stock
if (!inventoryResult.inStock) {
return { status: 'failed', reason: 'out-of-stock' };
}
// Step 2: Process payment
// Configure specific retry logic for payment processing
const paymentResult = await step.do(
'process-payment',
{
retries: {
limit: 3,
delay: '30 seconds',
backoff: 'exponential',
},
timeout: '2 minutes',
},
async () => {
console.log(`Processing payment for order ${event.payload.orderId}`);
// Mock: In a real workflow, you'd call your payment processor
const paymentResponse = await this.env.PAYMENT_SERVICE.processPayment({
customerId: event.payload.customerId,
orderId: event.payload.orderId,
amount: calculateTotal(event.payload.items),
paymentMethodId: event.payload.paymentMethod.id,
});
// If payment failed, throw an error that will trigger retry logic
if (paymentResponse.status !== 'success') {
throw new Error(`Payment failed: ${paymentResponse.message}`);
}
// Return payment info as state for the next step
return {
transactionId: 'txn-789012',
amount: 129.99,
timestamp: new Date().toISOString(),
};
},
);
// Step 3: Send email confirmation
await step.do('send-confirmation-email', async () => {
console.log(`Sending confirmation email for order ${event.payload.orderId}`);
console.log(`Including payment confirmation ${paymentResult.transactionId}`);
return await this.env.EMAIL_SERVICE.sendOrderConfirmation({ ... })
});
// Step 4: Update database
const dbResult = await step.do('update-database', async () => {
console.log(`Updating database for order ${event.payload.orderId}`);
await this.updateOrderStatus(...)
return { dbUpdated: true };
});
// Return final workflow state
return {
orderId: event.payload.orderId,
processedAt: new Date().toISOString(),
};
}
}
Cette combinaison de durabilité, de nouvelles tentatives automatiques et de persistance des états fait de Workflows une solution idéale pour développer des applications distribuées fiables, capables de gérer sans problème les défaillances réelles.
Human-in-the-Loop (Humain dans la boucle)
Les flux de travail ne sont constitués que de code, et c'est ce qui les rend extrêmement puissants : vous pouvez définir des étapes de manière dynamique et à la volée, ou mettre en œuvre des sections conditionnelles et lancer des appels d'API vers les systèmes dont vous avez besoin. Il peut arriver toutefois que vous ayez besoin d'un flux de travail permettant de patienter qu'un événement se produise dans le monde réel.
Par exemple :
L'approbation d'un humain pour poursuivre la progression.
Un webhook entrant, par exemple à la suite d'un paiement Stripe ou d'un événement GitHub.
Un changement d'état, tel que l'importation d'un fichier vers R2, qui déclenche une notification d'événement, puis transmet une référence au fichier vers le flux de travail, afin que le fichier puisse être traité (ou exécuté dans un modèle IA).
C'est précisément ce que permet la nouvelle API waitForEvent
disponible dans Workflows :
let event = await step.waitForEvent<IncomingStripeWebhook>("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" })
Vous pouvez ensuite transmettre un événement à une instance spécifique depuis n'importe quel service externe capable d'effectuer une requête HTTP :
curl -d '{"transaction":"complete","id":"1234-6789"}' \
-H "Authorization: Bearer ${CF_TOKEN}" \
\ "https://api.cloudflare.com/client/v4/accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/events/{event_type}"
… ou via l'API Workers au sein d'une instance Worker elle-même :
interface Env {
MY_WORKFLOW: Workflow;
}
interface Payload {
transaction: string;
id: string;
}
export default {
async fetch(req: Request, env: Env) {
const instanceId = new URL(req.url).searchParams.get("instanceId")
const webhookPayload = await req.json<Payload>()
let instance = await env.MY_WORKFLOW.get(instanceId);
// Send our event, with `type` matching the event type defined in
// our step.waitForEvent call
await instance.sendEvent({type: "stripe-webhook", payload: webhookPayload})
return Response.json({
status: await instance.status(),
});
},
};
Vous pouvez même attendre plusieurs événements, en utilisant le paramètre de type
, ou lancer plusieurs événements avec Promise.race
pour continuer en fonction de l'événement reçu en premier :
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
let state = await step.do("get some data", () => { /* step call here */ })
// Race the events, resolving the Promise based on which event
// we receive first
let value = Promise.race([
step.waitForEvent("payment success", { type: "payment-success-webhook", timeout: "4 hours" ),
step.waitForEvent("payment failure", { type: "payment-failure-webhook", timeout: "4 hours" ),
])
// Continue on based on the value and event received
}
}
Pour visualiser waitForEvent
un peu plus en détail, supposons que nous avons un Workflow déclenché par un agent d'examen de code qui surveille un référentiel GitHub.
Sans la possibilité d'attendre les événements, notre flux de travail ne peut pas facilement obtenir l'approbation d'un humain pour rédiger des suggestions (ou encore, soumettre son propre PR). Il peut potentiellement interroger un état qui a été mis à jour, mais cela signifie que nous devons appeler step.sleep
pour des périodes arbitraires, interroger un service de stockage pour obtenir une valeur mise à jour et recommencer si cette dernière n'existe pas. Cela représente beaucoup de code et augmente le risque d'erreur :

Sans waitForEvent, il est plus difficile d'envoyer des données à une instance Workflow en cours d'exécution
Si nous modifions ce même exemple pour intégrer la nouvelle API waitForEvent, nous pourrions l'utiliser pour attendre l'approbation d'un humain avant d'effectuer une modification ou une mutation :

L'ajout de waitForEvent à notre flux de travail de révision du code, afin qu'il puisse demander une approbation explicite.
Vous pourriez même imaginer un agent IA lui-même envoyant ou agissant pour le compte d'un humain ici : waitForEvent
expose simplement un moyen pour un Workflow de récupérer et de mettre en pause un élément du monde pour le changer avant de poursuivre (ou non).
Détail important, vous pouvez appeler waitForEvent
comme n'importe quelle autre étape de Workflows : vous pouvez l'appeler de manière conditionnelle, une ou plusieurs fois, ou encore, dans une boucle. Les flux de travail sont de simples Workers : vous disposez de toute la puissance d'un langage de programmation, et vous n'êtes pas limité par un langage spécifique à un domaine (DSL) ou un langage de configuration.
Tarification
Bonne nouvelle, nous n'avons pas changé grand-chose depuis l'annonce initiale de la version bêta ! Nous ajoutons une tarification de stockage pour l'état stocké par vos flux de travail et conservons notre tarification basée sur le processeur et la requête (invocation), comme suit :
Unité | Workers : offre gratuite | Workers : offre payante |
Temps processeur (ms) | 10 ms par flux de travail | 30 millions de millisecondes processeur incluses par mois +0,02 $ par million de millisecondes processeur supplémentaires |
Requêtes | 100 000 d'invocations de Workflow par jour (partagées avec Workers) | 10 millions inclus par mois +0,30 USD par million supplémentaire |
Stockage (Go) | 1 Go | 1 Go inclus par mois + 0,20 USD/Go-mois |
La tarification du stockage étant nouvelle, nous ne facturerons pas réellement le stockage avant le 15 septembre 2025. Nous informerons les utilisateurs au-delà de la limite incluse de 1 Go avant de facturer le stockage et, par défaut, les flux de travail expireront après trois (3) jours (pour l'offre gratuite ) ou trente (30) jours (pour l'offre payante).
Si vous vous demandez à quoi correspond le « temps processeur » : il s'agit de la durée pendant laquelle votre flux de travail consomme activement des ressources informatiques. Il ne compte pas le temps passé à attendre les appels API, le raisonnement des LLM ou d'autres E/S (comme l'écriture dans une base de données). Cela peut paraître négligeable, mais dans la pratique, cela représente un certain cumul : la plupart des applications ont un temps processeur à un seul chiffre, et plusieurs secondes de temps d'exécution ; une API ou deux prenant de 100 à 250 ms pour répondre, les secondes finissent par s'accumuler !

Facturez le temps processeur, pas le temps passé tandis qu'un flux de travail est inactif ou en attente.
Les moteurs de flux de travail, en particulier, ont tendance à passer beaucoup de temps à attendre : la lecture de données à partir du stockage d'objets (comme Cloudflare R2), l'appel d'API ou de LLM tiers, comme o3-mini ou Claude 3.7, même l'interrogation de bases de données comme D1, Postgres ou MySQL. Avec Workflows, tout comme Workers, vous ne payez pas le temps d'attente de votre application.
Commencez le développement
Vous maîtrisez bien Workflows (c'est-à-dire son fonctionnement), et vous souhaitez commencer à développer. Quelle est la suite ?
Consultez la documentation de Workflows pour en savoir plus sur le fonctionnement de la solution, comprendre l'API Workflows et les bonnes pratiques à adopter
Passez en revue le code dans le projet de démarrage
Enfin, déployez le démarreur sur votre propre compte Cloudflare en quelques clics :