Real-time coaching alerts during your ride, powered by rules + optional AI. Built as a native Karoo extension using the official karoo-ext SDK.
PacePilot watches your power, heart rate, cadence, and workout data in real time, detects what kind of ride you're doing, and delivers contextual coaching cues via Karoo's InRideAlert system.
Rule-based engine (works offline, no API key needed):
- 25+ coaching rules across 5 ride modes
- Zone drift detection, interval compliance, cadence monitoring
- Weight-based fueling reminders (reads weight from Karoo profile)
- HR:power decoupling, climb pacing, multi-climb effort budgeting
- Pre-climb prep cues, recovery ceiling enforcement
- Late-ride fatigue protection
AI layer (optional, needs API key + internet):
- Gemini 2.0 Flash — recommended, context caching, ~$0.01/ride
- Mercury-2 — experimental, 1,000+ tokens/sec via Inception Labs (10M free tokens)
- 30-ride rolling history for personalization
- Falls back to rule-based if offline — rider always gets a message
Integrations (graceful fallback when absent):
- NomRide — carb balance, burn rate, carbs eaten → deficit-aware fueling
- 7Climb — distance to summit, climb number → pre-climb prep + effort budget
- Headwind — wind speed, relative wind → context for future wind pacing
| Mode | Detection | Coaching Focus |
|---|---|---|
| Workout | Structured workout loaded | Interval compliance, target power, recovery quality |
| Endurance | Steady Z2, low variability | Zone discipline, fueling cadence, pacing consistency |
| Climb | Grade > 3%, elevation gain | Power ceiling, cadence management, summit proximity |
| Adaptive | No workout, auto-detect | Observes 10 min, then applies endurance/unstructured coaching |
| Recovery | User-selected | Strict Z1 ceiling, easy-day enforcement |
Karoo streams + NomRide/7Climb/Headwind (when present)
→ TelemetryAggregator (power, HR, cadence, workout, weight, carbs)
→ RideContext snapshot (every tick)
→ ModeDetector → CoachingEngine
→ Rules evaluate → CooldownManager filters
→ InRideAlert dispatch (static message)
→ Gemini or Mercury upgrades async (if enabled)
Key design decisions:
- Hybrid AI: Rules fire instantly (<1ms), AI upgrades the message async if connected
- No server needed: Everything runs on-device. Gemini calls go direct to Google's API
- Cooldown-aware: Global + per-rule suppression prevents alert fatigue
- Mode transitions: Adaptive mode observes first, then delegates to appropriate coaching strategy
Download the APK from the releases page and skip to Install on Karoo below.
Requires Android Studio or Gradle CLI and a GitHub account (the karoo-ext SDK is hosted on GitHub Packages).
# 1. Clone
git clone https://github.com/alainchristopher/pacepilot.git
cd pacepilot
# 2. Configure credentials
cp gradle.properties.example gradle.properties
# Edit gradle.properties — add your GitHub username + PAT (read:packages scope)
# Create PAT at: https://github.com/settings/tokens/new
# 3. Build
./gradlew :app:assembleDebugThere are three ways to install. Option A is the easiest.
- Download the APK to your phone from the releases page
- Open the Hammerhead Companion app on your phone
- Go to Extensions → Manage → Upload APK (or use the share sheet to share the APK file to the Companion app)
- On iPhone you can also AirDrop the APK to your phone first, then share it to the Companion app from Files
The Companion app will push the APK to your Karoo over Bluetooth automatically.
Enable developer mode on Karoo first: Settings → About → tap Build number 7× → Developer options → USB debugging ON
# Mac: install ADB
brew install android-platform-tools
# Plug in via USB, allow debugging when Karoo prompts, then:
adb install -r PacePilot-v1.1.0.apkWindows: download platform-tools, unzip, run adb install -r from that folder.
# Enable Wireless Debugging on Karoo (Developer options), note the IP shown
adb connect <karoo-ip>:5555
adb install -r PacePilot-v1.1.0.apkadb shell am start -a android.intent.action.VIEW -d "pacepilot://config?gemini_key=YOUR_KEY"
# Or for Mercury-2: pacepilot://config?mercury_key=YOUR_KEY&provider=mercuryKaroo disconnects from Wi-Fi when a ride starts. To keep internet connected for AI coaching:
- Start your ride on Karoo first
- Then enable hotspot on your phone
- Karoo reconnects automatically within ~30 seconds
Enabling hotspot before starting the ride will not work — Karoo drops Wi-Fi on recording start.
adb uninstall io.hammerhead.pacepilot.debug # debug build
# adb uninstall io.hammerhead.pacepilot # release buildOpen PacePilot from the Karoo extensions menu:
- App toggle — Master on/off (PacePilot Active)
- FTP / Max HR — Override Karoo profile (or use weight/HR from Karoo profile)
- Coaching alerts — Enable/disable, fueling reminders, alert frequency
- Alert policy — Min gap between alerts, max alerts per hour
- AI coaching — Choose provider: Gemini 2.0 Flash, Mercury-2, or Off
- Snooze / Undo — 15-min snooze or undo via BonusActions on alerts
When enabled, PacePilot uses a two-phase approach:
- Instant: Rule fires → static message shown immediately
- Upgrade: AI gets the coaching event + live telemetry + ride narrative + 30-ride history → generates a personalized message → replaces the static one if it arrives before auto-dismiss
Gemini 2.0 Flash (recommended): Context caching keeps costs ~$0.01/ride. Free key at aistudio.google.com.
Mercury-2 (experimental): OpenAI-compatible API via platform.inceptionlabs.ai. 10M free tokens. ~1,000+ tokens/sec. No server-side cache — full context sent per call.
- Current power/HR/cadence zones and trends
- Ride narrative (events so far: zone changes, fueling, climbs)
- 30-ride rolling history (avg power, duration, dominant zones, consistency)
- The specific coaching rule that fired and why
- GPS location or route
- Personal information beyond ride data
- Data from other apps
# Run all unit tests
./gradlew :app:testDebugUnitTestTests cover zone calculation, power analysis, workout classification, coaching rules, cooldown logic, mode detection, ride simulation scenarios, and FIT-based backtest replay.
app/src/main/kotlin/io/hammerhead/pacepilot/
├── ai/ # Gemini/Mercury clients, context builder, ride narrative
├── coaching/ # Rules engine (workout, endurance, climb, adaptive)
├── detection/ # Ride mode detection and transitions
├── fields/ # Custom data fields (coaching_status, zone_time, ride_score)
├── fit/ # FIT developer-field export for coaching events
├── history/ # 30-ride history, PostRideIntelligence, RideSummaryBuilder
├── integrations/ # NomRide, 7Climb, Headwind adapters
├── model/ # Data classes (RideContext, CoachingEvent, etc.)
├── settings/ # User preferences (SharedPreferences)
├── state/ # Active-ride state snapshot and restore
├── telemetry/ # Karoo SDK stream aggregation, FuelingIntelligence
├── util/ # Zone calculator, extensions
├── workout/ # Workout tracking and classification
├── MainActivity.kt # Settings UI (Jetpack Compose)
└── PacePilotExtension.kt # Main extension service
- Kotlin + Coroutines/Flow for reactive telemetry
- karoo-ext 1.1.8 — Hammerhead's official extension SDK
- Jetpack Compose + Material3 — Settings UI
- OkHttp — HTTP client for Gemini/Mercury APIs
- kotlinx-serialization — JSON parsing
- Timber — Structured logging
- Gemini 2.0 Flash or Mercury-2 — Optional AI coaching layer
PRs welcome. The coaching rules are pure functions — easy to add new ones:
fun myNewRule(ctx: RideContext): CoachingEvent? {
if (/* condition not met */) return null
return CoachingEvent(
ruleId = RuleId.MY_RULE,
message = "Your coaching message",
priority = CoachingPriority.MEDIUM,
alertStyle = AlertStyle.COACHING,
suppressIfFiredInLastSec = 300,
)
}Add it to the relevant evaluateAll() function and register the rule ID in RuleId.
MIT
- App toggle (master on/off)
- Weight-based carb calculation from Karoo profile
- Workout-type-aware coaching (Sweet Spot / Threshold / VO2max)
- Fueling + drink reminders in climb mode (was missing)
- Pre-climb prep cues + multi-climb effort budgeting
- NomRide, 7Climb, Headwind adapters (graceful fallback)
- 3 custom data fields: coaching_status, zone_time_sec, ride_score
- Snooze 15 min + Undo Snooze BonusActions
- Alert policy: min gap + max per hour
- FIT developer-field export for coaching events
- Active-ride state snapshot every 30s — restored on service restart
- Post-ride summary + pattern detection
- Mercury-2 AI provider alongside Gemini 2.0 Flash
- FIT-file backtest replay in unit test suite
- Wind-adjusted pacing (Headwind data already ingested, coaching logic pending)
- Finish-line / negative-split strategy mode
- Log Fuel BonusAction (in-ride carb logging without NomRide)
- Hammerhead Extension Library submission
- Companion phone app for post-ride review
- Intervals.icu / TrainingPeaks plan sync