nav2
The nav-vertical core tool skill for robium: bringup, the BT Navigator and
behavior trees, costmap layers, the planner/controller/smoother servers,
localization (AMCL and slam_toolbox), waypoint following, and tuning a
running stack. Nav2 config in this skill targets ROS 2 Jazzy Jalisco
(LTS, supported to May 2029) rather than Lyrical Luth, the current
overall LTS the ros2 skill defaults to — Nav2 has not yet shipped binary
packages for Lyrical (tracked in ros-navigation/navigation2#6123 as of
2026-07). Gazebo Harmonic is Jazzy's paired simulator. Re-check that gap
before starting a new project, and treat picking the distro itself as
architect's call, not this skill's — load this skill once architect has
routed you to the navigation vertical.
When to use this skill
- Any autonomous mobile-robot navigation task: bringing up Nav2, tuning
costmaps or the planner/controller, adding or debugging a behavior tree,
choosing between AMCL and slam_toolbox, following waypoints.
- The trigger phrases in the description: 'navigation', 'nav2', 'costmap',
'path planning', "robot won't move to goal", 'localization', 'SLAM',
'AMCL', 'waypoint'.
- A robot receives a goal and never moves, or moves erratically — start here
(see
references/common-failures.md), not in application logic.
- Cross-references — go to the sibling skill instead when the question is:
- ROS 2 substrate this stack runs on (workspaces, colcon, packages, nodes,
QoS, launch syntax, TF2 concepts/broadcasters) →
ros2. This skill's
only TF responsibility is verifying the map→odom→base_link chain exists
and is current before tuning anything else; teaching TF2 itself stays in
ros2 — see that skill's interfaces-and-qos and debugging references.
- Simulating the robot Nav2 drives → the
gazebo skill.
- Visualizing costmaps, TF, or BT execution (RViz2, Foxglove) → the
visualization skill.
- Arm/manipulation tasks, learned policies →
lerobot. Nav2 is mobile-base
navigation only.
- Environment/Docker setup for the ROS 2 + Nav2 + Gazebo stack →
environments.
- The whole-stack decision this feeds into →
architect (routes here).
Key directives
- Delegation posture: embed + links. The navigation-specific concepts
(BT Navigator, costmap layers, server roles, AMCL vs slam_toolbox) live in
this skill and its references in depth, because no single upstream page
covers them as a coherent whole for a new project — but every parameter
table and default value is a link back to docs.nav2.org or the
navigation2 GitHub repo, not re-typed from memory. See References.
- Start from the official minimal config, change one subsystem at a
time.
examples/nav2-params-diffdrive.yaml is adapted from
nav2_bringup's own nav2_params.yaml (jazzy branch) — begin a new robot
there, verify it navigates, then change exactly one subsystem (footprint,
controller plugin, costmap layer) before touching the next. Changing
costmap, controller, and planner parameters simultaneously makes a
regression impossible to bisect.
use_sim_time must be consistent across every node, every time. In
simulation, every Nav2 node, the map/odom TF broadcasters, and the sim
clock source must all agree on use_sim_time: true (or all agree on
false on real hardware) — one node left on the wrong value produces TF
extrapolation errors and rejected goals that look like a planning bug but
are a clock mismatch. Set it once, globally, in the params file passed to
every node (see examples/nav2-params-diffdrive.yaml and
examples/bringup-launch-snippet.py), not per-node.
- Verify the TF tree — map→odom→base_link — before tuning anything
else. Costmap2D blocks activation until a full TF tree is available, and
AMCL will not publish
map→odom until it has an initial pose (from
RViz's "2D Pose Estimate" or the /initialpose topic). A robot that
"won't move" is, more often than a bad planner or controller parameter, a
broken or incomplete TF chain — check this first with ros2 run tf2_ros tf2_echo map base_link every time, before touching costmap or controller
tuning. See references/common-failures.md.
- Never write Nav2 parameter defaults, plugin names, or version/status
claims from memory. They change release to release (the controller
default alone has moved from DWB to MPPI upstream). Verify against
docs.nav2.org or the
navigation2 GitHub repo before repeating a claim in
a real project — every example in this skill is marked status: unverified for exactly this reason, and each reference states how its
claims were checked this session.
Quick start
1. Confirm the ROS 2 substrate is ready. A sourced Jazzy workspace with
Nav2 installed (sudo apt install ros-jazzy-navigation2 ros-jazzy-nav2-bringup
— re-verify the package name against docs.nav2.org's install page before
running it) — see the ros2 skill if the workspace itself isn't set up yet.
2. Bring up Nav2 with the example config. Copy
examples/nav2-params-diffdrive.yaml and examples/bringup-launch-snippet.py
into your project, keeping the params filename the launch snippet expects
(or updating both together — see Customization), then:
ros2 launch ./bringup-launch-snippet.py map:=/path/to/your_map.yaml use_sim_time:=true
3. Verify before tuning. Confirm every managed node is active
(ros2 lifecycle get /controller_server etc.) and the TF chain is complete
(ros2 run tf2_ros tf2_echo map base_link) — see
references/common-failures.md if either check fails.
4. Send a goal through RViz2's "Nav2 Goal" tool, or programmatically —
see the "send goals programmatically" usage pattern below.
Usage patterns
Bringup with an existing map. Pass a saved map YAML and leave slam
false — Nav2 launches nav2_map_server + nav2_amcl for localization
against that static map. Set the robot's initial pose (RViz "2D Pose
Estimate" or publish to /initialpose) immediately after launch; AMCL does
not publish map→odom until it has one. See
examples/bringup-launch-snippet.py (map:= argument) and
references/nav2-architecture.md's localization section.
SLAM-then-navigate. Launch with slam:=true and no map:= argument —
this runs Nav2 without nav2_map_server/nav2_amcl and expects a SLAM node
(slam_toolbox's online_async_launch.py, launched alongside) to publish
/map and the map→odom transform instead. Drive the robot to explore,
then save the resulting map with nav2_map_server's map_saver_cli (see
map_saver's params in examples/nav2-params-diffdrive.yaml) once mapping
is done, so the next run can go back to AMCL-on-a-fixed-map. See
references/nav2-architecture.md.
Send goals programmatically. Use nav2_simple_commander's
BasicNavigator Python class rather than hand-rolling NavigateToPose
action clients: goToPose() / goThroughPoses() for single/multi-pose
goals, followWaypoints() for a waypoint list, and non-blocking
isTaskComplete()/getResult() polling for feedback in a single-threaded
script. See references/nav2-architecture.md's commander-API section for a
minimal snippet shape.
Tune for a new robot footprint/speed. Start from
examples/nav2-params-diffdrive.yaml's local_costmap/global_costmap
robot_radius (switch to an explicit footprint polygon for a non-circular
base), then the controller's velocity/acceleration limits and
velocity_smoother's max_velocity/max_accel/max_decel — change these
before touching planner or BT internals, since a wrong footprint or speed
limit makes every downstream navigation attempt look broken. See
references/tuning-guide.md.
Platform gotchas
- Jazzy, not Lyrical, until Nav2 ships Lyrical binaries. See the intro
paragraph above; this is a binding, repo-wide fact (
architect and
ros2 both reference it) — don't silently "upgrade" a nav2 project to
Lyrical without re-checking ros-navigation/navigation2#6123 first.
- AMCL is silent, not erroring, without an initial pose. A freshly
launched AMCL-based stack with no
/initialpose published will sit idle —
no error, just no map→odom transform and a costmap that never
activates. This looks identical to a hung launch; check for a missing
initial pose before debugging anything else. See
references/common-failures.md.
- Composed (
use_composition:=true) vs standalone nodes change crash
behavior. nav2_bringup defaults to component-container composition; a
crash inside one composed node can take down the whole container process,
whereas standalone nodes (use_composition:=false, with
use_respawn:=true) restart independently. Prefer standalone + respawn
while iterating on a new robot; composition is a later performance
optimization, not a default to fight while still debugging.
- Gazebo Harmonic's
/clock must actually be publishing before any node
with use_sim_time:=true will progress — a paused or not-yet-started
Gazebo world leaves every Nav2 node waiting on TF timestamps that never
arrive, which looks like a Nav2 hang rather than a sim issue.
Customization
- Different robot footprint or drive type: swap
robot_radius for an
explicit footprint polygon in both local_costmap and global_costmap
in examples/nav2-params-diffdrive.yaml, and change FollowPath's
motion_model (e.g. "DiffDrive" → "Omni") if the base isn't
differential-drive — see references/tuning-guide.md.
- Different controller/planner plugin: the params file's
controller_server.FollowPath.plugin and
planner_server.GridBased.plugin fields select the algorithm; swapping
requires the matching plugin name and its own parameter block (e.g.
nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController or
nav2_smac_planner::SmacPlannerHybrid) — verify exact plugin/class names
against docs.nav2.org's configuration guide before writing them, they
are not interchangeable strings. See references/tuning-guide.md.
- Different params filename or launch structure:
examples/bringup-launch-snippet.py's params_file default and
examples/nav2-params-diffdrive.yaml's own filename must be kept in sync
if you rename either — the launch snippet resolves the params path
relative to itself, so a silent rename of one without the other produces a
"params file not found" failure at launch, not a subtle runtime bug.
- Reverting to Lyrical Luth: once Nav2 ships Lyrical binaries (re-check
ros-navigation/navigation2#6123), swap jazzy for lyrical in every
install command and Docker base image in the environments skill's
Dockerfile.ros2 example; nothing in this skill's params/launch content itself is
distro-specific beyond the install step.
References
references/nav2-architecture.md — the BT Navigator and default behavior
trees, the planner/controller/smoother/behavior/waypoint-follower servers,
costmap 2D layers (global vs local), the lifecycle manager, AMCL vs
slam_toolbox, and the nav2_simple_commander API.
references/tuning-guide.md — costmap resolution/update-rate/inflation
tuning, footprint vs radius, controller/planner plugin selection,
velocity/acceleration limits, and the "one subsystem at a time" workflow.
references/common-failures.md — the "robot won't move" diagnostic
checklist: lifecycle state, TF tree, use_sim_time consistency, costmap
obstacle sourcing, goal rejection, and cmd_vel not reaching the base.
examples/nav2-params-diffdrive.yaml — adapted from nav2_bringup's
official minimal diff-drive nav2_params.yaml (status: unverified — file
header states the exact source and the deviations made).
examples/bringup-launch-snippet.py — a project launch file that includes
nav2_bringup's own bringup_launch.py, pointed at this skill's example
params file (status: unverified — file header states the exact source).
- Upstream: Nav2 documentation (primary source for
this skill, reachable via direct fetch this session), navigation2 GitHub
repo, jazzy branch
(source of the params/launch examples, fetched directly via raw GitHub
URLs this session), nav2_simple_commander
docs, slam_toolbox
GitHub. Sibling skills:
ros2 (foundation, load alongside), gazebo (sim),
visualization (debugging), environments (Docker/env
setup), architect (routes here).
Changelog
1---2name: nav23description: Nav2 mobile-robot navigation for ROS 2: bringup, behavior trees, costmaps, planner/controller servers, localization (AMCL, slam_toolbox), waypoint following, and tuning. Use when: 'navigation', 'nav2', 'costmap', 'path planning', 'robot won't move to goal', 'localization', 'SLAM', 'AMCL', 'waypoint', or any autonomous mobile robot task. Load after architect selects the ROS nav stack; pairs with ros2 (foundation), gazebo (sim), and visualization (debugging). Not for: manipulation (lerobot) or generic ROS 2 issues (ros2).4---56# nav278The nav-vertical core tool skill for robium: bringup, the BT Navigator and9behavior trees, costmap layers, the planner/controller/smoother servers,10localization (AMCL and slam_toolbox), waypoint following, and tuning a11running stack. Nav2 config in this skill targets ROS 2 **Jazzy Jalisco**12(LTS, supported to May 2029) rather than **Lyrical Luth**, the current13overall LTS the `ros2` skill defaults to — Nav2 has not yet shipped binary14packages for Lyrical (tracked in `ros-navigation/navigation2#6123` as of152026-07). Gazebo Harmonic is Jazzy's paired simulator. Re-check that gap16before starting a new project, and treat picking the distro itself as17`architect`'s call, not this skill's — load this skill once `architect` has18routed you to the navigation vertical.1920## When to use this skill2122- Any autonomous mobile-robot navigation task: bringing up Nav2, tuning23 costmaps or the planner/controller, adding or debugging a behavior tree,24 choosing between AMCL and slam_toolbox, following waypoints.25- The trigger phrases in the description: 'navigation', 'nav2', 'costmap',26 'path planning', "robot won't move to goal", 'localization', 'SLAM',27 'AMCL', 'waypoint'.28- A robot receives a goal and never moves, or moves erratically — start here29 (see `references/common-failures.md`), not in application logic.30- Cross-references — go to the sibling skill instead when the question is:31 - ROS 2 substrate this stack runs on (workspaces, colcon, packages, nodes,32 QoS, launch syntax, TF2 concepts/broadcasters) → `ros2`. This skill's33 only TF responsibility is verifying the map→odom→base_link chain exists34 and is current before tuning anything else; teaching TF2 itself stays in35 `ros2` — see that skill's interfaces-and-qos and debugging references.36 - Simulating the robot Nav2 drives → the `gazebo` skill.37 - Visualizing costmaps, TF, or BT execution (RViz2, Foxglove) → the38 `visualization` skill.39 - Arm/manipulation tasks, learned policies → `lerobot`. Nav2 is mobile-base40 navigation only.41 - Environment/Docker setup for the ROS 2 + Nav2 + Gazebo stack →42 `environments`.43 - The whole-stack decision this feeds into → `architect` (routes here).4445## Key directives4647- **Delegation posture: embed + links.** The navigation-specific concepts48 (BT Navigator, costmap layers, server roles, AMCL vs slam_toolbox) live in49 this skill and its references in depth, because no single upstream page50 covers them as a coherent whole for a new project — but every parameter51 table and default value is a link back to docs.nav2.org or the52 `navigation2` GitHub repo, not re-typed from memory. See References.53- **Start from the official minimal config, change one subsystem at a54 time.** `examples/nav2-params-diffdrive.yaml` is adapted from55 nav2_bringup's own `nav2_params.yaml` (jazzy branch) — begin a new robot56 there, verify it navigates, then change exactly one subsystem (footprint,57 controller plugin, costmap layer) before touching the next. Changing58 costmap, controller, and planner parameters simultaneously makes a59 regression impossible to bisect.60- **`use_sim_time` must be consistent across every node, every time.** In61 simulation, every Nav2 node, the map/odom TF broadcasters, and the sim62 clock source must all agree on `use_sim_time: true` (or all agree on63 `false` on real hardware) — one node left on the wrong value produces TF64 extrapolation errors and rejected goals that look like a planning bug but65 are a clock mismatch. Set it once, globally, in the params file passed to66 every node (see `examples/nav2-params-diffdrive.yaml` and67 `examples/bringup-launch-snippet.py`), not per-node.68- **Verify the TF tree — map→odom→base_link — before tuning anything69 else.** Costmap2D blocks activation until a full TF tree is available, and70 AMCL will not publish `map`→`odom` until it has an initial pose (from71 RViz's "2D Pose Estimate" or the `/initialpose` topic). A robot that72 "won't move" is, more often than a bad planner or controller parameter, a73 broken or incomplete TF chain — check this first with `ros2 run tf2_ros74 tf2_echo map base_link` every time, before touching costmap or controller75 tuning. See `references/common-failures.md`.76- **Never write Nav2 parameter defaults, plugin names, or version/status77 claims from memory.** They change release to release (the controller78 default alone has moved from DWB to MPPI upstream). Verify against79 docs.nav2.org or the `navigation2` GitHub repo before repeating a claim in80 a real project — every example in this skill is marked `status:81 unverified` for exactly this reason, and each reference states how its82 claims were checked this session.8384## Quick start8586**1. Confirm the ROS 2 substrate is ready.** A sourced Jazzy workspace with87Nav2 installed (`sudo apt install ros-jazzy-navigation2 ros-jazzy-nav2-bringup`88— re-verify the package name against `docs.nav2.org`'s install page before89running it) — see the `ros2` skill if the workspace itself isn't set up yet.9091**2. Bring up Nav2 with the example config.** Copy92`examples/nav2-params-diffdrive.yaml` and `examples/bringup-launch-snippet.py`93into your project, keeping the params filename the launch snippet expects94(or updating both together — see Customization), then:9596```bash97ros2 launch ./bringup-launch-snippet.py map:=/path/to/your_map.yaml use_sim_time:=true98```99100**3. Verify before tuning.** Confirm every managed node is active101(`ros2 lifecycle get /controller_server` etc.) and the TF chain is complete102(`ros2 run tf2_ros tf2_echo map base_link`) — see103`references/common-failures.md` if either check fails.104105**4. Send a goal** through RViz2's "Nav2 Goal" tool, or programmatically —106see the "send goals programmatically" usage pattern below.107108## Usage patterns109110**Bringup with an existing map.** Pass a saved map YAML and leave `slam`111false — Nav2 launches `nav2_map_server` + `nav2_amcl` for localization112against that static map. Set the robot's initial pose (RViz "2D Pose113Estimate" or publish to `/initialpose`) immediately after launch; AMCL does114not publish `map`→`odom` until it has one. See115`examples/bringup-launch-snippet.py` (`map:=` argument) and116`references/nav2-architecture.md`'s localization section.117118**SLAM-then-navigate.** Launch with `slam:=true` and no `map:=` argument —119this runs Nav2 without `nav2_map_server`/`nav2_amcl` and expects a SLAM node120(slam_toolbox's `online_async_launch.py`, launched alongside) to publish121`/map` and the `map`→`odom` transform instead. Drive the robot to explore,122then save the resulting map with `nav2_map_server`'s `map_saver_cli` (see123`map_saver`'s params in `examples/nav2-params-diffdrive.yaml`) once mapping124is done, so the next run can go back to AMCL-on-a-fixed-map. See125`references/nav2-architecture.md`.126127**Send goals programmatically.** Use `nav2_simple_commander`'s128`BasicNavigator` Python class rather than hand-rolling `NavigateToPose`129action clients: `goToPose()` / `goThroughPoses()` for single/multi-pose130goals, `followWaypoints()` for a waypoint list, and non-blocking131`isTaskComplete()`/`getResult()` polling for feedback in a single-threaded132script. See `references/nav2-architecture.md`'s commander-API section for a133minimal snippet shape.134135**Tune for a new robot footprint/speed.** Start from136`examples/nav2-params-diffdrive.yaml`'s `local_costmap`/`global_costmap`137`robot_radius` (switch to an explicit `footprint` polygon for a non-circular138base), then the controller's velocity/acceleration limits and139`velocity_smoother`'s `max_velocity`/`max_accel`/`max_decel` — change these140before touching planner or BT internals, since a wrong footprint or speed141limit makes every downstream navigation attempt look broken. See142`references/tuning-guide.md`.143144## Platform gotchas145146- **Jazzy, not Lyrical, until Nav2 ships Lyrical binaries.** See the intro147 paragraph above; this is a binding, repo-wide fact (`architect` and148 `ros2` both reference it) — don't silently "upgrade" a nav2 project to149 Lyrical without re-checking `ros-navigation/navigation2#6123` first.150- **AMCL is silent, not erroring, without an initial pose.** A freshly151 launched AMCL-based stack with no `/initialpose` published will sit idle —152 no error, just no `map`→`odom` transform and a costmap that never153 activates. This looks identical to a hung launch; check for a missing154 initial pose before debugging anything else. See155 `references/common-failures.md`.156- **Composed (`use_composition:=true`) vs standalone nodes change crash157 behavior.** nav2_bringup defaults to component-container composition; a158 crash inside one composed node can take down the whole container process,159 whereas standalone nodes (`use_composition:=false`, with160 `use_respawn:=true`) restart independently. Prefer standalone + respawn161 while iterating on a new robot; composition is a later performance162 optimization, not a default to fight while still debugging.163- **Gazebo Harmonic's `/clock` must actually be publishing** before any node164 with `use_sim_time:=true` will progress — a paused or not-yet-started165 Gazebo world leaves every Nav2 node waiting on TF timestamps that never166 arrive, which looks like a Nav2 hang rather than a sim issue.167168## Customization169170- **Different robot footprint or drive type:** swap `robot_radius` for an171 explicit `footprint` polygon in both `local_costmap` and `global_costmap`172 in `examples/nav2-params-diffdrive.yaml`, and change `FollowPath`'s173 `motion_model` (e.g. `"DiffDrive"` → `"Omni"`) if the base isn't174 differential-drive — see `references/tuning-guide.md`.175- **Different controller/planner plugin:** the params file's176 `controller_server.FollowPath.plugin` and177 `planner_server.GridBased.plugin` fields select the algorithm; swapping178 requires the matching plugin name and its own parameter block (e.g.179 `nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController` or180 `nav2_smac_planner::SmacPlannerHybrid`) — verify exact plugin/class names181 against `docs.nav2.org`'s configuration guide before writing them, they182 are not interchangeable strings. See `references/tuning-guide.md`.183- **Different params filename or launch structure:**184 `examples/bringup-launch-snippet.py`'s `params_file` default and185 `examples/nav2-params-diffdrive.yaml`'s own filename must be kept in sync186 if you rename either — the launch snippet resolves the params path187 relative to itself, so a silent rename of one without the other produces a188 "params file not found" failure at launch, not a subtle runtime bug.189- **Reverting to Lyrical Luth:** once Nav2 ships Lyrical binaries (re-check190 `ros-navigation/navigation2#6123`), swap `jazzy` for `lyrical` in every191 install command and Docker base image in the `environments` skill's192 Dockerfile.ros2 example; nothing in this skill's params/launch content itself is193 distro-specific beyond the install step.194195## References196197- `references/nav2-architecture.md` — the BT Navigator and default behavior198 trees, the planner/controller/smoother/behavior/waypoint-follower servers,199 costmap 2D layers (global vs local), the lifecycle manager, AMCL vs200 slam_toolbox, and the `nav2_simple_commander` API.201- `references/tuning-guide.md` — costmap resolution/update-rate/inflation202 tuning, footprint vs radius, controller/planner plugin selection,203 velocity/acceleration limits, and the "one subsystem at a time" workflow.204- `references/common-failures.md` — the "robot won't move" diagnostic205 checklist: lifecycle state, TF tree, `use_sim_time` consistency, costmap206 obstacle sourcing, goal rejection, and `cmd_vel` not reaching the base.207- `examples/nav2-params-diffdrive.yaml` — adapted from nav2_bringup's208 official minimal diff-drive `nav2_params.yaml` (status: unverified — file209 header states the exact source and the deviations made).210- `examples/bringup-launch-snippet.py` — a project launch file that includes211 nav2_bringup's own `bringup_launch.py`, pointed at this skill's example212 params file (status: unverified — file header states the exact source).213- Upstream: [Nav2 documentation](https://docs.nav2.org/) (primary source for214 this skill, reachable via direct fetch this session), [navigation2 GitHub215 repo, jazzy branch](https://github.com/ros-navigation/navigation2/tree/jazzy)216 (source of the params/launch examples, fetched directly via raw GitHub217 URLs this session), [nav2_simple_commander218 docs](https://docs.nav2.org/commander_api/index.html), [slam_toolbox219 GitHub](https://github.com/SteveMacenski/slam_toolbox). Sibling skills:220 `ros2` (foundation, load alongside), `gazebo` (sim),221 `visualization` (debugging), `environments` (Docker/env222 setup), `architect` (routes here).223224## Changelog225226<!-- One dated line per battle-tested change, added by skill-author hardening sessions. -->