# Policy Deployment (lerobot-rollout)

`lerobot-rollout` is the single CLI for deploying trained policies on real robots. It supports multiple execution strategies and inference backends, from quick evaluation to continuous recording and human-in-the-loop data collection.

## Quick Start

No extra dependencies are needed beyond your robot and policy extras.

```bash
lerobot-rollout \
    --strategy.type=base \
    --policy.path=lerobot/act_koch_real \
    --robot.type=koch_follower \
    --robot.port=/dev/ttyACM0 \
    --task="pick up cube" \
    --duration=30
```

This runs the policy for 30 seconds with no recording.

---

## Strategies

Select a strategy with `--strategy.type=<name>`. Each strategy defines a different control loop with its own recording and interaction semantics.

### Base (`--strategy.type=base`)

Autonomous policy execution with no data recording. Use this for quick evaluation, demos, or when you only need to observe the robot.

```bash
lerobot-rollout \
    --strategy.type=base \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --task="Put lego brick into the box" \
    --duration=60
```

| Flag             | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `--duration`     | Run time in seconds (0 = infinite)                     |
| `--task`         | Task description passed to the policy                  |
| `--display_data` | Stream observations/actions to the visualization backend (`--display_mode`; add `--display_extra_data` for a policy's imagined predictions) |

### Sentry (`--strategy.type=sentry`)

Continuous autonomous recording with periodic upload to the Hugging Face Hub. Episode boundaries are auto-computed from camera resolution and FPS so each saved episode produces a complete video file, keeping uploads efficient.

Policy state (hidden state, RTC queue) persists across episode boundaries: the robot does not reset between episodes.

```bash
lerobot-rollout \
    --strategy.type=sentry \
    --strategy.upload_every_n_episodes=5 \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --dataset.repo_id=${HF_USER}/rollout_eval_data \
    --dataset.single_task="Put lego brick into the box" \
    --duration=3600
```

| Flag                                   | Description                                                 |
| -------------------------------------- | ----------------------------------------------------------- |
| `--strategy.upload_every_n_episodes`   | Push to Hub every N episodes (default: 5)                   |
| `--strategy.target_video_file_size_mb` | Target video file size for episode rotation (default: auto) |
| `--dataset.repo_id`                    | **Required.** Hub repository for the recorded dataset       |
| `--dataset.push_to_hub`                | Whether to push to Hub on teardown (default: true)          |

### Highlight (`--strategy.type=highlight`)

Autonomous rollout with on-demand recording via a memory-bounded ring buffer. The robot runs continuously while the buffer captures the last N seconds of telemetry. Press the save key to flush the buffer and start live recording; press it again to save the episode.

```bash
lerobot-rollout \
    --strategy.type=highlight \
    --strategy.ring_buffer_seconds=30 \
    --strategy.save_key=s \
    --strategy.push_key=h \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=koch_follower \
    --robot.port=/dev/ttyACM0 \
    --dataset.repo_id=${HF_USER}/rollout_highlight_data \
    --dataset.single_task="Pick up the red cube"
```

**Keyboard controls:**

| Key                | Action                                                   |
| ------------------ | -------------------------------------------------------- |
| `s` (configurable) | Start recording (flushes buffer) / stop and save episode |
| `h` (configurable) | Push dataset to Hub                                      |
| `ESC`              | Stop the session                                         |

| Flag                                   | Description                                    |
| -------------------------------------- | ---------------------------------------------- |
| `--strategy.ring_buffer_seconds`       | Duration of buffered telemetry (default: 30)   |
| `--strategy.ring_buffer_max_memory_mb` | Memory cap for the ring buffer (default: 2048) |
| `--strategy.save_key`                  | Key to toggle recording (default: `s`)         |
| `--strategy.push_key`                  | Key to push to Hub (default: `h`)              |

### DAgger (`--strategy.type=dagger`)

Human-in-the-loop data collection. Alternates between autonomous policy execution and human intervention via a teleoperator. Intervention frames are tagged with `intervention=True`. Requires a teleoperator (`--teleop.type`).

See the [Human-In-the-Loop Data Collection](./hil_data_collection) guide for a detailed walkthrough.

**Corrections-only mode** (default): Only human correction windows are recorded. Each correction becomes one episode.

```bash
lerobot-rollout \
    --strategy.type=dagger \
    --strategy.num_episodes=20 \
    --policy.path=outputs/pretrain/checkpoints/last/pretrained_model \
    --robot.type=bi_openarm_follower \
    --teleop.type=bi_openarm_mini \
    --dataset.repo_id=${HF_USER}/rollout_hil_data \
    --dataset.single_task="Fold the T-shirt"
```

**Continuous recording mode** (`--strategy.record_autonomous=true`): Both autonomous and correction frames are recorded with time-based episode rotation (same as Sentry).

```bash
lerobot-rollout \
    --strategy.type=dagger \
    --strategy.record_autonomous=true \
    --strategy.num_episodes=50 \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --teleop.type=so101_leader \
    --teleop.port=/dev/ttyACM1 \
    --dataset.repo_id=${HF_USER}/rollout_dagger_data \
    --dataset.single_task="Grasp the block"
```

**Keyboard controls** (default input device):

| Key     | Action                                      |
| ------- | ------------------------------------------- |
| `Space` | Pause / resume policy execution             |
| `Tab`   | Start / stop human correction               |
| `Enter` | Push dataset to Hub (corrections-only mode) |
| `ESC`   | Stop the session                            |

Foot pedal input is also supported via `--strategy.input_device=pedal`. Configure pedal codes with `--strategy.pedal.*` flags.

| Flag                                 | Description                                             |
| ------------------------------------ | ------------------------------------------------------- |
| `--strategy.num_episodes`            | Number of correction episodes to record (default: 10)   |
| `--strategy.record_autonomous`       | Record autonomous frames too (default: false)           |
| `--strategy.upload_every_n_episodes` | Push to Hub every N episodes (default: 5)               |
| `--strategy.input_device`            | Input device: `keyboard` or `pedal` (default: keyboard) |
| `--teleop.type`                      | **Required.** Teleoperator type                         |

### Episodic (`--strategy.type=episodic`)

Episode-oriented recording that mirrors the behavior of `lerobot-record`. The policy drives the robot for each episode; an optional teleoperator can drive the robot during the reset phase between episodes.

```bash
lerobot-rollout \
    --strategy.type=episodic \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --teleop.type=so100_leader \
    --teleop.port=/dev/ttyACM1 \
    --dataset.repo_id=${HF_USER}/my_eval_data \
    --dataset.num_episodes=20 \
    --dataset.episode_time_s=30 \
    --dataset.reset_time_s=10 \
    --dataset.single_task="Pick up the red cube"
```

Teleop is optional — if omitted the robot holds its position during the reset phase.

**Keyboard controls:**

| Key         | Action                           |
| ----------- | -------------------------------- |
| `→` (right) | End the current episode early    |
| `←` (left)  | Discard episode and re-record it |
| `ESC`       | Stop the recording session       |

| Flag                                            | Description                                                                |
| ----------------------------------------------- | -------------------------------------------------------------------------- |
| `--dataset.num_episodes`                        | Number of episodes to record                                               |
| `--dataset.episode_time_s`                      | Duration of each recording episode in seconds                              |
| `--dataset.reset_time_s`                        | Duration of the reset phase between episodes in seconds                    |
| `--teleop.type`                                 | Optional. Teleoperator to drive the robot during resets                    |
| `--strategy.reset_to_initial_position`          | Whether to reset the robot to its initial position between episodes        |
| `--strategy.smooth_leader_to_follower_handover` | Whether to turn on or off the leader -> follower smooth handover behavior. |

---

## Inference Backends

Select a backend with `--inference.type=<name>`. All strategies work with both backends.

### Sync (default)

One policy call per control tick. The main loop blocks until the action is computed.

Works with all policies. No extra flags needed.

### Real-Time Chunking (`--inference.type=rtc`)

A background thread produces action chunks asynchronously. The main control loop polls for the next ready action while the policy computes the next chunk in parallel.

Use RTC with large, slow VLA models (Pi0, Pi0.5, SmolVLA) for smooth, continuous motion despite high inference latency.

```bash
lerobot-rollout \
    --strategy.type=base \
    --inference.type=rtc \
    --inference.rtc.execution_horizon=10 \
    --inference.rtc.max_guidance_weight=10.0 \
    --policy.path=${HF_USER}/pi0_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --task="Pick up the cube" \
    --duration=60 \
    --device=cuda
```

| Flag                                        | Description                                                    |
| ------------------------------------------- | -------------------------------------------------------------- |
| `--inference.rtc.execution_horizon`         | Steps to blend with previous chunk (default: varies by policy) |
| `--inference.rtc.max_guidance_weight`       | Consistency enforcement strength (default: varies by policy)   |
| `--inference.rtc.prefix_attention_schedule` | Blend schedule: `LINEAR`, `EXP`, `ONES`, `ZEROS`               |
| `--inference.queue_threshold`               | Max queue size before backpressure (default: 30)               |

See the [Real-Time Chunking](./rtc) guide for details on tuning RTC parameters.

---

## Common Flags

| Flag                              | Description                                                       | Default |
| --------------------------------- | ----------------------------------------------------------------- | ------- |
| `--policy.path`                   | **Required.** HF Hub model ID or local checkpoint path            | --      |
| `--robot.type`                    | **Required.** Robot type (e.g. `so100_follower`, `koch_follower`) | --      |
| `--robot.port`                    | Serial port for the robot                                         | --      |
| `--robot.cameras`                 | Camera configuration (JSON dict)                                  | --      |
| `--fps`                           | Control loop frequency                                            | 30      |
| `--duration`                      | Run time in seconds (0 = infinite)                                | 0       |
| `--device`                        | Torch device (`cpu`, `cuda`, `mps`)                               | auto    |
| `--task`                          | Task description (used when no dataset is provided)               | --      |
| `--display_data`                  | Stream telemetry to the visualization backend                     | false   |
| `--display_mode`                  | Visualization backend: `rerun` or `foxglove`                      | rerun   |
| `--display_extra_data`            | Also stream a policy's intermediate predictions (e.g. a world model's imagined video) on a dedicated channel; implies `--display_data`, sync inference only | false   |
| `--display_compressed_images`     | JPEG-compress images before streaming (less bandwidth, more CPU)  | false   |
| `--display_ip` / `--display_port` | Remote Rerun server address (or bind interface/port for foxglove) | --      |
| `--interpolation_multiplier`      | Action interpolation factor (upsamples the control rate)          | 1       |
| `--use_torch_compile`             | Enable `torch.compile` for inference                              | false   |
| `--resume`                        | Resume a previous recording session                               | false   |
| `--play_sounds`                   | Vocal synthesis for events                                        | true    |

---

## Programmatic Usage

For custom deployments (e.g. with kinematics processors), use the rollout module API directly:

```python
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
from lerobot.rollout.inference import SyncInferenceConfig
from lerobot.rollout.strategies import BaseStrategy
from lerobot.utils.process import ProcessSignalHandler

cfg = RolloutConfig(
    robot=my_robot_config,
    policy=my_policy_config,
    strategy=BaseStrategyConfig(),
    inference=SyncInferenceConfig(),
    fps=30,
    duration=60,
    task="my task",
)

signal_handler = ProcessSignalHandler(use_threads=True)
ctx = build_rollout_context(
    cfg,
    signal_handler.shutdown_event,
    robot_action_processor=my_custom_action_processor,       # optional
    robot_observation_processor=my_custom_obs_processor,     # optional
)

strategy = BaseStrategy(cfg.strategy)
try:
    strategy.setup(ctx)
    strategy.run(ctx)
finally:
    strategy.teardown(ctx)
```

See `examples/so100_to_so100_EE/rollout.py` and `examples/phone_to_so100/rollout.py` for full examples with kinematics processors.
