Inspiration
I run Beat ID — a live guess-the-song night, Kahoot but for music. Teams race to name tracks and the crowd votes on what gets played next. The product works. The bottleneck is Booking one students' unites: find the rightcontact, learn enough about their venue and programme to write something that isn't obviously a mail mece, then negotiate a feeand a date over a week of email. There are over 150 students' unions in the UK, and I'm also the pers Every part of that is delt calls — what fee I'llaccept, what dates I can do, when to say no. So I built an agent that does the work and asks me only abo What it does Greenroom runs the full o It researches each union ic and current — Nottingham Trent's venue winning Best Bar None Live Music Venue, Leeds celebrating the 25th anniversary of Fruitth the source URL. Itgenerates a poster, writes a pitch that opens with that fact in my voice, and sends it. Then it handles the replysses a Gatekeeper thattreats it as hostile before any other component sees it. Legitimate replies get classified and their e, capacity, exclusivity —and those terms are checked against my policy file deterministically, in code. Inside my envelope, it ans from my real calendar.Outside it, it drafts a holding reply that agrees to nothing, escalates to me, and cites the exact rule:s seems low".
The twist is that it earntarts in review — nothingsends without me. Three consecutive approvals with no edits promote it to veto, where drafts send atop them. Any edit demotesit immediately, and my edits become diffs that train a style memo. Escalations are always review, howeveas earned.
How I built it
Gemini 3.5 Flash on Verteone Cloud Run serviceholding the agents, the inbound endpoint and the dashboard. Firestore for state, Pub/Sub for inboun hourly tick, SecretManager for the OAuth token, Cloud Storage for posters, gemini-3-pro-image for the posters, gemini-2.5-fng brief aloud, andOpenTelemetry into Cloud Trace.
Five components, each holding only the tools it needs:
- Researcher — Google Search grounding and URL context. No send tool exists in its process.
- Writer — no tools at all.
- Gatekeeper — no tools. n classifies intent.
- Negotiator — no tools, and cannot send. It emits a draft; a job queue and the Scheduler do the se
- Scheduler — deliberately not an LLM. It decides whether the clock says 09:00 and whether a counter iwould add latency, costand non-determinism to the one component whose entire job is predictability.
Two decisions I'd defend to anyone:
A model never decides whether a deal is acceptable. It extracts what was asked for; policy.py decides agl can't talk the agentinto a bad deal, because the agent isn't the thing doing the accepting.
The Gatekeeper is a hard boundary, not a filter. What crosses it is a typed verdict — an intent enum,t three quotes capped at200 characters, terms as numbers and booleans. Never the raw email. If it quarantines, the pipeline
Containment is structuralend tool refuses anyaddress not in targets.csv, checked immediately before every Gmail call including replies; the Gm archive or untrash methodat all; the calendar tool is create-only; the Gmail watch is scoped to a label, never INBOX.
Challenges
Imagen no longer exists. imagen-* endpoint returns404 in every region — the deprecation is enforced, not advisory. I found that on day one by checking raturned a Monday emergencyinto a Saturday note.
The agent's mailbox wasn't a Google account. Our company address turned out to be on Zoho — confirmed frur failed OAuth attempts.The Gmail API can't serve a mailbox hosted elsewhere, so no configuration was ever going to work. Greenated Google account, whichis better anyway: its inbox contains nothing but its own threads.
Image generation quota. Twelve of twenty posters hit 429 RESOURCE_EXHAUSTED. The retry machinery handlion was wrong — a 429 means "not now", not "broken", and treating it as a failure burns a retry attempt against a job that was ne
What I learned
Config is upstream of eveed my brand file with "DJnights" and "artist liaison" because they sounded like things an events company does. Neither wasnight. The Writer used them exactly as instructed; it was told to invent nothing and it obeyed perfectly. The whole system is built it still put false claims about my own company in front of a real inbox, because the fabrication was one layer up where no tes
A requirement I never wround that Blur playedGoldsmiths in 2009 — specific, checkable, sourced, and a terrible reason to book a night in 2026. "Spcurrent" was only in myhead.
Four bugs lived entirely between "a job is queued" and "a job runs". Follow-ups were queued wily, so a nudge wouldn't bewritten a week early — and nothing filled them in, so day three would have sent a blank email. It paqueue was built, because no test ever let a follow-up execute.
A false success is worse than a failure. Dry-run means "contact nobody". Three times I used it to gate sody — a label, a watch, an image. The third produced twenty drafts, zero posters, and reported complete success. A failure is visnd no artefacts aren't.
And the worst gap was somthan built. Cloud Trace was on the stack and in my README. The dependencies were declared and the logging code read span IDs to cort no exporter was everconfigured, so there were no traces. I'd written the consumer and never the producer, and nothing errailure mode for correlating logs against a trace that doesn't exist.
What's next
The domain lives in three prompts, one schema and two field names — the statemachine, job queue, Gatekengine and containment areall domain-free. The same machine already fits any outreach where the deal isfee, date and headcount: and music venues,festivals, corporate socials. Swap the terms schema and it fits recruitment, partnerships or procureme
The part worth productisitools don't have: most will happily agree to whatever a prospect asks. Greenroom structurally cannot, and tells you which line it w
Built With
- cloud-build
- cloud-run
- cloud-scheduler
- cloud-storage
- cloud-trace
- docker
- fastapi
- firestore
- gemini
- gemini-3-pro-image
- gemini-tts
- gmail-api
- google-adk
- google-calendar-api
- google-cloud
- jinja
- oauth2
- opentelemetry
- pub-sub
- pydantic
- python
- secret-manager
- uvicorn
- vertex-ai
Log in or sign up for Devpost to join the conversation.