Inspiration

Disasters bring floods of messages. After the 2010 earthquake in Haiti, phone networks went down and people turned to whatever channel still worked, overloading networks: 80,000 messages, and 45,000 that were actually unique. The message that matters, like "correction: there's still someone inside," gets buried under duplicates and old news.

LoRa mesh radios (Meshtastic) work with no towers and no internet, but every radio shares one narrow channel and each packet carries only a few hundred bytes. We wanted detailed reports to fit through the radio, and the most important unresolved incident to stay on top as new information arrives.

What it does

  • Send: a field team types a report. A small language model (SmolLM2-360M) compresses it 5x with numeric encoding before it goes over the radio. The receiver decodes it to the exact original text or rejects it visibly. Nothing is silently truncated.
  • Intake: each report is stored once, byte for byte, before any model sees it. Retransmissions don't create duplicates.
  • Local AI, no internet: Laya, running on the laptop, suggests what each report means (possible life-safety concern, active need, or information) and which incident it belongs to. Its scores are shown as scores, not as urgency.
  • Claude when online: Claude builds a working picture of each incident (site, subareas, claims, chronology, uncertainty), and every claim cites the report IDs it came from. It is labeled "unconfirmed."
  • A human decides: only an operator's approval changes the confirmed queue. A possible life-safety concern goes into a review band right away and is never silently downgraded. A late, older all-clear can't erase a newer concern.
  • Shared, live: a 3D map of San Francisco's Outer Sunset with an incident heatmap. A review band, a ranked queue, and each incident's full report history stay in sync across operator screens.
  • Honest under failure: if the database drops, it can still handle throughput, as connections are not the most stable in disaster-struck areas.

How we built it

  • SpacetimeDB is the command post. Relational tables, server logic and real-time sync all run in one local process on a single laptop, with no internet. Reports, incidents, proposals, jobs and review events live in one SpacetimeDB module, and every write goes through reducers that apply our ranking and review rules. That keeps the most urgent unresolved incidents on top, and every confirmed change is pushed live to all operator screens. The radio bridge, the AI worker and the operator all call the same functions. Those functions check the caller's role (a worker cannot approve), ignore repeated commands, reject stale approvals with an input signature, and commit each approval in one transaction. There is no separate API server and no sync layer.
  • Shared rules: the ranking and review rules are pure TypeScript in packages/domain. The database module imports them, and the offline replay runs them too, so live and replay behave the same.
  • Claude (Anthropic SDK) runs in a coordinator outside the database. It has read-only tools plus one tool to submit a proposal. It has no tool that approves or dispatches anything.
  • Laya runs locally in a Python worker, in its own job loop so that Claude's slower calls never delay it.
  • Radio: a Python Meshtastic bridge adds a 10-byte envelope (message ID, attempt, sender time) to each codec packet and waits for a decode acknowledgement. It retries once in raw form if decoding fails, and delivers each report to the database exactly once.
  • Dashboard: React and Three.js, with real street geometry from OpenStreetMap. The scenario is a synthetic exercise: 55 reports, 19 places and 16 teams moving along real streets.

Challenges we ran into

  • Making AI useful without letting it decide. Model confidence isn't urgency. We had to design the review band so that uncertain but dangerous reports get more attention, not less.
  • Time is messy. Sent time, reported event time and receive time are different, and a late, old report must not override a newer one. We kept all three separate everywhere.
  • Codec edge cases. The encoder accepted any length, but the decoder stops at 300 tokens, so long reports became undecodable on the receiver. We fixed the boundary without changing the arithmetic coding.
  • SpacetimeDB in practice: errors come back as HTTP 530. The module runtime has no global URL, so our JSON Schema validator needed a small shim. A fresh Docker volume wasn't writable at first.

Accomplishments that we're proud of

  • A novel application of a numeric compression concept by extreme lightweight LLMs, resulting in a 1/5th compression of field reports, which leads to doubling throughput of any existing or upcoming LoRa mesh networks.
  • An AI-assisted desk where the database itself enforces "AI proposes, a human approves."
  • 21 of 21 checks passed against a real SpacetimeDB server: duplicates, worker-can't-approve, stale approvals, run reset.
  • Zero false links and zero unjustified downgrades on held-out cases, with every result labeled as measured, calculated or fixture.

What we learned

Being honest about uncertainty is a feature. In a disaster, a system that says "I'm not sure, a human should look" is worth more than one that's confidently wrong. Putting the rules in the database, and training our agent on escalation made that a guarantee instead of a convention.

What's next

  • Field-test with people who run these networks.
  • Measure delivered complete reports against raw text and standard compression on real radios.
  • One-way upload of the confirmed picture to the cloud whenever a connection appears, so other agencies can see it.

Built With

Share this project:

Updates

Submission history