How I built LastLift for Graph Hacks: Boston's subway as a flow network in FalkorDB, certified max-flow, and AI agents that act only after a person says yes.
TL;DR
- 89 of the MBTA's 366 step-free platforms depend on a single lift. Lose that one lift and a wheelchair user can't reach the platform.
- LastLift finds them with max-flow in FalkorDB, certifies every result, and serves it to AI agents over MCP.
- The agents answer only from the graph, notice when their answers go out of date, and file real tickets only after a person approves.
If you use a wheelchair, a subway station isn't a building. It's a path: street, entrance, fare gates, a corridor, a lift (an elevator), a platform. Take away the lift and, for you, the platform is gone. The trains still run. The stairs are right there. You just can't get to them.
Transit agencies publish when a lift is out. What they don't publish is the question that matters before it breaks: which platforms depend on this one lift, with no other way down?
So I asked it of Boston's MBTA, using nothing but its open data. The answer:
89 of the 366 step-free platforms, about one in four, hang on a single lift.
LastLift finds those lifts, proves the answer, and gives it to AI agents that can act on it.
๐ Live: lastlift.vercel.app ยท Code: github.com/murthyroshan/LastLift ยท Tickets the agents filed: lastlift-ops
๐ฌ The two-and-a-half-minute demo, in my own voice:
Why this is a graph problem, not a counting problem
The obvious approach is to count lifts: "this station has two lifts, so it's fine." It isn't. Two lifts can serve different platforms, or both sit on the same corridor. 74 of the 89 single-lift platforms are at stations with two or more lifts, so counting lifts would call them safe.
The right question is: how many independent step-free routes reach this platform? That's a max-flow problem.
- Turn every station into a graph from the MBTA's GTFS-Pathways data: entrances, gates, corridors, lifts and platforms. Stairs and escalators are kept in the model but never used for step-free routes.
- Give every lift an edge of capacity 1, and every walkway a very large capacity.
- The max-flow from the street to a platform is then exactly the number of lifts that must fail before it is cut off. A value of 1 means one lift stands between a rider and that platform.
Take Alewife. With lift 961 closed, lift 813 is the only step-free link to the Red Line platform. If 813 goes too, the max-flow drops to 0. LastLift sees that before the second failure happens.
Zoom out to the whole network and the graph shows something no single station can: 69 lifts can each cut off a platform on their own, but only 15 can cut a station off from the step-free network entirely. For the other 54, the network can re-route riders through other stations, with a median worst-case detour of 11 minutes.
FalkorDB does the work, and every answer is checked
The whole network lives in FalkorDB: stations, platforms, lifts and their connections. algo.maxFlow computes the redundancy for every platform and direction. Closing a lift retypes its edge (SF becomes SF_CLOSED), which takes it out of every algorithm at once. What-ifs run on a GRAPH.COPY, so the live state is never touched.
Each computation is one Cypher call per platform and direction:
MATCH (s:Node) WHERE s.uid IN $src WITH collect(s) AS S
MATCH (t:Node) WHERE t.uid IN $dst WITH S, collect(t) AS T
CALL algo.maxFlow({sourceNodes: S, targetNodes: T,
relationshipTypes: ['SF'], capacityProperty: 'cap'})
YIELD edges, edgeFlows, maxFlow
RETURN [e IN edges | e.eid], edgeFlows, maxFlow
Another Graph Hacks team, Chokepoint, found that algo.maxFlow on FalkorDB 4.22 can return a wrong value on some networks. I reproduced their case and made sure a wrong number can never reach a rider. Every result is certified before it is used: capacities respected, flow conserved, and no augmenting path left. If certification fails, the flow is re-solved exactly. A separate checker then verifies a proof: k lift-disjoint routes plus a k-lift cut that blocks them all.
On the real MBTA network, 734 of 734 max-flow results are certified, and NetworkX re-derives every headline number from the raw data.
It also generalizes. The same rules, with no agency-specific code, run on every transit feed in the Mobility Database that publishes open GTFS-Pathways: 14 feeds. Every result is solved exactly in Python and again with FalkorDB, and 2,884 of 2,884 agree. Run on the MBTA's feed, the generic rules find the same 89 single-lift platforms.
Checked against what actually happened
A model of the network is only useful if it matches reality, so I ran two checks.
Every reported closure since 2024, replayed. MassDOT and the MBTA publish their elevator alerts. Replaying every reported closure since January 2024 through today's graph: 251 platform-directions lost step-free access at least once, and 95% of those lost hours came down to a single lift. One archive glitch is excluded by a written rule: 104 closures that all "started" in the same minute on 22 February 2025 and appear nowhere in the official portal. The before-and-after numbers are published.
The operator's own words. I labelled MBTA's alert texts blind, without seeing the model's answers, then compared. Of the 8 live alerts both could assess, 6 agree and none disagree. Of 17 archived closures, 16 agree. The one that doesn't is listed, not hidden: at Medford/Tufts, the open data gives the platform a second lift route that MBTA's alert didn't offer. LastLift always tells riders to follow MBTA's advice.
An agent that answers only from the graph
Ask LastLift "What happens at Alewife if lift 813 also fails?" and a language model answers. But it can't make anything up:
- It reaches the data only through 13 read-only tools over MCP: resolve a place, platform redundancy, what-if closures on a copy, step-free routes, and more. The model never writes Cypher.
- Its final answer is a list of claims, each citing the tool call it rests on. Every claim is checked against what that call returned. If any claim is unsupported, the answer is rebuilt from a template that uses only the tool results.
Does the graph actually help? I pre-registered the evaluation: the same model and the same 30 questions, with and without the graph. On the 24 questions whose answer must name a lift or platform, the right ids appeared in 23 answers with the graph, and 5 without it. To be transparent: 8 of the 25 drafts the model wrote failed the check and were replaced, and 13 of the 30 final answers came from the template. Nothing ungrounded was shipped. The full table, failures and errata included, is in the repo.
Answers that know when they're out of date
Lift status changes by the hour. An answer that was true at 9am can be dangerous at 10am. So the agent's memory lives in the graph too:
(:Run)-[:CALLED]->(:ToolCall)-[:READ]->(:Lift) and (:Answer)-[:CITES]->(:ToolCall)
When a lift changes state, the same step marks every answer that read it as stale, and only those. In the chat you can watch it happen: an answer flips from "โ Still true" to "โ Out of date: lift 813 changed state after this answer", with an Ask again button. Agents should know when they're wrong, and here the graph tells them.
Agents that act, but only after a person says yes
Three operations agents handle every lift closure, with no language model in the decision path:
- Monitor reads the MBTA's live elevator alerts and opens an incident.
- Analyst runs certified max-flow and picks a playbook: access lost, preventive (another lift just became the last one), or no loss.
- Dispatcher proposes an action, but only if the graph hasn't changed since the analysis. A stale belief can't act.
Then a person approves, and only then is a real ticket filed. Two are live in lastlift-ops:
- Protect lift 891 at Downtown Crossing: it still works, but with 869 closed it's now the last lift for two platforms.
- Repair lift 885 at Revere Beach.
The token can touch nothing but that repository's issues, and filing is accepted only from the operator's own machine.
The architecture
Plug in your own agent
None of this is locked inside a website. LastLift is a public MCP server with tools, resources (stations, Mermaid drawings, lift dossiers, open incidents) and prompts. Any agent that speaks MCP can use the same graph and get the same certified answers:
{ "mcpServers": { "lastlift": { "url": "https://lastlift-api.onrender.com/mcp/" } } }
Making people feel it
Numbers alone don't land. So the site opens with a short film: a cutaway station whose lift shaft glows red. There's also a walkable 3D platform modeled on Alewife and built from the same data. You're in a wheelchair, lift 961 is already taped off, and lift 813 fails in front of you. The only way out is the train, one stop back to Davis. It's the same finding, but you feel it.
Honest limits
- This is unofficial decision support built on open data. The model can differ from conditions on the ground, so always check mbta.com/alerts/access.
- No rider or MBTA staff member has validated it yet; the agreement checks so far were labeled by me.
- On free hosting, an agent answer takes a median of about 8 seconds, mostly waiting on rate limits.
What I'd tell another builder
- Pick the graph question no table can answer. "Which lift is the last one?" is a min-cut, not a filter.
- Certify, don't trust. A provable answer beats a fast one, and a proof makes your agent's claims checkable.
- Give agents memory with an expiry date. Context that can go stale should say so.
- Let agents act, behind a human. Read-only by default, guarded by state, approved by a person.
Try it
- ๐ฌ Demo video (2:32): youtu.be/XLDNu4cGaN8
- ๐ Live site: lastlift.vercel.app
- ๐งญ Judge's tour (seven stops): lastlift.vercel.app/#/tour
- ๐ฎ 3D walkthrough: Alewife, lift 813 fails
- ๐ MCP endpoint:
https://lastlift-api.onrender.com/mcp/ - ๐ซ Tickets the agents filed: lastlift-ops issues
murthyroshan
/
LastLift
Find the last lift before it fails. Every MBTA station's step-free routes as a flow network in FalkorDB: certified max-flow redundancy, live lift closures, what-ifs and step-free reroutes, served to AI agents over MCP. Unofficial; open data.
Over to you: if you work on transit, accessibility or graph databases, I'd love to hear what you'd ask this graph next, or where the model is wrong. Drop it in the comments.
Built for Graph Hacks (FalkorDB ร WeMakeDevs), Track 1: Agents That Act on Connected Data. The code, data rules, evaluation and every query are open source.
Find the last lift before it fails.







Top comments (0)