# Despliegue de políticas (lerobot-rollout)

`lerobot-rollout` es la única CLI para desplegar políticas entrenadas en robots reales. Admite múltiples estrategias de ejecución y backends de inferencia, desde una evaluación rápida hasta la grabación continua y la recopilación de datos con intervención humana.

## Inicio rápido

No se necesitan dependencias adicionales más allá de los extras de tu robot y tu política.

```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
```

Esto ejecuta la política durante 30 segundos sin grabación.

### Elegir un dispositivo

Pase `--device=cpu` o `--device=cuda` para anular el dispositivo del checkpoint en un host donde ese dispositivo esté disponible. El dispositivo resuelto se aplica a la configuración de la política antes de cargar la política y construir sus procesadores. Por ejemplo, agregue `--device=cpu` al comando anterior para ejecutar un checkpoint entrenado en CUDA en un host solo de CPU, si la política admite la ejecución en CPU. Verifique que la inferencia en CPU pueda cumplir con la tasa de control requerida antes de desplegar en un robot.

Cuando se omite `--device`, rollout utiliza el dispositivo de la configuración de la política si está definido; de lo contrario, selecciona automáticamente un dispositivo disponible. El mismo comportamiento se aplica a `RolloutConfig(device=...)` con `build_rollout_context`.

---

## Estrategias

Seleccione una estrategia con `--strategy.type=<name>`. Cada estrategia define un bucle de control diferente con sus propias semánticas de grabación e interacción.

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

Ejecución autónoma de la política sin registro de datos. Úsela para evaluaciones rápidas, demostraciones o cuando solo necesite observar al 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             | Descripción                                            | Predeterminado |
| ---------------- | ------------------------------------------------------ | ------- |
| `--duration`     | Tiempo de ejecución en segundos (0 = infinito)                     | 0       |
| `--task`         | Descripción de la tarea pasada a la política                  | --      |
| `--display_data` | Transmitir observaciones/acciones a Rerun para su visualización | false   |

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

Grabación autónoma continua con carga periódica al Hugging Face Hub. Los límites de los episodios se calculan automáticamente a partir de la resolución de la cámara y los FPS, de modo que cada episodio guardado genera un archivo de video completo, manteniendo las cargas eficientes.

El estado de la política (estado oculto, cola RTC) persiste a través de los límites de los episodios: el robot no se reinicia entre episodios.

```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                                   | Descripción                                           | Predeterminado |
| -------------------------------------- | ----------------------------------------------------- | ------- |
| `--strategy.upload_every_n_episodes`   | Subir al Hub cada N episodios                          | 5       |
| `--strategy.target_video_file_size_mb` | Tamaño de archivo de video objetivo para la rotación de episodios           | auto    |
| `--dataset.repo_id`                    | **Obligatorio.** Repositorio de Hub para el conjunto de datos grabado | --      |
| `--dataset.push_to_hub`                | Si se debe enviar al Hub al desmontar                    | true    |

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

Despliegue autónomo con grabación bajo demanda mediante un búfer circular con límite de memoria. El búfer captura los últimos N segundos de telemetría. Presiona la tecla de guardar para vaciar el búfer e iniciar la grabación en vivo; presiónala de nuevo para guardar el episodio. Con un backend asíncrono, cada conmutación pausa la inferencia, invalida el movimiento en cola/interpolado y solicita una retención local compatible antes del drenaje o guardado bloqueante. Una ejecución saludable se reanuda desde una captura nueva después.

```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"
```

**Controles de teclado:**

| Clave                | Acción                                                   |
| ------------------ | -------------------------------------------------------- |
| `s` (configurable) | Iniciar la grabación (vacía el búfer) / detener y guardar el episodio |
| `h` (configurable) | Subir el conjunto de datos al Hub                                      |
| `ESC`              | Detener la sesión                                         |

| Flag                                   | Descripción                                    |
| -------------------------------------- | ---------------------------------------------- |
| `--strategy.ring_buffer_seconds`       | Duración de la telemetría en búfer (predeterminado: 10)   |
| `--strategy.ring_buffer_max_memory_mb` | Límite de memoria para el búfer circular (predeterminado: 1024) |
| `--strategy.save_key`                  | Tecla para alternar la grabación (predeterminada: `s`)         |
| `--strategy.push_key`                  | Tecla para publicar en Hub (predeterminado: `h`)              |

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

Recopilación de datos con intervención humana. Alterna entre la ejecución autónoma de la política y la intervención humana a través de un teleoperador. Los fotogramas de intervención se etiquetan con `intervention=True`. Requiere un teleoperador (`--teleop.type`).

Consulte la guía [Recolección de datos con intervención humana](./hil_data_collection) para un recorrido detallado.

**Modo solo correcciones** (predeterminado): Solo se registran las ventanas de corrección humana. Cada corrección se convierte en un episodio.

```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"
```

**Modo de grabación continua** (`--strategy.record_autonomous=true`): Se graban tanto los fotogramas autónomos como los de corrección con rotación de episodios basada en el tiempo (igual que en 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"
```

**Controles de teclado** (dispositivo de entrada predeterminado):

| Clave     | Acción                                      |
| ------- | ------------------------------------------- |
| `Space` | Pausar / reanudar la ejecución de la política             |
| `Tab`   | Iniciar / detener la corrección humana               |
| `Enter` | Subir el conjunto de datos al Hub (modo solo correcciones) |
| `ESC`   | Detener la sesión                            |

La entrada mediante pedal de pie también se admite a través de `--strategy.input_device=pedal`. Configure los códigos del pedal con las banderas `--strategy.pedal.*`.

Cuando el robot admite la retención de posición de despliegue, una pausa actualiza el último objetivo de posición aplicado realmente por el controlador, incluyendo su recorte y el objetivo del agarre. Antes del primer comando, una pose medida y validada proporciona el objetivo. Esto preserva el error de posición comandado en lugar de cambiar el objetivo a la pose medida; el robot puede terminar de estabilizarse hacia ese objetivo mientras el avance de la trayectoria de la política se detiene. Valide el comportamiento del brazo cargado y del agarre en el robot real; esta retención por software no garantiza la retención del agarre ni la inmovilidad física.

| Flag                                 | Descripción                                                                                                                                                    |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--strategy.num_episodes`            | Número de episodios de corrección a registrar (predeterminado: `--dataset.num_episodes`)                                                                                    |
| `--strategy.record_autonomous`       | También registra fotogramas autónomos (predeterminado: false)                                                                                                                  |
| `--strategy.upload_every_n_episodes` | Subir al Hub cada N episodios (predeterminado: 5)                                                                                                                      |
| `--strategy.input_device`            | Dispositivo de entrada: `keyboard` o `pedal` (predeterminado: keyboard)                                                                                                        |
| `--strategy.smooth_handover`         | Transfiere el control de forma suave al iniciar la pausa / corrección (predeterminado: true). Desactívalo para teleoperaciones de tipo clutch que redefinen la referencia en la pose actual del robot al activarse |
| `--teleop.type`                      | **Obligatorio.** Tipo de teleoperador                                                                                                                                |

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

Grabación orientada a episodios que replica el comportamiento de `lerobot-record`. La política controla el robot en cada episodio; un teleoperador opcional puede controlar el robot durante la fase de reinicio entre episodios.

```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}/rollout_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 es opcional: si se omite, el robot mantiene su posición durante la fase de reinicio.

**Controles de teclado:**

| Clave         | Acción                           |
| ----------- | -------------------------------- |
| `→` (derecha) | Terminar el episodio actual antes    |
| `←` (izquierda)  | Descartar el episodio y volver a grabarlo |
| `ESC`       | Detener la sesión de grabación       |

| Flag                                            | Descripción                                                                                                                                                |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dataset.num_episodes`                        | Número de episodios a registrar                                                                                                                               |
| `--dataset.episode_time_s`                      | Duración de cada episodio de grabación en segundos. El valor predeterminado es `--duration` cuando este último está establecido y este no lo está                                               |
| `--dataset.reset_time_s`                        | Duración de la fase de reinicio entre episodios en segundos                                                                                                    |
| `--teleop.type`                                 | Opcional. Teleoperador para controlar el robot durante los reinicios                                                                                                    |
| `--strategy.reset_to_initial_position`          | Si se debe restablecer el robot a su posición inicial entre episodios                                                                                        |
| `--strategy.smooth_leader_to_follower_handover` | Si se debe activar o desactivar el comportamiento de transición suave de líder a seguidor.                                                                                 |
| `--strategy.smooth_handover`                    | Transfiere el control suavemente al teleop al inicio del reinicio (predeterminado: true). Desactívalo para teleops de tipo clutch que se re-referencian en la pose actual del robot al activarse |

### Aporta tu propia estrategia

`--strategy.type` acepta cualquier estrategia registrada en el entorno, no solo las integradas anteriores. Una estrategia consiste en dos clases en un paquete instalable con pip: una configuración registrada en `RolloutStrategyConfig` y una subclase de `RolloutStrategy` con el mismo nombre pero sin el sufijo `Config`, siguiendo la misma convención `SomethingConfig`/`Something` que [el hardware personalizado](./integrate_hardware#the-4-core-conventions), resuelta desde el módulo o paquete de la configuración.

```python
from dataclasses import dataclass
from typing import ClassVar

from lerobot.rollout import RolloutContext, RolloutStrategy, RolloutStrategyConfig

@RolloutStrategyConfig.register_subclass("patrol")
@dataclass
class PatrolStrategyConfig(RolloutStrategyConfig):
    dataset_mode: ClassVar[str] = "required"  # the engine creates the dataset for you
    legs: int = 2  # becomes --strategy.legs

class PatrolStrategy(RolloutStrategy):
    def run(self, ctx: RolloutContext) -> None:
        ...  # the paced control loop: see the contract on RolloutStrategy.run

    def teardown(self, ctx: RolloutContext) -> None:
        ctx.data.dataset.finalize()
        self._teardown_hardware(ctx.hardware, ctx.runtime.cfg.return_to_initial_position)
```

La configuración declara lo que el motor organiza en nombre de la estrategia, por lo que nada en LeRobot necesita conocer su tipo:

| Declaración                     | Efecto                                                                                                                                     |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `dataset_mode`                  | `"none"` (predeterminado) rechaza las banderas `--dataset.*`, `"optional"` las registra cuando se proporcionan, `"required"` hace que `--dataset.repo_id` sea obligatorio |
| `requires_teleop`               | Hace que `--teleop.type` sea obligatorio                                                                                                            |
| `supports_interactive`          | Permite `--interactive=true`; `run()` debe entonces respetar el contrato reiniciable documentado en `RolloutStrategy`                             |
| `supports_sim_robot`            | Permite `--robot.type=sim`; `run()` debe iniciar un nuevo episodio cada vez que termina uno (ver `lerobot.robots.sim_robot`)                        |
| `requires_streaming_encoding()` | Devuelve `True` cuando los fotogramas se escriben desde dentro del bucle de control: `--dataset.streaming_encoding` se fuerza a activar                            |
| `extra_dataset_features()`      | Columnas de datos propiedad de la estrategia (por ejemplo, la `intervention` de DAgger); cada cuadro grabado debe incluirlas                               |

Nombra la distribución `lerobot_strategy_<name>` y `lerobot-rollout` importa el paquete antes de analizar la CLI: solo se ejecuta su `__init__.py`, por lo que importa la configuración allí. Un paquete con cualquier otro nombre se carga, incluidos los submódulos, con `--strategy.discover_packages_path=<package>`. Los campos del dataclass de la estrategia se convierten en banderas `--strategy.*`:

```bash
lerobot-rollout \
    --strategy.type=patrol \
    --strategy.legs=3 \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --dataset.repo_id=${HF_USER}/rollout_patrol \
    --dataset.single_task="patrol the aisle"
```

`SentryStrategy` (grabación, seguro para interacción) y `BaseStrategy` (sin grabación) son las implementaciones de referencia de las que copiar; `send_next_action`, `safe_push_to_hub` y `estimate_max_episode_seconds` de `lerobot.rollout.strategies` son las utilidades sobre las que se construyen.

Para los backends asíncronos, el contrato de dispatch es obligatorio. Publique cada observación capturada con `engine.notify_observation(obs_processed)`, llame a `send_next_action(...)` en **cada tick autónomo del motor**, incluidos los ticks de interpolación y los retenidos, y luego llame a `engine.pump_query(obs_processed)`. Llamar solo a `engine.get_action()` omite el apretón de manos de permiso y retención: una consulta de texto planificada puede esperar indefinidamente, y una falla terminal puede no señalar nunca el apagado.

Las estrategias de terceros pueden llamar a `self._tick(timer, new_cycle=interpolator.needs_new_action())` en lugar de `timer.tick(...)` para incluir informes periódicos de salud después de `_init_engine(ctx)`.

`send_next_action` se basa en `lerobot.inference.dispatch_tick` y sus partes (`next_interpolated_action`, `commit_action`, `dispatch_permitted`), que codifican ese orden una sola vez: `dispatch_allowed()` antes y después de obtener una acción y de nuevo antes de enviarla, la invalidación de la interpolación más un hold local admitido y `acknowledge_hold()` cuando se deniega, y `record_dispatch()` después de enviar. Un hold que falla nunca se reconoce. Use esas utilidades en lugar de reimplementar la secuencia; `send_next_action` solo añade el frame del dataset, el procesador de acciones del lado del robot y la contabilidad de cadencia. Los contextos remotos tienen `ctx.policy.policy`, `preprocessor` y `postprocessor` establecidos en `None`; use `ctx.policy.inference` y las pipelines del lado del robot en `ctx.processors`.

Envuelva el trabajo de grabación bloqueante conocido durante la ejecución en `self._pause_for_recording(ctx)`, incluyendo el vaciado del búfer circular y la finalización del episodio. Esto invalida el movimiento asíncrono antes de la pausa de grabación y solo se reanuda si la ejecución sigue siendo saludable y activa. Las estrategias con fases controladas por el operador deben pasar su permiso de movimiento actual como `resume_allowed`. La siguiente acción debe provenir de una observación nueva; el trabajo guardado no debe reactivar una ejecución en pausa, detenida o con fallo.

---

## Visualización de las predicciones de la política

Con `--display_data`, el visor también muestra lo que la política predice además de las acciones: fotogramas de cámara imaginados por un modelo de mundo, un subtask predicho, cajas delimitadoras dibujadas sobre una cámara. Cada política decide qué predice y lo activa a través de su propia configuración, por ejemplo `--policy.return_predicted_video=true` para LingBot-VA. Una predicción se nombra según lo que predice: Rerun muestra una `observation.images.top` predicha como `prediction.images.top`, un subtask predicho como `prediction.subtask`, y las cajas bajo su cámara (`<camera>/prediction`); Foxglove las publica en los temas `/prediction/...` y en las anotaciones de imagen `<camera topic>/prediction`. Con RTC, la predicción se actualiza una vez por chunk. Consulte [Bring your own policy](./bring_your_own_policies) para devolver una predicción desde una política.

## Backends de inferencia

Seleccione un backend con `--inference.type=<name>`. La inferencia local síncrona y la de RTC utilizan la política configurada a continuación. El [backend remoto](./remote_inference) obtiene su contrato de política de un servidor y no carga los pesos en la computadora del robot.

### Sync (predeterminado)

Una llamada de política por cada tick de control. El bucle principal se bloquea hasta que se calcula la acción.

Funciona con todas las políticas. No se necesitan banderas adicionales.

### Segmentación en tiempo real (`--inference.type=rtc`)

Un hilo en segundo plano produce fragmentos de acciones de forma asíncrona. El bucle de control principal consulta la siguiente acción lista mientras la política calcula el siguiente fragmento en paralelo.

Use RTC con modelos VLA compatibles (Pi0, Pi0.5, SmolVLA) para superponer la inferencia y el movimiento. La ejecución continua sigue requiriendo suficiente reproducción de acciones utilizables para cubrir la latencia de inferencia y su variación.

```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                                        | Descripción                                                                                         |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `--inference.rtc.execution_horizon`         | Pasos para fusionar con el fragmento anterior (predeterminado: varía según la política)                                      |
| `--inference.rtc.mode`                      | `guided` (predeterminado) o trained-prefix `trained` para puntos de control Pi05 compatibles                      |
| `--inference.rtc.max_guidance_weight`       | Intensidad de la aplicación de la consistencia (predeterminado: varía según la política)                                        |
| `--inference.rtc.prefix_attention_schedule` | Programa de mezcla: `LINEAR`, `EXP`, `ONES`, `ZEROS`                                                    |
| `--inference.queue_threshold`               | Umbral de backpressure; el RTC entrenado requiere al menos su retraso máximo                             |
| `--inference.action_starvation_grace_s`     | Esperar un movimiento nuevo después de la inanición en un robot compatible (predeterminado `1.0`; `0` falla de inmediato) |

Consulte la guía de [Real-Time Chunking](./rtc) para obtener detalles sobre la configuración de los parámetros de RTC.

Local RTC se ejecuta en un hilo en segundo plano dentro del proceso de rollout, sin transporte. Un servidor y un cliente en la misma computadora usan `--inference.type=remote` en su lugar. Consulte la [guía remota](./remote_inference) para la alineación/mezcla de fragmentos y el [comportamiento de espera](./remote_inference#what-the-robot-does-while-waiting-or-stopping) compartido por los backends asíncronos.

**La espera requiere un robot compatible.** El RTC local ordinario sigue disponible sin esa capacidad, pero las pausas planificadas por cambios de texto/instrucción y el agotamiento recuperable la necesitan. Los seguidores con control de posición SO, OMX, Koch, HopeJr y Rebot lo admiten; OpenArm, Reachy2 y los envoltorios bimanuales tienen restricciones de configuración enumeradas en la [tabla de soporte de robots](./integrate_hardware#waiting-between-predictions). Con soporte, un búfer vacío puede retener el último objetivo aplicado durante `action_starvation_grace_s` (1 segundo por defecto), incluido el tiempo necesario para drenar la inferencia antigua y predecir desde una captura nueva. Una vez que el movimiento ha comenzado, una gracia cero o un robot no compatible falla inmediatamente al agotarse; el arranque tiene su propio plazo. A diferencia de la antigua ruta de RTC local, el agotamiento ya no es un tick omitido indefinidamente. La gracia nunca extiende los límites de solicitud expirada o de frescura.

Local RTC expone estos presupuestos adicionales a través de `--inference.`:

| Parámetro               | Predeterminado | Significado                                      |
| ----------------------- | ------- | -------------------------------------------- |
| `max_observation_age_s` | `5 s`   | Edad máxima de la fuente a través de la ejecución de la acción  |
| `action_timeout_s`      | `10 s`  | Solicitud de acción pendiente                   |
| `startup_timeout_s`     | `120 s` | Preparación inicial y arranque de acción fresca |
| `language_timeout_s`    | `120 s` | Operación de idioma planificada                   |

Estos son valores iniciales, no garantías de hardware medidas. RTC debe conservar suficiente **reproducción posterior al ajuste** para otra inferencia y un margen de tiempo. Aumentar `queue_threshold` no puede ayudar si la inferencia consume rutinariamente el horizonte de predicción utilizable; reduzca la latencia o valide un horizonte más largo/frecuencia de acción diferente.

Un fallo terminal nunca reanuda automáticamente el movimiento de la política. Con la adquisición de observaciones utilizables y el control de los actuadores, el apagado respeta `--return_to_initial_position` (valor predeterminado: true) y se desconecta. El retorno lee una observación completa, por lo que un fallo persistente de la cámara puede impedirlo. La aplicación fallida de un comando suprime el movimiento adicional de apagado para todos los backends, incluido el síncrono. Esto no modifica la predicción síncrona ni introduce una recuperación específica de la cámara.

Cambios de compilación e instrucciones

La compilación de RTC calienta tanto las llamadas iniciales como las con prefijo, incluyendo un retraso no nulo representativo, antes del movimiento. Aunque `--compile_warmup_inferences` tiene un valor predeterminado de 2, RTC habilitado requiere al menos 3; los registros de inicio informan la cantidad efectiva. El calentamiento debe encajar en `startup_timeout_s`. Las instrucciones posteriores que cambian las formas de los tensores pueden provocar recompilación y exceder la reproducción o un plazo de operación.

Un cambio de tarea durante la inferencia descarta el resultado obsoleto. Los robots compatibles se pausan y solicitan nuevas acciones; sin esa capacidad de espera, las acciones restantes pueden agotarse. En esos robots, se prefiere cambiar las tareas entre ejecuciones.

---

## Sesiones interactivas

Añada `--interactive=true` para controlar el despliegue desde la terminal en lugar de iniciar de inmediato. El hardware se conecta y la política se carga como de costumbre, pero **el robot permanece inmóvil hasta que escriba `/start`** — útil cuando desea posicionar la escena primero, reindicar la política entre intentos o realizar varias tomas sin volver a pagar el tiempo de carga.

```bash
lerobot-rollout \
    --strategy.type=base \
    --policy.path=${HF_USER}/my_smolvla_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" \
    --interactive=true
```

| Comando             | Acción                                                                                                                                                                                                                                                                                                                  |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/start`            | Iniciar (o reiniciar) el bucle de control de la política                                                                                                                                                                                                                                                                              |
| `/subtask <text>`   | Cambia la instrucción dentro de la ejecución actual; la inferencia asíncrona puede entrar en una pausa planificada. Sin argumento, imprime la tarea actual. Solo afecta a las políticas que condicionan el comportamiento en el lenguaje (SmolVLA, π0/π0.5 y similares). Con un [planificador externo](#external-planner), `/subtask` también devuelve el control desde autosteer |
| `/vqa <text>`       | Haga una pregunta sobre lo que el robot ve actualmente. Solo mientras una ejecución esté en curso y solo con una cabeza de texto de política o un [planificador externo](#external-planner)                                                                                                                                                       |
| `/autosteer <goal>` | Indica un objetivo de alto nivel y deja que la cabecera de texto de la política o el [planificador externo](#external-planner) elijan las subtareas. `/autosteer off` devuelve el control; sin argumento, se informa del objetivo actual                                                                                                                  |
| `/reset`            | Detener el movimiento, devolver el robot a su posición de inicio y restaurar la instrucción `--task`                                                                                                                                                                                                                           |
| `/stop`             | Finalice la sesión y ejecute las rutinas de apagado normales                                                                                                                                                                                                                                                                    |
| `/help`             | Listar los comandos                                                                                                                                                                                                                                                                                                       |

```text
> /start
Rollout running — task 'pick up the cube'. /subtask <text> to change it, ...
> /subtask put the cube in the box
Task: 'pick up the cube' → 'put the cube in the box' (applies from the next policy inference)
> /vqa is the cube inside the box?
Asked: 'is the cube inside the box?' — answering from the next observation...
Q: is the cube inside the box?
A: No — the cube is on the table to the left of the box.
> /reset
Task restored to 'pick up the cube'
Resetting — returning the robot to its initial position...
Robot reset — holding at initial position. /start to run.
> /stop
```

Las líneas que comienzan con `>` son las que usted escribe; la sesión no muestra ningún indicador propio.

`Ctrl-C` sigue cerrando la sesión como de costumbre, y cerrar stdin (`Ctrl-D`, o el final de un script por tubería) termina la sesión: por lo tanto, un script por tubería debe mantener stdin abierto durante el tiempo previsto:

```bash
(printf '/start\n'; sleep 60; printf '/stop\n') | lerobot-rollout ... --interactive=true
```

**`/subtask`** actualiza la tarea para la inferencia posterior. Con `--inference.type=sync`, se descartan las acciones de fragmentos precalculadas. Con RTC local o inferencia remota, un cambio durante una solicitud de acción descarta el resultado obsoleto. En un robot que admita retención local, el motor invalida el movimiento en cola o interpolado, espera a que el bucle de control confirme la retención y luego solicita un movimiento nuevo bajo la nueva tarea. Espere una parada durante esta transición; no es una transición continua garantizada. Un cambio entre solicitudes puede conservar las acciones ya aceptadas para mantener la continuidad. El RTC local en un robot sin soporte de retención también descarta el resultado obsoleto, pero puede agotar sus acciones restantes y terminar. Cambie las instrucciones entre ejecuciones si ese robot no puede retener de manera adecuada.

Con `--use_torch_compile=true` local, una instrucción que modifica las formas de los tensores procesados puede provocar una recompilación. El relleno (padding) puede mantener algunos cambios de tarea con la misma forma, pero no se garantiza que la compilación posterior se ajuste al resto de la reproducción o al plazo de cambio de instrucción: puede causar un fallo terminal. Mantenga la compilación desactivada al explorar instrucciones, a menos que esas transiciones hayan sido validadas. Los cambios durante el calentamiento de la compilación inicial no solicitan una retención de movimiento, porque el movimiento de la política aún no ha comenzado.

`/vqa` y `/autosteer` requieren una política que declare soporte para generación de texto e implemente `PreTrainedPolicy.generate_text()`, como un checkpoint de EO1 entrenado de forma adecuada, o un [planificador externo](#external-planner). La capacidad se verifica antes de aceptar una consulta; el comportamiento específico del checkpoint aún necesita validación.

**`/vqa`** responde a partir de la siguiente observación e imprime la respuesta cuando está lista, de modo que puedes seguir escribiendo mientras tanto; solo hay una pregunta en curso a la vez (las propias consultas de autosteer comparten ese hueco). Con un planificador externo, la pregunta se responde en un hilo de trabajo mientras el robot sigue actuando, y nada de lo siguiente aplica. Las backends asíncronas primero entran en una retención local reconocida e invalidan el movimiento en cola/interpolado, y luego envían una observación nueva a la ruta de texto. Una ejecución activa y saludable se reanuda desde un nuevo trozo de acciones tras la respuesta; las ejecuciones en pausa o detenidas permanecen inactivas. La generación de texto síncrona se ejecuta en línea en el hilo de control y descarta las acciones precalculadas de la política, por lo que el siguiente tick se reanuda desde una inferencia nueva. El tiempo de consulta cuenta contra `--duration`. Las preguntas solo se aceptan mientras una ejecución está en curso: un motor inactivo no tiene una vista actual. Si la instrucción o el contexto de ejecución invalida una respuesta asíncrona, la pregunta recibe un aviso de cancelación en lugar de la respuesta obsoleta. El fin del segmento también informa sobre las preguntas sin responder, incluidas las generaciones aún en curso; informar la cancelación no interrumpe una llamada de modelo en ejecución ni libera su hueco de consulta de forma anticipada.

**`/autosteer`** le entrega a la política un objetivo y le pide el siguiente subtask cada `--autosteer_interval_s` segundos (por defecto, 10), aplicando cada uno a través de la misma ruta que `/subtask` y anunciándolo en el chat. Cada consulta es una generación de texto completa, por lo que el intervalo es un parámetro de coste: valores más bajos replanean antes, pero gastan más del bucle generando texto en lugar de actuar. El progreso del plan reside en la política, no en el rollout, por lo que el plan muere con el segmento; el secuenciador también se detiene ante `/reset`, `/subtask`, `/autosteer off` y el primer fallo de planificación.

**Los registros por debajo de ERROR se silencian mientras la sesión está en ejecución** para que la salida rutinaria no se intercale con lo que estás escribiendo; los errores y las fallas fatales de inferencia siguen mostrándose, y la registración normal se reanuda cuando la sesión termina. Los [resúmenes de cadencia](#cadence-reporting) se imprimen en el chat, y ejecutar sin `--interactive` te devuelve el registro en vivo.

Las sesiones funcionan a través de SSH y en máquinas sin interfaz gráfica, sin necesidad de un servidor de display.

**Grabación en modo interactivo.** `--strategy.type=sentry` también acepta `--interactive=true` y graba de forma continua mientras usted dirige: cada segmento `/start`…`/reset` guarda episodios completos más un episodio parcial final, y el conjunto de datos permanece abierto hasta el cierre. Los fotogramas llevan la procedencia de la tarea de la acción realmente enviada, por lo que las acciones ya en cola o interpoladas bajo la tarea anterior conservan la etiqueta antigua.

Las guardas de episodios a mitad de ejecución bloquean la inferencia asíncrona, invalidan las acciones en cola o interpoladas y solicitan una retención local compatible. Se reanudan desde una captura nueva solo si la ejecución sigue activa y sana. La misma protección se aplica a las guardas de DAgger y a las descargas/guardas del búfer circular de Highlight. Las pausas de grabación y el agotamiento ordinario comparten el comportamiento de espera física, pero sus plazos y motivos de reanudación permanecen separados.

```bash
lerobot-rollout \
    --strategy.type=sentry \
    --policy.path=${HF_USER}/my_smolvla_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --dataset.repo_id=${HF_USER}/rollout_cube_sessions \
    --task="pick up the cube" \
    --interactive=true
```

Las otras estrategias de grabación (episodic, DAgger, highlight) no son compatibles: asignan sus propios controles de teclado y finalizan el conjunto de datos cuando `run()` termina, por lo que no se pueden reanudar segmento por segmento.

### Planificador externo

`--planner.model_id=<name>` enruta `/vqa` y `/autosteer` a través del cliente VLM del pipeline de anotación (extra `annotations`) en lugar de la cabeza de texto de la política. Las llamadas se ejecutan en un hilo de trabajo mientras el robot sigue actuando, y no se solicita retención de lenguaje para ellas, con una excepción deliberada: cuando `/autosteer` comienza, el backend de inferencia se pausa hasta la primera respuesta del planificador, porque el objetivo es contexto para el planificador, no una instrucción para la política. Los backends asíncronos (`rtc`, `remote`) descartan las acciones ya en cola o en proceso de cálculo para la instrucción anterior; el backend síncrono retiene el robot. Una vez que se aplica el primer subtask, el backend se reanuda desde una observación nueva. `/subtask` sigue aplicando tu instrucción directamente y cancela la planificación automática; las respuestas tardías del planificador no pueden anularla.

El planificador funciona con cada backend de inferencia. Con inferencia local, reutiliza la receta de subtareas del procesador cargado cuando existe, y de lo contrario solicita una instrucción breve; esto no establece que un punto de control pueda seguirla. `--planner.instructions='["pick up the cup","open the drawer"]'` restringe la salida del planificador: se le pide al VLM que elija una de estas cadenas, y una respuesta de subtarea siguiente fuera de la lista es un error. Se ignoran las mayúsculas, los espacios adicionales y un punto final, y la propia redacción de la lista se envía a la política. Sin esta bandera, la lista se lee de las etiquetas de tareas del conjunto de datos de entrenamiento del punto de control (a través de su `train_config.json`). Cada etiqueta se incluye en el prompt, así que pase la bandera con una lista más corta para conjuntos de datos con muchas tareas. Si el conjunto de datos no se puede leer, se registra una advertencia y la salida del planificador permanece libre. La inferencia remota posee la política, por lo que ni la receta ni el vocabulario de entrenamiento están disponibles localmente: pase `--planner.instructions` explícitamente o las respuestas permanecerán libres. El planificador se ha probado únicamente en hardware con inferencia local.

El planificador externo recibe las últimas parejas de observación/orden de `--planner.history` (4 por defecto), seguidas de las imágenes actuales. Cada par contiene la observación utilizada para solicitar una instrucción y la instrucción aceptada posteriormente por el motor; la aceptación no implica una ejecución exitosa. Las instrucciones repetidas permanecen en el historial para que el VLM pueda evaluar la falta de progreso. El historial se limpia al tomar el control manual, al reiniciar, detener, al final de un segmento o al establecer un nuevo objetivo de autosteer. Establezca `--planner.history=0` para desactivarlo. `/vqa` puede inspeccionar este historial, pero no añade entradas. Para una solicitud de subtarea siguiente, el VLM debe describir primero la escena y declarar si la orden anterior está completada, en curso o fallida, y solo entonces nombrar la siguiente instrucción. Una respuesta de `done` significa que el objetivo está completo: autosteer se detiene, la ejecución termina y el robot mantiene su pose hasta `/reset` o `/start`. Un `done` tardío de antes de un `/subtask`, `/reset` o un nuevo objetivo se descarta. Cada subtarea aplicada se imprime con la evaluación de la escena del modelo y la latencia de la solicitud.

Inicie un servidor de VLM compatible con OpenAI por separado y luego conéctelo:

```bash
transformers serve Qwen/Qwen3-VL-8B-Instruct --port 8000
# Alternatively: vllm serve Qwen/Qwen3-VL-8B-Instruct --port 8000
lerobot-rollout ... --task="pick up the cup" --interactive=true \
    --planner.model_id=Qwen/Qwen3-VL-8B-Instruct \
    --planner.api_base=http://localhost:8000/v1
> /start
> /vqa What is the next short instruction to clear the table?
> /subtask put the cup in the sink
> /autosteer clear the table
```

Un modelo alojado funciona de la misma manera. Este ejemplo utiliza Hugging Face Inference Providers, con un token que tiene el permiso "Make calls to Inference Providers":

```bash
export HF_TOKEN=hf_...
lerobot-rollout ... --task="put all colored cubes in the green bin" --interactive=true \
    --planner.model_id=Qwen/Qwen3.8-27B:novita \
    --planner.api_base=https://router.huggingface.co/v1 \
    --planner.api_key_env=HF_TOKEN
```

`/vqa` solo informa una respuesta; `/autosteer` aplica instrucciones. Configure el endpoint con `--planner.api_base` y la clave con `--planner.api_key_env=<variable>` (preferible, la clave se mantiene fuera de los comandos y registros) o `--planner.api_key`. `--planner.request_timeout_s` (predeterminado 600) y `--planner.request_max_retries` (predeterminado 2) limitan cada solicitud; `--planner.camera_key` selecciona una cámara; `--planner.log_path` añade cada intercambio como una línea JSON. Las dependencias de servicio de modelos pertenecen al entorno del servidor. `--autosteer_interval_s` mide el tiempo de movimiento del robot después de aplicar una respuesta, además de la latencia de la solicitud. La dirección multinivel y el inventario automático de escenas están fuera del alcance de este adaptador.

### Control programático

`RolloutController` expone las mismas operaciones que una API de biblioteca: `start()` / `reset()` / `stop()` / `set_task()` seguros para hilos, más un callback de `RolloutEvent`, sin entrada estándar, impresión ni silenciamiento de registros:

```python
from threading import Event, Thread

from lerobot.rollout import (
    LinkedEvent,
    RolloutController,
    RolloutEvent,
    build_rollout_context,
    create_strategy,
)

parent = Event()  # your application's shutdown signal
ctx = build_rollout_context(cfg, LinkedEvent(parent))  # loads policy, connects robot
strategy = create_strategy(cfg.strategy)
strategy.setup(ctx)

controller = RolloutController(strategy, ctx, on_event=print)  # or your own observer
serve_thread = Thread(target=controller.serve)  # serve() blocks; run it where you like
serve_thread.start()

controller.start()                        # robot starts executing the policy
controller.set_task("grab the red cube")  # re-instruct mid-run
controller.reset()                        # stop movement, return home, stay warm
controller.stop()                         # end serve()

serve_thread.join()
strategy.teardown(ctx)  # teardown stays with the caller
```

`RolloutController` requiere que el evento de cierre del contexto sea un `LinkedEvent`, construido como se indica arriba: el controlador finaliza los segmentos de ejecución a través de su bandera local, mientras que su evento `parent` sigue forzando un cierre completo. Establezca `play_sounds=False` a menos que desee los anuncios vocales.

---

## Banderas comunes

| Flag                              | Descripción                                                       | Predeterminado |
| --------------------------------- | ----------------------------------------------------------------- | ------- |
| `--policy.path`                   | **Obligatorio.** ID de modelo de HF Hub o ruta local del punto de control            | --      |
| `--robot.type`                    | **Obligatorio.** Tipo de robot (p. ej., `so100_follower`, `koch_follower`) | --      |
| `--robot.port`                    | Puerto serie para el robot                                         | --      |
| `--robot.cameras`                 | Configuración de la cámara (diccionario JSON)                                  | --      |
| `--fps`                           | Frecuencia de inferencia de la política y de grabación del conjunto de datos                  | 30      |
| `--status_interval_s`             | Segundos entre registros de estado (0 desactiva; solo no interactivo)    | 5       |
| `--duration`                      | Tiempo de ejecución en segundos (0 = infinito; por segmento si es interactivo)    | 0       |
| `--device`                        | Dispositivo Torch (`cpu`, `cuda`, `mps`)                               | auto    |
| `--task`                          | Descripción de la tarea (se usa cuando no se proporciona un conjunto de datos)               | --      |
| `--display_data`                  | Transmitir telemetría a la visualización de Rerun                           | false   |
| `--display_ip` / `--display_port` | Dirección del servidor de Rerun remoto                                       | --      |
| `--interpolation_multiplier`      | Comandos de robot por acción de la política (control a `fps × N` Hz)        | 1       |
| `--interactive`                   | Sesión de chat en stdin; el robot permanece inactivo hasta `/start` (base, sentry)  | false   |
| `--use_torch_compile`             | Habilitar `torch.compile` para inferencia                              | false   |
| `--resume`                        | Reanudar una sesión de grabación anterior                               | false   |
| `--play_sounds`                   | Síntesis vocal para eventos                                        | true    |

---

## Informe de cadencia

El registro de despliegue no interactivo registra su estado cada cinco segundos. Cambia el intervalo con `--status_interval_s=10` o desactívalo con `--status_interval_s=0`.

```text
Rollout: loop 29.7/30 Hz · over budget 2/149 · starved 0 · infer 18.7 ms (worst 40.1) · process 2.41 GiB · GPU 5.12/79.6 GiB
```

La latencia es la mediana/peor en el intervalo (`turnaround` para RTC/remoto). Se omiten los campos no disponibles; la memoria de GPU es de todo el dispositivo, y la memoria remota es local. El informe se pausa cuando el bucle se bloquea.

Cada estrategia informa lo que su bucle de control logró realmente, al igual que `lerobot-record`, `lerobot-teleoperate` y `lerobot-replay`: todos avanzan según el mismo temporizador. Cada episodio guardado recibe un resumen de una línea, y cuando el bucle termina —incluido con `Ctrl-C`— se muestra un bloque que abarca toda la ejecución:

```
Cadence (episode 1): 29.84 Hz policy vs 30 Hz target · 1200 ticks, 20.1 s measured · 6/598 cycles over the 33.3 ms budget (work mean 18.7 ms, worst 48.1 ms)
Cadence (episode 2): 29.84 Hz policy vs 30 Hz target · 1200 ticks, 20.1 s measured · 6/599 cycles over the 33.3 ms budget (work mean 18.7 ms, worst 48.1 ms)
Cadence summary — whole run, 2 episodes · target 30 Hz × 2 (16.7 ms tick slot, 33.3 ms cycle budget): 2400 ticks, 1197 cycles judged
  effective cadence: 29.84 Hz policy / 59.67 Hz commands over 40.2 s measured
  cycles over the 33.3 ms work budget: 12/1197 (1.0%) — work mean 18.7 ms, worst 48.1 ms
  ticks over their 16.7 ms slot: 40/2400 (costs interpolation smoothness only)
  loop-body steps (share of measured work):
    observe      mean   3.13 ms · worst   9.00 ms ·  31.7% of work · 2400 calls
    process_obs  mean   0.50 ms · worst   1.00 ms ·   5.1% of work · 2400 calls
    infer        mean   5.18 ms · worst  40.00 ms ·  52.4% of work · 2400 calls
    send         mean   0.40 ms · worst   0.40 ms ·   4.1% of work · 2400 calls
    record       mean   0.67 ms · worst 620.00 ms ·   6.8% of work · 2402 calls
  pacing headroom: 7.4 ms slept per tick on average (max 13.0 ms) — near zero means the loop is saturated
```

Léelo de arriba hacia abajo: **la cadencia efectiva** es lo que se obtuvo frente a `--fps`, **ciclos sobre el presupuesto** indica con qué frecuencia el bucle no pudo mantenerla, el **desglose del paso** muestra dónde se fue el tiempo, y un **margen de ritmo** cercano a cero significa que no queda nada para absorber un tick lento. Una alta proporción de `infer` apunta a la política o al dispositivo; una alta proporción de `observe` apunta a las cámaras. Un `worst` de un paso muy por encima de su `mean` es un caso aislado: arriba, `record` es 0,67 ms por cuadro y el valor atípico de 620 ms es el `save_episode` bloqueante que cerró un episodio.

Un _ciclo_ aquí equivale a la cantidad de ticks de una acción de política (`--interpolation_multiplier` de ellos). Solo los ciclos completos sobre el presupuesto de `1/fps` pueden costarte frames; un solo tick que exceda su asignación solo hace que el movimiento interpolado sea menos fluido, por lo que ambos se cuentan por separado. Sin interpolación, un ciclo es un solo tick, por lo que el informe elimina la distinción y cuenta ticks en todo momento.

En [sesiones interactivas](#interactive-sessions), cada segmento `/start` tiene su propio temporizador, por lo que el informe se envía al chat una vez por segmento (`/reset`, `/stop` o `--duration`) en lugar de una vez por proceso.

---

## Uso programático

Para las extensiones, `lerobot.inference` es el paquete público para configuraciones de backend, motores locales y contratos de programación compartidos. `lerobot.remote_inference` posee la implementación del motor remoto, el protocolo, el despliegue y la sesión; `lerobot.rollout` posee el bucle del robot, las estrategias, la interpolación y la grabación. Importa los símbolos públicos desde su paquete en lugar de desde los archivos de implementación. Las dependencias remotas opcionales solo son necesarias cuando se utiliza el backend remoto o el servidor.

Para implementaciones personalizadas (p. ej., con procesadores de cinemática), use directamente la API del módulo de rollout:

```python
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
from lerobot.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)
```

Vea `examples/so100_to_so100_EE/rollout.py` y `examples/phone_to_so100/rollout.py` para ejemplos completos con procesadores de cinemática.

