Public agent on Render
A public agent deployment is Thunderbolt for visitors who never sign in: each one gets an anonymous session and chats with a single agent you configure, for example at an event. It is built from the Render Blueprint in deploy/render/public-agent.yaml. Nothing about a particular deployment lives in the repository; every per-deployment value is entered in Render.
The hosted agent has no tools yet. The app talks to it directly, and it answers from its model alone. To offer visitors the built-in agent as well, which has tools, keep
DISABLE_BUILT_IN_AGENT=falseand give it a Tinfoil key (TINFOIL_API_KEY).
What it runs
| Resource | Name in the Blueprint | Notes |
|---|---|---|
| Web app | public-agent-web |
Static site built from main. Sends the headers the app needs and refuses to be framed (see below). |
| API | public-agent-api |
Node service from backend/, one instance running one process (see Limits), health check on /v1/health. |
| Database | public-agent-db |
Its own PostgreSQL, reachable only from inside Render. |
| Settings | public-agent env group |
Read by the API. |
There is no sync service. Anonymous sessions never sync, and the API runs without one when POWERSYNC_URL is unset, so visitors’ chats stay in their browsers.
Before you start
- Access to the Render workspace and the Thunderbolt project.
- An Anthropic API key used by this deployment only, ideally in its own Anthropic workspace with a spend limit, so a leak or a burst cannot touch production.
- A Tinfoil API key used by this deployment only. The built-in agent’s default model runs on Tinfoil.
- Two hostnames, one for the app and one for the API, for example
agent.example.comandagent-api.example.com. If Cloudflare proxies them, keep each one level below the zone so the universal certificate covers it. - Optionally, a PostHog project of its own for product analytics. Never reuse production’s.
Deploy
- In the Render dashboard choose New > Blueprint, pick
thunderbird/thunderboltand themainbranch, and set Blueprint Path todeploy/render/public-agent.yaml. - Enter the values Render asks for (next section) and apply. Render creates the four resources.
- Create an environment for the deployment under the Thunderbolt project and move the four resources into it. Render keeps a resource’s environment across later syncs.
- Add the app hostname as a custom domain on
public-agent-weband the API hostname onpublic-agent-api, point a CNAME at each, and wait for the certificates. - Turn off each service’s
onrender.comsubdomain, so the custom hostnames are the only way in. The Blueprint cannot, because it names no domain. - Check that
https://<api host>/v1/healthreturns{"status":"ok"}, then open the app.
Values Render asks for
Render prompts once for every value below and never overwrites them afterwards. Change them later on the env group or the service.
| Setting | What to enter |
|---|---|
VITE_THUNDERBOLT_CLOUD_URL |
https://<api host>/v1. It is built into the app, so changing it means a rebuild. |
APP_URL, CORS_ORIGINS |
https://<app host> |
BETTER_AUTH_URL |
https://<api host> |
ANTHROPIC_API_KEY |
The deployment’s own key. |
TINFOIL_API_KEY |
The deployment’s own key. While the built-in agent is on, chats on its default model fail without it. |
FIREWORKS_API_KEY |
Blank. Nothing routes to Fireworks yet. |
POSTHOG_API_KEY, POSTHOG_HOST |
The deployment’s own project, or blank for no analytics. |
AGENT_ENABLED |
true to mount the hosted agent. |
DISABLE_BUILT_IN_AGENT |
false for now; true once the app supports the hosted agent. |
ENABLED_AGENTS |
Blank for now; hosted-agent once the built-in agent is off. |
AGENT_MODEL |
An Anthropic model with a row in the price table. claude-opus-5 ships priced. |
AGENT_SYSTEM_PROMPT |
The agent’s knowledge (next section). |
AGENT_NAME, AGENT_DESCRIPTION, AGENT_ICON |
How the agent is presented. |
AGENT_MAX_STEPS, AGENT_MCP_SERVERS |
Blank. Neither has an effect until the agent has tools. |
INFERENCE_QUOTA_ANONYMOUS_5H_CENTS, INFERENCE_QUOTA_ANONYMOUS_7D_CENTS |
Each visitor’s spending cap, in cents. Enter numbers: a blank value stops the API at startup. |
ANONYMOUS_SIGN_IN_RATE_LIMIT_MAX, ANONYMOUS_SIGN_IN_RATE_LIMIT_WINDOW_SECS |
Blank keeps the defaults, 10 per 60 seconds. Without a captcha provider the API refuses to start with more than 10 or a window under 60 seconds. |
The rest is fixed by the Blueprint. The env group turns on anonymous sessions (AUTH_ALLOW_ANONYMOUS=true), lets them discover the agent (ALLOW_ANONYMOUS_AGENT_DISCOVERY=true) and stops users adding their own agents (ALLOW_CUSTOM_AGENTS=false). It also sets TRUSTED_PROXY=cloudflare: all traffic to a Render web service passes through Cloudflare, which writes the caller’s address to CF-Connecting-IP, so the rate limits key on each visitor’s own address. Without it the API sees Render’s proxy as every visitor, and they all share one sign-in budget. The app is built with VITE_AUTH_ENABLE_ANONYMOUS, VITE_BYPASS_WAITLIST and VITE_SKIP_ONBOARDING, so a visitor lands straight in a chat. BETTER_AUTH_SECRET is generated. Every setting is described in Configuration.
The agent’s knowledge
| Kind of knowledge | Where it goes |
|---|---|
| Fixed material: an FAQ, venue facts, policies | AGENT_SYSTEM_PROMPT. It is sent with every request, so write facts, not prose. Keep the source out of this repository. |
| Data that changes: a schedule, rooms, speakers | MCP servers listed in AGENT_MCP_SERVERS, once the agent has tools. Until then, have the prompt say where the schedule is. |
Visitors’ project and skill instructions are appended after AGENT_SYSTEM_PROMPT, under a header that ranks them below it. That ranking is a request to the model, not an enforced rule, so treat the prompt as readable by visitors and not as a hard guardrail.
The agent is not told the current date or time yet, so questions such as “what’s on this afternoon?” need the schedule tools.
Headers
The web app sends Cross-Origin-Embedder-Policy: require-corp and Cross-Origin-Opener-Policy: same-origin. They make the page cross-origin isolated, which the app’s local database needs, so a site without them does not start. It also sends X-Frame-Options: DENY and frame-ancestors 'none', because nothing embeds a public agent.
Usage report
The API can report users, turns, tokens and spend for any window, split between anonymous and signed-in users, by day and by model, with the busiest hour. It reports counts only. Run it in an SSH session on the API (render ssh), from the backend directory:
bun run usage-report --from 2026-10-26 --to 2026-11-02 --tz Europe/London--from is inclusive and --to exclusive. Add --json for machine-readable output. Usage rows are deleted along with their user, so run the report before removing users or the database.
Freeze before the event
Every merge to main redeploys the deployment while auto-deploy is on. A few days before the event:
- On the Blueprint’s settings page, set Auto Sync to No.
- Turn Auto-Deploy off on both services.
- Deploy the commit you tested to each service:
render deploys create <service id> --commit <sha>.
Ship a fix during the event the same way, by deploying its commit.
Teardown
- Run the usage report and keep the output.
- Disconnect the Blueprint. While it is connected, Render recreates a deleted resource on the next sync.
- Delete both services, the database and the env group.
Deleting the database removes everything the deployment kept on the server: the anonymous accounts and sessions, the usage records and the rate-limit counters. The server keeps no conversations, since the hosted agent is stateless and nothing syncs. Visitors’ chats stay in their browsers until they clear the site’s data. PostHog data, if you collected any, lives in that project and is removed there.
Limits
- One live deployment of the Blueprint per Render workspace, because its resource names are fixed.
- Without a captcha provider, anonymous sign-in allows at most 10 per IP address per minute. Venue Wi-Fi, where many visitors share a few addresses, reaches that quickly.
- The hosted agent calls one model, on Anthropic or Fireworks (see
AGENT_MODELin Configuration), and has no tools yet. - The API runs as one instance with one process (
WEB_CONCURRENCY=1). The hosted agent allows a visitor one run at a time but tracks that per process, so scaling out would let parallel runs exceed the spending cap. Scale out once the agent has a shared run lock.