-
-
Setback's front door: type your neighbour's DA number, that's the whole signup step.
-
The docket board: every case Setback has run, at a glance, behind its own passphrase.
-
A real DA under review, chat on the left, Grounds/Evidence/Overlay/Documents tabs on the right.
-
Two outcomes on the same page: one ground shipped with a citation, one got refused with a real reason.
-
No resident photo? Setback falls back to Street View, attributed, badged, never passed off as more than it is.
-
The refusal moment: the card says plainly that property value "is not a matter listed in s4.15(1)."
-
The overlay: Setback points at the exact spot on the real site plan it's citing.
-
Click the overlay, get the full-resolution drawing, not a thumbnail pretending to be evidence.
-
The actual output: a submission letter with real statutory citations, ready to copy or email.
-
Same case, dark mode. The refused-ground pill goes warm-brown, not red, in either theme.
-
Works on your phone: stacked layout, no sideways scrolling, buttons big enough to actually tap.
-
When there's no shadow photo, the gap is filled with Veo (live for judges, pre-generated for the public), always labelled not evidence.
Setback
Setback puts your neighbour's development application on trial. Two AI reviewers check every objection ground against the actual NSW planning act. One reads the law, one reads the evidence, and neither can see the other's homework. If they split, an adjudicator breaks the tie, and a deterministic gate makes the final call with the real statutory reason attached. The chat interview is just how Setback gathers your story. The tribunal is the product, and it runs on its own: about 35 seconds, less than half a cent, no hand-holding.
Inspiration
NSW planning law only lets a council weigh a specific, fairly short list of things when it decides whether to approve a development next to you: overshadowing, privacy, height and bulk, heritage, a handful of others written into s4.15(1) of the Environmental Planning and Assessment Act. Property value isn't on that list. Neither is "I don't like the applicant." Residents write those objections anyway, all the time, because that's genuinely how people feel about a building going up next door, and the council quietly sets them aside without ever explaining why. The resident just never finds out the actual reason. That gap, between what people feel and what the law can act on, is what I wanted to build something for.
What it does
You type in a DA number. Setback fetches the real development application from the council's own register, interviews you about your concerns in plain language, and reads whatever plans or photos you upload. Then a small panel of AI reviewers checks every ground you raised against the actual statutory list before anything gets submitted. A concern that's legally viable ships as a proper objection with a real citation. A concern that isn't doesn't get quietly dropped. It gets refused, explained plainly, and the resident is told what would actually make it work instead. Where a plan or elevation drawing supports a ground, Setback shows an annotated overlay pointing at exactly what it looked at, so nothing is asserted without pointing to the page it came from.
How I built it
I orchestrated it, I didn't type most of it. The whole codebase runs on Google's Agent Development Kit (ADK 2.8) with three model tiers doing three different jobs: gemini-3.5-flash-lite runs the resident interview and the grounding/reviewer passes, gemini-3.7-flash sits on the adjudication bench for when two reviewers disagree, and Gemma (gemma-4-26b-a4b-it-maas, through Vertex AI's OpenAI-compatible endpoint) does the cheap clerical work: normalising concerns, writing refusal prose. All three run on Vertex AI. It deploys as two Cloud Run pieces (a console service and a tribunal job), backed by Firestore, Cloud Storage, Secret Manager, and Cloud Build/Artifact Registry for the pipeline. Veo 3.1 now runs two ways: pre-generated for the demo cases, and generated live (capped at ten runs total) for whoever's on a judge session, same label on both: "AI-generated illustration - not evidence." card, never mixed in with real evidence. The public demo also protects itself: daily per-visitor limits, a running dollar ceiling, and a judge key that skips both, so the budget can't run out before the judges get their turn.
I ran the build as a small multi-agent workflow rather than writing it line by line: several coding agents, each locked to a strict set of files, working concurrently, with an integration pass at the end of every round. Closer to running a small remote team for a weekend than prompting a single chatbot.
Challenges I ran into
The first challenge was just the calendar. I found out about the hackathon the Thursday night before, leaving basically one weekend to build it (the repo itself only exists from Saturday morning). I also missed the $150 GCP credit giveaway, so the whole thing ran on spare cloud credit I already had. A real budget, instead of a comfortable one, forced me to be more careful about what actually got built, and to think it through properly before spending anything.
The nastiest bug of the whole build was the deployed tribunal job silently losing every resident's uploaded evidence. The console's upload handling talked to a shared, durable store from early on. The job's own entrypoint didn't: it built a fresh, empty, in-process store every time it ran, because that code predated the durable store. Every real deployed run had been quietly discarding photos and documents, degrading to "no evidence provided," with nothing in any log pointing at why. No unit test caught it. I only found it by forcing a real, deployed run and reading the reviewer's verdict with my own eyes.
Right after that came a stranger one: the first real tribunal run crashed four stack frames deep inside ADK's own model wrapper with ValueError: No API key was provided, nowhere near any code I'd written. Turned out ADK's Agent() class builds its own internal client, separate from the rest of the app's, and reads Vertex-vs-public-API routing from environment variables nothing in the repo had set. It silently fell back to the public Gemini API and failed for lack of a key I never intended to need. Fixed with three os.environ.setdefault() calls at import time, after an afternoon spent tracing logic that wasn't even mine.
Then there was the one that only showed up once I stopped testing against my own fixture. My hand-curated demo case used exactly two documents, picked by a human, because that's the story I wanted to tell. The moment I pointed the pipeline at the one real DA on public exhibition, its register came back with twelve documents in unhelpful order. The overlay code grabbed the first plan-shaped-adjacent one off that list, which turned out to be a Resident Notification Letter, not a drawing. It drew "window" and "door" boxes on a paragraph of council correspondence, and the same bug had the submission text citing that letter as if it were a site plan. No fixture had ever contained a letter like that, so no test could have caught it. Fixed the same night by ranking documents plan-shaped-first, then re-verified live against a fresh real case.
Picking the right document turned out to be only half of it, though. Not every evidence document has a front view of the building. One real case's plan is a site plan: a straight-down view from above, not an elevation, and asking it for window and door boxes made no sense, since neither is visible from overhead. That's exactly why the image-reading agent got split into two steps: the first call only describes what the drawing contains and where (site plan, elevation, floor plan, whatever's on it), and only then does a second call draw the boxes, using the vocabulary the first call handed it. Before the split, boxes were being drawn where they had no business being: elevation words like "window" and "door" on a drawing that was never going to have either.
I also had a quieter one: a fallback that worked perfectly and simply never appeared. The Street View fallback (shown when a resident has no photo of their own) genuinely fetched a real image every time. What it never did was tell the console there was anything to render, so every no-photo case's Evidence tab silently rendered empty. Nothing crashed. Nothing logged an error. I only caught it by loading a real case page in a browser and noticing the section was blank.
Getting a generated video that was actually useful took its own round of tryouts on the model. Plain text prompting alone wasn't enough to ground it in the real evidence, so the next step was conditioning the model on the case's own elevation drawing instead. From there it was three rounds: generate a clip, watch it frame by frame against a written rubric, fix what's wrong, generate again. A morphing sun icon and black letterbox bars on the first attempt, an unlabelled highlight and windows turning into flat solid blocks under shadow on the second, until the third clip fixed all of it and cleared every axis. All three rounds together cost under five dollars.
Accomplishments that I'm proud of
What I'm proudest of, underneath the specific stuff below, is simpler than any of it: building this in one weekend, on a small budget, by orchestrating AI agents rather than typing every line myself, the way DISCLOSURE.md describes it. It held up under a real deadline, not just as an idea.
I got this validated against a real, currently-exhibited development application, not just a fixture: PAN-661190 / DA2026/0359 at Georges River Council. A full tribunal run against it costs about three-tenths of one cent, and the overlay it draws lands on the DA's actual Site Plan drawing, with the correct site-plan vocabulary (site boundary, building footprint, existing pool, easement, north arrow). That's because I benchmarked the pricier grounding model against the cheaper one on the real fixture instead of assuming bigger meant better, and the pricier one put every box on the wrong drawing. The cheaper one didn't.
I'm also proud of the refusal card. I borrowed GOV.UK's instinct that an outcome which isn't good news should still read as calm and legitimate, not alarming. A refused ground renders as a plain, warm-toned panel, not a red error banner. Telling a resident "this doesn't qualify, here's exactly why, here's what would work instead" is doing right by them, and I wanted the UI to actually say that.
The single biggest jump in how the product feels happened in one overnight round, after I used my own deployed app like a resident would and came back with a binding, item-by-item spec: kill the docket board as the front door, stop the interview looking like a debug log, make the overlay mean something. I took my own notes as seriously as a client's, and shipped the whole list in one pass.
What I learned
A green test suite doesn't tell you the whole story. Every bug described above was invisible to a fully offline suite that grew from a few hundred tests to over 600 across the build, and every one only surfaced against a genuinely deployed run. I ended up treating "run it for real, watch it fail, fix it, re-run" as a standing discipline, not a one-off QA pass at the end. A mock is excellent at proving your own logic is correct and completely blind to two processes disagreeing about where the data actually lives.
Even "run it for real" has a ceiling. My one polished demo fixture was still a story I wrote by hand, and only pointing the pipeline at a real council's uncurated document list surfaced the bug that mattered most. And documentation drifts fast the moment several agents work on the same codebase concurrently. A doc caught overclaiming once makes you stop trusting the rest of it, so the fix wasn't "write better docs," it was treating every sentence as a claim that has to survive being checked against the source.
One smaller thing worth writing down: turning a model's thinking mode off is genuinely valuable when you want it to behave in a very specific way and not hallucinate something clever instead. For the resident interview chat and the reviewer passes, thinking off was striking on two fronts: how much cheaper it got, and how close it lands to deterministic without ever quite getting there. Not identical run to run, close enough to reason about.
What's next
Mostly it's about raising the floor on quality: sharper output from the reviewers and the other agents, a better generated video, evidence drawings that read even more precisely. None of this costs much to run, a full tribunal run is a fraction of a cent, and that's real value on its own: a quick, cheap way to make the process fairer for whoever's about to get a new development next door.
Specifically, live ingest still only speaks to one council's register (Georges River, the only one with a currently-exhibited DA I could test against), so widening it to more NSW councils on the same eTrack-family tracker is the obvious next step. There's also a soft finding flagged rather than fixed under time pressure: the clerk model sometimes classifies a property-value objection as overshadowing when the resident's wording leans on "bulky"/"overbearing" language, weaker refusal wording than it should be.
But the big one is feedback from actual users. Everything in Setback so far comes from publicly available information and my own reading of the law, and my own experience of receiving these letters, not from a resident telling me, in their own words, what actually bothers them about a development next door or how they'd naturally report it. Asking real people directly would surface more than reading the register myself ever could.
Built With
- cloud-run
- cloud-storage
- fastapi
- firestore
- gemini
- gemma
- google-adk
- google-maps
- python
- secret-manager
- veo
- vertex-ai
Log in or sign up for Devpost to join the conversation.