rosetta_ws
August 1, 2026 · View on GitHub
The pixi-powered workspace for Rosetta, which connects ROS 2 robots to robot-learning frameworks like LeRobot. Full documentation: https://iblnkn.github.io/rosetta/
This workspace has two jobs:
- Get you running fast. One
pixi run setupinstalls ROS 2 Jazzy, LeRobot, and every Rosetta package into a committed, reproducible environment. Nothing installs system-wide, and you don't need Docker or a stack of virtualenvs. - Show a complete stack. The workspace is a working example of Rosetta built into a ROS 2 system: the package repos, the launch files, and the full record, train, deploy workflow wired together with tasks.
If you want Rosetta inside an existing ROS 2 install instead, see the installation guide. Rosetta itself is a set of plain ROS 2 packages and doesn't require pixi.
Quick start
Install pixi (one-liner, v0.72+), then:
git clone https://github.com/iblnkn/rosetta_ws.git
cd rosetta_ws
pixi run --frozen setup # clone package/library repos + install the default env
pixi run build # colcon build -> install/
pixi run ros2 launch rosetta episode_recorder_launch.py contract_path:=<your contract>
(--frozen is only needed for that first setup: until it clones libs/,
pixi can't validate the lockfile's editable path dependencies. Every later
command is plain pixi run ....)
pixi shell drops you into an activated environment (ROS sourced, overlay
sourced, Zenoh RMW selected) for interactive work.
From here, the first-policy tutorial walks the full loop: write a contract, record demos, train, deploy.
Why pixi
Installing ROS 2 and LeRobot together is a real dependency problem: the ML frameworks pin torch versions that conflict with each other, and the ROS python stack has its own numpy and opencv opinions. Docker is the usual answer, but it trades one problem for another: GPU and hardware passthrough, X11 forwarding, image rebuild cycles. Pixi resolves the whole dependency graph natively instead.
If you know the traditional ROS setup, the mental model is a direct swap.
apt installs Debian packages from Ubuntu, with packages.ros.org layered on
top. Pixi installs conda packages from
conda-forge, with
RoboStack layered on top in exactly the same
way: the ROS ecosystem repackaged as conda packages (ros-jazzy-desktop,
ros-jazzy-cv-bridge, and the rest). The difference is that nothing is
installed system-wide and nothing is tied to one OS:
- Everything lives in the workspace. ROS, torch, CUDA-enabled wheels,
and compilers all install under
.pixi/, isolated per project. Delete the folder and they're gone. pixi.lockpins every dependency, transitive ones included, and is committed. Your laptop, CI, and the robot solve to byte-identical environments.- ROS and ML dependencies are one solve. The numpy/opencv/torch conflicts
between ROS and LeRobot are resolved once, in
pixi.toml, for everyone, not per machine at install time. - Tasks replace README run-books. The workflow lives in
pixi.toml(pixi run build,pixi run train,pixi run convert-bags), not in tribal knowledge. The Tasks section lists all of them.
Two caveats worth knowing: RoboStack is a community-maintained (Tier 3) ROS
platform, not an Open Robotics product; and rosdep doesn't work inside a
pixi environment (it shells out to apt/pip), so dependencies are added with
pixi add / pixi.toml instead. The package.xml files stay
rosdep-complete regardless, so the plain-ROS install path keeps working.
Devcontainer
Pixi already provides the isolation and reproducibility most people reach
for Docker for, with native performance and no hardware passthrough
friction. But if your team standardizes on containers, or you want a pinned
CUDA userland, the workspace ships a devcontainer that wraps the same setup.
Open in VS Code → "Reopen in Container". The image
(docker/Dockerfile) is toolchain-only: CUDA, pixi, and
X11 client libs. No source, no dependencies, and no build are baked in.
postCreateCommand runs the exact same pixi run setup as the host path,
into the bind-mounted workspace:
src/andlibs/live on the host (the workspace folder is a bind mount), so branches and uncommitted work persist across container rebuilds..pixilives in a named volume, so the installed environments survive rebuilds too. Rebuilding the image only refreshes the toolchain. (The volume also dodges case-insensitive-filesystem corruption on macOS/Windows hosts: some conda packages ship files that differ only in case.)- Credentials (
~/.cache/huggingface,~/.config/wandb,~/.netrc,~/.ssh) are mounted from the host. Log in withhf auth loginorwandb loginon the host first.
Because the environment definition is pixi.toml either way, the container
and host paths can't drift apart: the Dockerfile has nothing to keep in
sync.
Tasks
Everything routes through pixi tasks. pixi task list shows all of them,
and the VS Code tasks in .vscode/tasks.json are one-line wrappers around
them:
| Task | Env | Description |
|---|---|---|
setup | bootstrap | Fresh-clone setup: vcs import + pixi install + colcon mixins (run with --frozen the first time) |
build | default | colcon build → install/ |
build-with-tests | default | build with BUILD_TESTING=ON |
test | default | colcon test + result report |
clean | default | remove build/install/log |
lint | default | ruff (ruff.toml) over src/action + scripts; pre-commit hooks over workspace files |
start-zenoh | default | Zenoh RMW router (own terminal, leave running) |
convert-bags / convert-bags-parallel | default | bag → LeRobot dataset conversion |
train / resume-train | default | LeRobot policy training (scripts/train_policy.py) |
docs-build / docs-serve / docs-linkcheck | default | Sphinx site from src/action/rosetta/doc (build with warnings-as-errors, live-reload server, external link check) |
export-repos | default | pin current checkouts back into repos/*.repos |
The ML workflow end-to-end, as tasks:
pixi run convert-bags --raw-dir ... --contract ... --repo-id ...
pixi run train --help
pixi run ros2 launch rosetta policy_runner_launch.py \
contract_path:=... pretrained_name_or_path:=...
Colcon behavior (merge-install, symlink-install, cmake-args, base-paths: src)
is centralized in .colcon/defaults.yaml, activated
via COLCON_DEFAULTS_FILE, so a bare colcon build inside pixi shell
behaves exactly like pixi run build. Extra args pass straight through:
pixi run build --packages-select rosetta, pixi run build --mixin debug
(mixins come from .colcon/mixin/, registered by setup).
Environments
| Environment | Stack | Use |
|---|---|---|
default | ROS 2 Jazzy + LeRobot (torch 2.10) | daily driver for pixi run ... |
ci | ROS 2 only (torch-free, COPY install) | CI builds |
bootstrap | vcstool + git only | powers pixi run setup on a fresh clone |
Plain pixi run <task> picks the right environment automatically
(default-environment in pixi.toml); -e is only needed to override.
Workspace structure
rosetta_ws/
├── pixi.toml # THE environment definition (envs, deps, tasks)
├── pixi.lock # committed lockfile (reproducible everywhere)
├── .colcon/ # colcon defaults + local mixins
├── src/action/ # ROS2 packages (from repos/src.repos)
│ ├── rosetta/ # Core package (contracts, recorder, porter, policy runner)
│ ├── rosetta_interfaces/ # ROS2 action/service definitions
│ ├── lerobot_rosetta/ # rosetta -> LeRobot adapter (dataset writer, policy runner, servers)
│ ├── lerobot_robot_rosetta/ # LeRobot-discovered Robot plugin
│ └── lerobot_teleoperator_rosetta/ # LeRobot-discovered Teleoperator plugin
├── libs/ # Python libraries (from repos/libs.repos, editable installs)
│ └── lerobot/ # LeRobot
├── models/ # Trained policies
├── datasets/ # bags/ (recordings) + lerobot/ (converted datasets)
├── repos/ # vcs import manifests (src.repos, libs.repos)
├── scripts/ # activation script + ML workflow implementations
├── docker/ # Toolchain-only devcontainer image
└── .devcontainer/x86/ # VS Code devcontainer (thin wrapper: mounts workspace, runs pixi)
src/action packages and libs/ libraries are each their own git repo,
imported by pixi run setup; repos/*.repos pins what a fresh clone gets
(pixi run export-repos updates the pins). libs/COLCON_IGNORE keeps colcon
out of the python libraries. Each package README says what that package is
and where it fits; the rosetta docs
cover how it all works.
VS Code
Works out of the box, container or not:
- The Python extension auto-detects pixi environments via the
.pixifolder; if it doesn't, point "Python: Select Interpreter" at.pixi/envs/default/bin/python(the devcontainer pre-configures this). .vscode/tasks.jsonwraps the pixi tasks, so build/test/lint are available from the task picker with problem matchers wired up.- The community Pixi extension adds a task explorer and dependency management in the UI (installed automatically in the devcontainer).
Tab completion
Two one-time additions to ~/.bashrc (zsh users: pixi completion --shell zsh and argcomplete's zsh hooks are the equivalents):
# pixi itself: subcommands, and task names for `pixi run <TAB>`
eval "$(pixi completion --shell bash)"
# ros2/colcon argument completion (subcommands, packages, launch files),
# registered lazily on the first TAB press. Eager registration here won't
# work: `pixi shell` sources ~/.bashrc BEFORE activating the env, so
# register-python-argcomplete isn't on PATH yet at rc time. The stub looks
# it up at completion time instead, swaps in the real argcomplete hook, and
# re-dispatches; outside the env it returns 1 and you get plain filename
# completion.
_pixi_lazy_argcomplete() {
local reg realfn
reg=$(command -v register-python-argcomplete) || return 1
eval "$("$reg" "\$1")"
realfn=$(complete -p "\$1" 2>/dev/null | sed -E 's/.*-F ([^ ]+).*/\1/')
[ -n "$realfn" ] && [ "$realfn" != "_pixi_lazy_argcomplete" ] && "$realfn" "$@"
}
complete -o nospace -o bashdefault -o default -F _pixi_lazy_argcomplete ros2 colcon
Know what each layer can and can't complete:
pixi run <TAB>completes pixi task names only (build,test,start-zenoh, ...). It will never complete env binaries likeros2, and it can't complete their arguments either: argcomplete has to run the target command in a shell where the environment is active, which is never true for the outer shell drivingpixi run.- For
ros2 launch <pkg><TAB>and friends, work insidepixi shell, where the lazy stub gives full ros2/colcon completion.
Notes: argcomplete ships with the env, so nothing extra needs installing.
Registering from a pixi activation script doesn't work either: activation
propagates environment variables, not interactive shell hooks. And
argcomplete's activate-global-python-argcomplete hook is a dead end here:
RoboStack's ros2/colcon entry-point scripts lack the
PYTHON_ARGCOMPLETE_OK marker it keys on. See
pixi#2366 for background.
Contributing notes
pixi.lockis committed (and marked generated in.gitattributes). After changingpixi.toml, runpixi lockand commit both. The lock can only solve whenlibs/is populated (editable path deps), so runpixi run --frozen setupfirst on a fresh clone.- CI (
.github/workflows/ci.yaml) builds the torch-freecienvironment withfrozen: true; an out-of-date lockfile fails the build. - CI and
setupuse--frozenrather than--locked: pixi's lock up-to-date check currently mis-reads lerobot's[tool.uv.sources]cu128 index pin and reports a false mismatch (the lock itself is correct;pixi lockregenerates it byte-identical). - Non-pixi consumers: keep
package.xmlrosdep metadata honest. The plain-ROS path in Rosetta's installation guide depends on rosdep resolving everything frompackage.xmlalone.