Self-contained demo apps that show how to use the Agents SDK. These are user-facing learning material — keep them simple, clear, and consistent.
Each example should focus on one feature or concept (e.g., MCP servers, email routing, workflows). The exception is playground/, which is the kitchen-sink showcase covering the full spread of SDK features in a single app.
Most examples have both a frontend and a backend. This makes them immediately runnable and visually demonstrable — users can pnpm run start and see the feature in action, not just read server logs.
Focused protocol examples can be server-only when a frontend would obscure the concept. Keep MCP server examples minimal when they demonstrate Cloudflare Workers setup, raw transports, or elicitation behavior that must be exercised from an MCP client.
Full-stack examples use the Cloudflare Vite plugin for development and builds. Server-only examples use Wrangler directly.
Full-stack examples must have:
example-name/
package.json # name, dependencies, scripts
vite.config.ts # must use @cloudflare/vite-plugin
wrangler.jsonc # Workers config (not .toml)
tsconfig.json # must extend agents/tsconfig
index.html # Vite entry point
README.md # What this example demonstrates, how to run it
public/
favicon.ico # Cloudflare favicon
src/
server.ts # Worker entry point
client.tsx # React client entry
styles.css # Kumo + Tailwind imports
Server-only examples must have:
example-name/
package.json # name, dependencies, scripts
wrangler.jsonc # Workers config (not .toml)
tsconfig.json
README.md # What this example demonstrates, how to run it
src/
index.ts # Worker entry point
Full-stack examples use start (not dev) for the development server:
{
"scripts": {
"start": "vite dev",
"deploy": "vite build && wrangler deploy",
"types": "wrangler types env.d.ts --include-runtime false"
}
}Server-only examples can use Wrangler directly:
{
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"types": "wrangler types env.d.ts --include-runtime false"
}
}- Use
wrangler.jsonc(not.toml) - Include
"$schema": "../../node_modules/wrangler/config-schema.json" compatibility_date: "2026-06-11",compatibility_flags: ["nodejs_compat"]- Full-stack apps with client routing: add
"assets": { "not_found_handling": "single-page-application" } - Use
"run_worker_first"to route API/agent paths to the Worker - Do not set
"directory"in assets — the Vite plugin handles this
Full-stack examples must use the React, Cloudflare, and Tailwind Vite plugins. Examples that use @callable() or other decorators must also include the agents() plugin from agents/vite:
import { cloudflare } from "@cloudflare/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import agents from "agents/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [agents(), react(), cloudflare(), tailwindcss()]
});The agents() plugin handles TC39 decorator transforms (Oxc doesn't support them yet). It's safe to include even if the example doesn't use decorators.
Include the theme flash prevention script and favicon:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title>Example Title</title>
<script>
(() => {
const stored = localStorage.getItem("theme");
const mode = stored || "light";
document.documentElement.setAttribute("data-mode", mode);
document.documentElement.style.colorScheme = mode;
})();
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/client.tsx"></script>
</body>
</html>Extend the base config:
{
"extends": "agents/tsconfig"
}Generated by pnpm exec wrangler types. Do not hand-edit. Regenerate when bindings change.
If the example needs secrets (API keys, etc.), include a .env.example showing what keys are needed:
OPENAI_API_KEY=your-key-here
Never commit actual secrets. Use .env / .env.example.
Full-stack examples use Kumo (@cloudflare/kumo) for components, with Kumo's default theme (no custom overrides).
- Use Kumo components (
Button,Surface,Text,Badge,Empty, etc.) instead of hand-rolled HTML - Use
@phosphor-icons/reactfor icons (Kumo's icon library) - Use Kumo semantic color tokens (
text-kumo-default,bg-kumo-base,border-kumo-line, etc.) instead of raw Tailwind colors Textdoes not accept aclassNameprop — wrap in a<span>if you need custom classes- Use the
data-modeattribute for dark mode — nodark:Tailwind variants
@import "tailwindcss";
@import "@cloudflare/kumo/styles/tailwind";
@source "../../../node_modules/@cloudflare/kumo/dist/**/*.{js,jsx,ts,tsx}";Every full-stack example should include:
PoweredByCloudflare(from@cloudflare/kumo) — footer attribution badge (required in every example)- A dark mode toggle — inline a simple
ModeTogglecomponent usinguseState/useEffect+localStorage+ KumoButtonwith sun/moon icons - Connection status (for WebSocket-based examples) — inline a simple
ConnectionIndicatorshowing a colored dot + label
See /design/visuals.md for detailed Kumo usage patterns and known gaps.
Every example should include an info card at the top of the page explaining what the demo shows. Use the consistent pattern:
<Surface className="p-4 rounded-xl ring ring-kumo-line">
<div className="flex gap-3">
<InfoIcon
size={20}
weight="bold"
className="text-kumo-accent shrink-0 mt-0.5"
/>
<div>
<Text size="sm" bold>
Title
</Text>
<span className="mt-1 block">
<Text size="xs" variant="secondary">
Description of what this demo shows and how to use it.
</Text>
</span>
</div>
</div>
</Surface>Any example with a chat UI (useAgentChat from @cloudflare/ai-chat/react or @cloudflare/think/react, or useChat) must render the full shape of an assistant turn — not just text. Reference implementations: examples/ai-chat (streamdown + complete tool states) and examples/assistant (adds reasoning + approvals + branches).
Rules:
- Render
message.partsin array order (chronological). Walk the parts with a singlemessage.parts.map(...)and branch onpart.typeinside it. Never split parts into separate buckets (e.g. all text, then all tools) — that reorders the turn and hides which tool ran between which sentences. - Assistant text →
streamdown. Render assistant text parts with<Streamdown>(markdown, code highlighting). User messages stay plain text (getMessageText(message)) — user input is rarely markdown. - Reasoning traces. Render
part.type === "reasoning"parts as a distinct, muted "Reasoning"/"Thinking" block (reasoning is on by default for reasoning-capable models). - Tool parts — render input, output, AND errors:
- Input for every tool (not just a
codefield) — render it while the call is still streaming (input-streaming/input-available) with a running indicator, and after it resolves. - Output on
output-available. - Errors on
output-error— showpart.errorTextin a clearly-distinct (red) block. Don't let failed tools render as nothing. - Handle
approval-requested/output-deniedwhere the example uses tool approval.
- Input for every tool (not just a
- Avoid empty bubbles while streaming. Text and reasoning parts arrive as
{ state: "streaming", text: "" }before the first delta — gate onpart.text.length > 0 || part.state === "streaming".
streamdown (+ @streamdown/code) are already repo dependencies. To add markdown rendering to an example:
import { Streamdown } from "streamdown";
import { code } from "@streamdown/code";
<Streamdown
className="sd-theme"
plugins={{ code }}
controls={false}
isAnimating={isLastAssistant && isLastTextPart && isStreaming}
>
{part.text}
</Streamdown>;Tailwind ignores node_modules, so src/styles.css needs @source lines plus the .sd-theme bridge that maps Streamdown's tokens to Kumo. Copy the @source ".../streamdown/..." / @source ".../@streamdown/code/..." lines and the .sd-theme { ... } block from examples/assistant/src/styles.css verbatim.
Prefer @callable methods + useAgent/agent.call() over manual onRequest/agentFetch or raw WebSocket messages:
// server.ts
export class MyAgent extends Agent {
@callable()
async doSomething(arg: string) {
return { result: arg };
}
}
// client.tsx
const agent = useAgent({ agent: "my-agent", name: sessionId });
const result = await agent.call("doSomething", ["hello"]);- Keep example-specific dependencies minimal — these ship as learning material
- Shared dependencies (
react,vite,wrangler,@cloudflare/vite-plugin, etc.) live in the rootpackage.json - Only add dependencies in the example's
package.jsonif they're specific to what the example demonstrates
Every example needs one. Keep it short:
- One sentence: what this example demonstrates
- How to run it (
pnpm install && pnpm run startfor full-stack examples,pnpm install && pnpm run devfor server-only examples) - Any required env vars
- Code snippets showing the key pattern
- Links to related examples
See TODO.md in this folder for the full checklist.
cross-domain/has avite.config.tsbut does not use@cloudflare/vite-plugin