Why I built this

Closing shift notes are the problem. Someone writes "fridge stayed warm" or "till's off by 400" and then... nothing. The note sits there. Maybe someone reads it before opening tomorrow, maybe not. Nobody's actually assigned to check. I started calling that gap the missing hour and it's the whole reason Relay exists.

What it does

Relay is a shift handoff tool, built around cafés with more than one location. A handoff doesn't get marked "safe" unless every real risk — fridge, cash, stock, whatever — has an owner, a deadline, and a backup plan attached to it. An agent can go read the actual shift notes, figure out on its own if something in there is a real risk, even draft a plan for who should own it and by when. But it can't approve any of that. I didn't want to just tell the agent not to approve things — I wanted it to actually not be able to. So there's no tool it can call that marks anything as verified. That has to happen from a person clicking a button in the app.

I tested this the annoying way, by trying to break it. Asked an agent flat out to confirm a handoff was approved before I'd touched anything. It said no. Checked the audit log, saw nothing there, told me so. Didn't make something up to be helpful. That's when I knew the thing actually worked, not just looked like it worked in a demo.

How I built it

React, TypeScript, Vite. No backend. The agent talks to the app through one thing: document.modelContext.registerTool, the browser-native WebMCP API. Eight tools — six just read data (shift context, which risks are unowned, the raw notes, the audit trail, a summary across both locations), two that write, and even those two only ever create a draft. Never a done thing.

The bug I almost shipped

My tests all passed. Then I ran it in actual Chrome with WebMCP turned on instead of my own mocked version, and registration just... failed. "Illegal invocation." Took me a minute to get why — I'd grabbed registerTool off the object and called it on its own, detached, which a real browser method won't allow but a plain JS mock in a test doesn't care about. So my whole test suite had been quietly passing against something that behaved nothing like the real API. Fixed it with a .bind(). Then went and broke my own test stub on purpose so it can't lie to me like that again.

What's next

Real backend instead of seeded data, actual roles for owner/manager/staff, real POS hookups. The one rule that doesn't move: the agent can find stuff and suggest fixes. A person still has to say yes.

Built With

Share this project:

Updates

Submission history