You want to be a race engineer. Not the podcast kind. The pit wall kind. One lap just completed. The next is already forming. Position, gaps, tyre age, compound, laps left, safety car. The board is loud. The window is tiny. In a split second, you have to decide: box, or stay out.
That’s the job this project aims at. Building an AI race engineer is messier than it looks. Every decision depends on tyre life, gaps, traffic, stint length, weather, safety cars, and rivals’ strategies. Pit stops are rare, and a confident LLM will happily invent a gap that never existed.
So I started small. Not “full multi-agent pit wall.” Not “live race radio.” Just three boring, measurable phases:
Same board every time: Hamilton, British GP 2024, lap 22. Same question once the world model exists: pit or stay? Same held-out / frozen evals so the scoreboard doesn’t drift. Let’s walk through it.
At its core, this project turns Ergast-compatible F1 data (plus FastF1 tyre laps) into a single pit-wall view: position, gaps, stint age, compound, safety car flags. Then asks: Will this driver pit on lap + 1?
That’s deliberately narrow. Pit stops are sparse in the wild, so classifier F1 looks ugly early. That’s fine. Phase 0 gives you a board you can trust. Phase 1 gives you a reproducible baseline. Phase 2 swaps the float for an explainable strategy card. Every later idea has to hang off that same snapshot.
Concept breakdown:
Before any model or agent, you need one function that answers: what does the pit wall see right now?
python scripts/print_sample_state.py \ --year 2024 --name-contains British --driver-id 1 --lap 22
Race: British Grand Prix (2024)Driver: HAMCircuit: Silverstone CircuitLap: 22 / 52Current Position: P3Gap Ahead: +1.001sGap Behind: -1.688sTyres: Compound: MEDIUM Age: 22 laps (set)Safety Car: NoPit this lap: NoPit stops so far: 0Last lap: 91095 msCars on track: 19
Under the hood: load races/drivers/lap times/pit stops, compute gaps from cumulative lap times, derive stint age from the last pit, join safety-car windows and tyre compound/life. Output is a typed RaceState (and a human pit_wall_view).
Integrity: sample random (race, driver, lap) boards and check that the world model doesn’t contradict itself.
No decision yet. Just a board you can seek like a DVR. Everything below hangs off this.
Before any agent gets to sound smart, you need a dumb model that predicts the label from that board.
Label: y = 1 If the driver pits on lap + 1.
Features: lap fraction, stint age, gaps, position, pit count, pace delta, circuit, safety-car flags, compound, tyre life.
Split: train 2018–2022, val 2023, test 2024–2025. Threshold tuned on val for max F1.
python scripts/train_pit_baseline.pypython scripts/score_pit_sample.py \ --year 2024 --name-contains British --driver-id 1 --lap 22
On that Hamilton board, history says stay (y_true=0). The model still fires a pit with a high score: classic false positive. Mid-stint boards look “pit-ish” to a crude classifier.
Held-out test (primary = HGB):
Yes, the F1 is rough. Pit-next-lap is a sparse positive class; AUROC says the ranking isn’t random. Pit-next-lap prediction is an extremely imbalanced problem. Most laps are “stay out,” so a model can rank situations well while still struggling to identify the rare pit windows. Adding circuit / SC/compound helped realism more than the F1 number; timing alone already carried most of the signal for this label. That’s the baseline. Ship it. Plot it. Move on.
Same decision surface. Different contract: don’t return a float. Return a strategy card.
The prompt is built from an anonymized pit-wall view: withhold race/year/driver so the model can’t cheat by remembering the race. Circuit stays, pit loss, and undercut context matter.
System Prompt (abridged):
You are an F1 strategy engineer on the pit wall.Decide: pit on the upcoming lap (L+1) or stay out.Identity (race / year / driver) is withheld; circuit is kept.Reply with ONLY JSON: {action, tyre, push, reason, rationale}.Don’t invent numbers. Don’t name drivers or events.Dry race: at least one stop on a different compound before the flag.
User Prompt (abridged):
Race state (pit-wall feed):Race/Driver: (identity withheld)Circuit: Silverstone CircuitLap: 22 / 52 · P3 · Gap ahead +1.001s · Gap behind -1.688sTyres: MEDIUM, age 22 · SC: No · Pit stops so far: 0Decide: pit on the upcoming lap, or stay out?
Identity (race/year/driver) is withheld so the model can’t cheat by recalling the real weekend; circuit is kept because pit loss and undercut windows depend on the track.
Same Hamilton board, heuristic backend:
{ "action": "stay", "tyre": null, "push": "med", "reason": "Current stint still viable", "rationale": "Current stint still viable; stay out."}
Same board, OpenAI backend (one run, not deterministic):
{ "action": "pit", "tyre": "hard", "push": "high", "reason": "Stop now for hard before the 22-lap mediums lose further performance.", "rationale": "P3 is on 22-lap mediums with no stop completed at lap 22/52. Pit on the upcoming lap and use the hard for the required different-compound stint."}
decide_once.py always prints the same CrewChiefDecision shape: six keys every time. Call/wording will differ by model; the schema does not.
Heuristic stay matches history on this board; the LLM run above calls pit (a mandatory different-compound stint is still open). Schema validity on the frozen set: 100%.
Frozen eval (150 points, seed 42):
Sol trails the trained classifier here for a boring reason: zero-shot on a thin board vs a model that saw years of pit labels. The win in Phase 2 isn’t beating HGB yet. It’s explainable, structured decisions with a schema you can validate and retry.
Silverstone 2024 · HAM · lap 22: P3, +1.001s ahead, Mediums age 22, 30 laps left, no SC.
Same input. Three contracts. One recurring example, so you can argue about the number, not the setup.
A few things this stack will not pretend away:
If you’re building agents on sports (or any sparse event stream), ship the world model and the weak baseline early. Otherwise, you’ll spend a month polishing prompts with nothing to plot.
Phases 0–2 are intentionally unsexy: a seekable race replay, a classifier that misfires, and a JSON crew chief with a schema you can validate. Together, they give you something most “AI race engineer” demos skip: a board you can trust, a number you can plot, and a decision format that isn’t free-form vibes.
Part 2 picks up here: tool calling (gaps, stint age, remaining laps, undercut stats) so the agent can’t invent numbers in the rationale. Pace models and strategy sims come after that. For now: load the board, accept the ugly F1, and make the crew chief speak JSON.
Built on historical Ergast-compatible F1 data + FastF1 tyre laps.
Repo: github.com/tapanBabbar9/f1 — package f1-pitwall.
Building an AI Race Engineer (Part 1): From Race Replay to Crew Chief was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.