Development setup
This page walks you through getting a working OpenRAL development environment from scratch — whether on a local Ubuntu machine, inside a dev container, or in a GitHub Codespace. It also covers the day-to-day commands you'll use while contributing.
System requirements
| Requirement | Minimum | Recommended |
|---|---|---|
| OS | Ubuntu 22.04 or 24.04 | Ubuntu 24.04 (ROS 2 Jazzy) |
| Python | 3.12 (only — pyproject.toml pins >=3.12,<3.13) |
3.12 |
| RAM | 8 GB | 16 GB |
| Disk | 20 GB free | 40 GB free |
| GPU | CPU-only (limited) | NVIDIA, 8 GB VRAM |
| Docker | 24+ (for dev container) | 26+ |
macOS 14+ is supported for Python/tooling work. ROS 2 on macOS runs inside the dev container.
Option A — Local Ubuntu machine (fastest iteration)
1. Clone and bootstrap
git clone https://github.com/OpenRAL/openral
cd OpenRAL
just bootstrap # installs uv, ROS 2, system deps (~5–10 min)
source /opt/ros/jazzy/setup.bash # or 'humble' on Ubuntu 22.04
just sync # install Python workspace deps (always `just sync`,
# never bare `uv sync` — see toolchain.md)
The bootstrap script auto-detects Ubuntu 22.04 (→ ROS 2 Humble) or 24.04 (→ ROS 2 Jazzy).
2. Verify the install
just test # full unit suite, <30 s
just lint # ruff + mypy --strict; expect no errors
uv run openral doctor # diagnose host environment
just test should run the full unit suite and complete in well under
30 s. The current inventory (file + LOC counts, gaps, follow-ups) is in
tests/README.md.
uv run openral doctor is the canonical environment probe — see the
README's "Quick start" section
for a sample output table; that one is the single source of truth.
Each row will read ok / info / absent / missing depending on
which deps you have installed.
Option B — Dev container (VS Code / Docker Desktop)
Prerequisites
- VS Code with the Dev Containers extension
- Docker Desktop 24+ running locally
1. Open in container
F1 → "Dev Containers: Reopen in Container"
VS Code will build Dockerfile.dev (first build ~8 min; subsequent builds use the layer cache) and run uv sync --all-packages automatically.
2. Or use docker-compose directly
docker compose -f docker-compose.dev.yml up -d openral-dev
docker compose -f docker-compose.dev.yml exec openral-dev bash
# inside container (always `just sync`, never bare `uv sync`):
just sync
just test
The compose file also starts a Jaeger container for OpenTelemetry traces at http://localhost:16686.
Hardware access
The dev container is started with --privileged and /dev mounted. On Linux hosts this gives USB access to connected robots. On macOS/Windows Docker Desktop, hardware passthrough is limited — use Option A (native Ubuntu) for hardware-in-the-loop work.
Option C — GitHub Codespaces (browser / no local install)
- On the GitHub repo page: Code → Codespaces → Create codespace on main.
- Wait for the container to build and
uv sync --all-packagesto complete (~5 min). - Open a terminal and run
just test.
Note
Hardware mounts (/dev, /run/udev) are not available in Codespaces. All unit and sim tests work; HIL tests require a self-hosted runner.
Day-to-day commands
All commands use just as the task runner. Run just (no arguments) to list all targets.
Python tests
just test # all tests in tests/unit/
just test-k so100 # filter by keyword
uv run pytest tests/unit/test_schemas_fuzz.py -v # run a specific file
Linting and formatting
just lint # ruff check + ruff format --check + mypy --strict (CI parity)
just fmt # auto-fix: ruff format + ruff check --fix
Schema export
just schema-export # regenerate docs/reference/schemas/*.json
uv run python tools/schema_export.py --check # CI drift-check (exit 1 on drift)
If you change any Pydantic model in python/core/, run just schema-export and commit the updated JSON files.
ROS 2 build and test
source /opt/ros/jazzy/setup.bash
just ros2-build # colcon build --merge-install
source install/setup.bash
just ros2-test # colcon test + colcon test-result --verbose
just test-ros-live # the live-ROS pytest suite (see below)
just ros2-build first runs scripts/check_ros_build_deps.sh, which derives
the required ament packages from the in-tree package.xml files and reports
any that are not installed under /opt/ros/$ROS_DISTRO/share, with a ready
apt-get install line. Without it a single missing apt package (say
ros-jazzy-octomap-msgs) surfaces ~45 s in as a CMake find_package error
that aborts every remaining package, burying the cause. The check is
advisory — set OPENRAL_ROS_DEPS_STRICT=1 to make missing deps fatal — and
no-ops when ROS 2 is not sourced.
The live-ROS suite (OPENRAL_TEST_ROS_LIVE)
A set of integration tests — the reasoner node's dispatch, VRAM and async-LLM
paths, plus the Tier-C critic producer — needs more than an importable rclpy:
a real DDS graph and the colcon-built openral_msgs / openral_reasoner_ros /
openral_prompt_router overlay. They are gated behind OPENRAL_TEST_ROS_LIVE=1
so the ordinary uv run pytest run (where rclpy + DDS init can clash with a
glib pulled in transitively by torch/pyarrow) does not trip over them.
scripts/ros_live_tests.sh holds the target list and sets the variable. Both
callers exec it, so they cannot select different tests:
| Caller | Environment |
|---|---|
just test-ros-live |
your host, after just ros2-build && source install/setup.bash |
docker-build workflow, "Live ROS tests" step |
inside openral:x86, which bakes ROS 2 Jazzy + the colcon overlay |
No GPU is required — the free-VRAM readings are pinned at the nvidia-smi
process boundary. Pass extra pytest flags through the recipe, e.g.
just test-ros-live -k dispatch_watchdog.
Adding a live-ROS test means adding its file to TARGETS in that script, and
nowhere else. The docker-build workflow is the only CI surface with a real
ROS 2 install; the test-selective runner has none, so anything not listed here
silently importorskips in CI.
That workflow runs on merge to master, not on pull requests (a full image
build is ~15-20 min of runner time). Run it yourself with just test-ros-live
while developing, or dispatch the workflow against your branch
(gh workflow run docker-build.yml --ref <branch>) before merging a change to
these tests.
One live test — tests/unit/test_gstreamer_perception_tee.py's end-to-end
publish — is deliberately excluded: it also needs PyGObject, which the open
deploy image does not ship (the GStreamer media stack is OpenRAL Pro). just
test runs it on a host that has the gstreamer extra installed.
Sim evals (closed-loop, opt-in — needs HF weights ± GPU)
just sim-eval scenes/<name>.yaml # canonical entry point
just sim-libero # SmolVLA × LIBERO
just sim-xvla-libero # xVLA × LIBERO
just sim-pi05-libero # π0.5 × LIBERO (≥8 GB VRAM)
just sim-metaworld --task metaworld/reach-v3 # SmolVLA × MetaWorld MT50
just sim-act-aloha # ACT × gym-aloha bimanual cube
just sim-diffusion-pusht # Diffusion Policy × gym-pusht (CPU)
Docs
just docs # serve at http://localhost:8000 (live-reload)
just docs-build # full build (CI parity, strict mode)
Repository layout (quick reference)
python/core/ openral_core — Pydantic v2 schemas (normative)
python/cli/ openral_cli — `openral` CLI entry point
python/hal/ openral_hal — HAL Protocol + per-robot adapters
(so100_sim, so100_follower, franka_panda,
ur5e/ur10e, ros_control)
python/sensors/ openral_sensors — sensor catalog + vendor adapters
python/world_state/ openral_world_state — WorldStateAggregator (30 Hz snapshot)
python/rskill/ openral_rskill — Skill ABC, rSkill loader, runtimes,
SmolVLA adapter
python/sim/ openral_sim — openral sim run registry/runner; LIBERO/MetaWorld
packages/msgs/ — ROS 2 IDL (.msg, .action)
packages/world_state/ — WorldState lifecycle node
packages/openral_hal_*/ — per-robot lifecycle nodes (so100, ur5e,
ur10e, franka)
robots/ — canonical RobotDescription manifests
(so100_follower, franka_panda, sawyer,
aloha_bimanual, pusht_2d,
ur5e, ur10e); auto-discovered
by openral_sim at import.
skills/ — rSkill packages (manifest + eval/)
(smolvla-libero, smolvla-metaworld,
pi05-libero-int8,
xvla-libero, act-aloha,
act-aloha-insertion,
diffusion-pusht)
examples/ — runnable end-to-end demos +
scenes/ SceneEnvironment YAMLs
tests/unit/ — pytest unit tests (<30 s total)
tests/integration/ — launch_testing multi-node tests
tests/sim/ — closed-loop sim (CUDA + HF weights, opt-in)
tests/hil/ — hardware-in-the-loop (lab runners)
tools/schema_export.py — JSON Schema generator + drift check
tools/rskill_publisher.py — rSkill packaging / publish helper
docs/ — mkdocs-material site
Most directories carry a per-package README.md with usage examples and
links back to the canonical schemas — start with the package matching
the layer you're touching.
Full architecture is in docs/architecture/overview.md. For a per-module status canvas (working / in-dev / planned / out-of-scope, with inputs, outputs, and schemas), open docs/architecture/repo-state-map.html — keep it in sync per CLAUDE.md §4.3.
Making a pull request
- Create a branch:
git switch -c feat/your-feature. - Make your changes. Run
just lint && just testbefore pushing. - If you changed a Pydantic schema, run
just schema-exportand commit the updated JSON files. - Open a PR. The title should follow Conventional Commits — e.g.
feat(core): add FooSchema. - All CI checks must be green before merge. See
.github/workflows/for what runs.
The full PR checklist is in the repo-root CLAUDE.md (not linked from docs — open it directly in your editor).
Troubleshooting
uv sync fails with "package not found" (or "Unable to uninstall hf-libero")
Run just sync instead of any bare uv sync. The workspace has multiple
member packages (the root manifest does not list them as direct dependencies),
so the sync needs --all-packages — which just sync supplies — and the
just sync wrapper also runs scripts/repair_hf_libero_install.py before/after
to avoid the hf-libero==0.1.3 distutils-uninstall abort. Add opt-in groups
with just sync --group <name>. See
Managing the Python environment & dependency
groups.
mypy reports "missing library stubs or py.typed"
Both openral_core and openral_cli ship py.typed markers. If mypy still complains, make sure you ran just sync and that your interpreter is the workspace venv (which python should point into .venv/).
openral doctor shows ROS 2 as "missing" after bootstrap
Source the ROS 2 setup file:
source /opt/ros/jazzy/setup.bash # Ubuntu 24.04
# or
source /opt/ros/humble/setup.bash # Ubuntu 22.04
Add this line to your ~/.bashrc for permanent effect.
Docker build fails copying python/core/pyproject.toml
Make sure you're building from the repo root (the docker-compose.dev.yml sets context: .). Building directly with docker build -f Dockerfile.dev . from the repo root also works.
Pre-commit hooks fail
Install the hooks once after cloning:
just install-hooks
Then git commit runs ruff, ruff-format, mypy, codespell, and the
conventional-commit check on changed files automatically, plus DCO auto-sign-off.
Use just install-hooks rather than pre-commit install: the repo pins
core.hooksPath=.githooks (for DCO sign-off), and pre-commit install refuses
to run while core.hooksPath is set. just install-hooks instead wires the
committed .githooks/ wrappers (which call pre-commit run) and pre-builds the
hook environments.