Ship Production Software With AI Agents on Cloudflare

by Florian Renard, Founder / CEO

Every product we run in production — spectralChat, Gema, spectralMap, this website — ships the same way: a Better-T-Stack monorepo, infrastructure written in TypeScript with Alchemy, and AI coding agents doing most of the typing. This is the actual setup, step by step, including the parts that bit us. Nothing here is theoretical; I deployed this exact pipeline again this week.

The core insight first: agents are only as good as what they can read. Dashboards, wikis and tribal knowledge are invisible to them. When your API contract is typed end-to-end, your infrastructure is a TypeScript file in the repo, and every branch deploys to a real URL, an agent can see the entire system — and verify its own work. That is the whole trick.

Step 1: Scaffold with Better-T-Stack

One command generates the monorepo. This is the exact one this website came from:

pnpm create better-t-stack@latest myApp \
  --frontend tanstack-start --backend hono --runtime workers \
  --database sqlite --orm drizzle --api orpc --auth better-auth \
  --db-setup d1 --web-deploy cloudflare --server-deploy cloudflare \
  --addons mcp skills --examples todo --package-manager pnpm --install

You get apps/web (TanStack Start, SSR), apps/server (Hono + oRPC on Workers), and packages for db (Drizzle + D1), auth (Better-Auth), env, and infra. Two choices matter more than they look:

  • oRPC gives you one typed contract from database to browser. When an agent renames a field, the compiler finds every consumer. Typecheck becomes your first agent-verification gate, for free.
  • The skills addon drops framework documentation into .claude/skills/, so the agent working in your repo knows Hono, Better-Auth and shadcn conventions without you pasting docs into every prompt.

Step 2: Infrastructure is a TypeScript file

Alchemy replaces the Cloudflare dashboard with packages/infra/alchemy.run.ts. Resources are awaited values; bindings are just props:

const db = await D1Database("database", {
  migrationsDir: "../../packages/db/src/migrations",
});

export const server = await Worker("server", {
  cwd: "../../apps/server",
  entrypoint: "src/index.ts",
  domains: deployment.serverDomains,
  bindings: {
    DB: db,
    CORS_ORIGIN: deployment.webOrigin,
    BETTER_AUTH_SECRET: alchemy.secret.env.BETTER_AUTH_SECRET,
  },
});

export const web = await TanStackStart("web", {
  cwd: "../../apps/web",
  domains: deployment.webDomains,
  bindings: { VITE_SERVER_URL: deployment.serverOrigin, DB: db },
});

This file is the single source of truth for what exists in production — and because it lives in the repo, an agent can read it, modify it, and reason about bindings the same way it reasons about application code. D1 migrations apply automatically on deploy; agents never touch the migrations directory, they edit the schema and the pipeline does the rest.

Step 3: Stages — one config function, three worlds

Everything routes through a stage name: production is a fixed stage, every pull request gets pr-<number>, and local dev defaults to your OS username. One function maps stages to URLs and custom domains:

// prod stage → spectralgo.com + api.spectralgo.com
// pr-42      → pr-42.spectralgo.com + api.pr-42.spectralgo.com
// local      → localhost, no domains
function toDomain(domainName) {
  return { domainName, adopt: true };
}

The rule that keeps this honest: never put stage-specific URLs in .env files. CORS origins, auth URLs and server URLs are all derived from the stage config, so preview environments are never half-configured.

Step 4: Every pull request is a living deployment

The GitHub workflow is short and changes how you review software:

  1. PR opened → deploy stage pr-<number>: its own worker, its own D1 database, its own subdomain.
  2. The workflow rewrites the PR body with the preview URLs on every push.
  3. PR closed → a cleanup job destroys the stage — after a safety check that refuses to ever destroy the production stage.

This is where agents earn their keep. An agent opens a PR, the pipeline hands it a real URL, and the agent (or a browser-driving verifier agent) exercises the actual deployment — we capture recorded proofs of flows against preview stages before merging. Reviewers click a link and touch the real thing, not a screenshot.

Step 5: Production deploys with guard rails

State is the sharp edge of any IaC tool. Alchemy keeps state locally by default — fine for dev, dangerous for prod: a local deploy planning against stale state can decide your production database “doesn't exist” and recreate it. Our deploy-prod.sh forces every production deploy — from a laptop or CI — through the same remote state store on Cloudflare, requires the state token and encryption password, and makes you type deploy before it moves.

The gotchas we actually hit

  • Expired OAuth. Alchemy's Cloudflare login (not the same as Wrangler's!) has a refresh token that expires. Symptom: invalid_grant mid-deploy. Fix: alchemy login cloudflare, answer yes to overwrite.
  • Apex domains with history. If your root domain already has A/AAAA records (old site, parking page), the custom domain attach fails with a 409. Delete the stale records — and only those — in the dashboard first.
  • Partial deploys leave orphans. A run that fails after creating a worker leaves a resource Cloudflare knows about but your state doesn't. Set adopt: true on resources so reruns adopt instead of colliding.
  • Lowercase names. Cloudflare queue names must be lowercase; an app named in camelCase can generate invalid resource names. Check generated names before you ship a queue.

Why this is an agent superpower

Put the pieces together: typed contracts catch agent mistakes at compile time; infra in the repo means agents deploy by editing code, not clicking dashboards; preview stages give every agent branch a disposable production-shaped sandbox; and automatic teardown makes the whole thing cheap enough to run on every PR. A solo founder directing a handful of agents gets the delivery pipeline of a platform team — for roughly the price of a Cloudflare Workers subscription.

More articles

Proof or It Didn't Ship: Running AI Agents Like a Team

Recorded proofs on every PR, parallel audit agents with numbers, standing orders in the repo, and an orchestrator that never codes — the evidence system that makes agent-written software trustworthy.

Read more

Give Your Company a Memory: My Claude + Obsidian Vault

How a version-controlled Obsidian vault became my company's long-term memory — context files as an in-vault system prompt, git as persistence, and a legal decision register in markdown.

Read more

Tell us about your project

Our offices

  • Paris (Registered office)
    60 rue François 1er
    75008 Paris, France
  • Lisbon (Studio)
    Remote-first studio
    Lisbon, Portugal