Skip to content

cuRobo

CuroboPolicy wraps NVIDIA cuRobo's MotionPlanner. Unlike the sidecar VLA providers (GR00T, Cosmos 3), cuRobo runs in the same process as a CUDA library: there is no network round-trip, but a CUDA-capable GPU is required. It is a non-VLA, collision-aware motion planner - it reads its goal from **kwargs (target_pose / target_joints), ignores camera frames (requires_images = False), and never parses the instruction string for control.

Install

cuRobo is not on PyPI (the nvidia-curobo PyPI package is an unrelated v0.1 squatter). Install from the upstream source repository, then this package:

git clone https://github.com/NVlabs/curobo.git
pip install -e ./curobo
pip install "strands-robots[curobo]"   # extra is currently empty;
                                       # reserved for a future stable
                                       # cuRobo PyPI wheel

This policy targets cuRobo's restructured main API (MotionPlanner / MotionPlannerCfg / DeviceCfg / JointState / GoalToolPose). The on-device cuRobo APIs are still moving on main until upstream cuts a stable release; if you hit a fresh API shift, pin to a known-good commit or open an issue with the cuRobo SHA you tested.

Quickstart

from strands_robots.policies import create_policy

policy = create_policy(
    "curobo",                      # alias: "cumotion"
    robot_config="franka.yml",     # any cuRobo built-in YAML, or a dict
    action_horizon=16,
)

actions = policy.get_actions_sync(
    observation_dict={
        "observation.state": [0.0, -0.7854, 0.0, -2.3562, 0.0, 1.5708, 0.7854],
    },
    instruction="reach for the red block",   # ignored by planners
    target_pose=[0.5, 0.0, 0.4, 1.0, 0.0, 0.0, 0.0],   # [x,y,z, qw,qx,qy,qz]
)

Parameters

CuroboPolicy(
    robot_config="franka.yml",     # cuRobo built-in YAML name, a path, or a dict
    world_config=None,             # initial collision world (dict | None)
    action_horizon=16,             # waypoints streamed per get_actions() call
    device_cfg=None,               # None | "cuda:0" | torch.device | DeviceCfg
    motion_planner_kwargs=None,    # extra kwargs forwarded to MotionPlannerCfg.create
    motion_gen=None,               # pre-built planner (tests / advanced); skips build
    warmup=True,                   # warm the planner at construction
    # Legacy 0.7.x aliases (pass only one of each pair):
    tensor_args=None,              # alias of device_cfg=
    motion_gen_kwargs=None,        # alias of motion_planner_kwargs=
)

Supplying both a canonical kwarg and its legacy alias (e.g. device_cfg= and tensor_args=) raises ValueError rather than silently picking one.

Goal kwargs

cuRobo shares the non-VLA goal vocabulary with the rest of the planner family, so a goal can flow across providers without coupling to a backend:

Key Type Meaning
target_pose list[float] Cartesian goal [x, y, z, qw, qx, qy, qz] in the base frame
target_joints dict[str, float] Joint-space goal keyed by joint name (rad / m)
world_update dict \| None Per-call collision-scene refresh
replan bool Force a fresh plan even if cached waypoints remain

Pass exactly one of target_pose / target_joints. When neither is given, the policy makes a best-effort parse of a JSON target_pose / target_joints payload embedded in the instruction (for LLM-agent flows); if none is found it raises ValueError.

Trajectory chunking

The full collision-free trajectory is planned and cached on the first call. Each subsequent call yields up to action_horizon waypoints from the cache so the 50Hz execution loop in Robot streams per-step joint targets without re-planning. Force a fresh plan with replan=True (or policy.reset()) when the world changes mid-rollout. world_update is forwarded to MotionPlanner.update_scene (with a legacy update_world fallback) for per-call collision-scene refresh.

In simulation

from strands_robots import Robot

sim = Robot("panda")              # sim-by-default; needs a CUDA GPU for cuRobo
sim.run_policy(
    robot_name="panda",
    instruction="",               # ignored by the planner
    policy_provider="curobo",
    policy_config={"robot_config": "franka.yml", "action_horizon": 16},
    target_pose=[0.5, 0.0, 0.4, 1.0, 0.0, 0.0, 0.0],
    duration=10.0,
    control_frequency=50.0,
)

The LLM-agent demo path (Robot.start_task(..., policy_provider="curobo", target_pose=[...])) flows the same target_pose / target_joints kwargs through start_task's **policy_kwargs, so agents share one goal vocabulary across VLA and planner providers.

See also