Skip to main content
A launch file is a YAML file (typically named something.launch.yaml) that tells Basis which units to start, which processes to group them into, and optionally how to record their topics. Launch files support Jinja2 templating so you can parameterize them at runtime without modifying their content.

Core data structures

The launch system parses a .launch.yaml file into three nested C++ structs defined in cpp/launch/include/basis/launch/launch_definition.h.
struct UnitDefinition {
  std::string unit_type;
  std::vector<std::pair<std::string, std::string>> args;
  std::string source_file;
};

struct ProcessDefinition {
  std::unordered_map<std::string, UnitDefinition> units;
  std::string source_file;
};

struct LaunchDefinition {
  std::optional<RecordingSettings> recording_settings;
  std::unordered_map<std::string, ProcessDefinition> processes;
};
Every unit inside a process has a name (used for logging and coordination), a unit_type (the shared library to load), and an optional list of --key value argument pairs forwarded to the unit at startup.

Basic structure

A minimal launch file groups units under named groups. Each group becomes a process by default.
my_robot.launch.yaml
groups:
  perception:
    units:
      stereo_match:
        args:
          confidence: "0.6"
      lidar_processor: {}

  control:
    units:
      controller: {}
This produces two processes — perception and control — each containing the named units.

Unit entries

Under units, each key is the unit’s instance name within the process. The value is an optional map:
KeyDescription
unitOverride the unit type (shared library name to load). Defaults to the instance name.
argsMap of argument name → string value, forwarded as --name value to the unit.
units:
  my_camera:
    unit: v4l2_camera_driver      # load v4l2_camera_driver.unit.so
    args:
      device: /dev/video0
      topic_namespace: /camera
When unit is omitted, the instance name is used as the unit type: a unit named stereo_match loads stereo_match.unit.so.

Process splitting

By default, all units inside a groups entry share one process. To force a group into its own separate process, set process: true:
groups:
  my_group:
    process: true
    units:
      my_unit: {}
Nested groups with process: true each become an independent process. Groups without the flag share the parent’s process.

Recording settings

Add a recording block at the top level to record topics to an MCAP file while the launch is running.
recording:
  directory: /tmp/recordings/
  name: my_session
  async: true
  topics:
    - /camera/**
    - /lidar_data
    - /log
FieldTypeDefaultDescription
directorystringDirectory where the MCAP file is written.
namestring"basis"Base name of the output file.
asyncbooleantrueWhen true, serialization happens off the hot path on a background thread.
topicsstring[]Glob patterns for topics to record. Each pattern is converted to a regex.
The RecordingSettings struct in C++:
struct RecordingSettings {
  bool async = true;
  std::string name = "basis";
  std::vector<std::pair<std::string, std::regex>> patterns;
  std::filesystem::path directory;
};

Jinja2 templating

Launch files are rendered through the Inja template engine (a C++ Jinja2-compatible implementation) before being parsed as YAML. This lets you use conditionals, loops, and variable interpolation.

Arguments block

To declare typed arguments for a launch file, add an args document before the main content, separated by a YAML document marker (---):
my_pipeline.launch.yaml
---
args:
  device:
    type: string
    help: "Camera device path"
    default: /dev/video0
  enable_recording:
    type: bool
    default: false
---
recording:
{% if args.enable_recording %}
  directory: /tmp/
  topics:
    - /**
{% endif %}

groups:
  camera:
    units:
      camera_driver:
        args:
          device: "{{args.device}}"
Arguments are passed on the command line when launching:
basis launch my_pipeline.launch.yaml --device /dev/video1 --enable_recording true
The first YAML document (the args block) is parsed without Jinja2. The second document (the main content) is rendered as a Jinja2 template with args available as a JSON object.
The args block and the main content must be separated by ---. If args appears inline with launch content, the parser rejects the file with a fatal error.

LaunchContext variables

Inside the template the following variables are available:
VariableTypeDescription
args.<name>matches declared typeThe parsed value of each declared argument.
Additional context fields defined in LaunchContext:
struct LaunchContext {
  bool sim = false;
  std::string process_filter;
  std::vector<std::string> all_args;
  std::vector<std::string> launch_args;
  std::filesystem::path search_directory;
};
sim is intended for simulation mode overrides. process_filter can be used to restrict which processes are started (e.g. when re-using a launch file across environments).

Topic name templating

Topic names inside unit YAML files (not launch files) also support {{ }} expressions evaluated against the unit’s declared args. A launch file supplies the values:
groups:
  perception:
    units:
      stereo:
        args:
          camera_name: front
Inside stereo.unit.yaml:
handlers:
  OnImage:
    inputs:
      "{{args.camera_name}}/left":
        type: rosmsg:sensor_msgs::Image
At runtime the topic is resolved to /front/left.

Include directives

A launch file can include other launch files. Included content is merged into the current launch definition.
include:
  foxglove.launch.yaml: {}
  perception.launch.yaml:
    confidence: "0.8"
To include the same file multiple times with different arguments, use the list form:
include:
  - camera.launch.yaml:
      camera: front
  - camera.launch.yaml:
      camera: back
include can also appear inside a groups entry, merging the included file’s units into that group’s process:
groups:
  sensors:
    include:
      lidar.launch.yaml:
        resolution: high
    units:
      imu: {}

Recursion limit

The parser tracks include depth and hard-codes a maximum of 32 levels of recursion. Exceeding this limit is a fatal error:
constexpr int MAX_LAUNCH_INCLUDE_DEPTH = 32;
Include paths are resolved relative to the directory containing the including file.

Complete example

robot.launch.yaml
---
args:
  sim:
    type: bool
    default: false
    help: "Run in simulation mode"
  camera_device:
    type: string
    default: /dev/video0
---
recording:
  directory: /data/recordings/
  name: robot_session
  topics:
    - /camera/**
    - /lidar_data
    - /control/**

{% if not args.sim %}
include:
  hardware_drivers.launch.yaml: {}
{% endif %}

groups:
  perception:
    units:
      stereo_match:
        args:
          camera_name: front
      lidar_processor: {}

  control:
    process: true
    units:
      controller:
        args:
          mode: "{{args.sim and 'sim' or 'real'}}"

  visualization:
    include:
      foxglove.launch.yaml:
        split_process: true

Next steps

Unit YAML reference

Declare inputs, outputs, and sync for each unit

Code generation

Build and install unit shared libraries

Recording and replay

Details on MCAP recording and the recording settings API

Units

How units are loaded and initialized at runtime

Build docs developers (and LLMs) love