Overview
In this tutorial you’ll deploy a small AI code-review agent on Render, twice — each time as a full stack via a Blueprint. Pattern 1 provisions a web service and a Postgres database. Pattern 2 adds a background worker and a Key Value queue on top of its own web service and database. Both stacks stay up so you can compare them side by side. The agent code is identical in both. The only thing that changes is where the work runs.
This is the deployment half of the Workflow Agents Workshop. Use the TypeScript or Python track. The follow-up tutorial, Run AI agents as Render Workflows, takes the same agent and runs it on Render Workflows.
The agent
The agent reviews a public GitHub pull request. It runs a fixed pipeline:
flowchart LR pr(["GitHub PR URL"]) prep["prepare diff<br/>fetch per-file patches"] filter["filter diff<br/>drop lock files + bundles"] sec["security"] perf["performance"] ux["ux (frontend only)"] judge["judge<br/>approve / request-changes"] pr --> prep --> filter filter --> sec filter --> perf filter --> ux sec --> judge perf --> judge ux --> judge
- The prepare-diff step fetches the changed files from a public PR through the GitHub API.
- The filter-diff step drops noise such as lock files, minified bundles, and source maps before any tokens are spent. It returns the kept patches plus the list of dropped files, so the decision is visible in telemetry.
securityandperformancealways run.uxjoins only when the diff touches frontend files.judgeconsolidates the findings into anapproveorrequest-changesverdict.
The whole agent lives once in shared/agent and is imported unchanged by every pattern.
Repo layout
The workshop repo is a monorepo. Three packages each implement one runtime pattern, and three shared modules supply the code every pattern reuses.
workflow-agents-workshop-ts/├── packages/│ ├── naive-agent/│ │ └── server.ts # HTTP handler that awaits the review in-process│ ├── queue-agents/│ │ ├── web.ts # Web tier (producer) — enqueues jobs, streams progress via SSE│ │ ├── worker.ts # Background worker (consumer) — pulls jobs off the Valkey stream│ │ └── kv.ts # Valkey stream + pub/sub helpers (XADD, XREADGROUP, XACK)│ └── workflow-agents/│ ├── server.ts # Gateway — turns PR submissions into Render Workflow runs│ └── workflow.ts # Workflow service — auto-discovers and registers task definitions├── shared/│ ├── agent/ # The review pipeline, imported unchanged by every pattern│ │ ├── review.ts # Re-exports and types for the pipeline composition│ │ ├── agents.ts # Agent definitions (security, performance, ux, judge)│ │ ├── prepareDiff.ts # Fetches per-file patches from the GitHub API│ │ ├── model.ts # LLM provider selection (or mock model when no key is set)│ │ └── loop.ts # Agent execution loop│ ├── db/ # Telemetry store — reviews, findings, and agent spans│ │ ├── index.ts # Auto-selects Postgres (DATABASE_URL) or in-memory backend│ │ └── memory.ts # In-memory backend for zero-setup local dev│ └── ui/ # Workshop dashboard served by every pattern│ └── page.ts # Renders the telemetry viewer HTML shell├── package.json # npm workspaces root└── tsconfig.base.jsonThe repo uses npm workspaces. Each package and shared module is a workspace with its own package.json. A single npm install at the root resolves every dependency, and packages import shared modules as workspace references.
workflow-agents-workshop-py/├── packages/│ ├── naive_agent/│ │ └── server.py # HTTP handler that awaits the review in-process│ ├── queue_agents/│ │ ├── web.py # Web tier (producer) — enqueues jobs, streams progress via SSE│ │ ├── worker.py # Background worker (consumer) — pulls jobs off the Valkey stream│ │ └── kv.py # Valkey stream + pub/sub helpers (XADD, XREADGROUP, XACK)│ └── workflow_agents/│ ├── server.py # Gateway — turns PR submissions into Render Workflow runs│ └── workflow.py # Workflow service — auto-discovers and registers task definitions├── shared/│ ├── agent/ # The review pipeline, imported unchanged by every pattern│ │ ├── review.py # Re-exports and types for the pipeline composition│ │ ├── agents.py # Agent definitions (security, performance, ux, judge)│ │ ├── prepare_diff.py # Fetches per-file patches from the GitHub API│ │ ├── model.py # LLM provider selection (or mock model when no key is set)│ │ └── loop.py # Agent execution loop│ ├── db/ # Telemetry store — reviews, findings, and agent spans│ │ ├── __init__.py # Auto-selects Postgres (DATABASE_URL) or in-memory backend│ │ └── memory.py # In-memory backend for zero-setup local dev│ └── ui/ # Workshop dashboard served by every pattern│ └── __init__.py # Mountable FastAPI router for the telemetry viewer├── pyproject.toml # uv workspace root└── uv.lockThe repo uses a uv workspace. Each package and shared module has its own pyproject.toml. A single uv sync at the root resolves every dependency, and packages import shared modules as workspace path dependencies.
You only touch files inside one package at a time. The shared/ modules stay as-is throughout the workshop.
One agent, three runtime patterns
The workshop runs this agent three ways. You deploy the first two here:
| Pattern | Runtime pattern | Render resources |
|---|---|---|
| Naive agent | One web service, in-process | Web service + Postgres |
| Queue agents | Web + background worker + queue | + Background worker + Key Value |
| Workflow agents | Render Workflows | Covered in the next tutorial |
The point of the exercise is the contrast. The same review, run on a richer runtime pattern, gets durability and scale you would otherwise build by hand.
Why deploy-first
You could run all of this locally. The workshop deploys instead, because the lesson is about runtime behavior on Render. A queue that survives a crash, a worker you scale to three instances, a review that finishes after you redeploy the web tier: none of that shows up on a single laptop process. Local commands stay in this tutorial as a quick fallback, not the main path.
Demo PRs
The reviewer works against any public GitHub PR. The workshop uses a small set of public demo PRs so everyone can run the same flow without creating test repos or GitHub webhooks.
Each deployed service serves the same workshop dashboard. You paste or pick a PR URL, click Review, and compare the Status, Workflow, token count, run time, findings, and spans across runtime patterns.
Before you start
Set these up first:
-
A Render account. All services used in this workshop are free — Render covers the cost, so you won’t be charged anything. The workshop workspace won’t be available after today, but you can recreate these resources in your own workspace later to keep exploring.
-
The Render CLI installed and logged into the workshop workspace:
Terminal window # Install the CLI (macOS / Linux)curl -fsSL https://raw.githubusercontent.com/render-oss/cli/refs/heads/main/bin/install.sh | sh# Log in and select the workshop workspace when promptedrender login
Fork the workshop repo
You deploy from your own copy so a git push redeploys on Render.
- Open the workshop repo Go to render-examples/workflow-agents-workshop-ts.
- Fork it Click
Fork, then create the fork under your account. - Namespace your Blueprint resources In your fork, open the
Actionstab. Fresh forks have Actions disabled, so clickI understand my workflows, go ahead and enable themif you see that banner. Then chooseSetup attendee Blueprint namesand clickRun workflow. The Blueprint ships every project, service, and database name with a literal<YOUR_USERNAME>-prefix; the action replaces<YOUR_USERNAME>with your GitHub username and commits the change to your fork. Deploy before running it and Render rejects the Blueprint, because<YOUR_USERNAME>-isn’t a real name.
- Open the workshop repo Go to render-examples/workflow-agents-workshop-py.
- Fork it Click
Fork, then create the fork under your account. - Namespace your Blueprint resources In your fork, open the
Actionstab. Fresh forks have Actions disabled, so clickI understand my workflows, go ahead and enable themif you see that banner. Then chooseSetup attendee Blueprint namesand clickRun workflow. The Blueprint ships every project, service, and database name with a literal<YOUR_USERNAME>-prefix; the action replaces<YOUR_USERNAME>with your GitHub username and commits the change to your fork. Deploy before running it and Render rejects the Blueprint, because<YOUR_USERNAME>-isn’t a real name.
Run the setup workflow on your fork, not on render-examples/workflow-agents-workshop-ts. The workflow refuses to run on upstream main so the shared source stays unchanged. If GitHub Actions are disabled in your fork, clone it locally, run npm ci && npm run setup -- YOUR_GITHUB_USER, then commit and push the Blueprint changes.
Run the setup workflow on your fork, not on render-examples/workflow-agents-workshop-py. The workflow refuses to run on upstream main so the shared source stays unchanged. If GitHub Actions are disabled in your fork, clone it locally, run uv sync && uv run python scripts/setup_attendee.py, then commit and push the Blueprint changes.
Clone your fork
Clone the repo so you have it locally for later steps:
git clone https://github.com/<your-github-username>/workflow-agents-workshop-ts.gitcd workflow-agents-workshop-tsnpm cigit clone https://github.com/<your-github-username>/workflow-agents-workshop-py.gitcd workflow-agents-workshop-pyuv syncBy the end you’ll have two live deploys in your Render workspace and a clear sense of which one you’d reach for, and when.
Troubleshooting
Find the symptom that matches what you’re seeing, then apply the fix.
The Setup attendee Blueprint names workflow isn’t there or won’t run. GitHub disables Actions on fresh forks. Open your fork’s Actions tab and click the green I understand my workflows, go ahead and enable them button first. Then Setup attendee Blueprint names appears with a Run workflow button. If Actions are blocked by org policy, use the local fallback for your track from the Fork step, then commit and push.
The deploy fails, or Render rejects the Blueprint with <YOUR_USERNAME> in a name. You deployed before running setup, so the names are still the literal <YOUR_USERNAME>- placeholder. Run Setup attendee Blueprint names on your fork to replace it with your GitHub username, push, then redeploy. Always pick Create all as new services, never Update existing.
Is this free, or will there be surprise charges? Every service here runs on a paid plan: web and worker on Starter, Postgres on basic-256mb, Key Value on Starter. There is no free tier. In the shared workshop workspace, the host is billed for what you deploy and tears the workspace down after the session.
Reviews fail with a GitHub 403 and no findings. GitHub’s API allows only 60 unauthenticated requests per hour, per IP. A room behind one network exhausts that fast, and prepare-diff then returns 403 rate limit exceeded. Create a GitHub token (a fine-grained PAT with public-repo read is enough) and set it as GITHUB_TOKEN on each service. That raises the limit to 5,000/hour. The GITHUB_TOKEN slot already exists in every Blueprint as a sync: false env var.
render workflows ... says unknown command. Your CLI is older than 2.11. Homebrew can lag; reinstall via the releases page or the curl script, then confirm render --version. After render login, run render workspace set and pick the workshop workspace, or later checkpoints will show no services and look like nothing deployed.
The review output looks canned and always approves. That’s the deterministic mock model, the expected fallback when no provider key is set. It proves the deploy, request, and telemetry path end to end. Set ANTHROPIC_API_KEY or OPENAI_API_KEY (on the service that runs the review) for real findings.
Local fallbacks or tests fail on an older Node. This repo needs Node.js 22.12 or newer (node --version). On Node 20, npm install errors with Unsupported engine.
Local fallbacks fail with command not found: uv or ModuleNotFoundError. The Python track needs Python 3.12+ and uv. Run every local command through uv run (for example uv run python -m naive_agent.server), because the workspace packages live in the uv-managed .venv.
What you learned
- One code-review agent runs the pipeline prepare diff -> filter diff -> [security, performance, ux?] -> judge
- You deploy it two ways here: a naive web service, then a worker plus queue
- The filter-diff step drops noise before the expensive fan-out and records what it dropped
- Forked the workshop repo and namespaced Blueprint resources with your GitHub username
- You can review the same public PR against each pattern so the runtime pattern is the only variable
- Without an LLM key the agent uses a mock model that returns canned output, not real findings