Skip to content

smargolis/pitwall

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

87 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Pitwall β€” Trustable AI Racing Coach

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

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 / what this isn't

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.

Status

  • 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-can reader + simulator + DBC; 6 round-trip tests on interface='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), DebriefPipeline with ParallelAgent, BriefPipeline, Runner + persistent sessions for KV cache reuse, PitwallTracingPlugin + agent_traces DuckDB table, AgentMetaAgent, multi-turn Q&A (/coach/ask). All phases shipped ahead of original post-Sonoma schedule.
  • **pitwall-web Vue 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.

Repo layout

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

Quick start

Install

pip install flask duckdb requests python-can cantools pyserial

On-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.litertlm

ADK 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 bridge

Both backends fall back silently when unavailable β€” the hot-path canonical phrase coach always works.

Note: earlier docs referenced mediapipe + .task files. We use litert-lm (cross-platform macOS / Linux / Termux) with .litertlm format. Per ADR-017.

Run the bridge

# 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

Live CAN ingest

# 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.dbc

Run the test suite

python3 -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

ADK paddock Q&A

# 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

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ 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/                                    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ADK multi-agent coaching

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=True with LIMIT 500 enforced
  • PitwallTracingPlugin logs every agent run + tool call to agent_traces DuckDB table
  • Persistent ADK sessions per driver β€” lit serve clones KV cache across calls (~30–50% prefill reduction on warm requests)
  • POST /session/start triggers reset_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.

Performance budget

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

Hardware

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 ($60) or Macchina M2 ($90)
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.

Data privacy & residency

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.

Key documentation

Backend / bridge

  • [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_traces schema
  • [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

Recent ADRs

  • 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: Runner API, 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

Frontend (pitwall-web)

  • [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

On-track operators

  • [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

Pedagogy

  • [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

Build the docs site

pip install mkdocs-material
mkdocs serve -a 127.0.0.1:8889

Test status

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.py

Roadmap

The 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/stream with RunConfig(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

Frozen artefacts

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.

Team 2 (Intermediate, BMW M3)

Role Person
Tech Lead Hemanth HM
Edge / Telemetry Simon Margolis
AGY Pipeline Taha Bouhsine
Data Reasoning Vijay Vivekanand
UX / Frontend Aileen Villanueva

Acknowledgements

  • 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, BasePlugin and Runner power 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.

Contributing

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/ -q and python3 tests/test_endpoints_smoke.py before 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 in tests/test_bridge.py.
  • New ADK agents get an entry in AGENT_REGISTRY in adk_agents.py and 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). Update docs/adr/index.md in the same PR.

Code style

  • 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_tool decorator, SQL safety (LIMIT 500, non-SELECT rejected), file locks for any writes (fcntl.flock).

Changelog

Notable changes by date in CHANGELOG.md. Architecture decisions in [docs/adr/](docs/adr/index.md).

License

TBD β€” to be added before public release.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages

  • Python 44.7%
  • Vue 21.3%
  • Kotlin 20.3%
  • TypeScript 6.9%
  • HTML 5.7%
  • CSS 0.6%
  • Other 0.5%