> ## Documentation Index
> Fetch the complete documentation index at: https://docs.generalrobotics.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure navigation

> Override a profile, author a complete config, and select per-mission behavior.

Start with your robot's calibrated profile and change only the fields you
need. Navigation configuration controls the robot-side runtime: motion limits,
camera calibration, localization, geometry, collision handling, controller
tuning, recovery, and recording. It is different from the map and route
settings in the [mission editor](/navigation/mission-editor).

## Choose the right kind of change

| Goal | Interface | When it takes effect |
| - | - | - |
| Change a few calibrated settings | `make_nav(robot, overrides={...})` | New Nav construction |
| Select a robot profile | `profile`, optional `config_dir` and `omni` | New Nav construction |
| Supply your complete config | `cfg` as a dictionary or robot-side YAML path | New Nav construction |
| Change recording or replanning for one mission | `run_mission(..., **options)` | That run only |
| Inspect the effective config | `nav.config` | Snapshot returned at attach |

A Nav instance is shared by clients. **Any settings on an attach to an
existing instance are rejected**, even if they are identical. Stop does not
release the instance. Read its state, coordinate with its owner, then
`shutdown()` before constructing a replacement. Never use replacement as
an implicit way to interrupt someone else's mission.

## 1. Inspect what your robot uses

This attaches without changing settings and prints selected effective values.
`nav.config` is a plain dictionary, not a live editable config object.
It can be `None` on a runtime that does not report its configuration.

```python theme={null}
from grid_nexus_client import make_robot
from grid_nexus_client.nav import make_nav

robot = make_robot("YOUR_ROBOT")
nav = make_nav(robot)
print(nav.status())
if nav.config is None:
    raise RuntimeError("This runtime did not report a config; update it before exporting")
print(nav.config["robot"]["robot_type"])
print(nav.config["max_linear_vel"])
print(nav.config["runtime"])
```

With no explicit settings, Nexus uses the robot's `GRID_NAV_CONFIG` file when
configured; otherwise PointNav selects the profile matching the robot's
registry type. Bundled profile names currently include `r1pro`, `vision60`,
`go2`, `g1`, and `dr02`. A bundled profile's presence is not proof that a
particular deployment has navigation provisioned or its required sensors.

## 2. Override a few fields

Use dotted field paths and native Python values. For example, below we lower
the maximum forward speed and increase the localization query timeout while
keeping the rest of the robot's selected profile. Run this replacement only
when you own the current navigation workflow.

```python theme={null}
if nav.status().mission_running:
    raise RuntimeError("Wait for the current mission's owner before replacing navigation")
nav.shutdown()
nav = make_nav(
    robot,
    overrides={
        "max_linear_vel": 0.2,
        "localization.hloc_query_timeout_s": 15.0,
    },
    record="default",
)
print(nav.config["max_linear_vel"])
```

The override names are `max_linear_vel` and
`localization.hloc_query_timeout_s`, not mission arguments. Unknown dotted
paths are errors. Paths traverse nested dictionaries, not list indices:
replace an entire list when changing polygon points or similar fields.
Values are copied and validated. Geometry propagation fills selected
unset/default fields; it does not reliably refresh every derived value in
an exported configuration. See the geometry limitations below.

### Precedence

For a newly constructed Nav, settings are applied in this order:

1. Dataclass/schema defaults fill omitted fields.
2. The selected robot profile **or** explicit config supplies values.
3. A selected YAML/profile `omni` section is merged.
4. Dotted `overrides` replace existing fields.
5. Explicit runtime keywords such as `backend` and `record` take precedence.

The robot-side `GRID_SAFETY_MODE` environment variable is applied when a
YAML/mapping is loaded and is read again by safety construction. In this
runtime it can therefore supersede an explicit `safety.mode` override.
Have the operator remove that environment override or make it agree with
the intended mode; inspecting `nav.config` alone does not prove which mode
the later safety builder will select.

A full `cfg` replaces profile selection; it is **not merged with the
robot's calibrated profile**. A tiny config such as
`{"max_linear_vel": 0.2}` fills everything else from generic schema defaults,
including camera and robot defaults. Use dotted overrides for a small change.

## 3. Start a complete config from a known-good snapshot

This alternative preserves your current resolved configuration, changes two
fields, and saves a JSON copy on your computer. JSON is also useful to agents
because field values and types are explicit. Submit the dictionary over Nexus;
you do not need PointNav installed locally.

```python theme={null}
import copy
import json
from pathlib import Path

if nav.status().mission_running:
    raise RuntimeError("Do not replace a running mission")
if nav.config is None:
    raise RuntimeError("No resolved config is available")
custom = copy.deepcopy(nav.config)
custom["max_linear_vel"] = 0.2
custom["runtime"]["run_name"] = "inspection"
Path("navigation-config.json").write_text(json.dumps(custom, indent=2) + "\n")
nav.shutdown()
nav = make_nav(robot, cfg=custom)
```

The saved file contains calibration and robot-side asset paths. Keep it
associated with that robot and deployment. A config is not a map, mission,
credential file, or way to install missing robot assets.

## 4. Supply a YAML file on the robot

For deployment-managed configuration, install a YAML file on the robot's
filesystem and pass its absolute path. Nexus does not upload your laptop's
file when given a string.

```python theme={null}
nav = make_nav(robot, cfg="/opt/grid/config/navigation.yaml")
```

This is an alternative constructor after the old Nav has been released.
`config=` is an alias for `cfg=`; do not pass both. Do not combine an explicit
config with `profile` or `config_dir`.

The following excerpt shows the **shape**, not a complete robot calibration:

```yaml theme={null}
max_linear_vel: 0.2
max_angular_vel: 0.5
control_hz: 10.0
runtime:
  backend: mppi
  record: default
  run_name: inspection
  enable_cloud_replan: false
robot:
  robot_type: r1pro
  camera_name: head_camera_left
localization:
  hloc_query_timeout_s: 15.0
```

[Download a complete R1Pro omni example](/assets/navigation/r1pro-omni.yaml)
and [the configuration JSON Schema](/assets/navigation/navigation-config.schema.json).
Use the matching installed robot profile as your starting point; the R1Pro
example is not a generic config for other robots.
The [field reference](/navigation/config-reference) lists every schema field
and distinguishes generic defaults from profile-specific values.

### Advanced: export and validate on the robot

Customers use `grid_nexus_client.nav` and the `nav.config` export above from their laptop.
The following commands are for an operator on the robot (or a deployment
environment with its matching `grid-pointnav` installed). Export the profile
without opening robot hardware:

```bash theme={null}
grid-nav-run --robot-type r1pro --omni --generate-config > nav.yaml
```

Use your actual robot type. Select `--no-omni` for a profile's base settings.
To validate an edited YAML and inspect the resolved result:

```bash theme={null}
grid-nav-run --nav-config /absolute/path/nav.yaml --generate-config > resolved-nav.yaml
```

`--generate-config` exits before robot connection and mission execution.
The loader validates types and cross-field constraints. Structural JSON
Schema checks alone do not enforce all calibration, geometry, and mode rules.
Export the matching installed schema on that same machine:

```python theme={null}
import json
from pathlib import Path

from grid_pointnav.configs import navigation_config_json_schema

Path("navigation-config.schema.json").write_text(json.dumps(navigation_config_json_schema(), indent=2) + "\n")
```

### Paths, overlays, and calibration

* YAML must have a mapping root; duplicate keys are rejected. Use nested
  mappings, not dotted keys inside a YAML file.
* Relative model/calibration paths declared in YAML resolve relative to that
  file **on the robot**. Paths in a dictionary resolve from the robot
  process's working directory. Prefer explicit absolute paths or supported
  `package://grid_pointnav/...` asset references.
* Dotted path overrides currently bypass part of the asset/calibration resolver.
  For a changed calibration file or package-relative asset path, use a complete
  YAML/mapping configuration and verify its loaded camera metadata rather than
  assuming a dotted path change reloads intrinsics.
* A top-level `omni:` mapping is a YAML/profile overlay with the same nested
  shape. Explicit YAML configs apply it only with `omni=True`; dictionaries
  must already contain the resolved settings and cannot use that overlay.
* Profile-based R1Pro construction applies its omni section by default when
  present; `omni=False` selects its base profile. Other profiles default to
  their base settings.
* `robot.query_intrinsics` is a 3×3 matrix.
  `query_image_size` and `query_resolution` are `[width, height]`;
  an empty resolution preserves native pixels.
  `T_body_camera` and `T_body_lidar` are flat 16-element row-major transforms.
  Use the frame conventions of your calibrated profile.
* Keep camera intrinsics, distortion, extrinsics, LiDAR frame flags,
  self-masking, and robot geometry consistent. Changing a motion cap does not
  recalibrate a camera or shrink the physical robot.

For a custom profile directory, put a file named after the profile
(for example `r1pro.yaml`) on the robot and use this alternative:

```python theme={null}
nav = make_nav(robot, profile="r1pro", config_dir="/opt/grid/nav-profiles", omni=True)
```

An explicit `config_dir` wins over `GRID_POINTNAV_CONFIG_DIR`, then the
bundled directory. The profile's `robot.robot_type` must match its name.

## Geometry, safety zones, and controller timing

These are alternative construction recipes. Shut down an existing Nav before
creating another one with different settings; they do not retune an attached
instance in place.

### Body geometry and derived values

`robot_geometry.footprint_polygon` describes the measured body-frame hull:
metres, X forward and Y left. Supply at least three finite, non-collinear
`[x, y]` vertices enclosing the body origin. Body geometry uses a convex hull;
`footprint_padding_m` adds clearance around it. For profiles without a polygon,
`inscribed_radius_m` supplies the circular fallback.

Inspect the root geometry together with the controller and safety sections:

```python theme={null}
if nav.config is None:
    raise RuntimeError("No resolved config is available")
for section in ("robot_geometry", "dwa", "mppi_pipeline", "safety"):
    print(section, nav.config[section])
```

The loader copies some derived geometry into DWA, MPPI and the safety barrier.
Those copies can become stale after changing the root shape in an exported
config. Numeric backend fields do **not** generally accept `None` as an
inheritance setting. Start geometry changes from the matching installed
profile and review all consumer values, including `dwa.footprint_polygon`,
`dwa.footprint_padding`, `dwa.robot_radius`, both controller inflation radii,
and `safety.barrier_footprint_polygon`.

A nonpositive root `inflation_radius_m` asks the loader to derive an inflation
radius and store the resolved number. Exporting that number does not retain
an automatic relationship for later geometry edits. Explicit backend tuning
may differ intentionally; inflation is distance from an obstacle, not extra
body padding.

<Warning>
  The current runtime has known inconsistencies in DWA costmap radii/padding
  and MPPI padded-corner radius calculations. Config loading does not reject
  all malformed footprints. A successful config load is not evidence that a
  custom body shape is collision-safe. Geometry changes require checking the
  actual controller footprint and clearance in simulation or robot qualification.
</Warning>

With an articulated projection model, live joint geometry updates the shape
at runtime; the static polygon is its configured starting shape.

### Collision zones

`safety.mode="gate"` selects the legacy forward/reverse and swept-barrier
filter. Its front/rear bands, predictive rectangle, and barrier settings
apply to that mode. `safety.mode="nav2"` selects the planar collision monitor,
including lateral motion; its settings are under `safety.nav2`.
Omni MPPI requires the Nav2 mode. `safety.enabled` controls filtering.

Nav2 zone coordinates are **body-frame metres**, unlike mission keepouts,
which use map coordinates. Choose `type="polygon"`, `"circle"`, or
`"velocity_polygon"`:

* A polygon uses ordered `points` describing a simple shape. Concave zones
  are supported. Supply finite vertices without duplicate edges, zero area,
  or self-intersections; the current loader does not reject every invalid shape.
* A circle uses a positive `radius`.
* `use_robot_footprint=True` is for polygon zones. It uses the padded measured
  body and follows live footprint updates. Leave `points` unset.
* A velocity polygon uses `velocity_polygons`, each with `points`, inclusive
  `linear_min/max` and `theta_min/max`. In holonomic mode, linear range is speed
  magnitude and direction is constrained by `direction_start_angle` and
  `direction_end_angle` in radians. A start angle above the end wraps across π.
  First matching subpolygon wins; an uncovered command retains the last shape.
  The current runtime can pass an uncovered command before any shape has
  been selected. Cover the full intended velocity range and include an
  unconditional stop zone rather than relying on an initially unmatched zone.

Each zone selects `action_type`: `stop` zeros the command; `slowdown` multiplies
it by `slowdown_ratio` in \[0, 1]; `limit` scales it to the linear and angular
caps; `approach` scales it using predicted time to contact; `none` does nothing.
`min_points` is the positive count of obstacle returns required to trigger.
The trigger/release counts debounce consecutive evaluations, not seconds.
For approach, `simulation_time_step` must be positive and no greater than
`time_before_collision`.

This is a minimal body-stop policy. Replacing `polygons` removes all previous
zones, including a profile's slowdown and approach zones; include every zone
you intend to retain:

```python theme={null}
body_stop = {
    "name": "body-stop",
    "type": "polygon",
    "use_robot_footprint": True,
    "action_type": "stop",
    "min_points": 1,
    "trigger_consecutive_points": 1,
    "release_consecutive_points": 2,
}
nav = make_nav(
    robot,
    overrides={
        "safety.mode": "nav2",
        "safety.nav2.polygons": [body_stop],  # Replaces the complete zone list.
    },
)
```

The independent Nav2 filter uses `processing_frequency_hz`. `source_timeout`
limits scan age, `cmd_vel_timeout` limits command age, and `stop_pub_timeout`
controls continued zero publication. A zero source/command timeout disables
that age check. `base_shift_correction` compensates scan-time robot motion.
The legacy navigation-loop `safety.lidar_max_points` cap uses stride sampling;
zero evaluates the full scan. Sampling can discard obstacle returns and alter
`min_points` decisions; the independent Nav2 actuator filter uses the full scan.

### MPPI timing precedence

`controller_frequency_hz=0` inherits `control_hz`. The solve rate is that rate
divided by positive integer `command_decimation`; intermediate ticks hold the
last command. Native `model_dt` is the reciprocal of the solve rate.

Use explicit `time_steps` for ordinary configuration. In the current runtime,
`prediction_horizon_s` is used only in a legacy environment-override path,
not as a general duration control. With `GRID_MPPI_DECIM` unset in the robot
process, this example gives 30 prediction steps of 0.1 seconds:

```python theme={null}
nav = make_nav(
    robot,
    backend="mppi",
    overrides={
        "control_hz": 20.0,
        "mppi_pipeline.controller_frequency_hz": 0.0,
        "mppi_pipeline.command_decimation": 2,
        "mppi_pipeline.time_steps": 30,
        "mppi_pipeline.legacy_g1_env_overrides": False,
    },
)
# Requires GRID_MPPI_DECIM to be unset in the robot process.
# 20 outer ticks/s / 2 = 10 solves/s; 30 steps at 0.1 s = 3 seconds.
```

`legacy_g1_env_overrides=True` enables the legacy `GRID_MPPI_*` controls.
When `GRID_MPPI_HZ` or `GRID_MPPI_DECIM` is present in that mode, a duration
can derive the step count unless `GRID_MPPI_TSTEPS` is supplied. One current
exception is `GRID_MPPI_DECIM`: it applies even when the legacy flag is false.
Leave the flag false and have the operator unset `GRID_MPPI_DECIM` for the
explicit timing recipe above.

### Progress, recovery, and smoothing

The controller's progress checker uses movement radius, optional heading
change, and a time allowance under `dwa_pipeline` or `mppi_pipeline`.
The stall detector tracks sustained blocking/no progress. The separate `stuck`
section owns mission-level timeout and bounded recovery rearming; these are
different stages, not interchangeable thresholds.

`recovery.mode="none"` disables recovery. The current `simple` and `adaptive`
values both enable the configured recovery policy. A nonempty `schedule`
wins; otherwise `profile.enabled=True` generates a schedule from the profile.
`profile.adaptive_goals` separately enables perception-derived goals.
`pose_align` applies when the final waypoint requests a heading.

For velocity smoothing, `mode="legacy"` uses scalar acceleration limits;
`mode="nav2"` uses `[vx, vy, wz]` vectors. Acceleration and deadband values are
nonnegative; deceleration vector entries are nonpositive. `min_velocity`
must not exceed `max_velocity` on any axis. `feedback="CLOSED_LOOP"` uses
measured velocity; `OPEN_LOOP` uses the smoother's previous output.

## Per-mission options

These serializable options affect one run without rewriting `nav.config`:

| Option | Meaning |
| - | - |
| `run_name` | Label for run artifacts. |
| `record` | Select recording channels, described below. |
| `enable_cloud_replan` | Enable global replanning during this run; default false. |
| `cloud_replan_cooldown_s` | Minimum interval between cloud replans, nonnegative seconds; default 1.0. |
| `cloud_replan_timeout_s` | Positive cloud replan timeout, seconds; default 1.0. |
| `stream_viz_grx` | Enable GRX live streaming; not required for the CLI viewer. |
| `stream_viz_port` | GRX port 1–65535; zero or None requests automatic selection. |
| `constraints` | A versioned mapping of locally enforced polygon policies. |

Choose these after starting/localizing and reviewing your mission. This
example **moves the robot** and checks its terminal result.

```python theme={null}
MISSION = "YOUR_PATH_UUID"
if input("Drive the reviewed mission? Type RUN: ") != "RUN":
    raise RuntimeError("Mission not started")
result = nav.run_mission(
    MISSION,
    run_name="inspection",
    record="nav,pose,cmd,gt,rgb",
    enable_cloud_replan=True,
    cloud_replan_cooldown_s=2.0,
    cloud_replan_timeout_s=2.0,
)
if result.status != "succeeded":
    raise RuntimeError(f"Mission {result.status}: {result.message}")
```

Do not put controller fields such as `max_linear_vel` directly in
`run_mission`. Change them at construction through `overrides`.
Object-valued internal worker/test hooks are not portable Nexus inputs.

### Recording

`record="default"` (and `None`) selects `nav,pose,cmd,gt`.
`record="off"` disables bags. `record="all"` adds the heavier
`lidar,costmap,rgb,odom_fast` channels. A comma-separated string selects
**only** the named channels, so use `"nav,pose,cmd,gt,rgb"` to add images
to the default set. Unknown channel names are ignored with a warning;
check spelling. Recording is best-effort and requires robot-side GRX.

Bags are robot-side artifacts; the navigation proxy does not expose a bag
download method. The live viewer's terminal-log download is a different
artifact. Turning recording off does not turn off Nexus live telemetry.

### Polygon constraints

The current runtime enforces polygon **keepout** and positive **cost** zones
in its local rolling costmap. These are not editor drawing tools and do not
make the cloud planner constraint-aware. A route can still be globally
planned through a zone and then blocked locally; review the route accordingly.

The mapping has `version: 1`, optional `id` (default empty string),
`revision` (nonnegative integer, default 0), `zones` (default empty list),
and `semantic_rules` (must be empty today). Each zone requires a unique,
nonempty `id`, `type`, and `polygon` of at least three map-frame XY pairs.
A `cost` zone also requires `cost` from 1 to 252; keepout zones omit it.

This example only creates the policy dictionary. Replace the empty polygon
with coordinates from the active map before using it.

```python theme={null}
POLYGON = []  # Fill with at least three verified map-frame [x, y] vertices.
if len(POLYGON) < 3:
    raise ValueError("Define the polygon on your active map first")
constraints = {
    "version": 1,
    "id": "inspection-policy",
    "revision": 1,
    "zones": [{"id": "restricted-area", "type": "keepout", "polygon": POLYGON}],
    "semantic_rules": [],
}
```

After review, pass it as `constraints=constraints` to a mission call.
The broader stored contract also names `preferred`, `speed_limit`,
`planner_penalty`, `max_linear_mps`, and semantic rules, but the local
runtime rejects these unsupported policies rather than silently applying
part of them. There is no public remote method to edit constraints mid-run.

## Finish and verify

After constructing a new Nav, inspect `nav.config` again, then start the map
and verify localization before executing a mission. To finish, use
`nav.shutdown()` followed by `robot.shutdown()`.
See the [API contract](/navigation/api-reference) for runtime errors and the
[configuration field reference](/navigation/config-reference) for the complete shape.


## Related topics

- [Navigation through GRID CLI](/navigation/overview.md)
- [Navigation API contract](/navigation/api-reference.md)
- [Configuration field reference](/navigation/config-reference.md)
- [Agentic navigation](/cli/agentic-navigation.md)
- [Navigation troubleshooting](/navigation/troubleshooting.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.