Inspiration

Every friend group has a case file. For example, the housemate who ate the labelled leftovers, the one who still owes $3 from a taxi in March, or the chair that was clearly being saved. These disputes are too small for any real venue and too annoying to drop, so they get relitigated forever in the group chat.

We thought the joke would be funnier if the pettiness were treated with total ceremony. Instead of a chatbot that says "you're right, that was rude", we could have a full court with robes, a bench, a cited precedent, and a judge who is visibly disappointed in both of you.

What it does

You start by filing a grievance. The defence can submit a rebuttal, and either side can attach exhibits as evidence. Since half of these arguments start verbally anyway, you can also record your statement instead of typing it. Once both sides have had their say, the bench rules.

There are three judges to choose from. Bartholomew Grimsby is glacially formal and has never once been amused. Delphine Vex rules by vibe, with precedents that become less plausible the longer she speaks. Ruth Calloway scolds both parties for wasting the court's time before promptly ruling from the hip.

The case is then staged as a full courtroom hearing. The plaintiff gives their statement, the defendant responds, exhibits are presented, and the judge delivers the opinion aloud in their own voice.

Afterwards, the court generates a PDF order containing the citation, reasoning, ruling and damages, along with a QR code linking back to the case. That is the part people actually send to each other.

If you lose, you can appeal. The Lord Chief Justice hears the case, and he is worse.

How we built it

The judge is an LLM constrained by a Zod schema. Every ruling has to return four things: a made-up precedent with a year, two sentences of analysis capped at 55 words, the ruling in one sentence, and the damages. The damages are usually part money and part absurdly specific obligation with a deadline.

The judge's personality lives in the system prompt, while the structure and limits live in the schema. That separation turned out to be the key. We can change the judge completely without changing the shape of the output.

The court runs on OpenAI GPT-4o with an Anthropic fallback, selected at runtime depending on which API key is available. The model can also inspect uploaded exhibit images directly, with up to four exhibits per case.

ElevenLabs handles the voices. Its Flash model speaks the ruling in the judge's voice and each party's statement in theirs. Recorded testimony goes the other direction through Scribe, which transcribes it back into the written claim.

The courtroom itself is built with React Three Fiber using an orthographic camera, four fixed camera shots and three lighting presets. Eighteen Kenney blocky citizens share a single six-node rig, which means one set of animations can be reused across any cast of characters we place in the room. The frame loop only runs on demand, so when nothing is moving, the courtroom is not continuously rendering.

The hearing is not a hard-coded script either. It is a sequence of beats with durations generated from the actual case content. A small stage machine walks through them in order, from the gavel to the claim, rebuttal, exhibits, deliberation, opinion and adjournment.

Captions, camera angles, animations and speech bubbles all read from the current beat. Adding another stage to the hearing is therefore just another entry in the sequence rather than another block of orchestration logic.

Storage is handled by Upstash Redis over REST. Each case lives under a single key, the docket is maintained using a sorted set, and generated audio is split into chunks behind a versioned manifest.

Challenges we ran into

Our first version timed every caption using word count. That worked until we started using real recorded audio. Once speech speed varied, the judge's voice and the captions could drift apart within a couple of lines.

We now measure the actual duration of each audio clip when its metadata loads and scale the captions inside that block proportionally. That keeps every caption within the portion of audio it belongs to. Skipping a line also seeks directly through the audio instead of simply advancing an internal timer.

Audio created another problem once it became large enough. A sixty-second ruling encoded as base64 does not fit comfortably inside a single Redis value, so we split recordings into numbered chunks stored behind a manifest.

Before reading any chunks, we validate that manifest so corrupted metadata cannot trigger an unbounded fetch loop. Versioning the audio keys also solved another problem: when a case is appealed, the new ruling can be stored without overwriting the original, leaving both recordings available in the case history.

The challenge that bothered us most was the rebuttal. If nobody submitted a defence, the model would sometimes helpfully invent one for the defendant. For a product centred around deciding disputes fairly, that was a fairly serious problem.

The court now refuses to invent arguments for either side. If no rebuttal was filed, the judge explicitly says so on the record. We also added a test that fails if any defendant dialogue appears in a case where none was submitted.

Finally, the courtroom is the centrepiece of the project, but we did not want it to become a requirement for using it. If autoplay is blocked or audio is slow to load, the ruling still continues in writing. If the user prefers reduced motion, the scene remains still. If WebGL is unavailable entirely, the case falls back to a readable written record with the option to retry the courtroom.

Accomplishments that we're proud of

The biggest thing for us is that the hearing works end to end with real case content. Nothing happening inside the courtroom is a canned demo.

The words spoken by the plaintiff are the words they actually filed. The defendant's response is the one they submitted. The exhibits shown in the courtroom are the same images the judge analysed. The ruling being spoken aloud is the same ruling that appears in the final PDF.

The appeal system is probably our favourite part. Instead of overwriting the first result, an appeal archives the standing verdict into the case history, seats a different judge with a different voice, and leaves the original recording playable. You can listen to both decisions back to back and hear the court change its mind about you.

We are also proud of how lightweight the courtroom ended up being. Eighteen characters share one rig, the renderer only draws when something changes, and the entire experience degrades gracefully into a readable case record on devices that cannot render the 3D scene at all.

And it is genuinely tested. Forty-two unit tests cover the hearing beat timing, caption retiming, audio chunking and, importantly, the rule that the court never invents a rebuttal.

What we learned

The schema ended up doing more work than the prompt.

Once we fixed the structure of a ruling and imposed strict word limits, we could swap between very different judge personalities while keeping the result predictable enough for the rest of the application to use. We expected most of that consistency to come from prompt engineering, but the schema turned out to matter much more.

We also learned that a large part of making something feel alive has very little to do with visual complexity. The same few character animations can feel like a real hearing or a broken toy depending almost entirely on whether the audio, captions and camera cuts agree with each other to within a fraction of a second.

And the funniest part of the project is probably the part we wrote the least of. A perfectly formatted legal precedent, applied with complete seriousness to a stolen sandwich, usually does not need another punchline.

What's next for Small Claims Court

Multi-party cases, a jury drawn from the group chat, and precedent that accumulates over time, so today's ruling about the missing charger can be cited against you in March.

Built With

Share this project:

Updates

Submission history