# Cloudflare Workers AI Agents + Durable Objects: What Runs Where [2026]

> Cloudflare OS reframes Workers as an agent runtime. Here’s the practical architecture map: what belongs in stateless Workers vs Durable Objects vs Queues vs Workflows, plus the concurrency and retry traps that bite in production.

- Canonical: https://www.kunalganglani.com/blog/cloudflare-workers-ai-agents-durable-objects
- Author: Kunal Ganglani
- Published: 2026-08-06 · Updated: 2026-08-06
- Category: Cloud and DevOps · Tags: cloudflare-workers, durable-objects, edge-computing, ai-agents, architecture

## TL;DR

Cloudflare just reframed its developer platform as “Cloudflare OS,” a way to build agent-style apps on the edge. The confusing part is knowing what belongs where: which pieces should run in a fast, stateless Worker, which need a stateful Durable Object, and when to use Queues or Workflows for background work. The core takeaway is simple: keep requests thin, put per-user memory in one Durable Object, run tool calls asynchronously with a Queue, and use Workflows for multi-step jobs that must survive failures or wait for approvals. If you don’t design for retries and duplicates, your agent will eventually do the same thing twice.

Cloudflare Workers AI agents Durable Objects is suddenly the phrase every edge dev is searching for, and I get why. Cloudflare OS (announced Aug 5, 2026) takes what used to be “a serverless functions platform” and reframes it as a full agent + app runtime. That’s exciting. It’s also where a lot of teams are going to ship a demo, hit the state and concurrency wall, and then blame “agents” instead of their architecture.

**Key takeaways**

- Stateless Workers are for request/response, policy enforcement, and streaming UI. State belongs in Durable Objects, not in clever caches.
- Durable Objects give you a globally-unique, strongly consistent coordination point. That’s gold for per-user agent memory and serialization.
- Queues are for “do this later” tool execution with retries and DLQs. They are at-least-once, so you must design idempotency.
- Workflows are for durable multi-step work (minutes to weeks) and human-in-the-loop waits. Use them when “eventually” is not optional.
- Cloudflare OS “gatekeepers” should translate into explicit tool scopes, allowlists, and auditable decisions. If it’s not logged, it didn’t happen.
> If you can’t point to one component that owns state, your agent doesn’t have memory. It has a race condition.

## Introducing Cloudflare OS (and what it actually changes)

Cloudflare OS is Cloudflare’s new framing of the Workers platform as an open platform “for agents, apps, and work” (announced 2026-08-05) rather than a grab bag of primitives you stitch together yourself (Cloudflare OS launch post). The vibe is: “Every app is a Worker”, and the platform should be able to run agentic workflows safely, with governance built in.

![img IX mining rig inside white and gray room](https://cdn.sanity.io/images/vzekdneq/production/37b8d19900611e51f64f6b8b1bc1ac301b7c2a2c-1200x675.webp)

I like the framing because it forces you to stop hand-waving the hardest part.

Where does the agent live? Not “where do I deploy the code”. Where does state *live*, where does it get serialized, and what component gets to say “no” when the model asks to do something dumb.

When people say “I’m building an agent on the edge”, they usually mean something like:

- A request comes in (HTTP, WebSocket, Slack/Discord webhook, etc.)
- You call an LLM
- The model decides to call tools
- Tools hit internal APIs and third-party services
- You store memory
- You stream a response
On most stacks, you’d glue together a container, Redis, Postgres, a queue, and a workflow engine. Cloudflare OS is saying: you can do a lot of that natively with Workers + Durable Objects + Queues + Workflows + Workers AI.

The trap is assuming those primitives behave like the versions you already know. They don’t. Especially around **consistency**, **concurrency**, and **retries**.

I learned the “retries + identity” lesson the hard way building this site’s multi-agent publishing pipeline. Deterministic gates and idempotent steps matter more than “smarter” models. I rewrote slugs on live URLs once and burned **907K impressions** of link equity in one incident. Agents are the same story in a different costume. If you don’t design for one-way doors, retries, and stable IDs, your “smart” system will quietly destroy value.

[Insert illustration: “Cloudflare OS primitives on one page”]

## An agent workspace for everyone in your company

Cloudflare positions Cloudflare OS as something that can provide an “agent workspace” inside companies: research, docs, spreadsheets, collaborative apps, deterministic workflows, and so on (Cloudflare OS launch post).

![black ImgIX server system](https://cdn.sanity.io/images/vzekdneq/production/39dc63165a0cdf3ad5ba073f15f290f82aebb88b-1200x675.webp)

As a developer, the UI pitch is whatever. The important implication is this: the moment your agent is “for everyone”, you’re not building a toy anymore. You’re building multi-tenant software.

That’s when the real problems show up:

- **Per-user and per-team state:** memory can’t be global. It has to be namespaced.
- **Fairness and backpressure:** one enthusiastic teammate can DDoS your tool runtime.
- **Auditability:** you need to answer “who asked the agent to do this?” and “what did it see?” without guessing.
A practical mental model I’ve found holds up: treat “workspace” as the boundary for both policy and data.

- Workspace = auth boundary
- Workspace = data boundary
- Workspace = billing boundary
Keep that in your head. It’ll make the governance section feel obvious instead of like security theatre.

## What runs where? Workers vs Durable Objects vs Queues vs Workflows

Most write-ups skip the part that actually matters: the map. So here’s mine.

![Billboard displays "the superintelligence cloud" advertisement.](https://cdn.sanity.io/images/vzekdneq/production/1ac2e4aeac867d6e63e263d83493df1aa38ea1cc-1200x675.webp)

### Workers vs Durable Objects vs Queues vs Workflows (Agentic Apps)

| Concern | Stateless Worker | Durable Object (DO) | Queue + consumer Worker | Workflow |
| --- | --- | --- | --- | --- |
| HTTP routing, auth, rate limiting | **Best** | Rare | No | No |
| Streaming UI (SSE/WebSocket fan-out) | **Best** | Sometimes | No | No |
| Agent planning loop (short) | Good | Good (if stateful) | No | Good |
| Per-user memory (hot + consistent) | No | **Best** | No | Sometimes |
| Global coordination / locks | No | **Best** (globally unique) | No | Sometimes |
| Tool execution that can be slow | No | No | **Best** | Good |
| At-least-once background jobs | No | No | **Best** | Good |
| Multi-step with checkpoints | No | No | Meh | **Best** |
| Human-in-the-loop approvals | No | No | No | **Best** (`waitForEvent`) |
| Batch processing / buffering | No | No | **Best** | Sometimes |
| Strictly consistent counters / ordering | No | **Best** | Maybe (with dedupe) | Good |

The rule I keep coming back to: your request path should be thin.

- Worker: validate, authorize, enqueue, stream progress
- DO: own state, serialize decisions
- Queue: run tools, retry safely
- Workflow: orchestrate long-running multi-step flows
### Reference design: a practical agent on Cloudflare OS

This is the reference design I’d actually ship for an internal “company agent” that people will hammer all day:

1. **Ingress Worker** receives a chat message (HTTP/WebSocket). It attaches `workspaceId`, `userId`, and a `conversationId`.
1. The Worker forwards the message to a **per-user Durable Object** (one DO per `(workspaceId,userId)`), which is the agent brain for that user.
1. The DO loads “hot memory” from its SQLite-backed storage, compacts/summarizes if needed, and runs the planning step (LLM call).
1. For each tool call the plan wants, the DO writes an intent record to SQLite (with an idempotency key), then publishes a job to a **Queue**.
1. A Queue consumer Worker executes the tool call, writes results back to the same DO (or posts an event the DO can pull), and acknowledges.
1. The DO updates memory, emits progress events (WebSocket/SSE), and eventually returns a final answer.
1. For anything long-running or approval-gated (invoice payment, production deploy), the DO starts a **Workflow** and stores the workflow instance ID in memory.
If you squint, this is the classic split of API layer, state owner, async workers, orchestration. It just happens to be Cloudflare primitives instead of your own infra.

[Insert illustration: “Request lane vs async lane”]

## Durable Objects highlights (why they’re the center of agent state)

Cloudflare’s docs describe a Durable Object as “a special kind of Cloudflare Worker which uniquely combines compute with storage” and is “automatically provisioned geographically close to where it is first requested” ([Durable Objects docs](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/)).

Three properties matter for agents:

1. **Globally unique:** each DO has a globally-unique name/ID, so you can always talk to *the* state owner for a user/team.
1. **Strong consistency:** the storage is “strongly consistent yet fast to access” because it lives with the object ([Durable Objects docs](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/)).
1. **Actor model:** you get a single coordination point by default. This is not a nice-to-have. It’s how you stop your agent from forking into nonsense under concurrent requests.
Cloudflare also explicitly says you can have **millions** of these objects, provisioned near first request, and they shut down when idle ([Durable Objects docs](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/)). That scales with “one agent per user” way better than the “stateful pod per user” fantasy.

## Durable Objects features (the ones agent builders actually use)

Cloudflare lists a bunch of DO features. For agentic apps, I keep coming back to four:

- **In-memory state** for hot working state (but never treat it as durable).
- **Storage API** for durable memory and coordination.
- **WebSockets / realtime** if you’re doing collaborative agent experiences.
- **RPC / request routing** patterns when you want structured internal calls.
The win comes from being disciplined about what goes where:

- In-memory: ephemeral caches, session-level scratchpad, streaming progress buffers.
- Storage: memory you’d be upset to lose, idempotency records, tool audit logs.
If you want a deeper “agent memory” perspective, I’ve written separately about [AI agents](/pillars/ai-agents) and [AI agent memory state management](/blog/ai-agent-memory-state-management). The short version: memory that isn’t queryable and compactable turns into sludge.

## Actor programming model (and the concurrency pitfalls people keep missing)

Durable Objects are explicitly framed around an actor model in the docs ([Durable Objects docs](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/)). That’s a gift. Most runtimes make you build this yourself with locks and prayers.

But you can still shoot yourself in the foot. Easily.

Here are the three concurrency pitfalls I see over and over when teams build on DOs for the first time.

### Pitfall 1: “Single-threaded” doesn’t mean “no races”

A DO processes events serially, but the moment you `await` I/O you can create interleavings you didn’t intend.

Example failure mode (in words, not code):

- Request A reads state, calls out to an LLM (await)
- Request B arrives, reads the same state, calls out (await)
- A returns and writes “new memory”
- B returns and overwrites it with an older view
No threads. Still a race.

Design around it:

- Treat every state mutation as a transaction against a version.
- Use storage transactions for invariants.
- Serialize “agent turns” explicitly. Only one planning step per conversation at a time.
This is why I like the pattern “DO owns a per-conversation queue of turns” and everything else becomes events.

### Pitfall 2: Re-entrancy through callbacks

If tool execution calls back into the DO while the DO is mid-turn, you can end up with partial, inconsistent state. The agent will act “haunted” and you’ll blame the model.

Design around it:

- Separate “planning” from “tool result ingestion”.
- Use explicit states like `PLANNING`, `WAITING_FOR_TOOLS`, `FINALIZING`.
- Don’t allow tool results to trigger new planning unless the DO transitions deliberately.
### Pitfall 3: Using in-memory locks like they are durable

In-memory locks vanish on restart/hibernation. They also don’t help if you accidentally create multiple DOs for the same logical entity (which you should avoid).

Design around it:

- Put coordination keys in durable storage when correctness matters.
- Keep one canonical DO per entity (user/team/workspace) and route consistently.
If you’re thinking “this is a lot of state machine work”, yeah. That’s what production agentic AI looks like.

## Durable Object storage: SQL API, KV APIs, and PITR

Cloudflare’s newer SQLite-backed DO storage is the real enabler for agent memory that doesn’t suck.

The docs are blunt: DO storage is “private, persistent, strongly consistent and transactional” (SQLite-backed DO storage docs). That baseline is what you need for:

- idempotency
- dedupe
- memory compaction
- audit trails
### SQL API

SQLite-backed storage exposes an SQL API (for example, `sql.exec`) for doing real queries and schema design (SQLite-backed DO storage docs).

For agent memory, that means you can model:

- `messages` table (append-only)
- `summaries` table (checkpoints)
- `tool_calls` table (idempotency + audit)
- `artifacts` table (pointers into R2)
Then you can ask: “give me the last 20 messages since the last summary” without doing gross key-prefix scans.

### Synchronous KV API

The synchronous KV API is useful for small reads/writes where you want a simpler mental model than SQL and you’re not doing complex joins (SQLite-backed DO storage docs).

I use it mentally for:

- “current conversation pointer”
- “latest summary id”
- small config flags
### Asynchronous KV API

Same idea, but async. This is less about “async is better” and more about fitting your latency and batching needs (SQLite-backed DO storage docs).

### PITR (Point In Time Recovery) bookmark API

The PITR APIs (`getCurrentBookmark`, `getBookmarkForTime`, `onNextSessionRestoreBookmark`) exist for recovery semantics in DO storage (SQLite-backed DO storage docs).

Most teams ignore this until the day they need to answer: “what did the agent know yesterday at 3:12pm before it did the bad thing?” If your internal agent can touch production systems, you want an answer that isn’t “uhhhh, logs?”

## Cloudflare Queues: delivery guarantees, retries, and idempotency

Cloudflare Queues are designed to “send and receive messages with guaranteed delivery” and they call out batching, retries, delays, DLQs, and “no charges for egress bandwidth” (Queues docs). They’re also the clean way to get work off the request path.

Two things matter for agent tool execution:

1. **Queues are not a task scheduler.** They’re a reliable buffer.
1. **Guaranteed delivery is not exactly-once.** Assume duplicates.
### How do you avoid duplicate processing with Queues retries?

You do it the boring way. Idempotency.

- Every tool job gets an `idempotencyKey` (often derived from `conversationId + toolCallIndex + toolName + normalizedArgsHash`).
- The Durable Object writes a row `tool_calls(idempotencyKey, status, createdAt, ...)` before enqueueing.
- The consumer checks that row before doing the side effect.
- The consumer writes the result and marks it `DONE` (or `FAILED`) and only then acknowledges.
Yes, that’s more work. But if you skip it, you’ll eventually send the same email twice, charge a card twice, or create two Jira tickets. Pick which incident report you want to write.

For more on agent retries and checkpoints, see [AI Agent Control Flow Patterns [2026]: Retries, HITL, Checkpoints](/blog/ai-agent-control-flow-patterns) and AI in production.

## Workflows vs Queues (and why agents need both)

Cloudflare Workflows are built for “durable multi-step execution without timeouts” and can persist state for “minutes, hours, or even weeks”, with automatic retries and the ability to pause for external events/approvals using `waitForEvent` (Workflows docs).

So when do you use Workflows vs Queues?

- Use **Queues** when you have many independent jobs that can be retried and you mostly need buffering and background execution.
- Use **Workflows** when you have a multi-step process where intermediate state matters, you need durable checkpoints, or you need to wait for a human.
Concrete agent examples:

- Queue: “call GitHub API to open a PR”, “fetch 20 URLs”, “run a linter”, “generate embeddings”.
- Workflow: “provision a new customer workspace”, “run a compliance checklist with approvals”, “pay cart and send invoice”, anything that could take days.
The connection point is your DO memory:

- The DO stores the workflow instance ID.
- The Workflow emits progress events that the DO can ingest.
- The DO stays the “conversation truth” that turns workflow steps into user-visible updates.
If you’ve used Temporal, this should feel familiar. I wrote a workflow-engine perspective in [Temporal Workflow Engine: The Reliability Layer Your Distributed System Is Missing [2026 Guide]](/blog/temporal-workflow-engine-guide).

## A new security and governance framework (gatekeepers, least privilege, and tool policy)

Cloudflare OS leans hard on governance concepts like “gatekeepers govern resources and actions” and that “agents start with no access” (Cloudflare OS launch post). Good. Most agent demos today are basically: “here’s a model with prod credentials.” That’s not innovation. That’s negligence.

Here’s how I translate “gatekeeper” into something you can actually implement in Workers/DO right now.

### Tool scopes, not “tools”

Define a tool as `(action, resource, scope)`.

- Action: `read`, `write`, `delete`, `deploy`
- Resource: `github.repo`, `jira.project`, `r2.bucket`, `d1.database`
- Scope: the smallest possible target (`repo:my-org/my-repo`, `project:PLAT`)
Your DO should store, per workspace:

- granted scopes (explicit)
- denied scopes (explicit)
- approval-required scopes (HITL)
That’s your policy engine. Not prompts. Not “system messages”. Policy.

### Policy follows what the agent has seen (practical version)

Cloudflare’s line “policy follows what the agent has seen” is a useful rule. Don’t let the agent act on data it never showed the user.

Practical version:

- When the agent proposes a tool call, persist an “explanation” blob in DO storage.
- Require that explanation to cite the evidence artifacts (message IDs, doc IDs, URLs).
- Gatekeeper checks: “is the cited evidence allowed for this workspace?” and “did we show it to the user?”
This is where prompt injection gets real. Indirect prompt injection isn’t just a model problem. It’s a policy problem. For the full threat model, see AI security and [Agent-Specific Attack Surfaces Security [2026]: What AppSec Misses](/blog/agent-attack-surfaces-security).

### Audit logging is non-negotiable

Every tool execution should log:

- `workspaceId`, `userId`
- `conversationId`
- `idempotencyKey`
- tool name + normalized args
- decision: allowed/denied/approved
- result: success/failure + error category
If you can’t reconstruct what happened, you didn’t build a system. You built a rumor.

## A platform for building and sharing personal, modifiable apps

Cloudflare OS also pitches “a platform for building and sharing personal, modifiable apps” where “every app is a Worker” (Cloudflare OS launch post).

I’m bullish on this for one reason: it’s a credible story for “internal tools without Kubernetes.” But the “modifiable” part changes what you need to design for:

- You’re not shipping one app. You’re shipping a platform with user-authored code.
- The boundary between “agent logic” and “app logic” gets blurry fast.
If you let user apps call tools directly, your security story collapses. So I’d enforce:

- User apps can request tool calls.
- Only the gatekeeper layer (Worker/DO policy) can authorize.
- Tool execution runs in a separate queue consumer runtime with restricted secrets.
This is the same separation you want between a web frontend and a payments worker. It’s just that now the “frontend” is code people share.

## Use any model, and control what it costs

Cloudflare OS emphasizes model choice and cost control (Cloudflare OS launch post). If you’re building agents, treat model selection as a routing problem, not a religion.

Two practical suggestions:

1. **Model-per-job-shape.** In my own multi-agent pipeline for this blog, I’ve found “Sonnet-class models for tool loops, Opus-class models for prose” beats “one model everywhere” on both cost and quality. Agents are the same. Don’t burn your most expensive model on tool argument normalization.
1. **Budget at the workspace boundary.** If Cloudflare OS is a workspace, attach token budgets to the workspace and enforce them in the gatekeeper.
For cost math and token budgeting patterns, connect this to LLM cost and [Agent Per-Task Cost Calculation [2026]: Retries, Tools, Caching](/blog/agent-per-task-cost-calculation).

And if you’re evaluating local vs API models, I maintain a benchmark dataset at [kunalganglani.com/llm-benchmarks](/llm-benchmarks). Not Cloudflare-specific, but useful when you’re deciding whether “run it locally” is actually viable for a subtask.

[Insert illustration: “Model routing by job type”]

## Observability and debugging: trace it like a distributed system

Most agent outages aren’t “the model went crazy.” They’re boring:

- duplicate queue deliveries
- missing idempotency
- tool timeouts
- partial state writes
- no correlation across hops
So treat the whole thing like a distributed system. Because it is.

A simple observability plan for this stack:

- **Correlation IDs:** generate `traceId` in the ingress Worker. Pass it to the DO, include it in every queue message, and store it with tool call rows.
- **Structured logs:** log JSON with `workspaceId`, `conversationId`, `idempotencyKey`, `tool`, `status`, `latencyMs`.
- **Metrics:**
  - queue depth (backpressure indicator)
  - tool success rate (per tool)
  - retry counts (per error category)
  - DO storage transaction latency
  - workflow step durations
- **DLQ strategy:** a DLQ isn’t “a place to forget jobs.” It’s a backlog you must triage. Set an SLO like: “DLQ items older than 30 minutes page someone.”
If you want a concrete tracing spec for agents, I mapped this to OpenTelemetry in [OpenTelemetry Instrumentation for AI Agents [2026]: Ship It](/blog/opentelemetry-ai-agents-instrumentation).

## Where should I store agent memory? (DO vs D1 vs KV vs R2)

This PAA question is the right one. Memory placement is architecture.

Here’s my opinionated breakdown:

- **Durable Objects storage (SQLite-backed):** best for hot, correctness-sensitive memory that needs strong consistency and coordination. Conversation state, tool call ledger, locks.
- **Workers KV:** good for global-ish config and cached data that can be eventually consistent. Bad for anything you mutate frequently.
- **D1:** good when you need a relational database shared across many entities and you can tolerate its consistency/latency tradeoffs versus “state lives with compute.”
- **R2:** best for large artifacts (files, transcripts, embeddings dumps) that you reference from memory, not store inside it.
The rule I use: if the agent needs to *decide* based on it, store it in the DO. If the agent just needs to *fetch* it, store it in R2/D1 and keep pointers.

Also remember the lifecycle: DOs can shut down when idle. Your memory strategy has to survive restarts.

## How do I run tool calls without blocking the request?

Don’t run tool calls in the request path unless they’re guaranteed fast.

Pattern:

- Request comes in
- DO records the intent + publishes to queue
- Request returns immediately (or keeps a streaming connection open for progress)
If you keep a streaming connection, treat it as a UX channel, not a correctness channel. The source of truth is still DO storage.

## Limits and cost considerations you should know

A few concrete things pulled directly from Cloudflare’s current docs and positioning:

- Durable Objects docs were updated **Jul 15, 2026** and emphasize “millions” of objects and strong consistency ([Durable Objects docs](https://developers.cloudflare.com/durable-objects/concepts/what-are-durable-objects/)).
- Workflows docs were updated **Jun 2, 2026** and explicitly claim state can persist for “minutes, hours, or even weeks” (Workflows docs).
- Queues docs were updated **Apr 21, 2026** and explicitly say “no charges for egress bandwidth” (Queues docs).
- SQLite-backed DO storage docs were updated **May 27, 2026** and document SQL + PITR APIs (SQLite-backed DO storage docs).
Pricing and limits change. But architecturally, the durable primitives exist now and are stable enough to design around.

If you’re comparing edge platforms more broadly, I’d also read [Cloudflare Workers vs Vercel Functions 2026: Which Edge Platform Wins?](/blog/cloudflare-workers-vs-vercel-2026).

## Durable Objects Finally Make Sense (official mental model)

Sometimes the fastest way to align a team is to use the vendor’s own explanation. Cloudflare’s Developer Relations team has a solid walkthrough.

Here’s the official video:

[Watch: Durable Objects Finally Make Sense](https://www.youtube.com/watch?v=k4UXEfZf3sc)

Watch it, then come back and re-read the concurrency pitfalls section. That’s where the production bugs hide.

## Conclusion: the edge is becoming an OS. Your architecture has to act like one.

Cloudflare OS isn’t magic. It’s a consolidation of primitives into a coherent story: agents need a runtime, state, orchestration, and governance. Workers, Durable Objects, Queues, and Workflows cover a surprising amount of that without you standing up Kubernetes.

My prediction for 2027: the teams that win won’t be the ones with the fanciest model. They’ll be the ones with clean state ownership, the most boring idempotency layer, and the strongest gatekeeper policy.

If you’re building on Cloudflare OS, here’s the challenge: write down, in one sentence, which component owns state for a user. If you can’t, stop adding tools. Seriously. You’re about to ship a very fast, very distributed way to be wrong.

Photo by Valentin Lacoste on Unsplash.

## FAQ

### What are Cloudflare Durable Objects and when should I use them?

Durable Objects are a special kind of Cloudflare Worker that combines compute with attached, strongly consistent storage. Use them when you need a single, globally addressable state owner, like per-user agent memory, coordination, or strict ordering.

### Are Durable Objects strongly consistent?

Yes. Cloudflare’s documentation describes Durable Object storage as strongly consistent, and because storage lives with the object it can be fast to access while still being transactional. That makes them suitable for state you can’t afford to race.

### How do Durable Objects handle concurrency and scaling?

A Durable Object acts like an actor: it gives you a single coordination point per object instance, and you scale by having many objects (often one per user, room, or workspace). The main pitfall is async I/O, which can still create state races if you read-modify-write without transactions or turn serialization.

### How do Cloudflare Queues guarantee delivery and what is at-least-once delivery?

Cloudflare Queues are designed for guaranteed delivery, but you should assume at-least-once semantics, meaning a message can be delivered more than once (especially during retries). That’s why tool execution needs idempotency keys and deduplication checks.

### When should I use Cloudflare Workflows vs Queues?

Use Queues for background jobs and buffering where each message can be processed independently and retried safely. Use Workflows when you need durable, multi-step execution with checkpoints, long waits, or human approvals, because Workflows can persist state for long periods and resume automatically.

### Where should I store AI agent memory on Cloudflare (Durable Objects vs D1 vs KV vs R2)?

Store hot, correctness-sensitive memory (conversation state, tool ledgers, coordination) in Durable Objects. Use KV for eventually consistent config or caching, D1 for shared relational data across many entities, and R2 for large artifacts you reference from memory rather than store directly.
