Note
Ship your agent on a 100% free stack. Hands-on workshop · 90 minutes
Your agent works beautifully on localhost. Then you deploy it, someone clicks "Run", and 30 seconds later the request dies with a 504.
This repo is about the one architectural decision that fixes that — and a complete, working, deployed example of it.
Tip
Never run the agent inside the HTTP request
Accept the job, return an ID immediately, do the work in the background, let the client poll. Every timeout problem dissolves once that clicks.
POST /runs → 202 {"run_id": "abc"} ~200 ms, always
[background] → agent runs, writing each step to the database
GET /runs/abc → {status, steps[]} client polls every 1.5 s
The demo agent researches with Wikipedia, searches the live web through Tavily's MCP server when you give it a key, and holds a conversation — ask a follow-up and it remembers, because the history lives in Postgres rather than in the browser tab.
| If you… | Go to |
|---|---|
| want to learn this properly | the workshop website — Learn → Build → Deploy |
| want it running in 5 minutes | Quick start below |
| are running it yourself | docs/ — run of show, prep, prerequisites |
| want the slides | slides/index.html — open it, no build step |
The website is the main thing. It's written so someone who wasn't in the room can go from nothing to a deployed agent on their own.
You'll need a free Gemini API key and a free Supabase project. A free Tavily key is optional — it adds web search, so the agent can answer questions about recent events instead of only what Wikipedia has written up.
Important
Never commit your API key or database credentials. Use .env and .gitignore.
# 1. install uv (one tool for Python, venvs, and packages)
curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. install dependencies — exact versions, from app/uv.lock
cd app && uv sync
# 3. your keys
cp .env.example .env # then fill in the three values
# 4. create the tables
# paste database/schema.sql into the Supabase SQL editor and run it
# 5. go
uv run fastapi dev main.pyCheck http://localhost:8000/health — you want {"ok": true, "database": true}.
It also names the provider and model answering, and whether web search is on.
Then start the client, in a second terminal from the repo root:
cd client
npm install
npm run dev # http://localhost:5173Open http://localhost:5173, ask a question, and watch the steps appear.
Now tick "Naive mode" and ask again. Same agent, same answer, but a blank 30-second wait. That's the bug this whole project exists to fix.
cd app && uv run --extra dev pytest # 37 tests, no keys or network needed
cd client && npm run build # type-check + build the client
cd website && npm install && npm run dev # the workshop website, locally
cd app && uv run python -m agent.manual_loop "..." # the agent, no framework, from the CLI
cd app && uv run fastmcp dev tools/server.py # poke the MCP tools in the Inspector├── app/ the deployed application — its own uv project
│ ├── pyproject.toml dependencies, pinned by app/uv.lock
│ ├── main.py the API — /runs vs /runs/naive is the whole lesson
│ ├── agent/ the agent, plus the same loop written by hand
│ ├── tools/ the tools, and the same tools over MCP
│ ├── tests/ 37 tests that need no API key
│ └── http/ ready-made requests for driving the API by hand
├── client/ the client — Vite + React + TypeScript
│ └── src/
│ ├── api.ts the five HTTP calls — the whole backend contract
│ ├── useConversation.ts accept-and-poll + threads, with cancellation
│ └── useHealth.ts which model is answering, from /health
├── database/schema.sql three tables
├── deploy/ Render blueprint — the only platform-specific file
├── slides/ the deck
├── website/ the workshop website + slides route (Astro)
└── docs/ run of show, prep, prerequisites, free-tier notes
| Layer | Choice | Free tier |
|---|---|---|
| Model | Google Gemini via AI Studio | yes, no card |
| Agent | Pydantic AI | open source |
| Tools | FastMCP, mounted at /mcp |
open source |
| Backend | FastAPI on Render or FastAPI Cloud | yes, no card |
| Database | Supabase Postgres + pgvector | yes |
| Frontend | Vite + React on Vercel or GitHub Pages | yes |
Total cost: nothing, and no credit card at any point.
Those are defaults, not requirements. The model is three environment variables (other free providers — Cerebras, OpenRouter, Groq) and the host is a deploy setting (other free hosts — Hugging Face Spaces, Fly.io, Koyeb). Pick whichever you like; the architecture doesn't change.
Free tiers have real trade-offs — your server sleeps, your database pauses, your model rate-limits. The Stack section is honest about all of them.
"Deploying the app" is really two separate jobs, and only the first one is hard.
1 · The agent — app/, a FastAPI process that must keep working after it
has sent the response. This is what the workshop is about.
| Host | Deploy by | Notes |
|---|---|---|
| Render | connecting a GitHub repo | Primary. deploy/render.yaml has the settings; Root Directory app |
| FastAPI Cloud | GitHub in the dashboard, or fastapi deploy |
Second target. No config file — it reads pyproject.toml, uv.lock and .python-version. Public beta |
2 · The client (bonus) — client/, a folder of static files. Nothing here
can break the architecture, because nothing here runs your code.
| Host | Deploy by | Notes |
|---|---|---|
| Vercel | importing the repo | Root Directory client, VITE_API_URL = your backend URL. client/vercel.json is already written |
| GitHub Pages | pushing to main |
Already wired up — published at /demo/ alongside this site |
Do the agent first; the client needs a backend URL to point at. Then wire the
two together — VITE_API_URL on the client, ALLOWED_ORIGINS on the backend —
and redeploy both. That last step is where "Failed to fetch" comes from, every
single time.
Full walkthroughs: Deploy.
The MCP server is mounted inside the API, so once you deploy, add this to Claude Desktop or Claude Code:
{ "mcpServers": { "research-tools": { "url": "https://<your-service>/mcp" } } }Your Wikipedia tools show up as tools you can use in any conversation. No second deployment — see Learn · MCP.
cd website
npm install
npm run dev # http://localhost:4321The deck and the demo client are single-sourced from slides/ and client/;
website/scripts/sync-assets.mjs pulls them into the build — running the client's
own Vite build along the way — so there is never a second copy to keep in sync.
Edit slides/index.html and client/src/ in place.
Published to GitHub Pages by .github/workflows/deploy-pages.yml:
- Website —
https://<user>.github.io/deploying-agentic-ai-apps-workshop/ - Slides —
…/slides/ - Demo client —
…/demo/
Enable it once at Settings → Pages → Source: GitHub Actions.