Cloudflare Workflows: sleep and poll a Sume run, no open request

A Cloudflare Workflow can submit a Sume run, step.sleep for minutes, then check status again. Here are the step limits that bound how long you can poll.

6 min readSume
All posts

A Cloudflare Workflow lets you submit a Sume run in one step.do, wait with step.sleep, and check the status in another step.do, without any request held open. Sleeping instances do not use concurrency slots, and the longest sleep is 365 days, so the real limit is the step count: 1,024 on the Free plan. Poll at a sensible interval and you will not reach it.

The Cloudflare facts are from Workflows get started and Workflows limits, both read 2026-10-10. The Sume facts are from Runs and results and Create a run.

Which limits shape the design?

The limits page gives the numbers below. A step result that is not a stream may be at most 1 MiB, so store a run id and a status, never the finished media. Each step has a compute time budget, which is small on Free. The retained state of finished instances is also short.

Retention matters for audits. Finished instances keep their state for 3 days on Free and 30 days on Paid, so write the run id and the final status to your own storage if you need a longer record. Sume keeps the receipt on its side, which you can read again by run id.

Workflows limits (Cloudflare limits page, read 2026-10-10)
LimitFreePaid
Steps per workflow1,02410,000 default, 25,000 maximum
Compute time per step10 ms30 s default, 5 min maximum
Concurrent instances10050,000
Completed-instance state kept3 days30 days
Largest non-stream step result1 MiB1 MiB
Longest sleep365 days365 days

How many polls fit?

A poll loop of one step.do and one step.sleep per round uses 2 steps. With a submit step and a final step, a Free instance has 1,024 minus 2 = 1,022 steps left, which is 511 rounds. Sume's own ceiling is lower: Runs and results says a run is force-finalized as failed 90 minutes after creation, so polling once a minute needs at most about 90 rounds, or 180 steps. The step budget is not the problem; the interval is.

Waiting does not cost the compute time budget either, because the 10 ms Free limit applies to the work inside a step, not the sleeping between steps. The fetch inside a step is network wait, so confirm your own measurements on your own plan.

import { WorkflowEntrypoint } from "cloudflare:workers";

export class RenderFlow extends WorkflowEntrypoint {
  async run(event, step) {
    const H = { Authorization: `Bearer ${this.env.SUME_API_KEY}`, "Content-Type": "application/json" };
    const id = await step.do("submit", async () => {
      const r = await fetch("https://api.sume.com/v1/formats/acme/promo/runs", {
        method: "POST", headers: { ...H, "Idempotency-Key": `cf-${event.instanceId}` },
        body: JSON.stringify({ instruction: "Make the promo.", input: event.payload })
      });
      return (await r.json()).data.id;
    });
    for (let i = 0; i < 95; i++) {
      await step.sleep(`wait-${i}`, "1 minute");
      const s = await step.do(`poll-${i}`, async () => {
        const r = await fetch(`https://api.sume.com/v1/format-runs/${id}/status`, { headers: H });
        return (await r.json()).data.status;
      });
      if (s !== "queued" && s !== "processing") return { id, status: s };
    }
    return { id, status: "timeout" };
  }
}

What does the loop need to get right?

Step names must be unique per round, which is why the index is in the name. The idempotency key uses the instance id, so a retried submit step returns the original run through idempotency_hit: true. The statuses queued and processing come from the Sume runs page; treat everything else as an end state and read it from the status payload instead of guessing.

After the loop, a final step should fetch GET /v1/format-runs/{run_id}/result. It answers 409 run_not_completed until the run is terminal, so reaching it early is harmless.

Use an exponential interval instead of a fixed minute if the Format is short. Sume's own example sleeps with a doubling interval capped at 60 seconds, which finds fast runs quickly without spending steps on slow ones.

Should I wait for an event instead?

The get started guide also shows step.waitForEvent, which suspends the workflow until something sends it an event. Pair it with a communication.webhook_url on the Sume run: a Worker receives the format.run.terminal webhook, verifies the signature and sends the event to the instance. You then poll only as a fallback.

Choose the plain sleep loop when you want the fewest moving parts, and the event wait when the render is long and you would rather wake once.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume