import { useAgent } from "agents/react"; import { useState } from "react"; import { CheckIcon, CircleIcon, XIcon, PlayIcon, TrashIcon, ArrowsClockwiseIcon } from "@phosphor-icons/react"; import { Loader } from "@cloudflare/kumo"; import { Button, Input, Surface, Badge, Empty, Text, Meter } from "@cloudflare/kumo"; import { DemoWrapper } from "../../layout"; import { LogPanel, ConnectionStatus, CodeExplanation, type CodeSection } from "../../components"; import { useLogs, useUserId } from "../../hooks"; import type { BasicWorkflowAgent, BasicWorkflowState, WorkflowWithProgress } from "./basic-workflow-agent"; const codeSections: CodeSection[] = [ { title: "Define a workflow with AgentWorkflow", description: "Extend AgentWorkflow instead of WorkflowEntrypoint to get typed access to the originating agent. You get this.agent for RPC, this.reportProgress() for live updates, and this.broadcastToClients() to push messages over WebSocket.", code: `import { AgentWorkflow } from "agents/workflows"; import type { AgentWorkflowEvent, AgentWorkflowStep } from "agents/workflows"; class ProcessingWorkflow extends AgentWorkflow { async run(event: AgentWorkflowEvent, step: AgentWorkflowStep) { const params = event.payload; const result = await step.do("process-data", async () => { return processData(params.data); }); // Report progress back to the agent (non-durable, lightweight) await this.reportProgress({ step: "process", status: "complete", percent: 0.5, }); // Call agent methods via typed RPC await this.agent.saveResult(params.taskId, result); // Broadcast to all connected WebSocket clients this.broadcastToClients({ type: "task-complete", taskId: params.taskId }); // Mark completion (durable via step) await step.reportComplete(result); return result; } }` }, { title: "Start and track workflows from the agent", description: "Use this.runWorkflow() to start a workflow with automatic tracking in the agent's database. Override onWorkflowProgress and onWorkflowComplete to react to workflow events and broadcast them to connected clients.", code: `class MyAgent extends Agent { @callable() 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", instanceId, progress, })); } async onWorkflowComplete(workflowName: string, instanceId: string, result?: unknown) { console.log("Workflow completed:", instanceId); } }` }, { title: "Durable step helpers", description: "Steps have built-in helpers for common patterns: step.reportComplete() and step.reportError() for status, step.updateAgentState() and step.mergeAgentState() to durably update the agent's state (which broadcasts to all clients), and step.sendEvent() for custom events.", code: ` async run(event: AgentWorkflowEvent, step: AgentWorkflowStep) { // Durably update agent state (broadcasts to WebSocket clients) await step.updateAgentState({ status: "processing", startedAt: Date.now() }); const result = await step.do("process", async () => { return processTask(event.payload); }); // Merge partial state (keeps existing fields) await step.mergeAgentState({ status: "complete", result }); // Report completion await step.reportComplete(result); }` } ]; function WorkflowCard({ workflow }: { workflow: WorkflowWithProgress }) { const name = workflow.name || workflow.workflowName; const statusVariant: Record< string, "beta" | "primary" | "destructive" | "outline" | "secondary" > = { queued: "beta", running: "primary", complete: "primary", errored: "destructive", waiting: "beta" }; const statusIcons: Record = { queued: , running: , complete: , errored: , waiting: }; return (
{name}

ID: {workflow.workflowId.slice(0, 8)}...

{statusIcons[workflow.status] || statusIcons.queued} {workflow.status}
{/* Progress Bar */} {workflow.progress && (
{workflow.progress.message} {workflow.progress.step} / {workflow.progress.total}
)} {/* Error */} {workflow.error && (
{workflow.error.message}
)} {/* Timestamps */}
Started: {new Date(workflow.createdAt).toLocaleTimeString()}
{workflow.completedAt && (
Completed: {new Date(workflow.completedAt).toLocaleTimeString()}
)}
); } export function WorkflowBasicDemo() { const userId = useUserId(); const { logs, addLog, clearLogs } = useLogs(); const [workflowName, setWorkflowName] = useState("Data Processing"); const [stepCount, setStepCount] = useState(4); const [isStarting, setIsStarting] = useState(false); const [workflows, setWorkflows] = useState([]); const agent = useAgent({ agent: "basic-workflow-agent", name: `workflow-basic-${userId}`, onStateUpdate: (newState) => { if (newState) { addLog("in", "state_update", { progress: Object.keys(newState.progress).length }); refreshWorkflows(); } }, onOpen: () => { addLog("info", "connected"); refreshWorkflows(); }, onClose: () => addLog("info", "disconnected"), onError: () => addLog("error", "error", "Connection error"), onMessage: (message) => { try { const data = JSON.parse(message.data as string); if (data.type) { addLog("in", data.type, data); if (data.type.startsWith("workflow_")) { refreshWorkflows(); } } } catch { // ignore } } }); const refreshWorkflows = async () => { try { const list = await ( agent.call as (m: string) => Promise )("listWorkflows"); setWorkflows(list); } catch { // ignore - might not be connected yet } }; const handleStartWorkflow = async () => { if (!workflowName.trim()) return; setIsStarting(true); addLog("out", "startWorkflow", { name: workflowName, stepCount }); try { await agent.call("startWorkflow", [workflowName, stepCount]); await refreshWorkflows(); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); } finally { setIsStarting(false); } }; const handleClearWorkflows = async () => { addLog("out", "clearWorkflows"); try { const result = await agent.call("clearWorkflows"); addLog("in", "cleared", { count: result }); await refreshWorkflows(); } catch (e) { addLog("error", "error", e instanceof Error ? e.message : String(e)); } }; const activeWorkflows = workflows.filter( (w) => w.status === "queued" || w.status === "running" || w.status === "waiting" ); const completedWorkflows = workflows.filter( (w) => w.status === "complete" || w.status === "errored" || w.status === "terminated" ); return ( Extend{" "} AgentWorkflow {" "} to build durable multi-step workflows that integrate tightly with your agent. Each step runs exactly once — if the Worker crashes mid-execution, the workflow resumes from the last completed step. Use{" "} this.runWorkflow() {" "} to start a workflow with automatic tracking, and{" "} this.reportProgress() {" "} to stream live updates back to connected clients. } statusIndicator={ } >
{/* Left Panel - Controls */}
Start Workflow
) => setWorkflowName(e.target.value) } className="w-full" placeholder="Enter workflow name" />
setStepCount(Number(e.target.value))} className="w-full" />
2 6
How it Works
  • 1.{" "} runWorkflow() {" "} starts a durable workflow
  • 2. Workflow executes steps with{" "} step.do()
  • 3.{" "} getWorkflows() {" "} tracks all workflows
  • 4. Progress via{" "} onWorkflowProgress()
{/* Center Panel - Workflows */}
{/* Active Workflows */}
Active ({activeWorkflows.length})
{activeWorkflows.length > 0 ? (
{activeWorkflows.map((workflow) => ( ))}
) : ( )}
{/* Completed Workflows */}
History ({completedWorkflows.length}) {completedWorkflows.length > 0 && ( )}
{completedWorkflows.length > 0 ? (
{completedWorkflows.map((workflow) => ( ))}
) : ( )}
{/* Right Panel - Logs */}
); }