Inspiration
This came from a problem I experience in my day-to-day work. We regularly receive facilities requests from students, staff, faculty, and other teams, things like broken door locks, lighting issues, plumbing problems, or other maintenance needs. The challenge is that these requests don't always come to the same person. Some come to me, while others go to coworkers or different teams. Since we may not know someone else has already reported the issue, the same problem can be submitted multiple times from different accounts, creating different work-order IDs even when the facilities team is already working on it. This creates duplicate requests, inconsistent tracking, and unnecessary coordination. That real experience became the inspiration for Relay.
What we built
We asked a simple question: what if the system could understand an issue and check whether it had already been reported before creating another work order?
With Relay, a person describes a facilities problem in natural language and can attach a photo. The system understands the issue, identifies its category and urgency, checks for similar active incidents, and routes it to the appropriate facilities team. If the same issue has already been reported, the new report connects to the existing incident instead of creating another work order. If several people keep reporting the same unresolved issue, that becomes a signal the problem may need more attention, and the system escalates it.
If it genuinely cannot tell whether a report matches an existing incident, it says so and asks a person rather than guessing. That's live in the system right now: two card readers broke at two different library entrances, correctly opened as two separate incidents. A third report came in from someone who wasn't sure which entrance they meant. The system looked at both open incidents and refused to guess, explaining exactly why a false match was too risky, then flagged it for a person to check badge logs. That's the failure this whole project exists to prevent: guessing wrong doesn't just create a duplicate, it hides a second real problem behind someone else's ticket.
How we built it
Relay is an agentic workflow that follows a facilities request from reporting to resolution, six stages: triage, shortlist, deduplicate, prioritise, route, dispatch.
- Gemini 3.5 Flash, via Vertex AI, handles triage (what the report is about) and deduplication (does this match an existing incident), the two judgment calls a fixed rule can't make well.
- Google ADK. After the pipeline runs, an Incident Coordinator, an LlmAgent driven by an InMemoryRunner, looks at what happened and decides if anything more is needed: notify a team, ask the reporter a clarifying question, place an ambiguous report itself, or leave it for a human. These are real actions, not suggestions, each one writes to the database and gets logged with its reasoning.
- Firestore (Native mode) holds reports, incidents, work orders, campus configuration, and a full decision log.
- Cloud Run hosts the backend.
- Cloud Storage stores report photos, which also get passed to Gemini so triage can read the picture, not just the text.
- FastAPI on Python 3.12, with strict Pydantic validation so a malformed model response fails at the boundary instead of reaching the database.
- React, TypeScript, Vite for the operations dashboard.
- Pytest, 48 tests over the deterministic logic: priority, escalation, status transitions, candidate ranking.
Priority and routing are deliberately rule-based rather than model-decided. That's an auditability choice: the same evidence should always produce the same priority and the same team, reproducibly, months later, without re-running anything. Gemini only handles the two decisions a rule genuinely can't: what something is, and whether two reports describe the same thing.
Challenges we ran into
Duplicate detection was the hardest part, because people describe the same problem in very different ways, and two similar-sounding reports can actually be different issues. We had to balance preventing unnecessary duplicate work orders against not incorrectly merging unrelated problems. That balance is why a third outcome exists beyond "same" or "different": the system can decline to decide and hand it to a person instead.
A harder challenge was deciding how much responsibility to give the agent. We learned automation shouldn't mean removing humans from every decision, for uncertain cases, keeping a human in the loop makes the system more trustworthy, not less capable.
We also found a real engineering bug worth naming: our coordinator was silently failing. It caught every exception and returned before recording a decision, so a broken agent and a correctly idle one looked identical in the log, it was missing from the trail on 19 of 26 pipeline runs with no error anywhere. We fixed it so failure is now a recorded outcome instead of silence, and the very next run revealed the real cause: a dependency declared in requirements.txt that had never been installed in the environment actually running the server. The lesson: an agent's silence has to be as observable as its actions, or you can't tell "decided to do nothing" apart from "crashed."
Accomplishments that we're proud of
An agent that takes real, recorded, state-changing action, not narration. It notifies teams, asks reporters for missing details, places ambiguous reports itself, or deliberately escalates to a human when it shouldn't guess, and every one of those decisions is written down with the reasoning behind it.
What we learned
Agentic AI is especially useful for the coordination problems hidden inside everyday workflows. The goal isn't to replace facilities teams, it's to reduce repetitive work, prevent unnecessary duplication, and get the right information to the right team faster. Most importantly, we learned a useful agent shouldn't only know what to do, it should recognize when it doesn't have enough information to decide safely, and say so.
One issue can have many reporters. It shouldn't need many work orders.
What's next for Relay
Testing against messier, real-world report language beyond our synthetic data, connecting Cloud Scheduler so escalation runs unattended, and expanding what the coordinator is trusted to do now that its actions are proven to actually work.
Built With
- cloud-run
- cloud-storage
- fastapi
- firestore
- gemini
- google-adk
- pydantic
- pytest
- python
- react
- typescript
- vertex-ai
- vite
Log in or sign up for Devpost to join the conversation.