Project Info
Inspiration
Five of us packed into a car and drove from Chicago to San Francisco to attend the hackathon. Somewhere in the Nevada desert- no towns, no cell signal, just flat highway stretching to the horizon- our tire blew out. We had a spare. We had tools. What we didn't have was any idea how to use them properly. None of us remembered the lug nut torque sequence. Nobody knew if our car had a full-size spare or a donut with a speed limit. We couldn't look any of it up. We sat stranded on the side of the road for hours. Thirty seconds on Google would have answered every question. But we had no Google. That gap — between having a smartphone and having actual help — is exactly what Glovebox is built to close.
What it does
Glovebox is an offline AI copilot for stranded drivers. Open the app, describe your problem in plain English, and it walks you through it — step by step, in a conversational chat — with zero internet required. It's not just a generic how-to guide. Glovebox uses RAG (retrieval-augmented generation) over your vehicle's owner's manual, so when you ask "where's the jack storage on my car," it finds the right answer for your specific model and year — not a generic YouTube video that might not match your setup.
How we built it
Glovebox is a React Native app for iOS built in TypeScript. The core is on-device LLM inference via llama.rn, running a quantized GGUF model (Llama 3.2-1B-Instruct or Gemma-2-2B at Q4_K_M — under 1.5GB). All inference happens locally on the iPhone; no API calls, no backend. On top of the LLM, I built a lightweight keyword-based RAG pipeline that chunks and indexes the vehicle owner's manual and retrieves the most relevant sections to include in the model's context window for each query. Offline state is detected via React Native's NetInfo API and surfaced clearly in the UI — the "Offline mode: ON" indicator reflects real network state, not a hardcoded flag.
Challenges we ran into
Getting a quantized LLM to run acceptably fast on-device was the biggest challenge — model selection and quantization level directly affect both response latency and accuracy. Bundling the GGUF weights as a native resource in Xcode and wiring it into the React Native bridge via llama.rn took significant trial and error. Building this as a solo developer under hackathon time pressure meant making deliberate tradeoffs: iOS only for now, keyword-based RAG rather than vector embeddings, a curated set of emergency procedures rather than an exhaustive manual library.
Accomplishments we're proud of
Apart from the technical accomplishments, I'm proud that this solves a real problem I actually lived. The five of us stranded in Nevada weren't edge cases. There are 46 million roadside breakdowns in the US every year, and a huge chunk of them happen where there's no signal. Glovebox would have gotten us back on the road in 20 minutes instead of 3 hours.
What we learned
I learned that on-device AI is genuinely viable in 2025 — but model selection is everything. The difference between a 1B and a 3B parameter model isn't just size; it's whether the responses are actually useful under the constraints of a glove compartment emergency. Quantization level (Q4_K_M vs Q8) has a real effect on both latency and coherence, and finding that sweet spot took most of my first night. And building this solo taught me to be brutal about scope. No Android, no vector DB, no fancy UI, just one thing that works completely offline, answers car questions accurately, and launches in under three seconds. Constraints forced clarity.
What's next
Android support via the same llama.rn bridge Vector-based semantic RAG for more accurate retrieval creating an offline network mesh for users to connect
Glovebox
Offline-first roadside assistance for iOS. Diagnose car trouble with an on-device LLM and reach cached emergency help, built to keep working with zero or unreliable signal, at the exact moment you need it.
Built at the UC Berkeley AI Hackathon 2026.
A note on the images above: this environment has no Xcode/Simulator installed, so these aren't device screen captures. They're an HTML/CSS reconstruction of the five screens built directly from the app's real design tokens (
GBColor,GBGradient,GBFont, and the actualBrandIconasset) so the layout, copy, and states shown are accurate to the SwiftUI source. Swap in real device screenshots when you have Xcode available; see Building.
Table of contents
- Why Glovebox
- Development process
- Features
- How diagnosis works (RAG + on-device LLM)
- Safety design
- Travel Mode & the offline cache
- Architecture
- Project structure
- Building
- Debugging & screenshot-driving env vars
- Current limitations
- License
Why Glovebox
Roadside trouble tends to happen exactly where connectivity doesn't: a canyon road, a rural highway, a parking garage. Most "car help" apps assume you have a live connection to reach a chatbot, a map, or a tow dispatcher. Glovebox assumes the opposite: every screen defines what it does with zero signal, and does the expensive work (model inference, manual retrieval, POI search) before you need it, not during.
Development process
Built end-to-end during the UC Berkeley AI Hackathon 2026. I laid out the product
spec and architecture myself (the screen-by-screen flow, the safety philosophy behind
SafetyFilter, the requirement that every feature define its own offline/degraded
state), then deployed AI coding agents to orchestrate and execute that plan:
scaffolding the SwiftUI screens, wiring the RAG + on-device LLM pipeline, and
implementing the Travel Mode/background-caching logic against the spec, with review
and correction at each step rather than one unsupervised pass. The commit history
reflects that human-directed, agent-executed workflow.
Features
🔧 Vehicle-aware offline diagnosis
A chat interface backed by retrieval-augmented generation (RAG) running entirely
on-device. Each vehicle has its own cached manual/issue-reference index; a question
is matched against that vehicle's cached chunks, grounded into a prompt, and answered
by a local Llama 3.2 1B Instruct model via llama.cpp: no network round-trip, no
server, no data leaving the phone. Answers are direct and actionable (no "I'm not a
mechanic" hedging), cite their source ("From your cached common-issue guide"), and are
tagged SAFE TO DIY when the grounding chunk supports it.
🛡️ Code-enforced safety filter
Every request and every generated response is scanned for five safety-critical
systems (brakes, airbags/SRS, high-voltage EV/hybrid battery, fuel system, and
structural/frame work) using a deterministic regex classifier (SafetyFilter.swift),
not a model-side prompt that could be rephrased around. See Safety design
for the reasoning and exact behavior.
🧭 Travel Mode: predictive offline caching
Turn it on before a trip and Glovebox quietly pre-caches roadside help (mechanics,
towing, hospitals/urgent care, fuel & EV charging, non-emergency police) along your
route using CoreLocation + MKLocalSearch. It's battery-aware (refreshes every
~3 miles of movement, not continuous GPS polling), keeps only a trailing window of
data near you (auto-evicts anything >~40 miles behind or older than an hour), and
schedules background refreshes via BGTaskScheduler so the cache stays warm even
when the app isn't open.
🚨 Always-reachable Emergency screen
Reads straight from the on-device POI cache: it never silently requires a network call. Every cached entry shows a visible staleness label ("cached 6 min ago") and is flagged amber once it's past 15 minutes old, so stale data never masquerades as fresh. One tap to call or send a pre-filled SMS with your last known location, plus a sticky "I need help now" action and a direct 911 dial from anywhere in the app.
🚗 Garage: multi-vehicle profiles
Add multiple vehicles, switch which one is active, and re-sync a vehicle's cached
manual/issue data independently. SwiftData keeps everything local by default.
How diagnosis works (RAG + on-device LLM)
flowchart TD
A[User types a question] --> B[SafetyFilter.classifyInput]
B --> C[Retriever: keyword-overlap search\nover this vehicle's cached ManualChunks]
C --> D{Strong match?\nscore ≥ 0.18}
D -- yes --> E[PromptBuilder grounds the\nprompt with the matched chunk]
D -- no --> F[PromptBuilder falls back to\ngeneral automotive knowledge]
E --> G[LlamaInference streams tokens\nvia llama.cpp, off the main thread]
F --> G
G --> H[SafetyFilter.classifyOutput\non the generated answer]
H --> I[Answer rendered with source badge,\nSAFE TO DIY tag, and/or safety caution]
G -. model missing / times out .-> J[Low-confidence fallback bubble\n→ Find a mechanic]
The pipeline lives across four small, single-purpose files:
| Step | File |
|---|---|
| Orchestration (safety → retrieve → prompt → generate → safety → persist) | Chat/DiagnoseViewModel.swift |
| Retrieval: keyword-overlap scoring over cached manual chunks (title-weighted) | Retrieval/Retriever.swift |
| Prompt assembly in Llama 3.x instruct chat format | Retrieval/PromptBuilder.swift |
Inference: actor-isolated llama.cpp context, streamed off the main thread | LLM/InferenceEngine.swift, LLM/LlamaContext.swift |
A 120-second watchdog cancels generation on the simulator's slow CPU-only path without hanging the UI; a failed/empty/too-short answer degrades gracefully to a "find a mechanic" fallback bubble instead of showing nothing.
Safety design
"Glovebox is for drivers who may be stranded with no mechanic and no signal, so it does not withhold guidance. Instead, a hit surfaces a prominent 'safety-critical — proceed at your own risk' caution attached to the answer." (
SafetyFilter.swift)
This is a deliberate product decision, not an oversight: refusing to answer is only the safe choice if the driver has another option. Glovebox instead:
- Classifies both directions. Input is checked before generation; the generated output is checked again, so a caution still gets attached even if the risky topic only surfaces in the model's own steps (not the user's original phrasing).
- Can't be talked around. Detection is regex-based pattern matching in Swift
code (
SafetyFilter.swift), evaluated the same way regardless of how the question is worded; there's no prompt for a model to be argued out of. - Warns instead of blocking, for exactly five system categories: brakes (beyond a fluid-level check), airbags/SRS, high-voltage EV/hybrid battery (a 12V jump start is explicitly allowed), fuel-system repair (beyond a cap/line check), and structural/frame work. A hit renders a rust-colored caution banner inline with the answer: "⚠️ <Topic> — safety-critical. You can do this at your own risk. Get it professionally inspected when you can."
- Never fabricates confidence. If the model fails to load, times out, or produces a too-short answer, the UI shows an explicit low-confidence fallback rather than guessing.
Travel Mode & the offline cache
Travel/TravelService.swift runs a small state machine over CoreLocation:
- Throttled refresh: re-caches only after ~4.8 km (3 mi) of movement, so it isn't
polling GPS or hitting
MKLocalSearchcontinuously. - Sliding-window eviction: drops any cached POI more than ~64 km (40 mi) from the current position or older than an hour, bounding on-device storage automatically.
BGTaskSchedulerintegration: schedules an app-refresh and a processing task on backgrounding so caching continues (network permitting) while the app is closed.- Six categories per refresh via
MKLocalSearch(Travel/POISearch.swift): auto repair, towing, hospital/urgent care, gas stations, EV charging, and non-emergency police, up to 4 results per category within a ~32 km radius. - Honest byte accounting: the "storage used" figure sums actual cached record sizes; it doesn't fabricate a number for data that isn't actually stored (e.g. no map tiles are cached).
Architecture
| Concern | Implementation |
|---|---|
| UI | SwiftUI, iOS 17+, dark mode only, SF Symbols, Public Sans typeface |
| Local persistence | SwiftData: Vehicle, ManualChunk, ChatMessage, CachedPOI |
| Retrieval (RAG) | Keyword-overlap scorer in Retriever.swift; interface-isolated so a vector index can replace it later without touching callers |
| On-device LLM | llama.cpp via Vendor/llama.xcframework; model resolved by LLM/ModelLocator.swift (override → Documents drop-in → bundled resource) |
| Location / maps | CoreLocation + MapKit / MKLocalSearch |
| Background execution | BGTaskScheduler (app-refresh + processing tasks) |
| Connectivity signal | Network's NWPathMonitor, wrapped in Core/NetworkMonitor.swift |
Routing is minimal and state-derived, not a big coordinator: App/AppRootView.swift
picks welcome → addVehicle → sync → main purely from whether a Vehicle exists yet
in SwiftData, so app state survives relaunch without any separate onboarding flag.
Project structure
GloveboxApp/
├── App/ Entry point, app delegate, root routing, tab shell
├── DesignSystem/ Color/gradient/font/spacing tokens + reusable components
├── Data/ SwiftData models (Vehicle, ManualChunk, ChatMessage, CachedPOI)
├── LLM/ llama.cpp integration, model locator, safety filter
├── Retrieval/ Keyword retriever + Llama-format prompt builder
├── Chat/ Diagnose screen, view model, chat bubbles
├── Travel/ Location manager, MKLocalSearch POI fetch, cache service, BGTask
├── Features/ Onboarding, Home, Garage, Emergency, Help, Travel screens
└── Resources/ Assets, fonts (Public Sans), Info.plist
Building
The Xcode project is generated from project.yml with
XcodeGen. Two large binaries are intentionally
not committed (see .gitignore); fetch them locally first:
# 1) Tooling
brew install xcodegen
# 2) On-device model (GGUF): place at Models/Llama-3.2-1B-Instruct-Q4_K_M.gguf
# (any Llama-3.2-1B-Instruct Q4_K_M GGUF works; the path is configurable
# via ModelLocator, not hardcoded)
# 3) llama.cpp xcframework
mkdir -p Vendor && cd Vendor
curl -fsSL -o llama.zip \
https://github.com/ggml-org/llama.cpp/releases/download/b9748/llama-b9748-xcframework.zip
unzip -q llama.zip && mv build-apple/llama.xcframework . && rm -r build-apple llama.zip
cd ..
# 4) Generate + open
xcodegen generate
open Glovebox.xcodeproj
Build/run on an iPhone (or the Simulator). On the Simulator, inference runs CPU-only; Metal is used on device. Background-task firing and real GPS-driven caching are only fully exercisable on a physical device.
Debugging & screenshot-driving env vars
The app reads a handful of DEBUG-only environment variables (set as Xcode scheme
arguments) to jump straight to a screen or state, handy for demos and grabbing
screenshots without manually walking through onboarding every time:
| Variable | Effect |
|---|---|
GB_ROUTE | welcome | addVehicle | sync | syncDone | main: jump the root router, seeding a placeholder vehicle when needed |
GB_TAB | home | diagnose | travel | garage: open MainTabView straight to a tab |
GB_OPEN | emergency | help: present a sheet on launch |
GB_TRAVEL | 1: enable Travel Mode on launch |
GB_CHAT | any string: auto-sends it as a Diagnose query one second after launch, no taps needed |
GB_MODEL_PATH | absolute path: override the GGUF path for that run |
GB_FORCE_MODEL_FAIL | 1: deterministically exercises the model-load-failure fallback |
GB_FORCE_OFFLINE | 1: force the offline UI on a Simulator that's always online |
GB_EMPTY_CACHE | 1: skip seeding the placeholder Emergency POI cache |
Current limitations
Being upfront about what's real vs. scaffolded in this hackathon build:
- Manual/issue content is placeholder, not manufacturer data. No real owner's-manual
or make/model/year issue database is wired up yet;
PlaceholderManualData.swiftships clearly-labeled (isPlaceholder = true), deliberately generic guidance so the RAG pipeline and Sync screen are fully real end-to-end, but the content itself should never be treated as authoritative for a specific vehicle. - Retrieval is keyword-overlap, not a vector index.
Retriever.swiftis written behind a small interface specifically so a real embedding-based index can drop in later without touchingDiagnoseViewModelorPromptBuilder. - The roadside-assistance number and "last cached spot" in the Help sheet are
placeholders (see
TODOs inHelpSheetView.swift) pending a real cached-resource source. - iOS only: an earlier React Native/Android prototype was intentionally removed in favor of a focused native SwiftUI rebuild.
License
This project's Swift/SwiftUI source is licensed under the MIT License.
That covers the code in this repository only — the bundled Llama model weights and
the llama.cpp xcframework are fetched separately (see Building) and
carry their own licenses (Meta's Llama license and the MIT-licensed llama.cpp project,
respectively).
Analysis
View
Metric
- 9
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- SwiftIn code
- JavaScriptClaimed
- RubyClaimed
- TypeScriptClaimed
1 of 4 appear in the indexed code. 3 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeCommits
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
160 KB
Source files
48
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
hiratinspace/Glovebox
64 files · 2.5 MB · @ 9a68d08
Structure
Interface
4 files · 6%Screens, components and styles rendered to the user.
Application logic
49 files · 77%Domain rules, services and shared utilities.
+3 more
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- Swift98%
- Markdown2%
- YAML1%
Share of indexed source by file size. Binary and vendored files are excluded.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.