Caasi v0.2.0 Projects: init · setup · project · robot · scene · task

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]
ParameterKindTypeDefaultDescription
PATHargumentpath.Directory to create the project in (~ expanded, resolved to absolute).
--nameoptionstrdirectory nameProject name stored in caasi.yaml.
--forceoptionflagoffOverwrite an existing caasi.yaml.
--jsonoptionflagoffMachine-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]

COMPONENTisaacsim, 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

ComponentLooked for, in order
isaacsimregistry tools.isaacsimISAACSIM_PATH~/isaacsim, ~/.local/share/ov/pkg/isaac-sim*, /opt/isaac-sim*, /opt/isaacsim* → pip isaacsim
isaaclabregistry tools.isaaclabISAACLAB_PATH~/isaaclab, ~/IsaacLab, ~/workspace/isaaclab, ~/workspace/IsaacLab → pip isaaclab
ros2ROS_DISTRO / /opt/ros scan → distro's bin/ros2ros2 on PATH
pytorchpip metadata of torch (never imported)
dockerdocker 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:

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
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:

ExtensionDelegates toMode
.urdf · .xacro · .mjcfIsaac Sim's omni.importer.urdf through the resolved launcher: <launcher> -m omni.importer.urdf FILE FILE.usdconvert
.urdf · .xacro when no Isaac Sim resolvescheck_urdf FILE (ships with ROS 2)validate
.usd · .usda · .usdc · .usdzusdchecker FILEvalidate

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 100000000ros2 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