Inspiration
In New York, a trip that's "22 minutes" on the map can easily take 35. A delayed train, a long platform wait or a missed transfer is enough to make you late for a job interview, a doctor's appointment or a shift, and you usually find out once you're already underground. Map apps plan around the schedule. Riders make up the difference by guessing and leaving early "just in case." I wanted something closer to a friend who knows the subway: it knows where you need to be, watches how trains are actually running, and tells you when it's time to leave.
What it does
YoHo is a transit agent for New York City subway riders that plans trips around their Google Calendar.
- Ask in plain English. "When should I leave to get to 41 E 56th St by 7 PM?" YoHo gives you a leave-by time and step-by-step directions, and draws the route on a map.
- Knows where you'll be. After you sign in with Google, YoHo reads your calendar (read-only). Trips start from your current event's location or from your home, so you don't have to type where you are.
- Emails you before you're late. When an event with a location is coming up, YoHo plans the trip with live train data and emails you a "leave by" alert with directions.
- Plans for a realistic bad day. A delay model predicts 90th-percentile ride and transfer times from live MTA feeds, so YoHo's leave-by time holds up even when the subway doesn't run smoothly.
- Follows your preferences. "Avoid the L," "no buses," trips with several stops, and service alerts and station status along your route.
How I built it
- Agent: The agent runs on the Strands Agents SDK with Gemma 4 on Amazon Bedrock. It has more than a dozen tools: geocoding and place search, routing,
leave_by, route from here, from home, or to a calendar event, trips with several stops, station status, and a schedule-vs-live comparison. Tools that touch a user's data are tied to the signed-in user in code, so the model never sees or passes a user ID and can't be talked into reading another person's data. - Routing: A transit graph built from MTA GTFS static data (subway and all five boroughs' buses), searched with Dijkstra. Transfers between subway and bus include the walk, the time to exit the station, and the expected wait for the next vehicle.
- Live delays: A snapshot service polls MTA GTFS-realtime feeds and builds a rolling feature window. A Graph WaveNet model trained on historical subway trips (subwaydata.nyc) predicts 90th-percentile travel time for every subway ride and transfer. Those predictions become the weights the router uses.
- Web app: Flask behind gunicorn, with Google sign-in, a chat page and a map that draws the route.
- Alerts: Google Calendar notifies a webhook when an event changes. A scheduler process checks every minute for events coming up, plans the trip, and sends the email through Amazon SNS.
- Storage: Firestore, with home addresses, event locations and refresh tokens encrypted by the app before they're stored. Each user has a weekly token budget for chat.
Challenges I ran into
- Small models and tool calling. A small local model would rewrite directions badly (it turned every stop into a "transfer"), stop partway through requests with several steps, and make up answers when a tool failed. I moved the directions text into code, so the model only gets a summary, and switched to a larger model on Bedrock.
- Matching training to live data. A model is only as good as its inputs at prediction time. Features had to be computed the same way, on the same clock, from historical archives during training and from live feeds in production. I wrote a document tracking this and fixed several mismatches.
- Routes that zigzagged. Once subway waits were priced from live predictions, routes started hopping onto buses, because switching to a bus only cost the walk. I added realistic station-exit time and wait costs for transfers in both directions.
- Geocoding in NYC. Landmark names and ambiguous addresses (the same street address exists in more than one borough) often resolved to the wrong place.
- Calendar access in production. Reading calendars is a sensitive Google scope, which limits who can sign in to an unverified app, and calendar change notifications require a verified public HTTPS domain.
Accomplishments that I'm proud of
- A complete product rather than a proof of concept: sign in, set home, chat, see the route on a map, and get an email alert before a real calendar event.
- Routing weighted by a live graph neural network, instead of the static schedule.
- An agent that stays proactive: calendar changes flow through a webhook and a scheduler to a "leave now" email without the rider asking.
- Privacy built into the design: tools bound to the user, encryption for sensitive fields, and a model that never handles user IDs.
What I learned
- Clear, specific tools matter as much as the model. Tools that return short summaries and readable error messages made even small models far more reliable.
- Planning for the 90th percentile instead of the average changes what a "good" route is, and it's the right goal when being late is expensive.
- Training/serving skew is the easiest way to quietly ruin a model that looked good in testing.
- In transit data, the hard parts are the edges of the system: transfers, service changes, and messy addresses.
What's next for YoHo
- Live bus arrivals from MTA Bus Time, replacing the scheduled bus waits
- Deploying the agent on Amazon Bedrock AgentCore
- SMS and branded email (Amazon SES) for alerts
- Letting riders choose when an address matches more than one place
- Adding transit systems beyond NYC, such as LIRR, Metro-North, PATH and NJ Transit

Log in or sign up for Devpost to join the conversation.