Inspiration

Food rescue coordination is full of context that never makes it into a spreadsheet: a kitchen just quietly cut its capacity, a delivery is already promised to someone, an allergen exclusion can't be crossed no matter how good the match looks. Most agent demos skip that entirely, they either automate the whole decision or ignore the human's state completely. I wanted the agent working inside the same workspace the coordinator is looking at, seeing the same constraints, not a separate copy of the plan living in a chat window.

What it does

Second Serve matches surplus meal offers from six fictional donors to five fictional kitchens. A browser agent can read the live workspace, run a read-only what-if plan, ask exactly why a specific donor-kitchen pair got rejected, update a kitchen's declared capacity, and stage a proposal onto the board. The coordinator reviews it, approves it, and can protect specific commitments so they survive whatever happens next.

The demo scenario starts at 480 portions matched. Protect the 120-portion Green Kitchen to Hope Community commitment, drop Open Table's capacity to 30, and ask the agent to replan. It comes back with 410 portions matched, the protected 120 untouched, and an explicit statement of the 70-portion shortfall instead of quietly burying it. No deliveries actually go out, this is a planning surface, not dispatch.

How we built it

The app is React and TypeScript on Vite/Vinext. The solver in lib/rescue.ts is a deterministic integral min-cost max-flow implementation over donor supply, kitchen capacity, diet compatibility, allergen exclusions, travel time, donor windows, and closing times. Protected quantities get reserved in the flow graph before the rest of the plan is solved, so a better-looking route can't quietly bump them.

The WebMCP layer is six imperative tools registered through document.modelContext.registerTool(), with a navigator.modelContext fallback, each carrying a JSON Schema, read-only annotations, and real runtime validation. The UI and the agent read and write the same store, lib/use-rescue.ts is what binds it into React. Every mutating call takes an expected_revision, so if the coordinator changed something in between, the call fails cleanly instead of overwriting it. Tools only return once the shared state has flushed, so the agent's next read is never stale.

Approving a plan and unlocking a protected commitment are kept as UI-only actions on purpose. Tool annotations are a hint to the agent, not an actual security boundary, and I didn't want the app pretending otherwise.

Challenges we ran into

Holding protected quantities exact while still replanning everything else was the hard part. Reserve too aggressively in the flow graph and flexible offers get stranded behind whatever got assigned first. Reserve too loosely and "protected" stops meaning anything. Routing protected flow through the residual graph before anything else, then letting the remaining augmenting paths work around it, is what actually fixed this.

The second problem was staleness. An agent call that lands after the coordinator already changed the board shouldn't win by default. The revision check catches that part easily, getting the resulting error message to actually explain what changed took more iteration than the check itself did. Making unmet demand visible instead of just silently matching fewer portions needed the same level of care.

Accomplishments that we're proud of

The tools are genuinely browser-discoverable and callable, not a chat interface faking API calls behind the scenes. There's no separate backend holding a second copy of the plan, no screen-scraping, no simulated model. It runs with no account, no API key, and no server ever seeing the dataset. Constraint handling, atomic revisions, and the human-approval path are all covered by automated tests, and the 480 to 410, 120 protected, 70 short numbers reproduce exactly every run.

What I learned

The interesting problem wasn't handing the agent more tools, it was giving it the right shape of state: a proposal it can inspect before anything commits, a revision number it can fail against cleanly, and read-only what-if calls that touch nothing. Once that existed, the real design work moved to the human side, making sure approval, protection, and unmet demand are always shown directly instead of something the coordinator has to infer.

What's next for Second Serve

Real travel estimates in place of the illustrative 0-100 coordinate grid, transport and driver availability as an actual constraint, and a pilot with a real rescue coordinator to find out where these assumptions break against real data. Multi-user persistence, dispatch, and food-safety verification are explicitly not built. This is a planning tool, and it should stay honest about that until those pieces actually exist.

Built With

Share this project:

Updates

Submission history