AI Agents Workflows

Explore example implementations of AI Agents using Vercel AI SDK and React Flow.

These workflows are inspired by Anthropic's article on building effective agents.

Each workflow below is fully customizable and copy-paste ready. Simply use the shadcn CLI to copy the code directly into your project. All examples are built with TypeScript.

Chain Workflow

The Chain Workflow handles tasks by breaking them into ordered steps, where each step depends on the previous one. Each step can verify the work done before and determine what happens next.

The example below demonstrates this by taking an article, creating a summary, checking if the summary matches the original text, and then turning approved summaries into social media posts.

Perfect for:

  • Multi-step content creation
  • Processes that need quality checks
  • Content transformation pipelines
Chain Workflow
Agentic chain workflow.
Files
app/workflow/page.tsx
"use client";

import {
	Controls,
	type EdgeTypes,
	MiniMap,
	type NodeTypes,
	ReactFlowProvider,
} from "@xyflow/react";
import { Background, Panel, ReactFlow, useReactFlow } from "@xyflow/react";
import { type DragEvent, useEffect } from "react";
import { shallow } from "zustand/shallow";
import "@xyflow/react/dist/style.css";
import { Button } from "@/components/ui/button";
import { ErrorIndicator } from "@/components/error-indicator";
import { NodesPanel } from "@/components/nodes-panel";
import { NEWS_SUMMARY_WORKFLOW } from "@/lib/news-summarization-chain";
import { useWorkflow } from "@/hooks/flow/use-workflow";
import type { FlowNode } from "@/lib/flow/workflow";
import { GenerateTextNodeController } from "@/components/flow/generate-text-node-controller";
import { PromptCrafterNodeController } from "@/components/flow/prompt-crafter-node-controller";
import { StatusEdgeController } from "@/components/flow/status-edge-controller";
import { TextInputNodeController } from "@/components/flow/text-input-node-controller";
import { VisualizeTextNodeController } from "@/components/flow/visualize-text-node-controller";

const nodeTypes: NodeTypes = {
	"generate-text": GenerateTextNodeController,
	"visualize-text": VisualizeTextNodeController,
	"text-input": TextInputNodeController,
	"prompt-crafter": PromptCrafterNodeController,
};

const edgeTypes: EdgeTypes = {
	status: StatusEdgeController,
};

export function Flow() {
	const store = useWorkflow(
		(store) => ({
			nodes: store.nodes,
			edges: store.edges,
			onNodesChange: store.onNodesChange,
			onEdgesChange: store.onEdgesChange,
			onConnect: store.onConnect,
			startExecution: store.startExecution,
			createNode: store.createNode,
			workflowExecutionState: store.workflowExecutionState,
			initializeWorkflow: store.initializeWorkflow,
		}),
		shallow,
	);
	
	// biome-ignore lint/correctness/useExhaustiveDependencies: We want to initialize the workflow only once
	useEffect(() => {
		store.initializeWorkflow(
			NEWS_SUMMARY_WORKFLOW.nodes,
			NEWS_SUMMARY_WORKFLOW.edges,
		);
	}, []);

	const { screenToFlowPosition } = useReactFlow();

	const onDragOver = (event: DragEvent) => {
		event.preventDefault();
		event.dataTransfer.dropEffect = "move";
	};

	const onDrop = (event: DragEvent) => {
		event.preventDefault();

		const type = event.dataTransfer.getData(
			"application/reactflow",
		) as FlowNode["type"];

		if (!type) {
			return;
		}

		const position = screenToFlowPosition({
			x: event.clientX,
			y: event.clientY,
		});

		store.createNode(type, position);
	};

	const onStartExecution = async () => {
		const result = await store.startExecution();
		if (result.status === "error") {
			console.error(result.error);
		}
	};

	return (
		<ReactFlow
			nodes={store.nodes}
			edges={store.edges}
			onNodesChange={store.onNodesChange}
			onEdgesChange={store.onEdgesChange}
			onConnect={store.onConnect}
			nodeTypes={nodeTypes}
			edgeTypes={edgeTypes}
			onDragOver={onDragOver}
			onDrop={onDrop}
			fitView
		>
			<Background />
			<Controls />
			<MiniMap />
			<NodesPanel />
			<Panel position="top-right" className="flex gap-2 items-center">
				<ErrorIndicator errors={store.workflowExecutionState.errors} />
				<Button
					onClick={onStartExecution}
					title={
						store.workflowExecutionState.timesRun > 1
							? "Disabled for now"
							: "Run the workflow"
					}
					disabled={
						store.workflowExecutionState.errors.length > 0 ||
						store.workflowExecutionState.isRunning ||
						store.workflowExecutionState.timesRun > 1
					}
				>
					{store.workflowExecutionState.isRunning ? "Running..." : "Run Flow"}
				</Button>
			</Panel>
		</ReactFlow>
	);
}

export default function Page() {
	return (
		<div className="w-screen h-screen">
			<ReactFlowProvider>
				<Flow />
			</ReactFlowProvider>
		</div>
	);
}

Routing Workflow

The Routing Workflow directs incoming requests to the most suitable process based on what needs to be done.

In the example below, when a content creation request comes in, it gets analyzed to determine if it's for blog content, social media content, or SEO content. Based on this analysis, the request gets forwarded to one of three specialized agents: a blog writing expert, a social media content creator, or an SEO specialist. Each expert then processes the request according to their specialty.

Perfect for:

  • Content classification systems
  • Support request handling
  • Multi-path processing
Routing Workflow
Agentic routing workflow.
Files
app/workflow/page.tsx
"use client";

import {
	Controls,
	type EdgeTypes,
	MiniMap,
	type NodeTypes,
	ReactFlowProvider,
} from "@xyflow/react";
import { Background, Panel, ReactFlow, useReactFlow } from "@xyflow/react";
import { type DragEvent, useEffect } from "react";
import { shallow } from "zustand/shallow";
import "@xyflow/react/dist/style.css";
import { Button } from "@/components/ui/button";
import { ErrorIndicator } from "@/components/error-indicator";
import { NodesPanel } from "@/components/nodes-panel";
import { CONTENT_CREATOR_ROUTING_WORKFLOW } from "@/lib/content-creator-routing";
import { useWorkflow } from "@/hooks/flow/use-workflow";
import type { FlowNode } from "@/lib/flow/workflow";
import { GenerateTextNodeController } from "@/components/flow/generate-text-node-controller";
import { PromptCrafterNodeController } from "@/components/flow/prompt-crafter-node-controller";
import { StatusEdgeController } from "@/components/flow/status-edge-controller";
import { TextInputNodeController } from "@/components/flow/text-input-node-controller";
import { VisualizeTextNodeController } from "@/components/flow/visualize-text-node-controller";

const nodeTypes: NodeTypes = {
	"generate-text": GenerateTextNodeController,
	"visualize-text": VisualizeTextNodeController,
	"text-input": TextInputNodeController,
	"prompt-crafter": PromptCrafterNodeController,
};

const edgeTypes: EdgeTypes = {
	status: StatusEdgeController,
};

export function Flow() {
	const store = useWorkflow(
		(store) => ({
			nodes: store.nodes,
			edges: store.edges,
			onNodesChange: store.onNodesChange,
			onEdgesChange: store.onEdgesChange,
			onConnect: store.onConnect,
			startExecution: store.startExecution,
			createNode: store.createNode,
			workflowExecutionState: store.workflowExecutionState,
			initializeWorkflow: store.initializeWorkflow,
		}),
		shallow,
	);
	
	// biome-ignore lint/correctness/useExhaustiveDependencies: We want to initialize the workflow only once
	useEffect(() => {
		store.initializeWorkflow(
			CONTENT_CREATOR_ROUTING_WORKFLOW.nodes,
			CONTENT_CREATOR_ROUTING_WORKFLOW.edges,
		);
	}, []);

	const { screenToFlowPosition } = useReactFlow();

	const onDragOver = (event: DragEvent) => {
		event.preventDefault();
		event.dataTransfer.dropEffect = "move";
	};

	const onDrop = (event: DragEvent) => {
		event.preventDefault();

		const type = event.dataTransfer.getData(
			"application/reactflow",
		) as FlowNode["type"];

		if (!type) {
			return;
		}

		const position = screenToFlowPosition({
			x: event.clientX,
			y: event.clientY,
		});

		store.createNode(type, position);
	};

	const onStartExecution = async () => {
		const result = await store.startExecution();
		if (result.status === "error") {
			console.error(result.error);
		}
	};

	return (
		<ReactFlow
			nodes={store.nodes}
			edges={store.edges}
			onNodesChange={store.onNodesChange}
			onEdgesChange={store.onEdgesChange}
			onConnect={store.onConnect}
			nodeTypes={nodeTypes}
			edgeTypes={edgeTypes}
			onDragOver={onDragOver}
			onDrop={onDrop}
			fitView
		>
			<Background />
			<Controls />
			<MiniMap />
			<NodesPanel />
			<Panel position="top-right" className="flex gap-2 items-center">
				<ErrorIndicator errors={store.workflowExecutionState.errors} />
				<Button
					onClick={onStartExecution}
					title={
						store.workflowExecutionState.timesRun > 1
							? "Disabled for now"
							: "Run the workflow"
					}
					disabled={
						store.workflowExecutionState.errors.length > 0 ||
						store.workflowExecutionState.isRunning ||
						store.workflowExecutionState.timesRun > 1
					}
				>
					{store.workflowExecutionState.isRunning ? "Running..." : "Run Flow"}
				</Button>
			</Panel>
		</ReactFlow>
	);
}

export default function Page() {
	return (
		<div className="w-screen h-screen">
			<ReactFlowProvider>
				<Flow />
			</ReactFlowProvider>
		</div>
	);
}

Parallelization Workflow

The Parallelization Workflow handles multiple tasks at the same time instead of one by one. At the end, we can optionally aggregate the results with another agent.

In the example below, we show a workflow for creating exams. When someone requests an exam, the input goes to three different agents simultaneously: one creates multiple choice questions, another makes short answer questions, and the third develops essay questions. Their work happens in parallel, and then a final agent combines everything into a complete, well-structured exam.

Perfect for:

  • Parallel data processing
  • Multi-perspective analysis
  • High-speed task processing
Parallelization Workflow
Agentic parallelization workflow.
Files
app/workflow/page.tsx
"use client";

import {
	Controls,
	type EdgeTypes,
	MiniMap,
	type NodeTypes,
	ReactFlowProvider,
} from "@xyflow/react";
import { Background, Panel, ReactFlow, useReactFlow } from "@xyflow/react";
import { type DragEvent, useEffect } from "react";
import { shallow } from "zustand/shallow";
import "@xyflow/react/dist/style.css";
import { Button } from "@/components/ui/button";
import { ErrorIndicator } from "@/components/error-indicator";
import { NodesPanel } from "@/components/nodes-panel";
import { EXAM_CREATOR_PARALLELIZATION_WORKFLOW } from "@/lib/exam-creator-parallelization";
import { useWorkflow } from "@/hooks/flow/use-workflow";
import type { FlowNode } from "@/lib/flow/workflow";
import { GenerateTextNodeController } from "@/components/flow/generate-text-node-controller";
import { PromptCrafterNodeController } from "@/components/flow/prompt-crafter-node-controller";
import { StatusEdgeController } from "@/components/flow/status-edge-controller";
import { TextInputNodeController } from "@/components/flow/text-input-node-controller";
import { VisualizeTextNodeController } from "@/components/flow/visualize-text-node-controller";

const nodeTypes: NodeTypes = {
	"generate-text": GenerateTextNodeController,
	"visualize-text": VisualizeTextNodeController,
	"text-input": TextInputNodeController,
	"prompt-crafter": PromptCrafterNodeController,
};

const edgeTypes: EdgeTypes = {
	status: StatusEdgeController,
};

export function Flow() {
	const store = useWorkflow(
		(store) => ({
			nodes: store.nodes,
			edges: store.edges,
			onNodesChange: store.onNodesChange,
			onEdgesChange: store.onEdgesChange,
			onConnect: store.onConnect,
			startExecution: store.startExecution,
			createNode: store.createNode,
			workflowExecutionState: store.workflowExecutionState,
			initializeWorkflow: store.initializeWorkflow,
		}),
		shallow,
	);
	
	// biome-ignore lint/correctness/useExhaustiveDependencies: We want to initialize the workflow only once
	useEffect(() => {
		store.initializeWorkflow(
			EXAM_CREATOR_PARALLELIZATION_WORKFLOW.nodes,
			EXAM_CREATOR_PARALLELIZATION_WORKFLOW.edges,
		);
	}, []);

	const { screenToFlowPosition } = useReactFlow();

	const onDragOver = (event: DragEvent) => {
		event.preventDefault();
		event.dataTransfer.dropEffect = "move";
	};

	const onDrop = (event: DragEvent) => {
		event.preventDefault();

		const type = event.dataTransfer.getData(
			"application/reactflow",
		) as FlowNode["type"];

		if (!type) {
			return;
		}

		const position = screenToFlowPosition({
			x: event.clientX,
			y: event.clientY,
		});

		store.createNode(type, position);
	};

	const onStartExecution = async () => {
		const result = await store.startExecution();
		if (result.status === "error") {
			console.error(result.error);
		}
	};

	return (
		<ReactFlow
			nodes={store.nodes}
			edges={store.edges}
			onNodesChange={store.onNodesChange}
			onEdgesChange={store.onEdgesChange}
			onConnect={store.onConnect}
			nodeTypes={nodeTypes}
			edgeTypes={edgeTypes}
			onDragOver={onDragOver}
			onDrop={onDrop}
			fitView
		>
			<Background />
			<Controls />
			<MiniMap />
			<NodesPanel />
			<Panel position="top-right" className="flex gap-2 items-center">
				<ErrorIndicator errors={store.workflowExecutionState.errors} />
				<Button
					onClick={onStartExecution}
					title={
						store.workflowExecutionState.timesRun > 1
							? "Disabled for now"
							: "Run the workflow"
					}
					disabled={
						store.workflowExecutionState.errors.length > 0 ||
						store.workflowExecutionState.isRunning ||
						store.workflowExecutionState.timesRun > 1
					}
				>
					{store.workflowExecutionState.isRunning ? "Running..." : "Run Flow"}
				</Button>
			</Panel>
		</ReactFlow>
	);
}

export default function Page() {
	return (
		<div className="w-screen h-screen">
			<ReactFlowProvider>
				<Flow />
			</ReactFlowProvider>
		</div>
	);
}

Orchestrator-Workers Workflow

The Orchestrator-workers Workflow uses a coordinator to distribute tasks to specialists based on the user request.

The example below shows a product development process where a product manager handles technical feature requests. The manager analyzes if the work needs front-end, back-end, or database development, and assigns tasks accordingly. A senior developer then reviews and combines all the code into a final solution.

Perfect for:

  • Complex task management
  • Flexible work distribution
  • Coordinated processing
Orchestrator Workflow
Agentic orchestrator workflow.
Files
app/workflow/page.tsx
"use client";

import {
	Controls,
	type EdgeTypes,
	MiniMap,
	type NodeTypes,
	ReactFlowProvider,
} from "@xyflow/react";
import { Background, Panel, ReactFlow, useReactFlow } from "@xyflow/react";
import { type DragEvent, useEffect } from "react";
import { shallow } from "zustand/shallow";
import "@xyflow/react/dist/style.css";
import { Button } from "@/components/ui/button";
import { ErrorIndicator } from "@/components/error-indicator";
import { NodesPanel } from "@/components/nodes-panel";
import { DEVELOPER_TASKS_ORCHESTRATOR_WORKFLOW } from "@/lib/developer-tasks-orchestrator";
import { useWorkflow } from "@/hooks/flow/use-workflow";
import type { FlowNode } from "@/lib/flow/workflow";
import { GenerateTextNodeController } from "@/components/flow/generate-text-node-controller";
import { PromptCrafterNodeController } from "@/components/flow/prompt-crafter-node-controller";
import { StatusEdgeController } from "@/components/flow/status-edge-controller";
import { TextInputNodeController } from "@/components/flow/text-input-node-controller";
import { VisualizeTextNodeController } from "@/components/flow/visualize-text-node-controller";

const nodeTypes: NodeTypes = {
	"generate-text": GenerateTextNodeController,
	"visualize-text": VisualizeTextNodeController,
	"text-input": TextInputNodeController,
	"prompt-crafter": PromptCrafterNodeController,
};

const edgeTypes: EdgeTypes = {
	status: StatusEdgeController,
};

export function Flow() {
	const store = useWorkflow(
		(store) => ({
			nodes: store.nodes,
			edges: store.edges,
			onNodesChange: store.onNodesChange,
			onEdgesChange: store.onEdgesChange,
			onConnect: store.onConnect,
			startExecution: store.startExecution,
			createNode: store.createNode,
			workflowExecutionState: store.workflowExecutionState,
			initializeWorkflow: store.initializeWorkflow,
		}),
		shallow,
	);
	
	// biome-ignore lint/correctness/useExhaustiveDependencies: We want to initialize the workflow only once
	useEffect(() => {
		store.initializeWorkflow(
			DEVELOPER_TASKS_ORCHESTRATOR_WORKFLOW.nodes,
			DEVELOPER_TASKS_ORCHESTRATOR_WORKFLOW.edges,
		);
	}, []);

	const { screenToFlowPosition } = useReactFlow();

	const onDragOver = (event: DragEvent) => {
		event.preventDefault();
		event.dataTransfer.dropEffect = "move";
	};

	const onDrop = (event: DragEvent) => {
		event.preventDefault();

		const type = event.dataTransfer.getData(
			"application/reactflow",
		) as FlowNode["type"];

		if (!type) {
			return;
		}

		const position = screenToFlowPosition({
			x: event.clientX,
			y: event.clientY,
		});

		store.createNode(type, position);
	};

	const onStartExecution = async () => {
		const result = await store.startExecution();
		if (result.status === "error") {
			console.error(result.error);
		}
	};

	return (
		<ReactFlow
			nodes={store.nodes}
			edges={store.edges}
			onNodesChange={store.onNodesChange}
			onEdgesChange={store.onEdgesChange}
			onConnect={store.onConnect}
			nodeTypes={nodeTypes}
			edgeTypes={edgeTypes}
			onDragOver={onDragOver}
			onDrop={onDrop}
			fitView
		>
			<Background />
			<Controls />
			<MiniMap />
			<NodesPanel />
			<Panel position="top-right" className="flex gap-2 items-center">
				<ErrorIndicator errors={store.workflowExecutionState.errors} />
				<Button
					onClick={onStartExecution}
					title={
						store.workflowExecutionState.timesRun > 1
							? "Disabled for now"
							: "Run the workflow"
					}
					disabled={
						store.workflowExecutionState.errors.length > 0 ||
						store.workflowExecutionState.isRunning ||
						store.workflowExecutionState.timesRun > 1
					}
				>
					{store.workflowExecutionState.isRunning ? "Running..." : "Run Flow"}
				</Button>
			</Panel>
		</ReactFlow>
	);
}

export default function Page() {
	return (
		<div className="w-screen h-screen">
			<ReactFlowProvider>
				<Flow />
			</ReactFlowProvider>
		</div>
	);
}