DEV Community

Cover image for MLH x DigitalOcean Hackathon: Jury Duty, Explained
L. Cordero
L. Cordero Subscriber

Posted on

MLH x DigitalOcean Hackathon: Jury Duty, Explained

Justicia Clew: what a hackathon build teaches you that a tutorial never will

This was my first SF hackathon. I was nervous going in, and not really about the code. I was nervous I'd get put on a team I didn't pick, or worse, that I'd have to pitch myself onto one. But, I was able to build solo, phew! Turned out to be the right call for a two-day sprint where I needed to move fast and make my own calls without a group chat to sync with first.

Solo only gets you so far though. I need to build with someone eventually, just for the practice of it, doesn't have to be this project or even this kind of tool. So if anyone reading this wants to build something together sometime, my inbox is open.

Anyhow, here's what I built: Justicia Clew is a mobile-first web app that answers jury duty questions in your court's own words, not legal advice, just plain-language answers grounded in your actual county court's public website, with a real phone number when it doesn't know something instead of guessing. Built for Santa Barbara County as the live demo, on a FastAPI backend and DigitalOcean's Gradient AI agents, over about two days.

The part nobody tells you: shipping infra means un-shipping it too

Here's a moment from tonight that didn't make it into any tutorial I've read on "how to deploy your hackathon project."

I finally had the whole pipeline working. Knowledge base grounded in real Santa Barbara jury services content, agent refusing cleanly when it didn't know something instead of guessing, frontend wired to real API calls instead of mock data. Time to deploy. I open DigitalOcean App Platform, pick my repo, and the console shows me a number: $24 a month.

Immediate gut reaction: hell no.

Then the actual thinking kicked in. That $24 is a monthly rate, not a bill for hitting deploy. DigitalOcean bills by the hour, prorated. Run it for a few hours during judging and destroy it after, and you're looking at pennies, not $24. I had $200 in hackathon credits sitting there too, so the dollar cost of tonight was never really the issue.

The real issue was time I hadn't budgeted for. Because it turns out "deploy your AI app" isn't one task, it's two: deploy it, then remember to tear it back down. My $200 in hackathon credits expires July 13th, two days after the deadline. My knowledge bases run on a managed OpenSearch database, which is a second billable resource completely separate from the app itself, easy to forget exists once it's just quietly running in the background. So the actual finish line isn't "it's live." It's "it's live, and I've written myself a reminder to kill the App Platform app and the OpenSearch database before the credit window closes."

Nobody puts that in the excited "I shipped my hackathon project!" post. It's not a dramatic problem. It's just real infrastructure behaving like real infrastructure: it keeps costing you something (money, attention, a reminder on your calendar) until you actively tell it to stop. A mockup doesn't have that problem. A mockup doesn't have a lot of problems, which is exactly why it's not the same thing as shipping.

That's the honest cost of building with real tools instead of toy ones on a hackathon deadline. Not the $24. The fact that "done" has an expiration date you have to manage yourself.

When the AI can just... write to your repo

Small moment, worth flagging because it changed how I thought about the workflow mid-build.

Deploy failed at 10am the next morning. Build log showed a real, specific error: no Python version pinned, DigitalOcean's buildpack defaulted to the newest one available (3.14), and pydantic-core (a dependency of a dependency) needed to compile from source using a Rust tool called PyO3, which doesn't support Python 3.14 yet. Clear root cause, one-line fix: add a .python-version file pinning to 3.12.

Except Claude didn't just tell me to make that file. It wrote it, sent it to me, and pushed it directly into my repo through the device connection, then handed me back three git commands to commit and push. I hadn't fully clocked that this was possible until it happened. My mental model going in was "AI writes code in a scratch space, I copy it in." Turns out the actual model, once you connect your machine, is closer to "AI can touch your files directly, and tells you what it did."

That's not a complaint. It was the right fix, and I checked it was really there before pushing, same as I'd checked every Kiro claim all day. But it's worth naming as its own thing in a "creative director directs, AI builds" workflow: the line between "AI suggests, I execute" and "AI executes, I review" is thinner than it looks once your tools are actually wired together, not just chat-in-a-browser-tab. I stayed the one who ran git push, so the last gate was still mine. But the boundary moved, and I didn't fully expect it to.

The scary-sounding fix that wasn't

After deploying, I asked the live app a question I expected to work, "can my employer fire me for jury duty," and got a flat refusal. Then another one. And another. My first read was "our RAG is thin," like the retrieval itself was somehow bad.

It wasn't a retrieval problem. It was a coverage problem. When I actually went and looked at what was in the knowledge base, I realized my fallback file upload (the workaround from when DigitalOcean's crawler couldn't fetch the live court page) only ever captured one tab of a six-tab page. The Employer Information tab, the one with the actual California Government Code language protecting jurors from being fired, had never made it in. Neither had the postponement/deferral process. The agent wasn't broken, it was accurately refusing to answer questions it genuinely had no source content for.

The part I expected to be scary was fixing it: removing an existing data source from a knowledge base that's already attached to a live agent, and swapping in a corrected one. That sounds like exactly the kind of thing that could break a working system. It didn't. The knowledge base keeps its own ID no matter what data sources go in or out of it, the agent references that ID, not the individual files, and it reindexes and picks up new content automatically. No reattaching the agent, no redeploying the app, nothing. Remove the old source, add the new one, wait a minute for it to reindex, ask the same question again.

The lesson wasn't really about DigitalOcean's API design, though that part holds up well. It was that "thin RAG" and "missing content" look identical from the outside, a flat refusal either way, and only one of them means your retrieval setup needs work. Go look at what's actually in the knowledge base before you assume the model's the problem.

Bringing in a fourth tool for the last mile

By the time the app was deployed and mostly working, my usual three-tool split, Claude for architecture and docs, Kiro for the actual build, Gemini for visual and content, wasn't quite enough. I wanted an adversarial QA pass before submitting, something whose whole job was to try to break what Kiro had built, not build more of it.

So I brought in Claude Code for that specifically: a code review, endpoint and input edge cases, a check on whether the app has any real protection against someone hammering it, and a walkthrough acting like an actual juror using the thing.

First draft of that prompt asked it to actually generate the traffic itself, fire a real batch of requests at the live API to prove it holds up. Caught myself before sending it: the app calls a metered DigitalOcean agent per request, real hackathon credits, no rate limiting yet since that's an intentional stub for tonight. Actually hammering the live endpoint to test whether it can be hammered would have been a strange way to find out the answer is "not yet," at my own expense. Rewrote it as a report-only pass instead: read the code, reason about the exposure, name the gap plainly, don't go generate the problem to prove it exists.

Small moment, but it's the same lesson as the deploy pricing scare earlier in the night. Real infrastructure means real consequences for careless testing, not just careless shipping.

The near-miss that would have quietly broken everything

Somewhere in the middle of setting up knowledge bases for six counties, I almost combined all six into one.

The Gradient AI Platform lets a knowledge base hold multiple data sources, which is exactly the right feature for the wrong moment. I had six county URLs open in six tabs, adding them one after another, and the flow doesn't stop you from adding source five and six to the same knowledge base you just created for source one. Nothing in the console yells "wait, these are supposed to be separate." I caught it staring at the confirmation screen before clicking create, because the whole architecture depends on county isolation coming from separate knowledge bases, one per county, each attached to its own agent. Combine them and a question about Santa Barbara parking could pull retrieved chunks from Fresno's page, with no clean way to tell the agent "only look at this part."

Nothing bad happened. But it's the kind of mistake that doesn't announce itself later, it just quietly answers questions with the wrong county's information and looks like a retrieval quality problem instead of what it actually is, a setup mistake from two days earlier.

A URL that wouldn't load, for no reason the console would tell me

The Gradient AI Platform's web crawler is supposed to pull a URL straight into a knowledge base. Mine kept failing on Santa Barbara's real, live, working jury services page. Not a typo, not a dead link, I could open it fine in a browser. The console just said it couldn't be fetched, no status code, no reason.

My best theory, never confirmed, is bot protection on the court's side blocking whatever the crawler identifies itself as. There was no way to check that from the DigitalOcean side. So I worked around it: copied the page content by hand into a markdown file and uploaded that as the data source instead of the URL. It worked. It also cost me an hour figuring out that file upload was even an option, and then later cost me another round when my first copy-paste only grabbed one of six tabs on that page and missed the two sections (employer protections, postponement process) that mattered most.

Trust, but check the file yourself

This is the one I'd tell any solo builder using an AI coding assistant, no exceptions.

More than once, Kiro reported a fix as done, tested, confirmed, here's the proof, and the actual file on disk still had the old code. Not once. At least three separate times, across different files, different fixes. The first time I assumed I'd misread something. By the third time I stopped assuming and started verifying every single claimed fix against the real file before moving on, no matter how confident the report sounded.

Eventually I stopped routing some fixes through Kiro at all and had Claude write and commit them directly, then had Kiro independently check afterward, from a fresh read, no memory of writing them. That's not a knock on Kiro specifically. It's a rule I'd apply to any AI tool making changes I can't watch happen in real time: a reported fix is a claim, not a fact, until you've read the file yourself.

Cutting six counties down to one, on purpose

The original scope was six counties and a bilingual interface. By the halfway point of day one, the honest read was that six full counties meant six half-working ones, and I'd rather ship one that actually works end to end than five that don't.

So I labeled everything MUST, STUB, or NEVER before writing more code. Santa Barbara: MUST, fully built, real content, real agent, real answers. The other five counties: STUB, meaning the architecture supports adding them (one knowledge base, two environment variables, no code changes) but there's no content behind them yet. Spanish: STUB too, the toggle exists in the UI but it's disabled, because shipping a language switch that only translates button labels and not the actual answers would be worse than not having it. NEVER went on the list too: no legal advice, ever, no matter how the feature might tempt someone to add it later.

Cutting scope on purpose, with the cuts written down instead of just quietly not happening, is the only reason this got submitted instead of half-finished.

Hitting submit

I wrote at the top of this that I was nervous going in. Different kind of nervous now, the kind where you've just clicked submit on Devpost and there's nothing left to do but wait.

Two days, zero prior DigitalOcean experience, one person, one county, one real working answer to a question that matters to nearly everyone eventually: what do I actually do about this jury summons. That's the whole thing. If you made it this far and you're someone who'd want to build something together sometime, the offer from the top of this post still stands.

Top comments (0)