Inspiration

Every Reddit mod has the same problem: the modqueue shows you one report at a time. You see "this post was reported." You don't see "this post got reported 8 times in the last 3 minutes by accounts that have never reported anything before." You see "u/x posted." You don't see "u/x just posted 12 comments in 8 minutes across the sub." By the time the pattern is obvious, the damage is already done.

We built FirefighterMap to give mods a layer above the queue — one that watches the shape of activity, not just the items in it.

What it does

FirefighterMap is a live dashboard that lives inside your subreddit as a custom post. It watches every post, comment, and report flowing through r/yoursub and surfaces four kinds of "fires" the moment they form:

  • User burst: one account spiking far above their own normal posting rate. Catches sockpuppet flare-ups, hijacked accounts, and paid spam runs.
  • Target report burst: a single item taking an unusually fast wave of reports. Catches brigading and coordinated targeting.
  • Near-duplicate wave: many accounts pushing near-identical text. Catches affiliate spam, scam campaigns, and copypasta raids using MinHash + Jaccard similarity.
  • Coordinated reporters: groups of accounts flagging the same items together. Catches weaponized reporting. Currently dormant pending a Devvit API change that exposes reporter identity.

Each fire on the dashboard shows:

  • What happened, in plain English ("8 reports - well above normal for this post")
  • Who or what is involved (clickable subject chips)
  • A 15-minute activity sparkline
  • Three actions a mod actually uses: open the relevant Reddit view, escalate to modmail with auto-generated evidence, or snooze the fire for 15 min if it's a known event

A "Detection details" disclosure exposes the underlying z-score, observed count, and baseline for mods who want to see the math.

How we built it

Built entirely on Devvit Web in TypeScript. No external services, no ML model, no per-request API calls. Every dollar of operating cost is absorbed by the platform.

Detection is pure statistics, deliberately:

  • Welford's online algorithm tracks rolling per-user / per-target baselines in O(1) memory
  • z-score with a minimum-count floor decides when a spike is anomalous (suppresses false positives on quiet subs)
  • Near-duplicate detection uses FNV-1a MinHash (K=64) + Jaccard ≥ 0.7 + union-find clustering on a sliding 15-minute window
  • Reporter coordination uses set-intersection union-find across distinct-reporter buckets

State lives in Devvit Redis:

  • Sliding windows as sorted sets, scored by event timestamp
  • MinHash signatures persisted to a sorted set keyed by score = timestamp, so the detector survives serverless cold starts
  • Snoozed fire IDs as a sorted set, scored by expiry, auto-expire via zRemRangeByScore

Sensitivity presets for small / medium / large subs auto-tune count floors and z-score thresholds — the same code works for a 500-member niche sub and a million-member default.

22 unit + integration tests with an in-memory KV test double, including a cold-start resilience test that simulates the process-recycle bug we hit during development.

Challenges we ran into

1. Devvit's serverless cold starts wiped the near-duplicate detector. The first version kept MinHash signatures in an in-memory array. Fires would appear right after seeding and disappear on the next cron tick because the process had been recycled between requests. Fixed by persisting signatures to a Redis sorted set keyed by timestamp. The bug surfaced in a written integration test that builds two FireDetector instances against the same KV — exactly the scenario Devvit creates in production.

2. Stable fire IDs. Snooze was silently breaking because fire IDs initially included firstSeenMs, which slid every tick. The same logical fire would get a new ID on the next scan, and the snoozed-set lookup would fail. IDs are now derived from semantic identity (kind + sorted subjects, or sorted sample-event IDs for near-dup clusters) so a fire keeps its identity across scans.

3. Webview navigation whitelist. Devvit's host bridge silently no-ops on deep Reddit URLs (/user/X/submitted, /about/spam). We had to redesign the deep-links around the shallow URL set that actually works (/user/X, /r/X/about/modqueue, https://redd.it/X).

4. Mod-friendly UX. The first cut surfaced raw z-scores, composite scores, and baseline floats directly on the dashboard and in modmail. Completely opaque to a non-data-scientist mod. Rewrote the entire UI and modmail copy to lead with plain English ("8 reports — well above normal for this post") and tuck the math inside an opt-in "Detection details" disclosure. Per-fire-kind suggested next-step bullets in modmail.

Accomplishments that we're proud of

  • Transparent and debuggable: every fire can be drilled into to see the z-score, observed count, and baseline. No black box, no ML mystery meat.
  • 60-second end-to-end detection latency on Devvit's serverless infrastructure.
  • No external dependencies: runs entirely on the platform. No API keys, no rate limits, no per-request cost.
  • 22/22 tests passing, including a regression test for the exact cold-start bug that bit us during development.
  • Adaptive sensitivity: the same detector code works for tiny niche subs and million-member defaults via three preset profiles.

What we learned

  • Statistics beat ML for pattern detection. Transparent, fast, no training set, no labeled data. The first thing a mod asks is "why did it flag this?" - a z-score answers it; a neural-net score doesn't.
  • Devvit's serverless runtime recycles processes between requests more often than expected. Either persist all state to Redis or design detectors that don't carry state across calls.
  • Mod tools live or die on copy. The same data shown as "z=8.0, observed=8, baseline=0.0" is unusable. Shown as "8 reports - well above normal for this post" with a one-click "Open in modqueue" button, it's actionable.

What's next for FirefighterMap

  • Persist Welford baseline state to Redis so freshly-installed subs converge faster (right now they take ~10 minutes of cron ticks to stabilize).
  • Activate the Coordinated reporters detector once Devvit exposes reporter identity in PostReport / CommentReport triggers.
  • AutoMod rule suggestions: when a near-duplicate wave fires, propose a ready-to-paste body_regex or domain rule covering the shared text.
  • Per-mod email digest for chronic fires they keep snoozing, useful for surfacing patterns that need a sub-level config change.
  • Publish to the Devvit app directory so any subreddit can install with one click.

Built With

  • devvit
  • devvit-redis
  • devvit-web
  • esbuild
  • fnv-1a
  • jaccard
  • minhash
  • typescript
  • union-find
  • vitest
  • webview
  • welford's-algorithm
Share this project:

Updates