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 …
| 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/
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]
removeunregisters the component and deletes its directories that are now empty. Non-empty directories are kept and listed;--purgedeletes those too.listshows the whole catalog with each component's registration state and whether its directories are present or not created.inspectshows the manifest — name, stringversion,schema, registered components,requires/tested— plus the definition counts.
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
| 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
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:
caasi.yamlexists, parses, is a mapping, has aname, and itsschemais an integer this Caasi understands;- all base directories exist, and so do the directories of every registered component;
- 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 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
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 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:
| 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 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