Developer

Stop Duct-Taping AI Agents: Cloudflare Workflows and Durable Objects in Practice

Stop Duct-Taping AI Agents: Cloudflare Workflows and Durable Objects in Practice

I’ve spent too many nights debugging serverless backends for AI agents that were architectural disasters waiting to happen. The pattern is always the same: a developer tries to orchestrate a multi-step agent workflow using raw Cloudflare Workers, only to realize that HTTP requests don’t survive crashes, retries don’t preserve state, and managing dead-letter queues manually is a nightmare.

The result is a “giant backend” of duct-taped cron jobs, external databases, and fragile retry logic that costs more to maintain than it saves in compute time. I used to build these monoliths. Now I refuse to ship them.

The alternative isn’t to build a monolith. It’s to use the right primitives for durable execution. Cloudflare Workflows, combined with Durable Objects, offer a way to write long-running, resilient agent logic as if it were synchronous code, without the operational overhead of traditional backend infrastructure. This isn’t about hype; it’s about eliminating the failure modes that break production AI systems.

The Problem: Duct-Taping Async Jobs

When you build an AI agent that needs to call an LLM, parse the result, execute a tool, and then make another decision, you are building a state machine. In a traditional serverless environment, state is ephemeral. If your Worker times out, crashes, or gets evicted, that state is gone.

To make it work, engineers typically stitch together a complex web of services:
* Queues to buffer messages.
* Cron jobs to poll for completion.
* Databases (like D1 or external Postgres) to store intermediate states.
* Retry logic to handle transient failures.

This approach is brittle. It introduces latency, increases costs, and creates a maintenance burden. Worse, it leads to redundant costs. If a workflow crashes after an expensive LLM call but before the tool execution, a naive retry will re-run the LLM call. You pay twice for the same reasoning, and potentially trigger side effects you didn’t intend.

The “giant backend” pattern is overkill for many edge-native use cases. You don’t need a full microservices architecture to run a durable agent job. You need a runtime that understands durability.

The Solution: Workflows + Durable Objects

Cloudflare Workflows introduce Durable Execution to the edge. This concept allows you to write long-running processes as normal, synchronous-looking code. The runtime handles the complexity of checkpoints, retries, and waits.

At the core of this system is the step model. Each step in a workflow is a discrete unit of work. When you call step.do(), the runtime creates a checkpoint. If the workflow is interrupted—whether by a crash, a timeout, or a manual pause—it resumes exactly where it left off, replaying the steps up to the checkpoint. This is not a simple retry; it’s a deterministic replay.

This is where Durable Objects come in. They provide the stateful compute layer at the edge. While Workflows handle the orchestration and durability, Durable Objects manage the persistent state and long-lived connections. Together, they form a robust foundation for building resilient agents without the bloat of a traditional backend.

For a deep dive into the AgentWorkflow class and how to separate LLM calls from tool execution, see the official Cloudflare documentation on building durable AI agents.

Building a Durable Agent Job

The key to building a reliable agent is separating concerns. You need to distinguish between the orchestration (the workflow) and the state (the durable object).

Using AgentWorkflow for Bidirectional Communication

For AI agents, you often need more than just a one-way trigger. You might need to report progress to a user via WebSocket or pause for human approval. The AgentWorkflow class extends the standard Workflow capabilities to support this bidirectional communication.

This allows you to:
1. Report Progress: Send updates back to the client in real-time.
2. Broadcast Events: Notify multiple listeners of state changes.
3. Wait for Input: Pause the workflow until an external event occurs (e.g., a user clicking “Approve”).

Checkpointing with step.do()

The most critical pattern in durable workflows is checkpointing. Every time you call step.do(), you are saving a checkpoint. This is your insurance policy.

Consider an agent that needs to:
1. Call an LLM to generate a plan.
2. Execute a tool based on that plan.
3. Call the LLM again to evaluate the result.

If you structure this as three distinct steps, you ensure that:
* If the LLM call fails, you retry only the LLM call.
* If the tool execution fails, you skip the LLM call and retry the tool.
* If the workflow crashes after the LLM call but before the tool execution, it resumes at the tool execution step, skipping the LLM call entirely.

This prevents redundant API costs and avoids side effects. It’s a simple pattern, but it’s the difference between a fragile prototype and a production-ready system. For more on the technical details of the step model and state limits, check out Chapter 7: Workflows: Durable Execution.

Critical Constraints and Workarounds

Durability comes with constraints. Understanding these limits is crucial for designing efficient workflows.

The 1 MiB State Size Limit

Workflow step results are limited to 1 MiB. This is a hard limit. If your LLM response or tool output exceeds this size, you cannot pass it directly between steps.

The Workaround: Store the data externally. Use R2 for large blobs or D1 for structured data. Pass only the reference (e.g., the object key or row ID) between steps. This keeps your workflow lightweight and within the limits.

The 128 MB RAM and 6 TCP Connection Cap

Workflows inherit the constraints of Durable Objects. Each instance has a 128 MB RAM limit and a maximum of 6 concurrent TCP connections.

This means:
* You cannot hold massive datasets in memory.
* You cannot open hundreds of parallel HTTP requests to external APIs.

The Workaround: Stream data rather than holding it in memory. Use pagination for large datasets. If you need more parallelism, consider splitting your workflow into multiple smaller workflows or using Queues to distribute the load. For a community discussion on these limitations, see this Hacker News thread.

When to Choose What

  • Workflows: Best for multi-step orchestration, long-running processes, and complex state machines.
  • Durable Objects: Best for simpler async processing, real-time apps, and tasks that need persistent state but not complex orchestration.
  • Queues: Best for high-throughput, fire-and-forget tasks that don’t need to preserve state between steps.

Practical Implementation Guide

Building a durable agent involves a few key steps. Here’s how to approach it.

1. Set Up the Cloudflare Worker Project

Start with a standard Cloudflare Worker project. Install the @cloudflare/workflows package. Define your Durable Object class to handle any persistent state that needs to survive across workflow invocations.

2. Define Tools and Workflow Steps

Write your tools as separate functions. Each tool should be idempotent where possible. Then, define your workflow steps.

// Concrete implementation of durable steps
import { AgentWorkflow, step } from "@cloudflare/workers-types/agent-workflow";

export const workflow = new AgentWorkflow();

// Step 1: Generate plan
workflow.step("llm_call", async () => {
  const response = await llm.call(prompt);
  return response;
});

// Step 2: Execute tool (resumes here if workflow crashed after step 1)
workflow.step("tool_execution", async (context) => {
  const plan = context.previousStepResult;
  await executeTool(plan);
});

// Step 3: Evaluate result
workflow.step("evaluation", async (context) => {
  const result = context.previousStepResult;
  return await llm.evaluate(result);
});

3. Handle Human-in-the-Loop Patterns

For agents that require human approval, use waitForEvent. This pauses the workflow until an external event is received. This is crucial for tasks like code review, financial approvals, or any process where human judgment is required.

When to Walk Away

Despite the power of Workflows and Durable Objects, they are not a silver bullet. There are scenarios where you should reject this approach.

High-Frequency Tasks

If your task needs to run thousands of times per second, Workflows may introduce too much overhead. The checkpointing and state management add latency. For high-frequency, low-latency tasks, consider using Queues or simple Workers with in-memory caching.

Massive Data Processing

If your workflow needs to process terabytes of data, the 128 MB RAM limit will be a bottleneck. You’ll spend more time managing data streaming and external storage than actually processing. For big data, use dedicated data processing pipelines (like Spark or Flink) or Cloudflare’s R2 + Workers for batch processing.

Cost Considerations

While Workflows can reduce operational costs by eliminating the need for external queues and databases, they are not free. You pay for the compute time of the workflow steps. For very long-running workflows, this can add up.

Here is the concrete tradeoff: On the free tier, long-running workflows will hit execution time limits much faster than simple Workers. If your agent loop exceeds 10 steps, the cumulative compute cost of the workflow steps often outweighs the cost of D1 read/write operations plus Queue messages used in a traditional backend. Always profile your workflow’s duration and step count to estimate costs.

Sources and further reading

Keep exploring

Find more practical writing from the RodyTech archive.

RodyTech publishes practical writing on AI systems, infrastructure, and software that teams can actually ship. Use the archive paths below to keep reading by topic or browse the full library.

  • Browse the full archive by publication date and topic
  • Hands-on notes from real builds, deployments, and ops work
  • Category paths for AI, infrastructure, developer tools, and security
Browse all articles More in Developer Visit the main RodyTech site

Rody

Founder & CEO · RodyTech LLC

Founder of RodyTech LLC in Iowa. I write practical notes on automation, infrastructure, security, and software decisions for builders and business operators.

Next step

Turn one article into a working reading loop.

Keep the context warm: revisit the archive or stay inside the same topic while the thread is still fresh.

Explore the archive More Developer
Keep reading
Why Your RAG Prototype Is Lying to You: The Case for Rigorous Pre-Launch Evaluation OpenAI Agents SDK Sandbox Patterns: Safer File and Command Automation

No comments yet

Leave a comment

Your email address will not be published. Required fields are marked *