# plannerd Module Decision-Making Process in openpilot: Complete Technical Breakdown

> Explore the plannerd module's decision-making process in openpilot. Understand how it uses MPC, model predictions, and vehicle data to create safe acceleration trajectories.

- Repository: [comma.ai/openpilot](https://github.com/commaai/openpilot)
- Tags: deep-dive
- Published: 2026-03-05

---

**The `plannerd` module is openpilot's core longitudinal planning daemon that processes `modelV2` predictions, vehicle dynamics, and radar data through a Model Predictive Control (MPC) solver to generate safe acceleration trajectories and lane departure warnings.**

The `plannerd` process serves as the brain of commaai/openpilot's autonomous driving stack, executing a continuous decision-making pipeline that transforms raw sensor inputs into actionable control commands. This real-time module orchestrates the complex interplay between neural network predictions and vehicle physics to ensure safe longitudinal control.

## Initialization and Real-Time Configuration

On startup, `plannerd` configures itself as a low-priority real-time process using `config_realtime_process(5, Priority.CTRL_LOW)` in [`selfdrive/controls/plannerd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/plannerd.py) (lines 12-13). The module loads persistent vehicle parameters via `Params().get("CarParams")` to instantiate the `LongitudinalPlanner` and `LaneDepartureWarning` helpers (lines 15-21). Messaging infrastructure initializes through `PubMaster` for broadcasting `longitudinalPlan` and `driverAssistance` topics, while `SubMaster` subscribes to critical inputs including `modelV2`, `radarState`, and `carState` (lines 21-23).

## The Main Decision Loop

The core decision-making cycle triggers exclusively on fresh `modelV2` predictions. When `sm.updated['modelV2']` returns true (lines 25-28), the pipeline executes two parallel tracks: longitudinal trajectory planning and lane departure monitoring.

### Sensor Data Acquisition and modelV2 Triggers

The loop blocks on `sm.update()` until new data arrives. The decision gate at line 25 ensures computational resources are only consumed when the neural network produces fresh lane and object predictions, maintaining synchronization between perception and planning layers.

### Longitudinal Planning Pipeline

The `LongitudinalPlanner.update(sm)` method (lines 86-166 in [`selfdrive/controls/lib/longitudinal_planner.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/lib/longitudinal_planner.py)) executes a sophisticated multi-stage decision flow:

- **Vehicle Pitch Compensation**: Calculates coasting acceleration based on road gradient.
- **Cruise State Management**: Handles speed setpoints and reset logic when openpilot disengages.
- **Throttle Gating**: Applies safety limits based on model-predicted "gas press probability" to prevent unintended acceleration.
- **Lateral-Aware Acceleration Clipping**: Reduces longitudinal acceleration during high-curvature turns to respect tire friction limits.
- **MPC Trajectory Optimization**: Solves for optimal `v_solution`, `a_solution`, and `j_solution` (jerk) profiles through `self.mpc.update`.
- **Profile Smoothing**: Applies first-order filters and final acceleration clipping to ensure drivable comfort.
- **Experimental Mode Blending**: Selects between pure MPC output or minimum-of-MPC/E2E predictions when experimental features are enabled.

### Lane Departure Warning System

Simultaneously, `ldw.update()` (lines 16-36 in [`selfdrive/controls/lib/ldw.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/lib/ldw.py)) evaluates lane boundary violations by checking:

- **Driver Intent**: 5-second blinker cooldown period to suppress false positives during intentional lane changes.
- **Speed Thresholds**: Minimum velocity requirements (`vEgo > LDW_MIN_SPEED`).
- **Lane Confidence**: Model-predicted lane visibility and lane-change probability thresholds.

### Message Publishing and Broadcasting

The pipeline concludes with dual message emission. `longitudinal_planner.publish(sm, pm)` (lines 69-93) constructs the `longitudinalPlan` message containing trajectory solutions, solver timing metrics, lead vehicle flags, and forward collision warnings. Separately, lines 31-36 in [`plannerd.py`](https://github.com/commaai/openpilot/blob/main/plannerd.py) assemble the `driverAssistance` message with `leftLaneDeparture` and `rightLaneDeparture` Boolean flags, validated against `carState`, `carControl`, `modelV2`, and `liveParameters` checks.

## Key Source Files and Architecture

Three primary files constitute the decision-making architecture according to the commaai/openpilot source code:

- **[`selfdrive/controls/plannerd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/plannerd.py)**: Orchestrates the main loop and message routing.
- **[`selfdrive/controls/lib/longitudinal_planner.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/lib/longitudinal_planner.py)**: Implements the MPC-based trajectory optimization and constraint handling.
- **[`selfdrive/controls/lib/ldw.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/lib/ldw.py)**: Contains the lane departure detection logic.

## Minimal Implementation Example

The following standalone snippet mimics the core decision flow for testing or documentation purposes:

```python
import cereal.messaging as messaging
from openpilot.selfdrive.controls.lib.ldw import LaneDepartureWarning
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, Priority

def run_once():
    config_realtime_process(5, Priority.CTRL_LOW)

    # Load vehicle parameters

    params = Params()
    CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)

    # Helpers

    ldw = LaneDepartureWarning()
    planner = LongitudinalPlanner(CP)

    # Messaging setup

    pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance'])
    sm = messaging.SubMaster(['carControl', 'carState',
                              'controlsState', 'liveParameters',
                              'radarState', 'modelV2', 'selfdriveState'],
                             poll='modelV2')

    # One iteration (normally inside a while‑True loop)

    sm.update()
    if sm.updated['modelV2']:
        planner.update(sm)
        planner.publish(sm, pm)

        ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
        msg = messaging.new_message('driverAssistance')
        msg.valid = sm.all_checks(['carState', 'carControl', 'modelV2', 'liveParameters'])
        msg.driverAssistance.leftLaneDeparture = ldw.left
        msg.driverAssistance.rightLaneDeparture = ldw.right
        pm.send('driverAssistance', msg)

# Call run_once() repeatedly in production.

```

## Summary

- The `plannerd` module operates as a real-time process with `Priority.CTRL_LOW` timing constraints configured in [`selfdrive/controls/plannerd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/plannerd.py).
- Decision cycles trigger exclusively on `modelV2` updates to ensure perception-planning synchronization.
- The `LongitudinalPlanner.update()` method fuses vehicle pitch, cruise settings, and MPC optimization to generate safe acceleration profiles.
- Lane departure warnings run in parallel, applying blinker cooldowns and speed thresholds to reduce false positives.
- The system outputs `longitudinalPlan` trajectories and `driverAssistance` alerts through cereal messaging.

## Frequently Asked Questions

### What triggers the plannerd decision-making cycle?

The cycle activates only when `sm.updated['modelV2']` detects fresh neural network predictions in [`selfdrive/controls/plannerd.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/plannerd.py) (lines 25-28). This event-driven architecture ensures the longitudinal planner consumes the latest lane and object detection data before computing trajectories.

### How does the longitudinal planner prevent dangerous acceleration during turns?

The system implements **lateral-aware acceleration clipping** inside `LongitudinalPlanner.update()` ([`selfdrive/controls/lib/longitudinal_planner.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/lib/longitudinal_planner.py) lines 86-166). This logic reduces longitudinal acceleration limits based on current lateral dynamics, ensuring the vehicle maintains tire friction reserves for cornering.

### What is the difference between the longitudinalPlan and driverAssistance outputs?

`longitudinalPlan` contains the MPC-computed speed and acceleration trajectory (`v_solution`, `a_solution`) for the vehicle controller, while `driverAssistance` carries binary lane departure flags (`leftLaneDeparture`, `rightLaneDeparture`) for the user interface warnings.

### How does plannerd handle driver-initiated lane changes?

The `LaneDepartureWarning` class enforces a 5-second blinker cooldown period in [`selfdrive/controls/lib/ldw.py`](https://github.com/commaai/openpilot/blob/main/selfdrive/controls/lib/ldw.py) (lines 16-36). When recent turn signal activity is detected, the system suppresses departure warnings to avoid alerting during intentional maneuvers.