Skip to content

Volumes & Assets

Jobs need somewhere to write checkpoints and reports, and a reproducible way to read robot models, worlds, and props. Simulo separates those concerns:

Writable Identity Primary use
Volume Yes A name in your workspace State and outputs shared across jobs.
Asset No A catalog ref pinned to an immutable version Validated robot/world/prop inputs.
AssetSource No A file path — no catalog identity A USD/URDF file that isn’t published.

simulo.Volume.from_name(name, create_if_missing=True) declares a durable, writable mount for checkpoints, exported policies, or reports:

checkpoints = simulo.Volume.from_name("cartpole-checkpoints", create_if_missing=True)
app = simulo.App("my-app", mounts={"/out": checkpoints})
@app.job(system=simulo.SystemType.TIER_1)
def train() -> dict:
trainer.save(f"{checkpoints.path}/final.pt")
...

Constructing a Volume is just a metadata declaration. checkpoints.path resolves to the mounted directory only inside a running job; trying to read it during packaging raises instead of pretending the storage exists locally.

The same named volume can connect jobs in one app — for example, a train job writes a checkpoint that separate evaluate and rollout jobs read. Trained models uploaded by the platform are also available through simulo models; the volume is the job’s writable workspace, not a substitute for the model record.

simulo.Asset.from_registry(ref) declares a catalog input:

cartpole = simulo.Asset.from_registry("simulo/robot/cartpole:v1")
my_arm = simulo.Asset.from_registry("robot/my-arm:v3")

The optional publisher defaults to your active organization; simulo names the global catalog. The ref grammar is [publisher/]kind/name[:vN]. Omitting :vN selects the latest published version at submit time, after which the job records an immutable pin.

A robot handle can be passed directly to Robot:

self.robot = simulo.Robot(asset=my_arm, initial_pose=simulo.Pose.identity())

The handle is inert while the app is discovered on your machine. During execution it resolves to a digest-verified, read-only package mount. Its .path is meaningful only in execution mode (or from a local cache populated with simulo asset get).

Read Asset Catalogs & Version Pinning for publication, catalog scopes, --frozen, --strict-assets, and deletion safety.

simulo.Asset.usd(path) and simulo.Asset.urdf(path) still name a physical model source directly. They are useful for a path that already exists inside the packaged runtime context, but they do not publish, cloud-validate, version, or pin that source.

These are classmethods on the catalog handle, and what they hand back depends on where your code is running: an inert placeholder while the app is being discovered on your machine, and a real model source during execution. That is why they are typed as returning Any. If you want the model source itself, with a concrete type in every mode, that is simulo.AssetSource.

For a reusable robot or world, publish the package and use Asset.from_registry(...). That is the path the shipped scaffolds and demos use, and the one that preserves the exact input with every job.

simulo.AssetSource is the model-source type itself: a USD or URDF file named by path, with no catalog identity, no version, and no publication step.

source = simulo.AssetSource.usd("/models/gripper.usd") # -> simulo.USDAsset
arm = simulo.AssetSource.urdf("/models/arm.urdf") # -> simulo.URDFAsset
source.path # "/models/gripper.usd"

Both classes answer .usd() and .urdf(), which is exactly why they are easy to mix up. Two separate comparisons are worth keeping straight.

The choice that matters: from_registry() vs. AssetSource

Section titled “The choice that matters: from_registry() vs. AssetSource”

This is a real decision, and the two options differ in kind:

simulo.Asset.from_registry(ref) simulo.AssetSource.usd(path)
What it names A catalog entry, by ref A file, by path
Versioned & pinned Yes — the exact version is recorded with the job No
Validated on publish Yes No
Kind safety (robot vs. world) Yes No
Reading .path Only inside a running job Always — it’s the path you passed

Reach for Asset.from_registry(...). It is the right answer for essentially all job code: it pins a version, records the exact input with the run, and gives you kind safety, so passing a world where a robot belongs fails with an explanation instead of loading the wrong thing.

Reach for simulo.AssetSource when you need the model-source value rather than a job input — annotating a helper that builds sources, an isinstance check against simulo.USDAsset / simulo.URDFAsset, or constructing a source in plain library code with no job around it.

If you have a file path and you want it published, versioned, and reproducible, that is not an AssetSource — publish it and use Asset.from_registry(...).

The near-duplicate: Asset.usd() vs. AssetSource.usd()

Section titled “The near-duplicate: Asset.usd() vs. AssetSource.usd()”

These two are not a meaningful choice, and the difference is narrower than it looks. Where your job body actually runs, they are the same thing — both return an identical simulo.USDAsset:

# during execution, on the worker
simulo.Asset.usd("/models/gripper.usd") # -> USDAsset(path='/models/gripper.usd')
simulo.AssetSource.usd("/models/gripper.usd") # -> USDAsset(path='/models/gripper.usd') (equal)

They diverge only at submit, on your own machine, where Asset.usd() hands back an inert placeholder instead — it records the path without building anything, which is what keeps submitting fast and dependency-free. That is why it is typed as returning Any, while AssetSource.usd() returns a simulo.USDAsset in every mode.

Neither raises. If you want a precise static type, use AssetSource; if you are writing a task body, either works and Asset.usd() is the conventional spelling.