AGENTS.md@public/files/cloudflare-agents/examples · git:20260917.a2709ec · 2026-09-17 · sha256 a3d517f8dcf55882

AGENTS.md@public/files/cloudflare-agents/examples git:20260917.a2709ecB

Immutable. This exact content never changes and is served at /api/v1/blob/a3d517f8dcf55882.

# AGENTS.md — examples/

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.

## Example shape

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](https://developers.cloudflare.com/workers/vite-plugin/) for development and builds. Server-only examples use Wrangler directly.

## Required structure

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
```

## Conventions

### Scripts

Full-stack examples use `start` (not `dev`) for the development server:

```json
{
  "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:

```json
{
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy",
    "types": "wrangler types env.d.ts --include-runtime false"
  }
}
```

### wrangler.jsonc

- Use `wrangler.jsonc` (not `.toml`)
- Include `"$schema": "./node_modules/wrangler/config-schema.json"` — relative to the example root so it resolves both in the workspace and when the example is copied out and installed standalone
- `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

### vite.config.ts

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`:

```ts
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.

### index.html

Include the theme flash prevention script and favicon:

```html
<!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>
```

### tsconfig.json

Extend the base config:

```json
{
  "extends": "agents/tsconfig"
}
```

### env.d.ts

Generated by `pnpm exec wrangler types`. Do not hand-edit. Regenerate when bindings change.

### .env.example

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`.

### UI — Kumo

Full-stack examples use [Kumo](https://kumo-ui.com/) (`@cloudflare/kumo`) for components, with Kumo's default theme (no custom overrides).

#### Kumo basics

- Use Kumo components (`Button`, `Surface`, `Text`, `Badge`, `Empty`, etc.) instead of hand-rolled HTML
- Use `@phosphor-icons/react` for 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
- `Text` does not accept a `className` prop — wrap in a `<span>` if you need custom classes
- Use the `data-mode` attribute for dark mode — no `dark:` Tailwind variants

#### CSS imports (in `src/styles.css`)

```css
@import "tailwindcss";
@import "@cloudflare/kumo/styles/tailwind";
/* Tailwind ignores node_modules by default, so we source Kumo for class extraction. */
@source "../node_modules/@cloudflare/kumo/dist/**/*.{js,jsx,ts,tsx}";
```

The `@source` path is relative to `src/styles.css`, so it points at the example's own `node_modules` (`../node_modules`). That resolves both in the pnpm workspace (each package gets its own `node_modules` with symlinks) and when the example is copied out and installed standalone — don't use a monorepo-root-relative path like `../../../node_modules`.

#### Required UI patterns

Every full-stack example should include:

- **`PoweredByCloudflare`** (from `@cloudflare/kumo`) — footer attribution badge (**required in every example**)
- **A dark mode toggle** — inline a simple `ModeToggle` component using `useState`/`useEffect` + `localStorage` + Kumo `Button` with sun/moon icons
- **Connection status** (for WebSocket-based examples) — inline a simple `ConnectionIndicator` showing a colored dot + label

See `/design/visuals.md` for detailed Kumo usage patterns and known gaps.

#### Explainer section

Every example should include an info card at the top of the page explaining what the demo shows. Use the consistent pattern:

```tsx
<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>
```

### Chat message rendering

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:

1. **Render `message.parts` in array order (chronological).** Walk the parts with a single `message.parts.map(...)` and branch on `part.type` inside 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.
2. **Assistant text → `streamdown`.** Render assistant text parts with `<Streamdown>` (markdown, code highlighting). User messages stay plain text (`getMessageText(message)`) — user input is rarely markdown.
3. **Reasoning traces.** Render `part.type === "reasoning"` parts as a distinct, muted "Reasoning"/"Thinking" block (reasoning is on by default for reasoning-capable models).
4. **Tool parts — render input, output, AND errors:**
   - **Input** for every tool (not just a `code` field) — 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` — show `part.errorText` in a clearly-distinct (red) block. Don't let failed tools render as nothing.
   - Handle `approval-requested` / `output-denied` where the example uses tool approval.
5. **Avoid empty bubbles while streaming.** Text and reasoning parts arrive as `{ state: "streaming", text: "" }` before the first delta — gate on `part.text.length > 0 || part.state === "streaming"`.

#### Streamdown setup

`streamdown` (+ `@streamdown/code`) are already repo dependencies. To add markdown rendering to an example:

```jsonc
// package.json dependencies
"streamdown": "^2.5.0",
"@streamdown/code": "^1.1.1"
```

```tsx
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.

### Agent communication

Prefer `@callable` methods + `useAgent`/`agent.call()` over manual `onRequest`/`agentFetch` or raw WebSocket messages:

```typescript
// 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"]);
```

### Dependencies

- Keep example-specific dependencies minimal — these ship as learning material
- Shared dependencies (`react`, `vite`, `wrangler`, `@cloudflare/vite-plugin`, etc.) live in the root `package.json`
- Only add dependencies in the example's `package.json` if they're specific to what the example demonstrates

### README.md

Every example needs one. Keep it short:

1. One sentence: what this example demonstrates
2. How to run it (`pnpm install && pnpm run start` for full-stack examples, `pnpm install && pnpm run dev` for server-only examples)
3. Any required env vars
4. Code snippets showing the key pattern
5. Links to related examples

## Known issues to clean up

See `TODO.md` in this folder for the full checklist.

- `cross-domain/` has a `vite.config.ts` but does not use `@cloudflare/vite-plugin`