07 est. 45 min

Shipping it: deploy, monitor, iterate

Module 07 · est. 45 min · You’ll build: your agent deployed to real infrastructure, running on a trigger without you — using the exact boring stack that runs my fleet: GitHub Actions, Cloudflare Workers/Pages Functions, and cron.

TL;DR: Deploying an agent is not the hard part anyone makes it out to be. Scheduled agents run on GitHub Actions cron (free, five lines of YAML). Event-triggered agents run as Cloudflare Workers or Pages Functions (a webhook endpoint at the edge). Secrets live in the platform’s secret store. Monitoring is a log line piped to Slack. There’s no Kubernetes, no server to patch, no Python runtime to babysit. The “infra” is deliberately boring because boring is what runs for a year without you touching it.

[Operator’s read] My entire agent fleet runs on free or near-free tiers of GitHub Actions and Cloudflare. No servers. I have never SSH’d into a box to keep an agent alive. If your deploy story involves provisioning infrastructure, you’ve overcomplicated it. The whole point of these platforms is that the trigger and the runtime are someone else’s problem.


The deploy decision: which host for which shape

Match your Module 03 shape to its host. This is the whole architecture decision and it takes ten seconds:

ShapeHostWhy
ScheduledGitHub Actions cronFree cron, runs your repo, secrets built in
PollingGitHub Actions cron (frequent)Same as scheduled, just a tighter interval
Event-triggeredCloudflare Worker / Pages FunctionAn HTTP endpoint at the edge, fires on the webhook
ConversationalClaude Code / Cowork locallyA human’s present; it runs where they are

Most of your first agents are scheduled, so we’ll nail that path first and completely.

Path A — Scheduled & polling: GitHub Actions

This is where most of my fleet lives. The morning brief, the weekly recap, the AI-trends digest, the quarterly bonding scheduler — all GitHub Actions cron jobs. Here’s the entire deploy.

Step 1 — Your repo is your deploy unit. Push the agent repo (the Module 02 starter, hardened through Modules 04-06) to GitHub. That’s the deploy. No build step, no artifact.

Step 2 — Put your secrets in GitHub. Repo → Settings → Secrets and variables → Actions → New repository secret. Add ANTHROPIC_API_KEY and any others (SLACK_WEBHOOK_URL, etc.). These never touch your code. (Module 06’s least-privilege rule applies — scope these keys.)

Step 3 — The workflow file. Drop this at .github/workflows/agent.yml:

yaml
name: morning-brief
on:
  schedule:
    - cron: '0 11 * * *' # 11:00 UTC = 6am Central. Cron is ALWAYS UTC.
  workflow_dispatch: # manual "Run workflow" button — for testing. Always include.
jobs:
  run:
    runs-on: ubuntu-latest
    timeout-minutes: 10 # hard ceiling — a stuck run can't bill forever
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - name: Run evals first (don't ship a broken agent)
        run: npx tsx evals/run.ts
      - name: Run the agent
        run: npx tsx src/run.ts
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
          DRY_RUN: 'false' # flip to 'false' only once you trust it

That’s a deployed, scheduled agent. Note three things I bake into every one:

  • workflow_dispatch lets you hit “Run workflow” in the Actions tab to test on demand. Never wait until 6am to find a bug.
  • timeout-minutes is the infra-level seatbelt that complements Module 06’s MAX_TURNS. Two layers, because a stuck run is a billing event.
  • Evals run before the agent. If your Module 04 evals fail, the agent doesn’t run. Your test suite is now a deploy gate. This one line is why I can change a prompt and not lie awake.

Step 4 — Test it. Go to the Actions tab, hit “Run workflow,” watch the logs. Fix until green. Then let the schedule take over. You’re done. It now runs every morning whether you exist or not.

The timezone gotcha, stated plainly: cron is UTC. Always. Want 6am Central (UTC-6 in winter, UTC-5 in summer)? 0 12 * * * is 6am in summer, 0 11 * * * in winter — DST will shift it by an hour twice a year. For most agents an hour’s drift is fine. If it isn’t, run at a UTC time and let the agent check the local time and decide whether to act. Don’t fight cron’s UTC-ness; design around it.

Path B — Event-triggered: Cloudflare Workers / Pages Functions

When a webhook should fire your agent, you need an endpoint that’s always on. Cloudflare Workers (or Pages Functions, same engine) give you a URL at the edge with no server to run.

Step 1 — The function. A Pages Function lives at functions/webhook.ts in your repo (the file path becomes the URL). This is the event-triggered shape from Module 03, now deployed:

typescript
// functions/webhook.ts → becomes https://yoursite.pages.dev/webhook
export async function onRequestPost(context: {
  request: Request;
  env: { ANTHROPIC_API_KEY: string; WEBHOOK_SECRET: string };
  waitUntil: (p: Promise<any>) => void;
}) {
  const body = await context.request.text();

  // 1. Verify it's real (Module 06). Reject forgeries.
  if (!verifySignature(context.request, body, context.env.WEBHOOK_SECRET)) {
    return new Response('unauthorized', { status: 401 });
  }

  const event = JSON.parse(body);

  // 2. Acknowledge fast, run the agent async (Module 06 latency pattern).
  context.waitUntil(runAgentOn(event, context.env.ANTHROPIC_API_KEY));

  return new Response('ok', { status: 200 });
}

Step 2 — Deploy it. If you already host a site on Cloudflare Pages (I host alejandrorioja.com, an Astro site, on Pages), the function deploys with your normal git push — Pages picks up the functions/ directory automatically. For a standalone Worker: npx wrangler deploy.

Step 3 — Secrets. npx wrangler secret put ANTHROPIC_API_KEY (or set them in the Cloudflare dashboard for Pages). Same least-privilege discipline.

Step 4 — Point the webhook at it. In whatever system emits the event (Stripe, a form tool, GitHub), set the webhook URL to your deployed endpoint. Send a test event. Watch it fire.

That’s an event-triggered agent in production. The edge keeps it warm; you don’t run anything.

MCP servers: the clean way to give agents tools

As your tools get real (talking to Airtable, Slack, your calendar, a booking system), stop hand-writing every integration. MCP (Model Context Protocol) servers are reusable tool-providers your agent connects to — a standard way to plug an agent into a service. Instead of writing the Slack-posting code in every agent’s tools.ts, you connect to a Slack MCP server and the tools are just there. I lean on MCP servers for the services my agents touch repeatedly. The mental model: MCP servers are the USB ports; your agents are the devices. Standardize the connection, reuse it everywhere. (In Claude Code and Cowork, connecting an MCP server is config, not code.)

Iterate: the loop after launch

Shipping isn’t the end; it’s the start of the iterate loop. The discipline that keeps agents healthy:

1. Watch the first runs like a hawk. The first week, read every output. This is when you discover the inputs you didn’t anticipate. Each surprise becomes a new eval (Module 04) and usually a new prompt rule (Module 05).

2. Every real failure becomes a test. The agent does something dumb? Don’t just fix the prompt — add the failing case to evals/run.ts first, watch it go red, then fix until green. Now it can never regress. Over months, your eval suite becomes the agent’s accumulated wisdom.

3. Keep a kill switch. One environment variable (AGENT_ENABLED=false) that makes the agent no-op. When something’s wrong at 2am, you want to disable it in one click without deleting the schedule or redeploying.

typescript
if (process.env.AGENT_ENABLED === 'false') {
  console.log('Agent disabled via kill switch. Exiting.');
  process.exit(0);
}

4. Promote trust gradually. New agents run in DRY_RUN (print what they’d do) for days. Then human-gated (draft, you approve). Then, for low-stakes ones, autonomous. Don’t go from “wrote it” to “auto-sending to customers” in one step. Earn the autonomy.

5. Review the fleet weekly. Costs drifting? Error rates up? An agent that hasn’t run because cron silently broke? A five-minute weekly scan catches rot before it’s a problem. (Mine is — naturally — partly automated by the weekly recap agent.)

The whole deploy, in one breath

Push repo to GitHub. Secrets in the platform’s secret store. Scheduled agent → Actions cron with workflow_dispatch, a timeout, and evals-as-gate. Event agent → Cloudflare Pages Function with signature verify and async ack. Kill switch and DRY_RUN env vars for safety. One log line per run to Slack. That’s it. That’s production. No servers, no ops team, no 3am pages — because the boring infrastructure is doing exactly what boring infrastructure is supposed to: nothing you have to think about.

Hands-on lab

Deploy your agent for real.

Step 1 — Push & secret. Get your hardened agent repo on GitHub. Put your API key and any webhook URLs in repo Actions secrets. Confirm nothing sensitive is committed in code.

Step 2 — Deploy by shape. If scheduled/polling: add the agent.yml workflow with cron, workflow_dispatch, timeout-minutes, and the evals-before-agent step. If event-triggered: deploy a Cloudflare Pages Function with signature verification and the async-ack pattern, and point a test webhook at it.

Step 3 — Trigger it manually. Use workflow_dispatch (Actions) or send a test event (Cloudflare). Watch the logs. It must run green start to finish, with DRY_RUN still on.

Step 4 — Add the safety rails. Wire in the AGENT_ENABLED kill switch and confirm the one-line structured log (Module 06) shows up in your Slack channel.

Step 5 — Let it run live, gated. Flip DRY_RUN=false only when you’ve watched enough dry runs to trust it. For customer-facing agents, keep the human gate. Let the trigger fire on its own for the first time. The moment you get a Slack message from an agent you deployed and then walked away from — that’s the whole course paying off.

Deliverable: one agent, deployed, running on a trigger without you, with evals as a deploy gate, a kill switch, and live monitoring. You are now running a production agent. Next module: thirty more of them — every agent I run, opened up, so you can steal the patterns whole.

↵ to see all results esc esc to close