Inspiration
Two buses arrive together, then nothing comes for twenty minutes. Every Boston rider knows that feeling. It's called "bus bunching": one late bus picks up extra riders at every stop, slows down even more, and the bus behind it catches up. Dispatchers can fix it by holding a bus for a minute or two, but only if they see it in time.
I wanted two things: an app that catches bunching the moment it happens, and a way of running that app where AI agents do the day-to-day work of building, shipping and repairing it, inside rules they cannot skip.
What it does
Headway reads the MBTA's live data and watches every bus route in Boston.
- Live city map: every route and every bus. Bunched buses turn red and get a ring. The 9 busiest routes refresh every 10 seconds, all others every minute.
- Hold advice for dispatchers: two buses on the same route count as bunched when they are due at the same stop less than 2 minutes apart. For each bunched group, Headway says which bus to hold and for how long, for example "Hold bus 1853 here for about 1 min".
- Trip planner for riders: direct and one-transfer bus trips from live arrival predictions, longer-walk options, walking when it's faster, and the nearest subway when no bus works. Start from a stop, an address, your location or a point on the map.
- Alerts for your trip only: the map focuses on your routes, highlights the buses you can still catch, and warns you when your bus is bunched and the next one is a long wait.
How I built it
Headway is a Python FastAPI service with a plain JavaScript and Leaflet dashboard, running on Google Cloud Run.
The heart of the project is how it's run. People write GitLab issues. Two GitLab Duo agents do the work: "bus-crew" posts a plan, writes the code and its tests, and opens a merge request; "bus-auditor" writes an audit record for every shipped change. A 9-stage pipeline carries every change:
- test: unit tests, SAST, secret detection, and a tripwire for unreviewed pushes to
main - review: a fail-closed security gate, and auto-merge for agent work, refused for protected files like the pipeline and agent settings
- package: one container image
- staging: that image on a separate Cloud Run copy
- validate: 5 checks that use staging like a visitor would
- deploy: the same image to production, with keyless Workload Identity Federation
- release: a versioned GitLab release
- govern: failed agent work is sent back to bus-crew with the errors (at most 2 tries), and every shipped change is audited
- monitor: every 30 minutes the live app is checked, and a failure opens an issue for bus-crew
Tonight, an issue about the map looking empty at night went from issue to plan, merge request, staging, production and audit in about 9 minutes, with no human action after I filed it.
I also built and refined much of the dashboard myself, with help from an AI coding assistant, testing every change offline before committing. My own commits go through the same tests, staging checks and tripwire as the agents' work.
Challenges I ran into
- Honest self-healing: The first time monitoring fired (issue #9), the agents shipped a change in about 6 minutes, but the cause was a brief network failure, not a bug. The agent said so itself and flagged a real weakness in the monitoring script, which it correctly left to a human because that file is protected. I added retries so a single dropped connection no longer raises an alarm.
- Scans weren't really required. An audit (issue #10) noticed that GitLab's merge check only looked at a shorter merge-request pipeline, so the security scans weren't actually required before merging. I switched to full branch pipelines so the scans and gate always count.
- Agents fixing their own work: GitLab's built-in pipeline-fix flow only starts when a person clicks a button, so it can't be part of a hands-off loop. I built my own fix-request job and tested it with a planted failing test (issue #11): bus-crew rebuilt the feature from the error report, and it shipped as v1.0.247.
- Limited permissions: In the hackathon group I'm not a Maintainer, so I couldn't protect
mainor use a project bot token. The tripwire and a narrowly scoped, expiring token cover the gap, and GOVERNANCE.md lists what's still open. - Humans and agents editing the same file. Tonight my own change to the dashboard collided with an agent's merged change, and the agent's new test caught it before it reached production. The pipeline did its job against me.
- Respecting free data sources: MBTA rate limits and OpenStreetMap's one-request-per-second rule for address search shaped the design: batched requests, caching, and a slower refresh for less busy routes.
What I learned
- "The agent is told to" and "the agent cannot" are very different promises. I learned to label every safeguard as Enforced, Tested, Instructed or **Unverified, and to link proof for each.
- Agents that write their own tests can share blind spots with their code. Pipeline checks that the agent didn't write, like staging validation, matter most.
- An audit is only useful if it's honest. bus-auditor reports what actually happened, including when a human stepped in or a check was missing.
- Small, clearly written issues get the best agent work.
What's next
Show how full each bus is, add more cities with open transit data, let the self-heal agent close false alarms with an explanation instead of shipping a change, and protect main once the hackathon permissions allow it.
Built With
- css
- docker
- fastapi
- gitlab
- gitlab-ci
- gitlab-duo
- google-cloud-run
- html
- javascript
- leaflet.js
- mbta-api
- nominatim
- openstreetmap
- python
- workload-identity-federation
Log in or sign up for Devpost to join the conversation.