- Run Workflows
- Quick start
- AgentWorkflow class
- Agent workflow methods
- runWorkflow(workflowName, params, options?)
- sendWorkflowEvent(workflowName, instanceId, event)
- getWorkflowStatus(workflowName, instanceId)
- getWorkflow(instanceId)
- getWorkflows(criteria?)
- deleteWorkflow(instanceId)
- deleteWorkflows(criteria?)
- terminateWorkflow(instanceId)
- pauseWorkflow(instanceId)
- resumeWorkflow(instanceId)
- restartWorkflow(instanceId, options?)
- approveWorkflow(instanceId, options?)
- rejectWorkflow(instanceId, options?)
Run Workflows
Last updated Oct 12, 2026 Integrate Cloudflare Workflows with Agents for durable, multi-step background processing while Agents handle real-time communication.
Agents vs. Workflows
Agents excel at real-time communication and state management. Workflows excel at durable execution with automatic retries, failure recovery, and waiting for external events.
Use Agents alone for chat, messaging, and quick API calls. Use Agent + Workflow for long-running tasks (over 30 seconds), multi-step pipelines, and human approval flows.
Quick start
1. Define a Workflow
Extend AgentWorkflow for typed access to the originating Agent:
import { AgentWorkflow } from "agents/workflows";
export class ProcessingWorkflow extends AgentWorkflow {
async run(event, step) {
const params = event.payload;
const result = await step.do("process-data", async () => {
return processData(params.data);
});
// Non-durable: progress reporting (may repeat on retry)
await this.reportProgress({
step: "process",
status: "complete",
percent: 0.5,
});
// Broadcast to connected WebSocket clients
this.broadcastToClients({ type: "update", taskId: params.taskId });
await step.do("save-results", async () => {
// Call Agent methods via RPC
await this.agent.saveResult(params.taskId, result);
});
// Durable: idempotent, won't repeat on retry
await step.reportComplete(result);
return result;
}
}
import { AgentWorkflow } from "agents/workflows";
import type { AgentWorkflowEvent, AgentWorkflowStep } from "agents/workflows";
import type { MyAgent } from "./agent";
type TaskParams = { taskId: string; data: string };
export class ProcessingWorkflow extends AgentWorkflow<MyAgent, TaskParams> {
async run(event: AgentWorkflowEvent<TaskParams>, step: AgentWorkflowStep) {
const params = event.payload;
const result = await step.do("process-data", async () => {
return processData(params.data);
});
// Non-durable: progress reporting (may repeat on retry)
await this.reportProgress({
step: "process",
status: "complete",
percent: 0.5,
});
// Broadcast to connected WebSocket clients
this.broadcastToClients({ type: "update", taskId: params.taskId });
await step.do("save-results", async () => {
// Call Agent methods via RPC
await this.agent.saveResult(params.taskId, result);
});
// Durable: idempotent, won't repeat on retry
await step.reportComplete(result);
return result;
}
}
2. Start a Workflow from an Agent
Use runWorkflow() to start and track workflows:
import { Agent } from "agents";
export class MyAgent extends Agent {
async startTask(taskId, data) {
const instanceId = await this.runWorkflow("PROCESSING_WORKFLOW", {
taskId,
data,
});
return { instanceId };
}
async onWorkflowProgress(workflowName, instanceId, progress) {
this.broadcast(JSON.stringify({ type: "workflow-progress", progress }));
}
async onWorkflowComplete(workflowName, instanceId, result) {
console.log(`Workflow completed:`, result);
}
async saveResult(taskId, result) {
this
.sql`INSERT INTO results (task_id, data) VALUES (${taskId}, ${JSON.stringify(result)})`;
}
}
import { Agent } from "agents";
export class MyAgent extends Agent {
async startTask(taskId: string, data: string) {
const instanceId = await this.runWorkflow("PROCESSING_WORKFLOW", {
taskId,
data,
});
return { instanceId };
}
async onWorkflowProgress(
workflowName: string,
instanceId: string,
progress: unknown,
) {
this.broadcast(JSON.stringify({ type: "workflow-progress", progress }));
}
async onWorkflowComplete(
workflowName: string,
instanceId: string,
result?: unknown,
) {
console.log(`Workflow completed:`, result);
}
async saveResult(taskId: string, result: unknown) {
this
.sql`INSERT INTO results (task_id, data) VALUES (${taskId}, ${JSON.stringify(result)})`;
}
}
3. Configure Wrangler
{
"name": "my-app",
"main": "src/index.ts",
"upload_source_maps": true,
// Set this to today's date
"compatibility_date": "2026-10-27",
"durable_objects": {
"bindings": [{ "name": "MY_AGENT", "class_name": "MyAgent" }],
},
"workflows": [
{
"name": "processing-workflow",
"binding": "PROCESSING_WORKFLOW",
"class_name": "ProcessingWorkflow",
},
],
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }],
}
name = "my-app"
main = "src/index.ts"
upload_source_maps = true
# Set this to today's date
compatibility_date = "2026-10-27"
[[durable_objects.bindings]]
name = "MY_AGENT"
class_name = "MyAgent"
[[workflows]]
name = "processing-workflow"
binding = "PROCESSING_WORKFLOW"
class_name = "ProcessingWorkflow"
[[migrations]]
tag = "v1"
new_sqlite_classes = [ "MyAgent" ]
AgentWorkflow class
Base class for Workflows that integrate with Agents.
Type parameters
| Parameter | Description |
|---|---|
AgentType |
The Agent class type for typed RPC |
Params |
Parameters passed to the workflow |
ProgressType |
Type for progress reporting (defaults to DefaultProgress) |
Env |
Environment type (defaults to Cloudflare.Env) |
Properties
| Property | Type | Description |
|---|---|---|
agent |
Stub | Typed stub for calling Agent methods. For workflows started from a sub-agent, this is an RPC-only stub back to the originating facet; use sub-agent routing for HTTP or WebSocket fetch() traffic |
instanceId |
string | The workflow instance ID |
workflowName |
string | The workflow binding name |
env |
Env | Environment bindings |
Instance methods (non-durable)
These methods may repeat on retry. Use for lightweight, frequent updates.
reportProgress(progress)
Report progress to the Agent. Triggers onWorkflowProgress callback.
await this.reportProgress({
step: "processing",
status: "running",
percent: 0.5,
});
await this.reportProgress({
step: "processing",
status: "running",
percent: 0.5,
});
broadcastToClients(message)
Broadcast a message to all WebSocket clients connected to the Agent.
this.broadcastToClients({ type: "update", data: result });
this.broadcastToClients({ type: "update", data: result });
waitForApproval(step, options?)
Wait for an approval event. Throws WorkflowRejectedError if rejected.
const approval = await this.waitForApproval(step, {
timeout: "7 days",
});
const approval = await this.waitForApproval<{ approvedBy: string }>(step, {
timeout: "7 days",
});
Step methods (durable)
These methods are idempotent and will not repeat on retry. Use for state changes that must persist.
| Method | Description |
|---|---|
step.reportComplete(result?) |
Report successful completion |
step.reportError(error) |
Report an error |
step.sendEvent(event) |
Send a custom event to the Agent |
step.updateAgentState(state) |
Replace Agent state (broadcasts to clients) |
step.mergeAgentState(partial) |
Merge into Agent state (broadcasts to clients) |
step.resetAgentState() |
Reset Agent state to initialState |
DefaultProgress type
type DefaultProgress = {
step?: string;
status?: "pending" | "running" | "complete" | "error";
message?: string;
percent?: number;
[key: string]: unknown;
};
Agent workflow methods
Methods available on the Agent class for Workflow management.
runWorkflow(workflowName, params, options?)
Start a workflow instance and track it in the Agent database.
Parameters:
| Parameter | Type | Description |
|---|---|---|
workflowName |
string | Workflow binding name from env |
params |
object | Parameters to pass to the workflow |
options.id |
string | Custom workflow ID (auto-generated if not provided) |
options.metadata |
object | Metadata stored for querying (not passed to workflow) |
options.agentBinding |
string | Agent binding name (auto-detected if not provided). When called from a sub-agent, this is the root Agent binding name |
Returns: Promise<string> - Workflow instance ID
const instanceId = await this.runWorkflow(
"MY_WORKFLOW",
{ taskId: "123" },
{
metadata: { userId: "user-456", priority: "high" },
},
);
const instanceId = await this.runWorkflow(
"MY_WORKFLOW",
{ taskId: "123" },
{
metadata: { userId: "user-456", priority: "high" },
},
);
Starting workflows from sub-agents
Sub-agents can call this.runWorkflow() directly. The workflow is tracked in the originating sub-agent's SQLite database, and this.agent inside AgentWorkflow routes back to that same sub-agent for RPC calls, callbacks, state updates, and broadcasts.
Parent agents do not automatically list or control workflows that a sub-agent starts. SubAgentStub<T> only exposes user-defined methods, not inherited Agent methods such as approveWorkflow() or getWorkflow(). To control a child-started workflow from the parent, define small wrapper methods on the child and call those wrappers through the sub-agent stub.
export class ParentAgent extends Agent {
async startChildWorkflow(childName, task) {
const child = await this.subAgent(ChildAgent, childName);
return child.startWorkflow(task);
}
async approveChildWorkflow(childName, workflowId) {
const child = await this.subAgent(ChildAgent, childName);
return child.approveChildWorkflow(workflowId);
}
}
export class ChildAgent extends Agent {
async startWorkflow(task) {
return this.runWorkflow("CHILD_WORKFLOW", { task });
}
async approveChildWorkflow(workflowId) {
return this.approveWorkflow(workflowId);
}
async getChildWorkflow(workflowId) {
return this.getWorkflow(workflowId);
}
}
export class ParentAgent extends Agent {
async startChildWorkflow(childName: string, task: string) {
const child = await this.subAgent(ChildAgent, childName);
return child.startWorkflow(task);
}
async approveChildWorkflow(childName: string, workflowId: string) {
const child = await this.subAgent(ChildAgent, childName);
return child.approveChildWorkflow(workflowId);
}
}
export class ChildAgent extends Agent {
async startWorkflow(task: string) {
return this.runWorkflow("CHILD_WORKFLOW", { task });
}
async approveChildWorkflow(workflowId: string) {
return this.approveWorkflow(workflowId);
}
async getChildWorkflow(workflowId: string) {
return this.getWorkflow(workflowId);
}
}
For sub-agent origins, AgentWorkflow.agent is an RPC-only stub. Use it to call Agent methods, but use routeSubAgentRequest() or the /agents/{parent}/{name}/sub/{child}/{name} URL shape for external HTTP or WebSocket routing instead of this.agent.fetch().
Routing constraints
Because the originating identity is persisted durably in the workflow params and replayed on every callback, a few constraints apply to all workflows (sub-agent and top-level alike):
- Callbacks resolve the Agent by name. The runtime re-resolves the originating Agent with
getAgentByName(...). If you addressed the Agent by a raw Durable Object ID (idFromString/get(id)) instead of by name, callbacks land on a different instance. Start workflows from name-addressed Agents. - Class names must survive bundling. The originating path is keyed by
constructor.name. Configure your bundler to preserve class names (esbuildkeepNames: true) so progress, completion, andthis.agentRPC can be routed back to the right facet. agentBindingis the root binding. When you passoptions.agentBindingfrom a sub-agent, use the root Agent's Durable Object binding name, not a child binding.
sendWorkflowEvent(workflowName, instanceId, event)
Send an event to a running workflow.
await this.sendWorkflowEvent("MY_WORKFLOW", instanceId, {
type: "custom-event",
payload: { action: "proceed" },
});
await this.sendWorkflowEvent("MY_WORKFLOW", instanceId, {
type: "custom-event",
payload: { action: "proceed" },
});
getWorkflowStatus(workflowName, instanceId)
Get the status of a workflow and update the tracking record.
const status = await this.getWorkflowStatus("MY_WORKFLOW", instanceId);
// { status: 'running', output: null, error: null }
const status = await this.getWorkflowStatus("MY_WORKFLOW", instanceId);
// { status: 'running', output: null, error: null }
getWorkflow(instanceId)
Get a tracked workflow by ID.
const workflow = this.getWorkflow(instanceId);
// { instanceId, workflowName, status, metadata, error, createdAt, ... }
const workflow = this.getWorkflow(instanceId);
// { instanceId, workflowName, status, metadata, error, createdAt, ... }
getWorkflows(criteria?)
Query tracked workflows with cursor-based pagination. Returns a WorkflowPage with workflows, total count, and cursor for the next page.
// Get running workflows (default limit is 50, max is 100)
const { workflows, total } = this.getWorkflows({ status: "running" });
// Filter by metadata
const { workflows: userWorkflows } = this.getWorkflows({
metadata: { userId: "user-456" },
});
// Pagination with cursor
const page1 = this.getWorkflows({
status: ["complete", "errored"],
limit: 20,
orderBy: "desc",
});
console.log(`Showing ${page1.workflows.length} of ${page1.total} workflows`);
// Get next page using cursor
if (page1.nextCursor) {
const page2 = this.getWorkflows({
status: ["complete", "errored"],
limit: 20,
orderBy: "desc",
cursor: page1.nextCursor,
});
}
// Get running workflows (default limit is 50, max is 100)
const { workflows, total } = this.getWorkflows({ status: "running" });
// Filter by metadata
const { workflows: userWorkflows } = this.getWorkflows({
metadata: { userId: "user-456" },
});
// Pagination with cursor
const page1 = this.getWorkflows({
status: ["complete", "errored"],
limit: 20,
orderBy: "desc",
});
console.log(`Showing ${page1.workflows.length} of ${page1.total} workflows`);
// Get next page using cursor
if (page1.nextCursor) {
const page2 = this.getWorkflows({
status: ["complete", "errored"],
limit: 20,
orderBy: "desc",
cursor: page1.nextCursor,
});
}
The WorkflowPage type:
type WorkflowPage = {
workflows: WorkflowInfo[];
total: number; // Total matching workflows
nextCursor: string | null; // null when no more pages
};
deleteWorkflow(instanceId)
Delete a single workflow instance tracking record. Returns true if deleted, false if not found.
deleteWorkflows(criteria?)
Delete workflow instance tracking records matching criteria.
// Delete completed workflow instances older than 7 days
this.deleteWorkflows({
status: "complete",
createdBefore: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
});
// Delete all errored and terminated workflows
this.deleteWorkflows({
status: ["errored", "terminated"],
});
// Delete completed workflow instances older than 7 days
this.deleteWorkflows({
status: "complete",
createdBefore: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
});
// Delete all errored and terminated workflows
this.deleteWorkflows({
status: ["errored", "terminated"],
});
terminateWorkflow(instanceId)
Terminate a running workflow immediately. Sets status to "terminated".
await this.terminateWorkflow(instanceId);
await this.terminateWorkflow(instanceId);
Note
terminate() is not yet supported in local development with wrangler dev. It works when deployed to Cloudflare.
pauseWorkflow(instanceId)
Pause a running workflow. The workflow can be resumed later with resumeWorkflow().
await this.pauseWorkflow(instanceId);
await this.pauseWorkflow(instanceId);
Note
pause() is not yet supported in local development with wrangler dev. It works when deployed to Cloudflare.
resumeWorkflow(instanceId)
Resume a paused workflow.
await this.resumeWorkflow(instanceId);
await this.resumeWorkflow(instanceId);
Note
resume() is not yet supported in local development with wrangler dev. It works when deployed to Cloudflare.
restartWorkflow(instanceId, options?)
Restart a workflow instance from the beginning with the same ID.
// Reset tracking (default) - clears timestamps and error fields
await this.restartWorkflow(instanceId);
// Preserve original timestamps
await this.restartWorkflow(instanceId, { resetTracking: false });
// Reset tracking (default) - clears timestamps and error fields
await this.restartWorkflow(instanceId);
// Preserve original timestamps
await this.restartWorkflow(instanceId, { resetTracking: false });
Note
restart() is not yet supported in local development with wrangler dev. It works when deployed to Cloudflare.
approveWorkflow(instanceId, options?)
Approve a waiting workflow. Use with waitForApproval() in the workflow.
await this.approveWorkflow(instanceId, {
reason: "Approved by admin",
metadata: { approvedBy: userId },
});
await this.approveWorkflow(instanceId, {
reason: "Approved by admin",
metadata: { approvedBy: userId },
});
rejectWorkflow(instanceId, options?)
Reject a waiting workflow. Causes waitForApproval() to throw WorkflowRejectedError.
await this.rejectWorkflow(instanceId, { reason: "Request denied" });
await this.rejectWorkflow(instanceId, { reason: "Request denied" });
