Real, runnable Flue agents, workflows, and
channels on Cloudflare. Every
snippet builds with flue build, deploys a real ephemeral Worker with
wrangler, exercises it against live services, then deletes it. No mocks.
Built for Flue 1.0 (@flue/* 1.0.0-beta). Each snippet is a self-contained
Cloudflare app: its own package.json, wrangler.jsonc, and src/
(agents/, workflows/, channels/).
Demo site: flue.coey.dev, a guided tour of Flue on Cloudflare, auto-generated from this repo.
examples/ one CF product per folder, the smallest Flue snippet that uses it
recipes/ compositions, Flue + multiple primitives + receipts/proofs
templates/ forkable starters, production-shape, fork-and-ship
Most snippets are Flue workflows (finite input → result, invoked at
POST /workflows/<name>?wait=result). Snippets that need continuing per-user
state are agents (POST /agents/<name>/<id>), and verified provider webhook
ingress uses channels (POST /channels/<name>/<suffix>).
git clone https://github.com/acoyfellow/flue-snippets
cd flue-snippets
# https://dash.cloudflare.com/profile/api-tokens, Workers Scripts:Edit + Workers AI:Read
# Put them in .env (each run-e2e.sh sources it) or export them:
export CLOUDFLARE_API_TOKEN=...
export CLOUDFLARE_ACCOUNT_ID=...
bun ex:workers-ai # from the repo root
# or: cd examples/workers-ai && bash run-e2e.sh~60 seconds: install (first run), build, deploy, assert, delete. ~$0.0001 in
Workers AI usage. Each snippet is self-contained (its own package.json +
src/ + wrangler.jsonc) and installs its own deps on first run.
The workflow it just ran:
// examples/workers-ai/src/workflows/workers-ai.ts
import { defineAgent, defineWorkflow, type WorkflowRouteHandler } from '@flue/runtime';
import * as v from 'valibot';
export const route: WorkflowRouteHandler = async (_c, next) => next();
const agent = defineAgent(() => ({ model: 'cloudflare/@cf/moonshotai/kimi-k2.6' }));
export default defineWorkflow({
agent,
input: v.object({ message: v.optional(v.string()) }),
output: v.object({ answer: v.string() }),
async run({ harness, input }) {
const session = await harness.session();
const response = await session.prompt(input.message ?? 'Say hi.');
return { answer: response.text };
},
});Invoke it at POST /workflows/workers-ai?wait=result with { "message": "..." }.
One Flue snippet, one Cloudflare binding.
| Example | Cloudflare product |
|---|---|
| workers-ai | Workers AI |
| effect-hello | Workers AI + Effect v4 (smallest Flue workflow whose body is an Effect program) |
| kv | Workers KV |
| r2 | R2 |
| d1 | D1 |
| durable-objects | Durable Objects (agent: same id → same DO, isolated per instance) |
| ai-gateway | AI Gateway |
| queues | Queues |
| vectorize | Vectorize |
| browser-rendering | Browser Rendering |
| worker-loader | Dynamic Workers |
| hyperdrive | Hyperdrive |
| email-workers | Email Service |
Flue compositions with Cloudflare primitives and reviewable receipt, evaluation, or observability
layers (including @acoyfellow/lab,
gateproof, and Braintrust).
| Recipe | Composes |
|---|---|
| lab-receipt | Workers AI + lab |
| do-session | Durable Objects (agent, durable session memory) |
| do-governor | Durable Objects (workflow, repetition governor) |
| lab-checkpoint | Durable Objects + lab |
| ai-gateway | AI Gateway + Workers AI |
| gateway-lab | AI Gateway + Workers AI + lab |
| github-triage | Workers AI + structured output (valibot schema) |
| chat-thinking | Flue + Cloudflare Think (co-hosted DO chat agent) |
| virtual-sandbox | Flue virtual sandbox (just-bash) + R2 |
| mcp-client | Flue + co-hosted MCP server (Workers) |
| dynamic-workflow | Durable Objects + Workflows |
| event-trigger | Workers AI + structured output (one signed-webhook front door for Sentry / PagerDuty / GitLab CI / cron) |
| braintrust-trace | Braintrust application tracing + Workers AI |
| braintrust-ai-gateway | Braintrust AI Gateway + gateway logging |
| braintrust-eval | Braintrust Evals + deployed Workers AI endpoint |
| braintrust-otel | OpenTelemetry + Braintrust span exporter + Workers AI |
Each recipe's README explains what it composes, what it proves, and how to run it. The Braintrust
recipes require a BRAINTRUST_API_KEY (injected into the ephemeral Worker via
wrangler deploy --var); they run explicitly with bun rx:braintrust-* rather than in the CI
matrix. No ALCHEMY_PASSWORD — deploys use wrangler, not alchemy.
bun ex:<name> # examples/<name>/run-e2e.sh
bun rx:<name> # recipes/<name>/run-e2e.sh
bun tpl:<name> # templates/<name>/run-e2e.shSee package.json for the full list.
bun run updateThat repeatable maintenance command runs bun update in the repo and in
site/, refreshes the homepage's visible “Last dependency refresh”
timestamp when Bun bumps package manifest versions, then runs the site production
build. If the Flue CLI/runtime or a dependency used by a snippet changes shape,
also update the affected example/recipe/template and run its live E2E (bun ex:<name>, bun rx:<name>, or bun tpl:<name>) before shipping.
Every run-e2e.sh does the same five things:
flue build --target cloudflare, compiles the project (agents/workflows/channels undersrc/) intodist/<name>/with a Flue-generatedwrangler.json.wrangler deploy --config dist/<name>/wrangler.json, deploys the Worker, creating any per-run resource (KV/R2/D1/Queue/Vectorize) first and injecting secrets/vars with--var. Prints the URL.- Warmup, POSTs the workflow route (
/workflows/<name>?wait=result) or agent route (/agents/<name>/<id>) with retries (absorbs route propagation + Workers AI cold start). Flue 1.0 has no/healthroute. - Assert, workflows read
{ result }from?wait=result; agents read the conversation snapshot at?view=history; recipes run agateproof.plan.tswith aprobe.ts(purefetch+ JSON). wrangler delete(and delete any created resource), then verify zero leftover Workers. Trapped onEXIT INT TERM.
Flue owns the wrangler config: flue build merges its generated bindings +
migrations into dist/<name>/wrangler.json, and wrangler deploy ships it. Each
snippet is self-contained — its own package.json, wrangler.jsonc, and src/.
.github/workflows/e2e.yml is workflow_dispatch-only. From the Actions tab, pick all, examples, recipes, templates, or a single target. max-parallel: 1 because Workers AI rate-limits hard under parallel load on a personal account.
Secrets:
CF_API_TOKEN_E2E, Workers Scripts:Edit + Workers AI:Read, plus permissions for any product-specific targets you enable (R2 / D1 / KV / Queues / AI Gateway / Vectorize / Browser Rendering / Worker Loader / Hyperdrive / Email).CF_ACCOUNT_ID_E2E, Cloudflare account ID.EMAIL_FROM,EMAIL_TO, only needed if you enableexamples/email-workers. Without them, the send call returns a structured error and the assertion still passes (it accepts either a real send or a structuredE_*code).
The Braintrust recipes currently run explicitly with bun rx:braintrust-* rather than in this
matrix. Enrolling them requires a BRAINTRUST_API_KEY (injected into the ephemeral Worker via
wrangler deploy --var). No ALCHEMY_PASSWORD is needed — deploys use wrangler, not alchemy.
Does it really deploy? Yes. Each run-e2e.sh runs flue build then wrangler deploy, hits a real *.workers.dev URL, then wrangler deletes it. CI does the same. There is no mock layer.
What does it cost? ~$0.0001 per snippet per run (one Workers AI @cf/moonshotai/kimi-k2.6 call). Free tier is plenty for the entire matrix.
Do all snippets run on a stock token? Most do. A few need extra access and will otherwise report a clear, non-fatal error: hyperdrive (token needs Hyperdrive), braintrust-* (need BRAINTRUST_API_KEY; braintrust-ai-gateway also needs a model provider configured in your Braintrust AI Gateway), email-workers (a verified sender, else it returns a structured E_* code and still passes).
How does it deploy? Flue 1.0 owns the deploy artifact: flue build --target cloudflare emits dist/<name>/ with a generated wrangler.json (merged bindings + DO migrations), and wrangler deploy --config dist/<name>/wrangler.json ships it. Agents live in src/agents/, workflows in src/workflows/, verified provider ingress in src/channels/, and Cloudflare-native extras (extra DOs, Workflows, MCP servers) in src/cloudflare.ts.
Why does CI run sequentially? Workers AI rate-limits aggressively on personal accounts under parallel load. max-parallel: 1 keeps the matrix green.
Can I run only one? Yes, every example, recipe, and template is independent. bun ex:<name>, bun rx:<name>, or bun tpl:<name>. Or trigger a single target from the Actions dropdown.
See CONTRIBUTING.md. Security issues: SECURITY.md.
MIT.