Tests
Smoke
Routes
ADRs
Agents
PWA design
Python
Platform
Field test
License
Explainer
→ Project Explainer — architecture, cue distribution, Sonoma corner doctrine, team.
A real-time, on-device coaching system for track-day drivers. Runs on a
Pixel 10 via Termux: ingests CAN telemetry over USB from the car's OBD-II
port, makes coaching decisions with on-device Gemma 4 (via litert-lm),
and pairs with a Vue PWA (pitwall-web, sibling repo, fully designed in
[docs/vue/](docs/vue/README.md), implementation pending) for
presentation. Built for the May 23, 2026 Sonoma Raceway field test —
no cloud dependency for the core coaching loop.
Three LLM tiers, all on-device:
| Tier | Model | Runtime | When |
|---|---|---|---|
| Hot path | — | sonic_model + canonical phrases |
In-drive, < 50 ms |
| Warm path | Gemma 4 E2B | litert_lm.Engine (in-process) |
In-drive brief cues, < 100 ms |
| Paddock | Gemma 4 E4B | lit serve + 18 ADK agents |
Pre-brief, debrief, Q&A, 2–15 s |
Table of contents
- Status
- What this is / what this isn't
- Repo layout
- Quick start
- Architecture
- ADK multi-agent coaching
- Performance budget
- Hardware
- Data privacy & residency
- Key documentation
- Test status
- Roadmap
- Frozen artefacts
- Team 2 (Intermediate, BMW M3)
- Acknowledgements
- Contributing
- Code style
- Changelog
- License
The architecture is documented across 21 ADRs. The four most recent focus on the ADK paddock backend:
ADR-018
(field-readiness + pedagogy tuning from the 2026-04-29 Team 2 review),
ADR-019 (ADK multi-agent plan),
ADR-020 (7 structural fixes: PitwallOrchestrator, SequentialAgent/ParallelAgent pipelines, SQL safety, output_key pattern), and
ADR-021 (second audit: Runner API, KV cache via persistent sessions, PitwallTracingPlugin + DuckDB agent telemetry).
What this is: an in-cabin AI race coach for solo track-day drivers,
running offline on a Pixel + USB-CAN. Hot-path coaching cues at < 50 ms
(sonic_model + canonical phrase library, no LLM), warm-path verbal
coaching from on-device Gemma 4 E2B (paddock pre-brief + post-session
debrief, via litert-lm), and a full 18-agent ADK paddock coaching system
backed by Gemma 4 E4B. Coach avatar emotion (12-state taxonomy) is
Gemma-controlled — the LLM emits an [EMOTION: ...] tag with each
reply, surfaced to the PWA so the sprite plays the matching animation.
Telemetry sinks into local DuckDB (including agent traces); analytics
run client-side; no cloud is required for the core coaching loop.
What this isn't: a Garmin Catalyst (post-session debrief, fixed heuristics), an AiM Solo (pure logger, no coaching), or a cloud-only LLM tool. The differentiator is real-time coaching that works without cell coverage, grounded in published pedagogy (Ross Bentley) and adapted to driver skill level.
- Bridge: shipped. 56 HTTP endpoints, 358 tests passing, 0 skipped, smoke-tested end-to-end against a real 8273-frame Sonoma VBO with 51 assertions green.
- CAN pipeline: shipped.
python-canreader + simulator + DBC; 6 round-trip tests oninterface='virtual'. USB-CAN device enumeration via pyserial — adapter VID:PID lookup table covers CANable, Macchina M2, Korlan USB2CAN, PEAK, Kvaser, FTDI ELM327, CH340. - Termux foreground service: shipped. Drop-in install package in
[deploy/termux/](deploy/termux/INSTALL.md). - Three-tier coach architecture (ADR-017): shipped. In-drive cues are canonical phrases (no LLM); paddock brief + debrief are Gemma-via-litert-lm; cloud Gemini removed entirely.
- Field-readiness blockers + pedagogy tuning (ADR-018): shipped 2026-04-30. LLM friction sink, audio ducker, 1D Kalman dead-reckoning, intermediate-driver pedagogy refit.
- ADK multi-agent paddock backend (ADR-019–021): shipped 2026-05-01.
18 agents, 15 tools,
PitwallOrchestrator(BaseAgent),DebriefPipelinewithParallelAgent,BriefPipeline,Runner+ persistent sessions for KV cache reuse,PitwallTracingPlugin+agent_tracesDuckDB table,AgentMetaAgent, multi-turn Q&A (/coach/ask). All phases shipped ahead of original post-Sonoma schedule. **pitwall-webVue PWA**: design-only. 54 markdown files in[docs/vue/](docs/vue/README.md)— 38 screens specced, foundation- systems + sprite spec + character bible + Gemma-controlled emotion taxonomy + god navigation map. Code lives in a future sibling repo; implementation hasn't started.
- Sonoma field test: May 23, 2026.
pitwall/
├── src/pitwall/ # Pitwall Backend (Flask API)
│ ├── __main__.py # Application entrypoint
│ └── features/ # Feature-Sliced Design domains
│ ├── telemetry/ # can_reader, signals API
│ ├── session/ # vbo_parser, analytics, profiles, debrief API
│ ├── coaching/ # ADK agents, coach engine, TTS, LiteRT
│ ├── track/ # sonoma lore, track loaders, track API
│ └── realtime/ # Live cue streaming via SSE
├── src/simulator/ # Standalone HTTP Clients
│ ├── simulator.py # VBO-driven simulation runner
│ ├── pitwall_app.py # Standalone replay TUI
│ └── vbo_client.py # Standalone VBO parser for clients
├── scripts/ # Utility scripts
│ ├── best_sonoma_lap.py # S/F line projection utility
│ ├── import_sonoma_real_gps.py # OSM Overpass import
│ └── generate_sample_vbo.py # Synthetic VBO generation
├── data/
│ ├── dbc/pitwall.dbc # synthetic DBC (29 signals)
│ ├── registry/obd2_pids.json # signal registry seed (54 entries)
│ ├── tracks/sonoma.json + sonoma_real_gps.json
│ ├── reference/ # gold-standard lap traces
│ └── markers/sonoma/ # marker thumbnails
├── deploy/
│ └── termux/ # foreground-service install package
│ ├── INSTALL.md
│ ├── service/pitwall-bridge/{run, log/run}
│ └── boot/start-pitwall
├── docs/
│ ├── api.md # 56-endpoint reference
│ ├── adk-agent-architecture.md # ADK 18-agent topology, tools, KV cache, tracing
│ ├── adk-implementation-plan.md # as-built status + remaining backlog
│ ├── internal_architecture.md # post-2026-04-28 backend topology
│ ├── vue/ # CANONICAL pitwall-web design — 54 files
│ │ ├── README.md # index of the journey
│ │ ├── screens/ # 38 screen docs
│ │ └── …
│ ├── adr/ # 21 ADRs (019–021 cover ADK)
│ └── … # pedagogy, telemetry-pipeline, etc.
├── tests/ # 424 tests passing, modularised in tests/features/
├── android/ + android-app/ # FROZEN v1 native; deletes post-PWA
├── .claude/commands/adk.md # /adk slash command — ADK reference + audit tool
├── mkdocs.yml
├── CHANGELOG.md
└── README.md
pip install flask duckdb requests python-can cantools pyserialOn-device LLM coach — Gemma 4 E2B (warm path, in-drive):
pip install litert-lm
# Download the model (~2.4 GB, public HuggingFace repo, no token needed):
litert-lm import \
--from-huggingface-repo litert-community/gemma-4-E2B-it-litert-lm \
gemma-4-E2B-it.litertlm gemma-4-e2b
# Lands at ~/.litert-lm/models/gemma-4-e2b/model.litertlmADK multi-agent paddock backend — Gemma 4 E4B (pre-brief, debrief, Q&A):
pip install google-adk
# Download E4B (~4 GB) and start the server:
lit pull gemma-4-e4b
lit serve --port 8001 # run in a separate terminal before starting the bridgeBoth backends fall back silently when unavailable — the hot-path canonical phrase coach always works.
Note: earlier docs referenced
mediapipe+.taskfiles. We uselitert-lm(cross-platform macOS / Linux / Termux) with.litertlmformat. Per ADR-017.
# HTTP-only (no live CAN, no E4B server needed for basic operation)
python3 -m src.pitwall --track src/pitwall/features/track/sonoma.json
# → ✓ sonic_model loaded
# ✓ Track: Sonoma Raceway (12 corners)
# ✓ ADK coach_orchestrator loaded — 18 agents (LiteRT-LM E4B) ← if lit serve running
# 🏁 Pitwall Bridge v2 on http://127.0.0.1:8765
curl -s http://127.0.0.1:8765/health | python3 -m json.tool# Dev/test (virtual bus, no hardware needed)
python3 -m src.pitwall --can-channel pitwall_dev
# Replay a VBO file as CAN frames
python3 src/simulator/can_simulator.py \
--vbo "/path/Sonoma Intermediate - 1_47.5.vbo" \
--channel pitwall_dev --speed 2.0
# Production — USB-CAN on Pixel via Termux
python3 -m src.pitwall \
--can-interface slcan --can-channel /dev/ttyACM0 \
--can-dbc data/dbc/pitwall.dbcpython3 -m pytest tests/ -q
# → 424 passed, 0 skipped
python3 tests/test_endpoints_smoke.py --keep-db
# Ingests 8273-frame Sonoma VBO → exercises 56 endpoints → 51 assertions# Start a session, run a debrief, ask a question:
curl -X POST http://127.0.0.1:8765/session/start \
-H 'Content-Type: application/json' \
-d '{"driver": "taha", "track": "sonoma"}'
curl -X POST http://127.0.0.1:8765/coach/debrief \
-H 'Content-Type: application/json' \
-d '{"session_id": "<sid>", "driver_id": "taha"}'
curl -X POST http://127.0.0.1:8765/coach/ask \
-H 'Content-Type: application/json' \
-d '{"driver_id": "taha", "session_id": "<sid>", "question": "Why was lap 4 faster than lap 2?"}'
# Discover available agents:
curl http://127.0.0.1:8765/coach/agents | python3 -m json.tool┌──────────────────────────── Pixel 10 (in cabin) ─────────────────────────┐
│ │
│ USB-CAN adapter (CANable Pro / Macchina M2) │
│ │ slcan @ 500 kbps │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Termux foreground service (deploy/termux/) │ │
│ │ │ │
│ │ ┌──────────────┐ │ │
│ │ │ can_reader │ python-can + cantools DBC decode │ │
│ │ └──────┬───────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ DuckDB (pitwall_sessions.duckdb) │ │ │
│ │ │ telemetry · laps · coaching_notes · driver_events │ │ │
│ │ │ llm_friction · conversations · agent_traces │ │ │
│ │ └──────────────────────────────┬──────────────────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ HOT PATH <50ms │ │ │
│ │ │ sonic_model + RuleCoach + canonical phrase library │ │ │
│ │ ├──────────────────────────────────────────────────────────────┤ │ │
│ │ │ WARM PATH <100ms │ │ │
│ │ │ LitertCoach (Gemma 4 E2B, litert_lm.Engine in-process) │ │ │
│ │ ├──────────────────────────────────────────────────────────────┤ │ │
│ │ │ PADDOCK 2–15s │ │ │
│ │ │ PitwallOrchestrator → 18 ADK agents → Gemma 4 E4B │ │ │
│ │ │ (lit serve --port 8001, Tensor G5 NPU) │ │ │
│ │ │ brief · debrief · Q&A · voice scripts · agent telemetry │ │ │
│ │ └──────────────────────────────┬───────────────────────────────┘ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ Flask HTTP server 127.0.0.1:8765 (56 endpoints) │ │ │
│ │ │ GET /cues/stream → live coaching cues (SSE) │ │ │
│ │ │ GET /notifications → async inbox (SSE) │ │ │
│ │ │ POST /coach/brief → pre-session brief │ │ │
│ │ │ POST /coach/debrief → post-session debrief │ │ │
│ │ │ POST /coach/ask → multi-turn Q&A │ │ │
│ │ │ GET /coach/agents → agent registry (PWA) │ │ │
│ │ │ GET /session/<sid>/export.parquet │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────┬────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ pitwall-web (Vue 3 PWA) — IN DESIGN — │ │
│ │ 38 screens specced in docs/vue/ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
The paddock tier uses Google ADK with 18 specialist agents routed by a deterministic keyword classifier — no LLM routing.
PitwallOrchestrator(BaseAgent) ← keyword classifier, no LLM routing
├── DebriefPipeline (SequentialAgent)
│ ├── DebriefDataPhase (ParallelAgent) ← 3 agents run concurrently
│ │ ├── HighlightFinderAgent output_key=highlights_data
│ │ ├── TelemetryAgent output_key=telemetry_data
│ │ └── PedagogyAgent output_key=pedagogy_data
│ └── NarrativeAgentDebrief reads {highlights_data}
│ {telemetry_data}
│ {pedagogy_data}
├── BriefPipeline (SequentialAgent)
│ ├── PedagogyAgent output_key=pedagogy_data
│ └── NarrativeAgentBrief
└── 15 QA specialist agents → GoldLapAgent · WeatherAdaptationAgent
SessionPlannerAgent · IncidentReviewAgent
RacePaceAgent · GoalSettingAgent
MentalMapAgent · VoiceScriptAgent
LapComparisonAgent · CornerCoachAgent
ProgressTrackerAgent · SetupAdvisorAgent
MindsetCoachAgent · AgentMetaAgent
TelemetryAgent (default)
Key properties:
- All 15 tools query DuckDB
read_only=TruewithLIMIT 500enforced PitwallTracingPluginlogs every agent run + tool call toagent_tracesDuckDB table- Persistent ADK sessions per driver —
lit serveclones KV cache across calls (~30–50% prefill reduction on warm requests) POST /session/starttriggersreset_driver_session()for a clean session baseline
See [docs/adk-agent-architecture.md](docs/adk-agent-architecture.md) for the full topology, tool specs, KV cache design, and agent telemetry schema.
| Path | Budget | Notes |
|---|---|---|
| Hot path — sonic_model cue | < 50 ms | ADR-002 |
| In-drive canonical phrase (RuleCoach) | < 100 ms | ADR-017 |
| Pre-brief LLM (Gemma 4 E2B, cold) | 6–8 s | measured on Apple Silicon CPU |
| Post-session debrief LLM (E2B) | 8–12 s | measured |
| ADK paddock — cold call (new session) | 2–15 s | E4B via lit serve, Tensor G5 NPU |
| ADK paddock — warm call (persistent session) | ~30–50% less | KV cache clone; session instructions pre-filled |
| CAN frame → DuckDB row | < 5 ms | src/pitwall/features/telemetry/can_reader.py |
| Agent trace DuckDB write | < 1 ms | batch drain after run_adk() |
| End-to-end smoke (8273 frames + every endpoint) | ~12 s | tests/test_endpoints_smoke.py |
| Parquet export (90-min session) | < 500 ms | GET /session/<sid>/export.parquet |
For the May 23 Sonoma field test:
| Item | Role | Notes |
|---|---|---|
| Pixel 10 | Compute + display + audio gateway | Tensor G5 NPU for E4B + E2B; Termux + bridge + PWA in Chrome |
| USB-CAN adapter | Reads OBD-II powertrain bus | CANable Pro ( |
| Powered USB-C OTG hub | Charge passthrough during 4-hour session | needs USB-PD passthrough |
| OBD-II cable | Connects USB-CAN adapter to car port | standard OBD-II to DB9 |
| Pixel Earbuds | Audio coaching output (TTS) | ANC + tight fit handle cabin noise |
Test target: 2003 BMW M3 (E46) at Sonoma Raceway.
All coaching is on-device. Every LLM call goes through litert-lm
or lit serve locally — pre-brief, debrief, Q&A, /score. There is no
cloud LLM dependency.
All telemetry, lap data, coaching notes, driver profiles, and agent
traces live in a local DuckDB on the device running the bridge. The
bridge's HTTP server binds to 127.0.0.1 only and is not externally
reachable without an explicit tunnel or POST /spectator/token.
The PWA's analytics flow uses GET /session/<sid>/export.parquet to
hydrate a client-side DuckDB-Wasm instance — Parquet lives in browser
OPFS only, never sent off-device.
[docs/api.md](docs/api.md)— every endpoint with request/response shapes[docs/adk-agent-architecture.md](docs/adk-agent-architecture.md)— ADK topology: 18 agents, 15 tools,PitwallOrchestrator,Runner, KV cache,agent_tracesschema[docs/adk-implementation-plan.md](docs/adk-implementation-plan.md)— as-built status + remaining backlog (SSE streaming, state scopes, LoopAgent)[docs/internal_architecture.md](docs/internal_architecture.md)— backend topology with mermaid diagrams[docs/adr/index.md](docs/adr/index.md)— all 21 architecture decisions
- ADR-019 — ADK multi-agent plan (conversations table, agent roster, Q&A endpoints)
- ADR-020 — 7 structural fixes:
PitwallOrchestrator,SequentialAgent/ParallelAgent,output_key, SQL safety,save_voice_scripts - ADR-021 — second audit:
RunnerAPI, race condition fix, persistent sessions + KV cache,PitwallTracingPlugin+agent_traces - ADR-018 — field-readiness: LLM friction sink, audio ducker, Kalman dead-reckoning, pedagogy refit
- ADR-017 — three-tier coach; cloud Gemini removed
[docs/vue/README.md](docs/vue/README.md)— canonical PWA design: 38 screens, emotion taxonomy, navigation map[docs/vue/10-coach-emotions.md](docs/vue/10-coach-emotions.md)— 12-emotion taxonomy, Gemma prompt contract[docs/vue/11-navigation-map.md](docs/vue/11-navigation-map.md)— god mermaid + per-screen reference[docs/ux.md](docs/ux.md)— UX principles: audio-first, silence-is-coaching, fail-open
[deploy/termux/INSTALL.md](deploy/termux/INSTALL.md)— Pixel + Termux foreground service install[docs/android_parallel_llm_install.md](docs/android_parallel_llm_install.md)— sideload pitwall-parallel + android-llm-service on one device (embedded PWA + on-device LLM)[docs/hardware.md](docs/hardware.md)— sensor + adapter + cable spec[docs/sonoma_track_intelligence.md](docs/sonoma_track_intelligence.md)— corner-by-corner reference + danger zones + weather phases
[docs/pedagogy.md](docs/pedagogy.md)— Ross Bentley curriculum in the coaching engine[docs/coaching-engine.md](docs/coaching-engine.md)— rule + LiteRT-LM coach composition
pip install mkdocs-material
mkdocs serve -a 127.0.0.1:8889| Suite | Count | Notes |
|---|---|---|
tests/ (pytest) |
424 passed, 0 skipped | Unit + integration; vendored fixtures, no network |
tests/features/telemetry/test_can_pipeline.py |
9 of 424 | Round-trip on interface='virtual' + ADR-018 dead-reckoner wiring |
tests/features/telemetry/test_dead_reckoning.py |
13 of 424 | ADR-018 1D Kalman filter unit suite |
tests/features/coaching/test_coach_engine_litert.py |
8 of 424 | Live-Gemma tests; auto-skip when model file is absent |
tests/test_endpoints_smoke.py |
51 assertions, 0 failed | End-to-end against a real 8273-frame Sonoma VBO |
python3 -m pytest tests/features/test_api_core.py -q
python3 -m pytest tests/features/telemetry/test_can_pipeline.py -q
python3 -m pytest tests/features/telemetry/test_dead_reckoning.py -q
python3 -m pytest tests/features/coaching/test_coach_engine.py -q
python3 -m pytest tests/features/coaching/test_coach_engine_litert.py -q # auto-skip without model
python3 -m pytest tests/features/session/test_api_session.py -q
python3 tests/test_endpoints_smoke.pyThe May 23, 2026 Sonoma Raceway field test is the load-bearing milestone.
Before Sonoma (3 weeks):
- Scaffold
pitwall-web— Vue 3 + Vite + Tailwind PWA per[docs/vue/](docs/vue/README.md). MVP: boot path → session loop → 1 analytics screen → tutorial overlay. Bridge contract is complete. - Pre-rendered TTS phrase library — bake canonical phrases per coach into MP3 clips, packaged into
pitwall-web/public/audio/coaches/<id>/ - Per-car DBC packs — Subaru GR86 next; hardware enumeration table ready to extend
Post-Sonoma:
- ADK SSE streaming —
POST /coach/ask/streamwithRunConfig(streaming_mode=SSE)so the Vue PWA shows tokens as they arrive (critical for 2–15s E4B latency) - ADK state scopes —
user:prefix for driver preferences persisting across sessions;app:for shared track conditions - Vue PWA paddock Q&A screen — connects to
/coach/ask+/coach/agents - Multi-track support — Laguna Seca and Thunderhill slot ready in
data/tracks/ - Sprite generation — nano-banana prompts ready in
[docs/vue/assets/reference-sheet-source.md](docs/vue/assets/reference-sheet-source.md); ~56 frames for 4 non-T-Rod coaches - Public release + license — TBD post-field-test
- Framework refactor — per ADR-018: abstract
CoachContext, parameterise LSTM input layer, decouple spatial awareness engine
android/ and android-app/ contain the v1 Flutter + native Kotlin
implementation. No longer the active frontend — see
ADR-016. Both will
be removed once pitwall-web reaches feature parity.
| Role | Person |
|---|---|
| Tech Lead | Hemanth HM |
| Edge / Telemetry | Simon Margolis |
| AGY Pipeline | Taha Bouhsine |
| Data Reasoning | Vijay Vivekanand |
| UX / Frontend | Aileen Villanueva |
- Ross Bentley, Performance Driving Illustrated — pedagogical foundation for every coaching rule. Distilled into 9 concepts in
coach_engine.match_bentley_concept. - The T-Rod Sonoma transcript — track-specific coaching anchors (the bridge, the K-wall bend, Calamity Corner) that make verbal coaching feel native to Sonoma.
- Google ADK — multi-agent orchestration framework.
PitwallOrchestrator,SequentialAgent,ParallelAgent,BasePluginandRunnerpower the 18-agent paddock backend. - Google AI Edge /
litert-lm— on-device Gemma 4 inference, cross-platform. Replaced the original MediaPipe Genai approach. - Racelogic VBO format — canonical telemetry file format consumed by simulator and post-session import.
- python-can + cantools — CAN ingest stack.
- DuckDB — single-process SQL persistence with first-class Parquet support. Now stores coaching, conversations, and agent traces in one file.
- Team 2 architecture review (2026-04-29, "CONDITIONAL PASS") — punch list that became ADR-018: LLM friction logging, audio ducker, Kalman dead-reckoning, intermediate-driver pedagogy refit.
Pitwall is an internal sprint repository for the May 23, 2026 Sonoma field test. External contributions are not currently accepted.
For team members:
- Branch from
master; merge via PR. Tests must pass before merge. - Run
python3 -m pytest tests/ -qandpython3 tests/test_endpoints_smoke.pybefore opening a PR that touches ingest, lap detection, or the sink. - New endpoints get an entry in
[docs/api.md](docs/api.md)plus a test intests/test_bridge.py. - New ADK agents get an entry in
AGENT_REGISTRYinadk_agents.pyand a row in the agent catalogue in[docs/adk-agent-architecture.md](docs/adk-agent-architecture.md). - Architectural changes get a new ADR in
docs/adr/(ADR-NNN-slug.md). Updatedocs/adr/index.mdin the same PR.
- Python 3.10+. Type hints encouraged on new code.
- Standard PEP 8; 4-space indent; double quotes; f-strings.
- Module-level docstrings on every public file; short docstrings on exported functions.
- Imports: stdlib → third-party → local, separated by blank lines; no star-imports.
- Side effects in
if __name__ == "__main__":only. - Tests: real-shape fixtures over mocks; round-trip tests on virtual buses preferred.
- New ADK tools:
@_adk_tooldecorator, SQL safety (LIMIT 500, non-SELECT rejected), file locks for any writes (fcntl.flock).
Notable changes by date in CHANGELOG.md. Architecture decisions in [docs/adr/](docs/adr/index.md).
TBD — to be added before public release.