Cloudflare Runtime
Deploy Helix Agents on Cloudflare's edge network with two runtime options optimized for different use cases.
Two Approaches
The @helix-agents/runtime-cloudflare package provides two distinct runtime approaches:
| Approach | Best For | Streaming Cost | State Storage |
|---|---|---|---|
| Workflows | Step-level durability, D1 integration, automatic retries | Counts against 1000 subrequest limit | D1 Database |
| Durable Objects | Heavy streaming, real-time WebSocket/SSE, simpler architecture | FREE (direct to connections) | DO built-in SQLite |
Quick Comparison
| Feature | Workflows | Durable Objects |
|---|---|---|
| Execution Model | Cloudflare Workflows | PartyServer-based DO |
| Streaming | Via separate DO (subrequest per chunk) | Direct WebSocket/SSE (FREE) |
| Subrequest Limit | 1000 limit applies | Only LLM calls count |
| State Storage | D1 Database | DO built-in SQLite |
| Hibernation | Not supported | Supported (cost efficient) |
| Step Durability | Automatic retry per step | Manual checkpointing |
| Architecture | Worker → Workflow → D1/DO | Worker → AgentServer DO |
| Real-time | Polling or separate WebSocket | Native WebSocket/SSE |
| Complexity | Medium (multiple services) | Lower (single DO) |
Choosing an Approach
Use Workflows When:
- You need step-level durability - Each workflow step is automatically retried on failure
- You want to share D1 state - Other services can query the same D1 database
- You're already using Cloudflare Workflows - Integrate with existing workflow infrastructure
- Your agents don't require heavy streaming - Less than ~100 chunks per execution is safe
import { CloudflareAgentExecutor, AgentSteps, runAgentWorkflow } from '@helix-agents/runtime-cloudflare';
import { D1StateStore } from '@helix-agents/store-cloudflare';
// Uses Cloudflare Workflows + D1
const executor = new CloudflareAgentExecutor({
workflowBinding: env.AGENT_WORKFLOW,
stateStore: new D1StateStore(env.DB),
streamManager: doStreamManager,
});Use Durable Objects When:
- You need unlimited streaming - Bypasses the 1000 subrequest limit entirely
- You want real-time WebSocket/SSE - Native bidirectional communication
- You prefer simpler architecture - One DO per agent run, no D1 needed
- You want hibernation support - Cost-efficient sleep/wake with state preservation
- Streaming is performance-critical - Direct writes are faster than subrequests
import { AgentServer, DOAgentExecutor } from '@helix-agents/runtime-cloudflare';
// Export DO class
export { AgentServer };
// Uses Durable Objects directly
const executor = new DOAgentExecutor({
agentNamespace: env.AGENTS,
createLLMAdapter: () => new VercelAIAdapter({ model: openai('gpt-4o') }),
});The Subrequest Limit Problem
Cloudflare Workers have a 1000 subrequest limit per invocation. In the Workflows approach, each stream chunk write to the streaming Durable Object counts as a subrequest:
Worker → Workflow → Stream DO (subrequest!)
→ Stream DO (subrequest!)
→ Stream DO (subrequest!)
→ ... (1000 limit reached!)For streaming-heavy agents (chat responses, tool results, thinking tokens), this limit is easily reached. The DO runtime solves this by hosting execution inside the DO:
Worker → AgentServer DO
├── Stream to WebSocket (FREE!)
├── Stream to WebSocket (FREE!)
└── Stream to SSE (FREE!)Architecture Diagrams
Workflows Runtime
graph TB
subgraph Worker ["Worker"]
Exec["CloudflareAgentExecutor"]
subgraph WF ["Workflow (AGENT_WORKFLOW)"]
Steps["AgentSteps"]
D1["D1 Database (state)"]
StreamDO["Stream DO (streaming)<br/><i>← subrequest limit</i>"]
end
Exec --> WF
endDurable Objects Runtime
graph TB
subgraph Worker ["Worker"]
subgraph DO ["AgentServer (Durable Object)"]
JSExec["JSAgentExecutor<br/>(in-DO execution)"]
DOState["DOStateStore<br/>(SQLite, in-DO)"]
DOStream["DOStreamManager<br/>→ WebSocket/SSE <b>(FREE!)</b>"]
end
endInstallation
Both approaches use the same package:
npm install @helix-agents/runtime-cloudflare @helix-agents/coreFor Workflows, you also need:
npm install @helix-agents/store-cloudflarePrerequisites
Both Approaches
- Cloudflare account with Workers Paid plan
- Wrangler CLI:
npm install -g wrangler
Workflows Runtime
- D1 database (for state storage)
- Workflow binding configured
- Durable Objects (for streaming)
Durable Objects Runtime
- Durable Objects with SQLite enabled
- No D1 database required
Configuration Examples
Workflows (wrangler.toml)
[[d1_databases]]
binding = "DB"
database_name = "agent-state"
database_id = "your-database-id"
[[durable_objects.bindings]]
name = "STREAM_DO"
class_name = "StreamServer"
[[workflows]]
name = "agent_workflow"
binding = "AGENT_WORKFLOW"
class_name = "AgentWorkflow"
[[migrations]]
tag = "v1"
new_classes = ["StreamServer", "AgentWorkflow"]Durable Objects (wrangler.toml)
[[durable_objects.bindings]]
name = "AGENTS"
class_name = "AgentServer"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["AgentServer"] # Note: SQLite classes!Feature Availability
| Feature | Workflows | Durable Objects |
|---|---|---|
| Agent Execution | ✅ | ✅ |
| Tool Execution | ✅ | ✅ |
| Sub-Agents | ✅ | ✅ |
| Streaming | ✅ (limited) | ✅ (unlimited) |
| WebSocket | Via separate DO | ✅ Native |
| SSE | ✅ | ✅ |
| Multi-turn Conversations | ✅ | ✅ |
| Interrupt/Resume | ✅ | ✅ |
| Checkpoints | ✅ | ✅ |
| Hibernation | ❌ | ✅ |
| Step Retries | ✅ Automatic | ❌ Manual |
| D1 Integration | ✅ Native | ❌ Separate |
Concurrency Handling
| Approach | Mechanism |
|---|---|
| Workflows | Workflow instance uniqueness |
| Durable Objects | Single-threaded DO execution |
retry() Support
Both approaches support retry() for recovering from failures:
const result = await handle.result();
if (result.status === 'failed') {
const retryHandle = await handle.retry();
}Method Availability
| Method | Workflows | Durable Objects |
|---|---|---|
execute() | ✅ | ✅ |
resume() | ✅ | ✅ |
retry() | ✅ | ✅ |
getHandle() | ✅ | ✅ |
Migration Between Approaches
Both approaches use compatible agent definitions. Migrating typically involves:
- State migration - Export from D1, import to DO SQLite (or vice versa)
- Configuration changes - Update wrangler.toml bindings
- Code changes - Switch executor class and configuration
Agent definitions (defineAgent, defineTool) work identically in both approaches.
Common Patterns
Hybrid Approach
Use Workflows for orchestration and DOs for streaming:
// Workflow for durable orchestration
const workflow = new AgentWorkflow();
// Dedicated DO for unlimited streaming
const streamDO = env.STREAM_DO.get(streamId);External State Sharing
If you need to share state with other services while using DO runtime:
// DO runtime for execution
const executor = new DOAgentExecutor({ ... });
// Sync to D1 for external access
await syncToD1(result, env.DB);Performance Considerations
| Aspect | Workflows | Durable Objects |
|---|---|---|
| Cold Start | Worker + Workflow init | Worker + DO init |
| Streaming Latency | Higher (subrequest) | Lower (direct) |
| State Access | Network (D1) | Local (SQLite) |
| Memory | Workflow limits | DO limits |
| Concurrent Connections | Via polling | Native WebSocket |
Cost Considerations
| Cost Factor | Workflows | Durable Objects |
|---|---|---|
| Subrequests | High (streaming) | Low (LLM only) |
| D1 Reads/Writes | Yes | No |
| DO Duration | Streaming DO | Execution DO |
| Hibernation | N/A | Reduces cost |
Next Steps
- Workflows Runtime - Complete setup and API guide
- Durable Objects Runtime - Complete setup and API guide
- Storage: Cloudflare - D1 and DO storage details
- Examples - Working examples