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. |
Volumes — durable, writable storage
Section titled “Volumes — durable, writable storage”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.
Assets — versioned, read-only inputs
Section titled “Assets — versioned, read-only inputs”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.
Lower-level model sources: Asset.usd() and Asset.urdf()
Section titled “Lower-level model sources: Asset.usd() and Asset.urdf()”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.USDAssetarm = 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 workersimulo.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.