ADR-026 — Phase-2 ADK Agent Expansion: AiM signal coverage¶
Status: Accepted Date: 2026-05-28 Relates to: ADR-019, ADR-020, ADR-021, ADR-024, ADR-025
Context¶
ADR-019/020/021 shipped a 17-agent ADK paddock backend with 15 SQL-safe
tools. The agent roster was sized for the V1 telemetry envelope — the six
canonical signals (speed_ms, g_lat, g_long, combo_g, brake_bar,
throttle_pct) emitted by the original Racelogic VBO ingest. Every
existing agent reasons from that six-signal slice.
The AiM MXP pipeline (data/cars/bmw_e46_m3.yaml + the bridge's tall-sink
telemetry_signals table) emits a much richer signal set: four-corner TPMS
(pressure / temperature / alarm bitfield), 4-corner wheel speeds, 3-axis
gyros (yaw / roll / pitch rate), the powertrain stream (oil / water / fuel
pressure, three temp channels), and the safety-flag stream (abs_fail,
dsc_reg, mil_chk_eng, brake_switch). Inspection of the staged 2026-05-23
Sonoma recording showed 35+ AiM-canonical signals in telemetry_signals —
of which only one (combo_g) is used by the existing agent roster.
Five signal groups had no agent owner:
| Signal group | Untapped insight |
|---|---|
| TPMS | tire mgmt, cold→hot delta, leak detection |
| 3-axis gyro | measured U/O, weight-transfer rate |
| 4-corner wheel speeds | wheelspin, lockup, diff behaviour |
| Engine health | reliability, stint-limit warnings |
| Status flags | "ABS dropped at lap 4" explaining pace loss |
SetupAdvisorAgent came closest — it infers car balance from coasting
patterns — but it had no access to the directly measured yaw-rate signal
that would tell it which corners are over- vs. understeering, and no
access to TPMS or input-quality data.
A separate smoke test against the real Sonoma SQLite also surfaced two pipeline bugs the existing agents couldn't see:
- The YAML
0xFFFF"no reading" sentinel passes through unfiltered forbrake_press_psi → brake_bar, polluting any aggregate (max brake = 4519 bar from 0.085 % of frames). - The YAML §7 sign-convention
verify-on-first-capturestep had not been applied to this car — measured yaw and steering are sign-flipped relative to the bicycle-model expectation.
Decision¶
Phase-2 expansion: add six new ADK specialists that each own one AiM signal domain end-to-end, extend six existing agents with the new tools so they can cross-reference the new findings, expand the brief/debrief narrative pipelines so the orchestrator (the "god agent") consumes all the new signal-domain outputs.
Six new specialist agents¶
Each owns one signal domain and publishes findings under a named
output_key that the narrative templates cite.
| Agent | Signal domain | Output key | Backing tool |
|---|---|---|---|
TireManagerAgent |
TPMS pressure/temp/alarms | tire_data |
get_tire_thermal_window |
HandlingBalanceAgent |
yaw rate × steering bicycle model | handling_data |
get_handling_balance |
EngineHealthAgent |
oil/water/fuel pressure + temps | engine_health_data |
get_engine_health_timeline |
TractionAgent |
4-corner wheel-speed deltas | traction_data |
get_traction_events |
InputQualityAgent |
steering/throttle/brake jerk + smoothness | input_quality_data |
get_input_smoothness |
SafetyMonitorAgent |
ABS/DSC/MIL/TPMS alarm timeline | safety_data |
get_safety_events |
Agent count: 17 → 23. Tools: 15 → 21.
Six existing agent extensions¶
The QA agents that touch per-session questions gain the new tools so they can cross-reference what the specialists found. Routing is unchanged — they still own the same intent — but their reasoning is now richer:
TelemetryAgent←+get_safety_eventsLapComparisonAgent←+get_engine_health_timeline,+get_safety_eventsCornerCoachAgent←+get_handling_balance,+get_traction_eventsSetupAdvisorAgent←+get_handling_balance,+get_input_smoothness,+get_tire_thermal_windowHighlightFinderAgent←+get_input_smoothness,+get_tire_thermal_windowWeatherAdaptationAgent←+get_tire_thermal_windowSessionPlannerAgent←+get_tire_thermal_windowIncidentReviewAgent←+get_safety_events,+get_traction_eventsRacePaceAgent←+get_engine_health_timeline,+get_tire_thermal_window
Pipeline expansion: god agent reads everything¶
DebriefPipeline grows from 3 → 9 parallel data agents in its
DebriefDataPhase:
DebriefDataPhase = ParallelAgent(
HighlightFinderAgentDebrief, // highlights_data
TelemetryAgentDebrief, // telemetry_data
PedagogyAgentDebrief, // pedagogy_data
TireManagerAgentDebrief, // tire_data ← NEW
HandlingBalanceAgentDebrief, // handling_data ← NEW
EngineHealthAgentDebrief, // engine_health_data ← NEW
TractionAgentDebrief, // traction_data ← NEW
InputQualityAgentDebrief, // input_quality_data ← NEW
SafetyMonitorAgentDebrief, // safety_data ← NEW
)
DebriefPipeline = Sequential(DebriefDataPhase, NarrativeAgentDebrief)
BriefPipeline grows from 1 → 4 parallel data agents — only the domains
that carry from the prior session into today's pre-brief:
BriefDataPhase = ParallelAgent(
PedagogyAgentBrief, // pedagogy_data
TireManagerAgentBrief, // tire_data ← NEW
EngineHealthAgentBrief, // engine_health_data ← NEW
SafetyMonitorAgentBrief, // safety_data ← NEW
)
BriefPipeline = Sequential(BriefDataPhase, NarrativeAgentBrief)
Each pipeline-internal agent is a clone of its public sibling with the
same output_key (satisfies ADK's single-parent invariant).
The _NARRATIVE_TAIL template now exposes nine output slots:
Session highlights: {highlights_data?}
Telemetry analysis: {telemetry_data?}
Pedagogy context: {pedagogy_data?}
Tire window + alarms: {tire_data?}
Handling balance: {handling_data?}
Engine health: {engine_health_data?}
Traction events: {traction_data?}
Input quality: {input_quality_data?}
Safety events: {safety_data?}
Empty slots collapse to "" via ADK's {key?} optional binding so the
template stays valid when an upstream data agent has nothing to say (e.g. a
clean session with no safety events).
Intent classifier¶
Six new intents wired into _INTENT_TO_AGENT and the
PitwallOrchestrator.sub_agents list:
"tire": tire_manager_agent,
"handling": handling_balance_agent,
"engine_health": engine_health_agent,
"traction": traction_agent,
"input_quality": input_quality_agent,
"safety": safety_monitor_agent,
_INTENT_PATTERNS reordered so specialty intents win over the broader
corner / weather / telemetry catch-alls. The patterns dropped the
leading \b so compound tool-name identifiers like
get_tire_thermal_window also classify correctly — \b doesn't match
between _ and a letter (both are word chars), and ADK chat prompts
routinely reference tools by their compound names. setup lost the
understeer / oversteer / balance keywords — those route to
HandlingBalanceAgent now (which measures the balance) while
SetupAdvisorAgent keeps the inferential coast/oscillation/setup-language
path.
AiM-pipeline findings baked into the tools¶
The phase-2 tools encode lessons surfaced by the prior agent runs:
- Sentinel filtering —
_signal_aggregate(lo=, hi=)clamps the0xFFFFAiM no-reading marker (becomes 4519 bar afterpsi_to_bar, 200 °C on the temp channels). Documented in YAML §8; the bridge doesn't apply it, so the tools do. - Lap-modulo distance attribution —
distance_mis cumulative across laps in the AiM ingest; tools fold via% TRACK_LENGTH_Mbefore matching corner bounds. - YAML §7 sign-convention warning —
get_handling_balancewatches the per-session counter-steer-event ratio and surfaces a calibration warning when > 50 % of samples have opposite-sign steering and yaw (indicating the first-capture sign verification hasn't been applied). - Engine-health load gating — oil-pressure-low anomaly only fires when
rpm > 3000 AND throttle_pct > 10so idle low-pressure (cold start, in-pit) doesn't generate false "bearing starvation" alarms.
Tool safety: catch SQL errors¶
_q() now wraps conn.execute(sql, params) in try/except and returns
[{"error": "..."}] on failure. Without it, an agent-generated SELECT
with a syntax error propagates sqlite3.OperationalError up through ADK's
Runner.run_async() and crashes the parallel TaskGroup of the
DebriefDataPhase — taking out all 9 data agents on the failure of any one.
Consequences¶
Positive:
- Every signal the AiM YAML emits now has an agent that can talk about it.
- The "god agent" (
PitwallOrchestrator) brief/debrief narratives see every signal domain. Drivers can ask "did I improve at T6?" and the CornerCoach answer cross-references measured handling balance and traction events for T6, not just lap-time grade history. - Sentinel-aware aggregates correct one of the YAML-pipeline bugs the
earlier agent runs surfaced (max brake
4519 bar → 90.8 barafter the filter, matching the realistic E46 M3 brake-pressure envelope). - The
SafetyMonitorAgentdirectly answers the "what changed" question when pace drops mid-session — previously this needed manual SQL. HandlingBalanceAgentfinally puts the gyros to work and emits the measurement engineers want (balance ratio per corner), instead of the inferential coast-percent heuristic.
Negative:
- 9 parallel data agents per debrief means 9× the LocalLLM token cost per paddock debrief call. On gemma4:e4b via Ollama that's ~80 s for the whole debrief on this Mac (vs. ~25 s for the V1 3-way phase). On Pixel 10 production targets — still acceptable for paddock-tier 2–15 s budget per agent, fan-out is parallel.
- Bigger surface to keep ADR-aligned. Six new tools to test, six new intents to keep classifier-tuned.
- The lap-modulo distance attribution assumes Sonoma —
TRACK_LENGTH_Mcomes frompitwall.features.track.sonoma. Multi-track sessions will need a per-session track length lookup; not blocking today.
Neutral:
- Phase-2 doesn't change anything about the LocalLLM transport (ADR-024/ADR-025), the hot path (ADR-017), or the warm path. Everything added is paddock-tier.
Validation¶
- 18/18 intent-classifier cases pass for the new + old patterns.
- All six new tools run cleanly against the real Sonoma SQLite, returning real numbers: 90.8 bar peak brake (sentinel-filtered), 1.81 G peak combo, oil pressure 0.77–5.79 bar with 1 load-gated anomaly, 321 wheelspin events, 94.1/100 smoothness score, 1 safety event (brake-switch).
- DebriefPipeline executes the 9-way parallel data phase end-to-end and produces a narrative citing tire / handling / engine numbers.
_qerror wrap prevents agent-generated SQL bugs from killing the TaskGroup.
References¶
- ADR-019 — original 18-agent topology
- ADR-020 — Sequential/Parallel pipeline pattern
- ADR-021 —
RunnerAPI + ParallelAgent race fix + KV-cache sessions data/cars/bmw_e46_m3.yaml— AiM MXP CAN config + sentinel documentationdata/formulas/standard.yaml— formula registry forpsi_to_bar,f_to_c, etc.apps/edge-daemon/pitwall/adk_tools.py— 21 tools (15 V1 + 6 phase-2)apps/edge-daemon/pitwall/features/coaching/adk_agents.py— 23 agents + orchestrator + pipelines