Inspiration
We actually started Friday planning a wildfire evacuation sim for Paradise, California. By Friday night we'd moved it to the river outside the building we were sitting in. The Treehouse is a short walk from the Charles, and Allston/Brighton is one of the neighborhoods Climate Ready Boston flags for river flooding. In January 2024, one storm pushed roughly 80,700 m³ of untreated sewage into the Charles from the Cottage Farm facility across the water.
The thing that bugged us is that disaster plans get made once, from a forecast, and then reality shows up. Someone needs a wheelchair van nobody knew about. A street is blocked. The hospital is on backup power with ten minutes left. Dispatchers handle these one call at a time, so the one accessible van gets sent somewhere early and nobody goes back to ask whether that was still the right call. Meanwhile, residents get an alert that says "evacuate zone B."
We wanted every person to get their own safety plan, and we wanted those plans to keep updating as people reported what was actually happening.
What it does
You text or call EMBER. It figures out what you said (two people, one in a wheelchair, River St is blocked), replans the entire district in under a second, and tells everyone whose plan changed what to do: which vehicle is coming, when, where to wait, what to bring.
It mostly calls rather than texts, because in an emergency people pick up a ringing phone. It calls on first contact, when your pickup is set or changes, and two minutes before the van shows up, then sends a text with the same instructions.
The same engine handles household rescues and infrastructure failures. If Mount Auburn Hospital loses power, EMBER decides where the one compatible generator should go. Anyone with an iPhone can join the demo by scanning a QR code. You become a household in the flood, EMBER calls you, and the plan has to save you.
How we built it
There are four pieces.
The engine is a numpy simulator of the real district. We pulled the streets and buildings from OpenStreetMap (586 intersections, 1,196 road segments, 17,810 buildings) and added the real hospital, sewage facility and EMS station. A plan is a list of jobs like "pick up this household" or "take the generator to the hospital." The simulator plays it forward minute by minute: vans drive around flooded roads, pickups fail if there's no wheelchair access, the hospital drains its backup, and the sewage station overflows when its storage fills. It's deterministic and takes about 6 ms per plan.
The planner generates candidate plans from a handful of rules, simulates all of them, then tweaks the best one with local search. Every move is scored by the simulator, never by the planner's own guesses. Plans are compared in strict order:
$$p^\star = \operatorname{lexmin}_p \big(\text{people not reached},\ \text{hospital outage},\ \text{sewage overflow},\ \text{distance}\big)$$
So people always come first, but the river is inside the objective, not bolted on afterwards. A full replan takes about 0.7 s.
The backend (FastAPI + SQLite) owns the state and runs the loop. A text or call comes in through Photon. Claude Sonnet 5.5, routed through Chalk's Model Gateway, turns it into a structured fact, or asks a follow-up question if it isn't sure. The planner replans, and only the people whose instructions actually changed hear from us. Claude Haiku 5.5 rewords the computed message so it sounds like a person. Then a guard checks that every number, vehicle and street in the reply came from the planner. If the model is down, a keyword parser takes over. The AI never decides who gets a van.
Calls run through ElevenLabs on our Photon number, with our backend acting as the agent's language model. That way a call can only say what a text would have said. Photon's shared iMessage lines can't message someone first, so joining is two steps: you register your number on the site, then the QR code opens Messages with "JOIN EMBER" pre-filled.
Daytona answers a different question: which plan survives bad luck? Before the storm, we run the top candidate plans and the baseline across thousands of perturbed versions of the night: slower roads, a van breaking down, less backup power, the storm arriving early. Each plan is scored by its worst 20% of worlds:
$$R(p) = \text{mean of the worst } \lceil 0.2M \rceil \text{ runs of people not reached}$$
The robust plan only replaces the forecast plan if it saves at least one more person, since every change means calling someone. The engine and town are baked into a snapshot, and the backend keeps 90 sandboxes warm. In our last measurement, 10,000 simulations took about 3 to 4 seconds on Daytona versus 24 on a laptop.
The website is React with a PixiJS isometric view of the neighbourhood. The water rises over real USGS elevation data and the traffic lights sit at the actual signalised junctions. There's a scoreboard against the baseline, a live feed of text → fact → plan, and you can click any van or house to see why the plan did what it did.
We wrote JSON schemas and fake data files on Friday so all four of us could build against them without waiting on each other.
Results
All of this is simulation, with synthetic households, on real streets.
| EMBER | One-call-at-a-time baseline | |
|---|---|---|
| People reached before the water (initial plan) | 78 | 58 |
| Sewage into the Charles (initial plan) | 0 m³ | 2,317 m³ |
| People reached after the wheelchair and hospital events (5 worlds) | 88.6 | 51.6 |
| Hospital outage | 11 min | 28 min |
| People stranded in the worst 20% of 10,000 storms | 15.7 (forecast plan: 30.3) | 40.8 |
When the hospital loses power, EMBER sends it the generator and tells you the price: the sewage station goes from 0 to 9,600 m³ of overflow. We also drive more vehicle-km than the baseline, which is the cost of pre-positioning.
Challenges
The AI was the easy part to get working and the hard part to trust. Our eval asks whether a misread text changes who gets out in time, rather than checking whether the JSON fields match. It caught things we'd never have noticed. Someone typed "we need a Welch's" (autocorrect for wheelchair). Our keyword parser read "I'm stuck in my house and I can't get out" as two people, "my house and I." The model now asks when a word looks garbled, and the parser bugs are fixed. With Sonnet through Chalk, 50 of 51 test messages pass, and the one miss asks a clarifying question instead of guessing.
The planner once left the hospital dark when moving the generator wouldn't have cost a single rescue. We only found it because we made every decision explain itself with a counterfactual simulation, and that explanation looked wrong.
Daytona had its own surprises. For a while our stress tests were quietly running on the laptop because of a missing dependency, which an end-to-end test finally caught. Half our runs were duplicates, because two candidate plans with different names were sometimes the same plan. And sending setup data to 90 sandboxes at once dropped about a third of the connections until we compressed it.
Then we made real phone calls around 3:30 AM. People say "okay" and wait, go silent, correct themselves mid-sentence, or ask for an ambulance. EMBER now ends calls cleanly, doesn't process a corrected sentence twice, and tells you to call 911 if anyone's actually in danger.
What we learned
If you let a simulator make the decisions, you can use an LLM for the messy human part without worrying about it inventing a van. Grading the model on outcomes instead of fields was the best testing decision we made. And being upfront about trade-offs, like the sewage number or labelling what's synthetic, made the whole thing easier to explain, not harder.
What's next
Real inputs: 311 data, utility outage feeds and a calibrated flood model. We'd also build a proper operator console for emergency managers. The loop doesn't care what the hazard is. We already run a synthetic chemical plume on the same engine (50.8 vs 28.6 people reached), and the same idea fits power-grid crew dispatch and heat-wave check-ins for vulnerable residents.
Built With
- anthropic
- chalk
- claude
- daytona
- deck.gl
- elevenlabs
- fastapi
- github-actions
- imessage
- maplibre
- numpy
- openstreetmap
- osmnx
- photon
- pixijs
- python
- react
- scipy
- shapely
- socket.io
- sqlite
- typescript
- usgs
- vite
- websockets

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