About the project

Inspiration

A community kitchen runs on volunteers, and volunteers cancel. When someone drops out four hours before a shift, a coordinator starts phoning down a list: first name, no answer; second name, can't; third name, yes. The work isn't hard. It's relentless, it arrives at the worst times, and it always lands on the same person.

It has a quieter failure too. Whoever is phoning reaches for the reliable names first, so the most dependable volunteers get asked the most and burn out the fastest. I wanted an agent that takes the phoning off the coordinator without inheriting that habit.

What it does

Fill In watches the roster and backfills cancelled shifts on its own:

  • It notices an open shift on its next wake-up. Nobody has to tell it.
  • It ranks who could cover it: the right certificate, available at that hour, and weighted towards the people who have carried the least recently.
  • It asks one person, then waits out their 20-minute window.
  • On a decline or a timeout it moves to the next name. On an accept it books them and stops.
  • It escalates to the coordinator — once, with the context to act — only when a human decision genuinely exists: five people asked with no yes, a shift starting within two hours, nobody qualified, or anything ambiguous.

The coordinator's dashboard has two columns. Handled without you shows every shift the agent is working and the trail it took down the roster. Waiting for you holds escalations, and its normal state is empty.

Who it's for

The volunteer coordinator at a community kitchen, food bank or shelter. Usually one person, often a volunteer themselves, with no budget for scheduling software and no appetite for another app to check. Fill In is built to not be opened.

How I built it

Fill In is a Strands Agents agent running on Amazon Nova 2 Lite through Amazon Bedrock. The same code runs Gemini or Claude by changing one environment variable. Nothing about the rules depends on which model is reasoning, because the rules live in the tools.

  • One step per wake-up. A scheduler calls tick(), which gives each open shift a fresh Agent and asks it for the single next step: check replies, book an acceptance, ask the next person, wait, or escalate. The agent has no loop of its own and no memory between wake-ups; all state lives in SQLite. In production the scheduler maps directly onto EventBridge calling a Lambda.
  • Six tools, with the rules inside them. get_shift, rank_candidates, send_ask, check_replies, confirm_and_book and escalate_to_human. Every hard rule is a return statement in tool code rather than a sentence in the prompt. send_ask refuses an uncertified volunteer, a second person while someone is still deciding, anyone but the fairest remaining candidate, a sixth ask, and any ask inside the two-hour window. The model can be confused or simply wrong and still cannot break them.
  • One channel to a human. escalate_to_human is the only path that reaches a person, and it attaches who could still cover the shift — fairest first, certificate checked — looked up from the database rather than written by the model, so every escalation is actionable. There is no fallback where the agent mentions a problem in its reply and hopes somebody reads it.
  • Fairness scoring. Recent shifts and recent asks push a volunteer down the ranking, and anyone asked six or more times in 30 days goes to the back of the queue.
  • A Flask dashboard reading the audit log and the escalations, with a filter, a CSV export of the whole audit trail for reporting, and a manual wake for demos.

Built With

Share this project:

Updates

Submission history