Caasi v0.3.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 + flat manifest (schema, name, version, components, requires)
├── 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)
├── artifacts/        # artifacts produced by runs
├── logs/             # project-local logs
├── .caasi/           # internal state: fingerprints, environment evidence, provenance
└── isaac/ · ros/ · nav/ · …   # component subtrees, created by `caasi project add`

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 project init is the canonical form — the root caasi init is a thin alias of it, with the same parameters and behaviour.

caasi init [PATH] [--name NAME] [--force] [--json]     # ≡ caasi project init …
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/
  artifacts/
  logs/
  .caasi/
  Next: create components with 'caasi project robot create <name>'.
caasi init ~/experiments/demo
Error: '/home/you/experiments/demo/caasi.yaml' already exists (use --force to overwrite)

The generated caasi.yaml — the flat, schema-keyed manifest (below):

schema: 1
name: demo
version: 0.0.0

JSON payload: {"root": "…", "name": "demo", "dirs": ["robots", "scenes", "tasks", "experiments", "datasets", "runs", "artifacts", "logs", ".caasi"]}. Exit 0 on success, 1 on collision or if PATH is an existing file.

The manifest: caasi.yaml

The project manifest is a flat mapping keyed by an integer schema marker (currently schema: 1), alongside the project's name, a string version, the registered components and their requires stubs:

schema: 1
name: warehouse-navigation
version: 0.1.0                 # the project's own version — a string, not the schema marker
components:
  isaac_sim: true
  ros2: true
requires:
  isaac_sim: { version: '>=5.0' }
  ros2: {}

Older projects may still carry the legacy form — kind: project with an integer version: 1. It is migrated on read to schema: 1, version: "0.0.0", and reading never rewrites the file: only project init/add/remove and project config set persist changes. caasi.yaml is dual-role — the manifest keys (schema, name, version, components, requires, tested) are read by the manifest loader, while config keys (language, tools, catalog, …) are read by the configuration layer; the two namespaces are disjoint. See Configuration.

project add / remove / list / inspect

A component catalog drives the directory tree: project init creates only the base directories, while project add <component> registers the component (sets components.<key>: true in the manifest), creates its directories and merges a requires stub. Dotted (isaac.lab) and underscore (isaac_lab) names both resolve to the same component.

caasi project add <component> [--json]
caasi project remove <component> [--purge] [--json]
caasi project list [--json]
caasi project inspect [--json]
shellcaasi project add isaac.lab && caasi project add nav2
Added component 'isaac.lab':
  isaac/lab/tasks/
  isaac/lab/environments/
  isaac/lab/configs/
Added component 'nav2':
  nav/maps/
  nav/params/
  nav/launch/
caasi project list
Component    Added  Directories
isaac.sim    •      isaac/sim/scenes, isaac/sim/configs, isaac/sim/extensions (not created)
isaac.lab    ✓      isaac/lab/tasks, isaac/lab/environments, isaac/lab/configs
ros2         •      ros/launch, ros/config, ros/params (not created)
nav2         ✓      nav/maps, nav/params, nav/launch
moveit       •      moveit/config, moveit/launch (not created)
robot        •      robot/urdf, robot/meshes, robot/usd (not created)
navigation   •      navigation/configs, navigation/launch (not created)
manipulation •      manipulation/configs, manipulation/launch (not created)
caasi project inspect --json | jq '.components'
[
  "isaac_lab",
  "nav2"
]

Removal is just as explicit — everything non-empty survives unless --purge is used:

shellcaasi project remove nav2
Removed component 'nav2'.
  - nav/maps/
  - nav/launch/
  kept non-empty 'nav/params/' (use --purge to delete)

project add-file

project add-file copies one file into its deterministic place in the component tree: <domain> picks the base directory (robot → robot/, isaac.sim → isaac/sim/, ros → ros/, …), <kind> picks the leaf (urdf, usd, scene → scenes/, config, launch, param, map, …). Missing directories are created and the original file is left untouched.

caasi project add-file <domain> <kind> <file> [--json]
shellcaasi project add-file robot urdf ~/assets/agv.urdf
Placed 'agv.urdf' at robot/urdf/agv.urdf.
caasi project add-file isaac.sim scene ~/assets/warehouse.usd
Placed 'warehouse.usd' at isaac/sim/scenes/warehouse.usd.
caasi project add-file ros config ~/params/nav.yaml
Placed 'nav.yaml' at ros/config/nav.yaml.

project config

Shows or edits this project's caasi.yaml config layer — the other half of the dual-role manifest. It is deliberately distinct from the global caasi config: project config reads and writes only the project file, while the global command merges every layer and set persists to ~/.config/caasi/config.yaml.

caasi project config show [--json]
caasi project config get KEY
caasi project config set KEY VALUE

show prints the whole file (manifest and config keys); get reads one dotted key and exits 1 when it is not set; set parses VALUE as YAML and writes it back into caasi.yaml.

shellcaasi project config show
schema: 1
name: demo
version: 0.0.0
components:
  isaac_lab: true
requires:
  isaac_lab:
    version: '>=2.0'
caasi project config set defaults.layout plain
Set defaults.layout = plain in caasi.yaml.
caasi project config get defaults.layout
plain
caasi project config get nope
Error: Key 'nope' is not set in this project.
exit code: 1

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

ComponentLooked for, in order
isaacsimregistry tools.isaacsim → ISAACSIM_PATH → ~/isaacsim, ~/.local/share/ov/pkg/isaac-sim*, /opt/isaac-sim*, /opt/isaacsim* → pip isaacsim
isaaclabregistry tools.isaaclab → ISAACLAB_PATH → ~/isaaclab, ~/IsaacLab, ~/workspace/isaaclab, ~/workspace/IsaacLab → pip isaaclab
ros2ROS_DISTRO / /opt/ros scan → distro's bin/ros2 → ros2 on PATH
pytorchpip metadata of torch (never imported)
dockerdocker on PATH, probed with docker --version (status skip when absent)

caasi project

The project group is the home of everything scoped to one project: init, the component workflow (add, remove, list, inspect, add-file, config) and the definition groups (robot, scene, task) documented below. This section covers the two commands that report on the project as a whole: info and validate.

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 — component-aware since v0.3.0:

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 project robot / scene / task

Three parallel groups manage the definition YAMLs — since v0.3.0 they live under project (caasi project robot …, caasi project scene …, caasi project task …); the old root-level caasi robot/scene/task commands no longer exist. 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 project robot list [--json]      # same for project scene / project task
shellcaasi project robot list
Name  Description
agv   Warehouse AGV
arm   6-DOF manipulator
caasi project 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 project robot create NAME [--description|-d TEXT]     # same for project scene / project task
shellcaasi project 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 project robot inspect NAME [--json]     # same for project scene / project 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 project robot info

caasi project robot info NAME

A short human summary of one robot — the fields that matter, skipping empty ones:

shellcaasi project 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 project robot import FILE [--dry-run] [-- CONVERTER_ARGS…]     # same for project 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 project robot import assets/agv.urdf --dry-run
Dry run — nothing was started:
  command: /opt/ros/jazzy/bin/check_urdf assets/agv.urdf
caasi project robot import assets/agv.urdf
Import started (validate via check_urdf): agv.urdf
  Follow it with: caasi logs 20260908-000657-agv -f
caasi project robot import assets/nope.urdf
Error: File 'assets/nope.urdf' not found.
exit code: 1
caasi project 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 project 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 project 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 project robot validate NAME [--json]     # same for project 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 project robot validate agv
Definition 'agv' is valid.
  urdf: /home/you/experiments/demo/assets/agv.urdf
caasi project 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 project robot validate bad
1 issue(s) in definition 'bad':
  • urdf: check_urdf reported errors.
exit code: 1
caasi project 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 project scene capture

caasi project 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 project 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 project 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 project 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 project scene reconstruct

caasi project scene reconstruct CAPTURE [--dry-run] [-- NUREC_ARGS…]

Turns a capture — a ros2 bag directory from project 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 project scene reconstruct datasets/nope
Error: Capture 'datasets/nope' not found.
exit code: 1
caasi project 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.

caasi.lock

caasi.yaml says what a project requires; caasi.lock records what was resolved on this machine when caasi env lock last ran. It is evidence, not a guarantee — a snapshot for audits and reproduction discussions, never a freeze or a promise. Alongside the manifest's requires keys it resolves a fixed baseline (isaac_sim, isaac_lab, ros2, python, gcc, cmake) and records every version it can detect.

caasi env lock [--json]
shellcaasi env lock
Wrote /home/you/experiments/demo/caasi.lock.
  4 resolved version(s) recorded.

The file itself:

schema: 1
project: {name: warehouse-navigation, version: 0.1.0}
required: {isaac_sim: {version: ">=5.0"}}
resolved: {isaac_sim: 6.0.1, ros2: jazzy, python: 3.12.3, ...}
verified: [...]
collected_at: "2026-09-09T12:00:00+00:00"

Outside a project it fails with Error: Not inside a Caasi project — run 'caasi project init' first. How this evidence feeds audits and run identity is described under Provenance.

Putting it together

a typical sessioncaasi setup
# → ros2 ok, isaacsim missing? follow the guide
caasi init ~/experiments/warehouse && cd ~/experiments/warehouse
caasi project add isaac.lab
caasi project robot create agv -d "Warehouse AGV"
caasi project scene create aisles -d "Racking aisles"
caasi project 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