Projects
A Caasi project is a plain directory with a caasi.yaml marker and a fixed layout for
robots, scenes, tasks, experiments, datasets and runs. No lock-in: everything is YAML and
folders you can inspect with ls.
Project layout
my-project/
├── caasi.yaml # project marker + metadata (kind, name, version)
├── robots/ # robot definitions (one YAML per robot)
├── scenes/ # scene definitions (one YAML per scene)
├── tasks/ # task definitions (one YAML per task)
├── experiments/ # experiment configs for `caasi sim run` / `train` / …
├── datasets/ # generated datasets
└── runs/ # project-local runs (optional; global runs live in ~/.caasi/runs)
Project commands find the root by walking upward from the current directory
until a caasi.yaml file appears — so they work from any subdirectory. Outside a
project they fail with:
shellcaasi project info
Error: Not inside a Caasi project (no caasi.yaml found). Run 'caasi init' first.
caasi init
Creates the scaffold. Never deletes anything: existing subdirectories are left untouched and
only caasi.yaml can be overwritten (with --force).
caasi init [PATH] [--name NAME] [--force] [--json]
| Parameter | Kind | Type | Default | Description |
|---|---|---|---|---|
PATH | argument | path | . | Directory to create the project in (~ expanded, resolved to absolute). |
--name | option | str | directory name | Project name stored in caasi.yaml. |
--force | option | flag | off | Overwrite an existing caasi.yaml. |
--json | option | flag | off | Machine-readable result. |
shellcaasi init ~/experiments/demo
Created project 'demo' at /home/you/experiments/demo
caasi.yaml
robots/
scenes/
tasks/
experiments/
datasets/
runs/
Next: create components with 'caasi robot create <name>'.
caasi init ~/experiments/demo
Error: '/home/you/experiments/demo/caasi.yaml' already exists (use --force to overwrite)
The generated caasi.yaml:
kind: project
name: demo
version: 1
JSON payload: {"root": "…", "name": "demo", "dirs": ["robots", "scenes", "tasks",
"experiments", "datasets", "runs"]}. Exit 0 on success, 1 on
collision or if PATH is an existing file.
caasi setup
Detects the five installable ecosystem components and — for a named component — prints a step-by-step installation guide tailored to how Caasi will later find it. Read-only: it never installs anything itself.
caasi setup [COMPONENT] [--json]
COMPONENT ∈ isaacsim, isaaclab, ros2,
pytorch, docker. Omit it for the summary table.
Summary mode
shellcaasi setup
Component Status Detail
isaacsim ✗ fail Isaac Sim not detected
isaaclab ✗ fail Isaac Lab not detected
ros2 ✓ ok distro 'jazzy', ros2 at /opt/ros/jazzy/bin/ros2
pytorch ✓ ok torch 2.4.0
docker ✓ ok Docker version 27.1.1, build 6312585
Missing core components: isaacsim, isaaclab. Run 'caasi setup <component>' for guidance.
exit code: 1
Exit code: 1 only when a core component
(isaacsim, isaaclab, ros2) fails detection —
pytorch/docker are optional. JSON payload:
{"components": {name: {"status", "detail"}}, "missing": [ … ]}.
Guide mode
shellcaasi setup isaacsim
✗ isaacsim — Isaac Sim not detected
Install Isaac Sim (pip, Omniverse launcher or NGC container), then either
register it:
caasi config set tools.isaacsim.versions."6.0".path /opt/isaac-sim-6.0
caasi config set tools.isaacsim.default "6.0"
or point the environment at it:
export ISAACSIM_PATH=/opt/isaac-sim-6.0
Each component has its own guide (ros2 → apt + sourcing, pytorch → pip index matching your
CUDA from caasi gpu status, docker → group setup + NVIDIA Container Toolkit).
Single-component mode exits 1 only if that component's status is
fail. JSON payload: {"component", "status", "detail", "guide"}.
Detection order per component
| Component | Looked for, in order |
|---|---|
isaacsim | registry tools.isaacsim → ISAACSIM_PATH → ~/isaacsim, ~/.local/share/ov/pkg/isaac-sim*, /opt/isaac-sim*, /opt/isaacsim* → pip isaacsim |
isaaclab | registry tools.isaaclab → ISAACLAB_PATH → ~/isaaclab, ~/IsaacLab, ~/workspace/isaaclab, ~/workspace/IsaacLab → pip isaaclab |
ros2 | ROS_DISTRO / /opt/ros scan → distro's bin/ros2 → ros2 on PATH |
pytorch | pip metadata of torch (never imported) |
docker | docker on PATH, probed with docker --version (status skip when absent) |
caasi project
caasi project info
caasi project info [--json]
shellcd ~/experiments/demo && caasi project info
Root /home/you/experiments/demo
Name demo
Robots 2
Scenes 1
Tasks 3
Experiments 1
caasi project info --json
{ "root": "/home/you/experiments/demo", "name": "demo",
"counts": { "robot": 2, "scene": 1, "task": 3, "experiments": 1 } }
Counts are simply the number of *.yaml files in robots/,
scenes/, tasks/ and experiments/.
caasi project validate
caasi project validate
Checks the whole layout and every definition file:
caasi.yamlexists, parses, is a mapping, and has aname;- all six directories exist;
- every
robots/*.yaml,scenes/*.yaml,tasks/*.yamlparses and itskind:field matches its directory.
shellcaasi project validate
2 issue(s) found:
✗ missing directory 'datasets/'
✗ robots/agv.yaml: kind is 'scene', expected 'robot'
exit code: 1
mkdir datasets && caasi project validate
Project at /home/you/experiments/demo is valid.
Exit 0 valid / 1 any issue. No --json.
caasi robot / scene / task
Three parallel groups manage the definition YAMLs. A definition is one file:
robots/<name>.yaml, scenes/<name>.yaml,
tasks/<name>.yaml. list, create and
inspect exist for all three; info is robot-only; import and
validate exist for robot and scene; capture
and reconstruct are scene-only.
list
caasi robot list [--json] # same for scene / task
shellcaasi robot list
Name Description
agv Warehouse AGV
arm 6-DOF manipulator
caasi robot list --json
[ { "name": "agv", "description": "Warehouse AGV",
"path": "/home/you/experiments/demo/robots/agv.yaml" }, … ]
An empty directory is not an error — it prints a “create one with…” hint and exits 0.
create
caasi robot create NAME [--description|-d TEXT] # same for scene / task
NAMEmust match[A-Za-z0-9][A-Za-z0-9_-]*(start alphanumeric, then letters/digits/-/_).- Never overwrites: an existing file fails with
Error: '<path>' already exists(exit 1). There is no--force— edit the file instead.
shellcaasi robot create agv -d "Warehouse AGV"
Created robot 'agv' at /home/you/experiments/demo/robots/agv.yaml.
The generated templates (fill them in with your asset paths):
# robots/<name>.yaml # scenes/<name>.yaml # tasks/<name>.yaml
kind: robot kind: scene kind: task
name: agv name: warehouse name: pick-place
description: Warehouse AGV description: "" description: ""
urdf: "" usd: ""
usd: ""
dof: 0
sensors: []
tasks: []
inspect
caasi robot inspect NAME [--json] # same for scene / task
Prints the definition file as-is (human: YAML; --json: the parsed object).
A missing definition fails with Error: No robot named 'agv2' (looked in 'robots/').
(exit 1).
caasi robot info
caasi robot info NAME
A short human summary of one robot — the fields that matter, skipping empty ones:
shellcaasi robot info agv
agv
Warehouse AGV
DOF: 4
URDF: assets/agv/agv.urdf
Sensors: lidar, camera_front
Tasks: navigate, dock
No --json (use inspect --json).
import
caasi robot import FILE [--dry-run] [-- CONVERTER_ARGS…] # same for scene
Hands one asset file to whichever tool the usd catalog domain resolves and starts
it as a tracked run (kind: import, backend: the tool that produced the
command). Caasi converts nothing itself; routing is by extension:
| Extension | Delegates to | Mode |
|---|---|---|
.urdf · .xacro · .mjcf | Isaac Sim's omni.importer.urdf through the resolved launcher: <launcher> -m omni.importer.urdf FILE FILE.usd | convert |
.urdf · .xacro when no Isaac Sim resolves | check_urdf FILE (ships with ROS 2) | validate |
.usd · .usda · .usdc · .usdz | usdchecker FILE | validate |
Args after -- are appended to that command line verbatim. On a machine with ROS 2
but neither Isaac Sim nor the USD tools, every absence is an explicit error rather than a silent
skip:
shell — this machine, no Isaac Sim / no USD toolscaasi robot import assets/agv.urdf --dry-run
Dry run — nothing was started:
command: /opt/ros/jazzy/bin/check_urdf assets/agv.urdf
caasi robot import assets/agv.urdf
Import started (validate via check_urdf): agv.urdf
Follow it with: caasi logs 20260908-000657-agv -f
caasi robot import assets/nope.urdf
Error: File 'assets/nope.urdf' not found.
exit code: 1
caasi robot import assets/arm.mjcf
Error: No converter found for 'arm.mjcf'. Install Isaac Sim (omni.importer.urdf) to convert, or check_urdf to validate only; overrides: catalog.usd.urdf_importer / catalog.usd.check_urdf.
exit code: 1
caasi scene import assets/scene.usd
Error: usdchecker not found — install the USD tools, or override catalog.usd.usdchecker.binaries in config.yaml.
exit code: 1
caasi robot import assets/part.stl
Error: Unsupported asset format '.stl' (expected .urdf, .xacro, .mjcf or .usd/.usda/.usdc).
exit code: 1
.mjcf has no check_urdf fallback — only the Isaac Sim importer reads
it — which is why it fails where .urdf degrades to validation.
validate
caasi robot validate NAME [--json] # same for scene
Checks the asset files a definition references — its urdf and usd
keys, resolved relative to the project root. Each file must exist, and when its checker is
installed it must also pass: check_urdf for urdf,
usdchecker for usd (30 s timeout each). A missing checker is not an
issue — the file is reported as <tool> not available; existence checked only
and still counts as valid:
shell — check_urdf present, usdchecker absentcaasi robot validate agv
Definition 'agv' is valid.
urdf: /home/you/experiments/demo/assets/agv.urdf
caasi robot validate ghost
1 issue(s) in definition 'ghost':
• urdf: referenced file '/home/you/experiments/demo/assets/ghost.urdf' does not exist.
exit code: 1
caasi robot validate bad
1 issue(s) in definition 'bad':
• urdf: check_urdf reported errors.
exit code: 1
caasi scene validate aisles --json
{
"name": "aisles",
"path": "/home/you/experiments/demo/scenes/aisles.yaml",
"valid": true,
"files": [
{
"key": "usd",
"path": "/home/you/experiments/demo/assets/scene.usd",
"exists": true,
"tool": null,
"ok": true,
"detail": "usdchecker not available; existence checked only"
}
],
"issues": []
}
JSON is {"name", "path", "valid", "files": [{"key", "path", "exists", "tool", "ok",
"detail"}], "issues"}; the first line of the checker's own output becomes
detail when it fails. Exit 0 with no issues, 1 with any —
in both modes. A definition that sets neither key has nothing to check and is valid.
caasi scene capture
caasi scene capture [TOPICS…] [--name NAME] [--dry-run] [-- BAG_ARGS…]
Records live topics into the project's datasets/ by delegating to
ros2 bag record — Caasi writes no bag itself. The destination is
datasets/<name>-<YYYYmmdd-HHMMSS> (--name defaults to
capture) and is passed as -o; with no topics it records everything via
-a. The recorder is a tracked run (backend: ros2,
kind: capture) that inherits your environment — when this shell has not sourced ROS 2,
Caasi captures a sourced environment for it — and the dataset directory gets a
metadata.json marking it status: capturing:
shellcaasi scene capture /scan /camera_front/image_raw --dry-run
Dry run — nothing was started:
command: /opt/ros/jazzy/bin/ros2 bag record /scan /camera_front/image_raw -o /home/you/experiments/demo/datasets/capture-20260908-000750
dest: /home/you/experiments/demo/datasets/capture-20260908-000750
caasi scene capture /scan --name aisle-loop
Capture started: /home/you/experiments/demo/datasets/aisle-loop-20260908-000910
Follow it with: caasi logs 20260908-000910-aisle-loop -f
caasi run stop aisle-loop
Run 20260908-000910-aisle-loop stopped.
metadata.json holds name, created,
kind: capture, the recorded topics, status and the
run_id — the same file caasi dataset
reads. Two edges worth knowing: there is no --json here, and TOPICS is
variadic, so anything Caasi does not recognise is collected into the topic list and lands
before -o. That is where extra ros2 bag record flags belong —
caasi scene capture /scan -- --max-bag-size 100000000 →
ros2 bag record /scan --max-bag-size 100000000 -o <dest> — and a stray
--json ends up there too, ros2 bag record rejects it, and the run shows
as failed in caasi run list. With no ros2 binary the command
fails before anything is created: Error: ros2 CLI not found; source a ROS 2 distro
first.
caasi scene reconstruct
caasi scene reconstruct CAPTURE [--dry-run] [-- NUREC_ARGS…]
Turns a capture — a ros2 bag directory from scene capture, or any
dataset path — into OpenUSD by delegating to a NuRec CLI: Caasi
resolves the nurec capability of the usd catalog domain (binary
nurec) and runs [<nurec>, CAPTURE, ARGS…] as a tracked run
(backend: nurec, kind: reconstruct). Two gates, in order — the capture
must exist, then a tool must resolve:
shell — this machine, no NuReccaasi scene reconstruct datasets/nope
Error: Capture 'datasets/nope' not found.
exit code: 1
caasi scene reconstruct datasets/aisle-loop-20260908-000910
Error: No neural reconstruction tool detected (nurec). Install NuRec, or point Caasi at its CLI: caasi config set catalog.usd.nurec.binaries "[<cli>]".
exit code: 1
The resolved-CLI transcripts, run naming and manifest fields are on Physics & Foundation Models.
Putting it together
a typical sessioncaasi setup
# → ros2 ok, isaacsim missing? follow the guide
caasi init ~/experiments/warehouse && cd ~/experiments/warehouse
caasi robot create agv -d "Warehouse AGV"
caasi scene create aisles -d "Racking aisles"
caasi task create navigate
# edit robots/agv.yaml → set urdf:/usd:, sensors, tasks
# write experiments/wave.yaml → see the Simulation page
caasi project validate && caasi sim run experiments/wave.yaml