Skip to content

Bring your own robot

You publish a robot once, as a USD or URDF package. Simulo validates it in the cloud and gives it a version number, such as robot/my-arm:v1, that never changes. Any job can then use that exact version.

Put the entry file and every mesh, texture or USD layer it uses under one folder. The client collects files only from inside that folder, called the package root.

my-arm/
├── robot.usda
├── geometry.usda
└── textures/
└── arm.png

If the folder holds more than one possible entry file, name the one to use with --entry. --root DIR sets a different package root.

--kind is required. It picks the checks validation runs, and Simulo never guesses it.

Terminal window
simulo asset publish ./my-arm --kind robot

The name defaults to the slugified entry-file or folder name. Pass --name to choose another. The command packages the files, uploads them, and waits for cloud validation. Validation opens the robot, lets it settle, and moves its joints, so a broken robot is caught here instead of in the middle of training. When it passes, the version is published:

Published robot/my-arm:v1 (org: <your-org>)
Use it:
asset = simulo.Asset.from_registry("robot/my-arm:v1")
robot = simulo.Robot(asset=asset, initial_pose=simulo.Pose.identity())
Details: https://console.simulo.ai/assets/robot/my-arm
Inspect it: simulo asset inspect robot/my-arm:v1

The Details: line links to the asset in Simulo Console. It appears only when the CLI can reach the Console, and never with --json; Inspect it: always appears.

Pass --detach to return right after the upload. Validation continues in the cloud, and simulo asset inspect robot/my-arm --wait picks it up again.

To run the same cloud validation without publishing a version:

Terminal window
simulo asset validate ./my-arm --kind robot

To try the flow before you have a robot of your own, publish this one-joint arm. Save it as tiny-arm/robot.urdf, then run simulo asset publish ./tiny-arm --kind robot.

<?xml version="1.0"?>
<robot name="tiny_arm">
<link name="base">
<inertial>
<mass value="2.0"/>
<inertia ixx="0.02" ixy="0" ixz="0" iyy="0.02" iyz="0" izz="0.02"/>
</inertial>
<visual><geometry><box size="0.2 0.2 0.2"/></geometry></visual>
<collision><geometry><box size="0.2 0.2 0.2"/></geometry></collision>
</link>
<link name="arm">
<inertial>
<origin xyz="0.15 0 0"/>
<mass value="0.5"/>
<inertia ixx="0.001" ixy="0" ixz="0" iyy="0.004" iyz="0" izz="0.004"/>
</inertial>
<visual>
<origin xyz="0.15 0 0"/>
<geometry><box size="0.3 0.05 0.05"/></geometry>
</visual>
<collision>
<origin xyz="0.15 0 0"/>
<geometry><box size="0.3 0.05 0.05"/></geometry>
</collision>
</link>
<joint name="shoulder" type="revolute">
<origin xyz="0 0 0.15"/>
<parent link="base"/>
<child link="arm"/>
<axis xyz="0 0 1"/>
<limit lower="-2.0" upper="2.0" effort="20.0" velocity="3.0"/>
<dynamics damping="0.5"/>
</joint>
</robot>
Terminal window
simulo asset inspect robot/my-arm:v1
simulo asset inspect robot/my-arm:v1 --report my-arm.validation.json

The summary shows the entry file, the package digest, when and where it was validated, and the facts validation measured: the articulation root, the joint names, the collision meshes, units, up axis and total mass. It also shows how many jobs use the version. --report saves the full validation report, and --json prints the whole record. See Asset validation for what each check means.

Write your task from these facts, especially the joint names.

Start from the starter app:

Terminal window
simulo create my_arm

In my_arm/task.py, add a handle for your robot at module level, next to the existing cartpole handle, and use it in build():

my_arm = simulo.Asset.from_registry("robot/my-arm:v1") # module level
self.robot = simulo.Robot(asset=cartpole, initial_pose=simulo.Pose.identity())
self.robot = simulo.Robot(asset=my_arm, initial_pose=simulo.Pose.identity())

Create the handle at module level, never inside build(). simulo run reads the module when you submit and never calls build(), so it only sees handles made at module level. A handle made inside build() makes the job fail before training starts, with an error that begins Asset 'robot/my-arm:v1' is not mounted.

Swapping the asset is one line, but the starter task is written for cartpole’s two joints. Update the task for your robot:

  • on_start: find your robot’s joints by name with self.robot.find_joints(...), using the names asset inspect showed.
  • observation_dim and action_dim: match the number of values your task produces and consumes.
  • get_observations, apply_actions, get_dones and reset_idx: use your joints, limits, and end-of-episode rules. Name each early end condition in the dictionary get_dones() returns as terminated, or return {} if your task never ends early.
  • The reward: score the behavior you want from this robot.

Run the preview that simulo create wrote before you submit training:

Terminal window
simulo run my_arm/preview.py

It checks the published robot inside this task, including its spawn pose, joints, action mapping, observations, rewards, episode conditions, and resets. Fix every failed finding, then rerun the preview. Preview before training shows a below-floor failure and how to use its recording.

When the preview passes, submit it like any other app:

Terminal window
simulo run my_arm/train.py --detach
simulo logs --follow

When you submit, simulo run pins each asset to an exact version. For release or CI runs, add two checks:

Terminal window
simulo run my_arm/train.py --frozen --strict-assets
  • --frozen fails the submit if any asset reference has no :vN version.
  • --strict-assets fails the submit if an asset version is deprecated or has never validated on the job’s runtime, instead of printing a warning.

If Simulo’s global catalog already has a robot you can use, reference it by its global name instead of publishing:

Terminal window
simulo asset list --global
cartpole = simulo.Asset.from_registry("simulo/robot/cartpole:v1")

See Browse and manage assets for searching, downloading and retiring versions.