GhostRunner: Turn Every Street Into a Thriller — A Local AI Audio Running Engine

GhostRunner uses a compile/runtime split to turn real streets into an offline interactive audio thriller.
GhostRunner is an open-source, local-first audio drama engine that turns a runner's real route into the stage for a zombie escape or spy extraction radio drama. Its core is a compile/runtime split: before the run, an LLM generates the story, landmarks, and voiceover in one pass; at runtime, only millisecond deterministic lookups and triggers occur. Story beats are anchored to specific meter marks along the route, landmarks come strictly from OpenStreetMap, and a validator rejects any fabricated landmark. The LLM serves only as director and beat writer, with all orchestration in pure Python. Pace is classified with TabPFN into four states to select story variants. Voice is the only usage-based cost, rendered once per route and replayable indefinitely, making a one-person business viable. The whole system runs on a laptop GPU with 4 GB of VRAM.
Listening to podcasts while running just isn't thrilling enough anymore. GhostRunner offers a bolder idea: turn the real streets beneath your feet into the stage for a thriller radio drama. Pick a theme — zombie escape, cyberpunk courier, spy extraction — set your distance and intensity, and the system plans a real route, identifies landmarks along the way, then generates an audio storyline where your surroundings are the set.
According to the developer's submission to the Hacktoberfest open-source AI challenge, GhostRunner is an open-source, local-first audio drama engine. It doesn't just serve as a running companion — the developer also designed it as a one-person business: producing and selling themed running/walking experience packs.

Core Mechanics: Spatially Precise, Landmark-Grounded, Zero Runtime Inference
What makes GhostRunner more than a "map API wrapper around a prompt" comes down to three design choices:
Spatially precise. Every story beat is anchored to a specific distance along the route. A line about an upcoming turn plays before the turn, not after.
Grounded in reality. The narrator can only reference landmarks that actually exist in OpenStreetMap along the route. A validator rejects any fabricated landmark.
Fast enough. Because the story isn't generated in real time during the run — it's compiled and voiced before execution. Playing a line is a lookup, not an inference.
All of this runs on a laptop GPU with just 4 GB of VRAM.
"Turn right onto Oak Street. See that old brick building on your left? That's where it's hidden."
Routes use "chainage" (meters along the path) as their coordinate system: a turn at 1,240 meters, a fountain at 1,310 meters on the left. This choice makes triggers distance-based rather than time-based, so pre-compiled beats work at any pace.
OpenStreetMap (OSM) is an open-source geographic database maintained by volunteers worldwide. Anyone can freely download, use, and modify its map data. Unlike commercial maps like Google Maps, OSM provides complete raw vector data (roads, buildings, points of interest, trails, etc.) under the ODbL open license. GhostRunner uses the Overpass API to query POIs (points of interest) in OSM along the route corridor — brick buildings, fountains, corner shops, and other landmarks — then feeds them to the LLM as narrative material. This is the foundation of its "grounded in reality" capability: landmarks aren't invented by the model but retrieved from real map data, and the validator builds a whitelist from them, rejecting any landmark name not on the list.
Compile/Runtime Separation: A Solution to Latency and Trust
A runner moves at about 3 meters per second, while an LLM on a small-VRAM GPU takes seconds per sentence, and TTS adds more on top. With reactive generation, a line about the clock tower would arrive after you've already passed it. Small models also confidently hallucinate.
GhostRunner splits the system by "when the work happens":
- Compile phase (before and during the run): planning → Valhalla routing → Overpass corridor POI → route model (chainage) → director (single LLM call) → beat planner → beat writer (3 variants per beat) → validator → TTS pre-rendering (rolling window)
- Runtime phase (per tick, no LLM on the normal path): GPS → pace classifier (TabPFN) → route localization → trigger check → variant selection → playback
Two LangGraph graphs divide the work clearly: compile_graph does the heavy lifting, while runtime_graph consists of deterministic nodes that complete in milliseconds. This split is also key to the business model — all expensive operations happen once at compile time, and the results can be reused indefinitely.
LangGraph is an orchestration framework from the LangChain team for building stateful, multi-step AI workflows, modeling tasks as nodes and edges on a graph. In GhostRunner, compile_graph is a heavier graph chaining route planning, POI fetching, director LLM, beat writer, validator, and TTS rendering nodes; runtime_graph is a lightweight graph with almost no model calls, doing only deterministic operations like GPS reading, pace classification, and trigger checks per tick. Splitting them into separate graphs gives the "heavy work" and "fast lookups" different execution frequencies, failure modes, and resource footprints — ensuring responsiveness in a real-time scenario like running.
The LLM Narrates, It Doesn't Orchestrate
Small models are unreliable at tool calling, and each tool-call round trip costs latency a runner can't afford. So all decisions are pure Python: which beat goes where, which landmark to use, what the maneuver text is, how far ahead to trigger. The LLM does only two things:
- Director (one structured call): produces three acts (with chainage ranges), 3-5 target landmarks, a villain premise, and a "story bible" (callsign, motifs, tone).
- Beat writer: for each beat, writes up to three variants of ≤30 words based on a structured brief (cruising, accelerating, exhausted).
The validator enforces word count, landmark-name whitelists, and safety rules (no jaywalking, no trespassing). On failure, it retries once with violation notes, then falls back to deterministic templates. The model sits behind an LLMClient interface — swapping it is a one-line config change.
Pace Awareness with TabPFN
Pace is treated as a small tabular classification problem. Each tick builds features: 30-second/2-minute/5-minute rolling speed, speed change and variance, gradient, time since last stop, and the ratio of current speed to the personal baseline from the previous two minutes.
TabPFN classifies the runner as CRUISING, ACCELERATING, EXHAUSTED, or STOPPED, with a 3-tick hysteresis filter to prevent the story from flip-flopping. It runs on CPU, throttled to once every 5 seconds, leaving all VRAM for the LLM.
The developer candidly notes one caveat: there's no labeled dataset here — training labels come from transparent heuristics applied to simulated running traces. TabPFN's value lies in being fast, well-calibrated, and retrainable in seconds. If it fails to load, a rule-based classifier takes over automatically.
TabPFN is a Transformer-based tabular data classifier whose key feature is that it can make small-sample predictions without gradient training on your dataset — the model has already been pretrained on a large number of synthetic tabular tasks, and at inference time training samples are fed directly into the forward pass as context, similar to in-context learning in the tabular domain. For scenarios like GhostRunner with no labeled dataset and low feature dimensionality (rolling speed, variance, gradient, etc.), TabPFN produces well-calibrated probability outputs, runs on CPU, and has near-zero retraining cost. A hysteresis filter is a common debounce mechanism in state machines: it only switches when a new state appears stably for several consecutive ticks, preventing the runner from oscillating between two categories and causing story variants to jump frequently.
Voice and Cost: The Only Usage-Based Billing Component
ElevenLabs voices each line, pre-rendered via a rolling window of the next three triggers. Audio clips are content-addressed (sha256(voice + text)), making repeated lines zero-cost. Reactive moments (off-route, speeding up, checkpoint countdown) use a pre-rendered "C-tier reserve pool" played from disk within 50 milliseconds.
The developer offers a candid breakdown of unit economics:
- Story text: local, unmetered. A route is roughly 25 beats × 3 variants × ~30 words ≈ 7-8k tokens — cheap even via API. What local models actually buy you is "unmetered, unthrottled" freedom to recompile and tune repeatedly.
- Routing, landmarks, maps: open data, no per-call fees. Valhalla runs locally, public Overpass has fair-use limits (production services should self-host), and H3 caching means landmarks are fetched only once.
- Voice: the only usage-based cost. A complete pack is roughly 25 beats × 3 variants × ~150 characters ≈ 11k characters. Because a pack is rendered once and replayed indefinitely, voice cost is calculated per route, not per listener.
This cost structure makes a one-person business viable: fixed cost per route, near-zero cost per run.
Content-addressed storage is a storage approach that uses a content hash (here sha256(voice + text)) as the key. Identical content always yields the same address, so deduplication is inherent — repeated lines need neither re-rendering nor re-billing. Rolling-window pre-rendering means always rendering audio for the next several trigger points (e.g., the next three beats) ahead of time at runtime. When the runner actually reaches the trigger distance, the audio is already ready, avoiding the hundreds of milliseconds to seconds of TTS request latency. Combined, these ensure voice cost is incurred only once per route at compile time, not per listener or per playback — the key engineering mechanism behind the "fixed cost per route, near-zero cost per run" unit economics.
Why Open Innovation Matters
For a project that might be paid for, the open parts determine whether the business can succeed.
Customer location data stays under their control. Runner GPS traces are sensitive, and target customers (clubs, schools, employers, tour operators) will ask where the data goes. In GhostRunner, real-time traces never leave the machine running the app, and raw traces are deleted after the session. "We can never see your runners' location" is a claim you can honestly make.
No lock-in on changeable parts. The narrator sits behind an LLMClient interface, voice behind a TTSProvider interface, with mock providers letting the entire pipeline run without any vendor.
The niche is a single street, and open data fills it. What general APIs handle worst is "that corner brick building" — no matter how large the model, it doesn't know, but OpenStreetMap does. Client-owned landmarks can be added via a single demo_pois.json file.
Works without signal. Saved missions replay with the LLM, router, and TTS all turned off — exactly what's needed for demos in parks, trails, or conference halls.
Commercialization Path and Current Status
The unit of sale is a mission pack: one route, one compiled story, and voiced audio — produced once, replayed unlimited times. Potential buyers include running clubs, walking tour operators, hotels, and campuses.
The developer openly admits the current state: no paid pilots or validated pricing yet, and saved missions can currently only be replayed from the local machine. The parts that make the product sellable (compile once, replay forever, no per-listener cost, swap landmarks and voice per client) are already built. Exporting mission packs as standalone packages that play on a customer's phone without a server is the next step.
The community can get the code from the GitHub repository. Running it locally just requires a conda environment plus docker compose to start Valhalla and Postgres, then running python tools/demo.py.
Summary
GhostRunner demonstrates a pragmatic design paradigm: front-load expensive AI work into compile time, leaving runtime to do only deterministic millisecond-scale lookups. Local-first doesn't just solve latency — it also solves privacy, offline usability, and unit economics. It's not yet a validated business, but it clearly proves what's possible when a creator treats the constraints of a 2-4B small model as design conditions rather than defects.
Related articles

Perplexity Hybrid Compute Tested: Privacy Data Never Leaves the Mac
Perplexity Hybrid Compute tested: how PrivacyGate intercepts sensitive data, how local and cloud models divide work, model differences on 16GB vs 32GB Macs, and privacy protection in real scenarios like bank statements and medical records.

Big Fat Fish Roasts Mainstream LLMs at Home and Abroad: Doubao Takes the Crown, DeepSeek Tied for First
Bilibili creator Big Fat Fish roasts mainstream LLMs: Doubao tops for emotional value, DeepSeek ties first for open-source value, with ERNIE, Hailuo, Yuanbao, Kimi, Qwen, Kling, Claude, Gemini, and Grok each branded with a distinct persona.

How to Truly Test AI Agents Before Production? A Practical Testing and Monitoring Framework
BotGage founder Pramin Pradeep shares a three-step framework for testing AI Agents before production: Agent registration visibility, red teaming, and production observability — plus a real case cutting QA costs from $180K to $80K and an analysis of IT roles shifting to Agent supervisors.