Claude Cursor Skill

environments

Set up reproducible robotics environments with uv, Docker, or GPU hosts.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download robium-ai-robium-skills_environments-498ea4e.zip · 18 KB
Part of robium-ai/robium — 44 skills

Install

skills CLI npx skills add https://github.com/robium-ai/robium/tree/main/skills/environments
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install robium-ai-robium@llmmart
Git git clone https://github.com/robium-ai/robium.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole robium-ai/robium collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Environments

Define the runtime contract before writing around machine-specific accidents. Local and remote runs should differ only where the hardware genuinely differs.

Choose the smallest sufficient boundary

  • uv: pure-Python robotics, ML, data, and tooling without required system packages. Commit the lockfile and run project commands through uv run.
  • Docker: ROS 2, native libraries, apt packages, an exact Linux userspace, or deployment as a container.
  • Docker plus uv: use Docker for the system layer and a project environment for substantial Python dependencies; do not turn the image's system Python into an untracked package set.
  • Remote GPU host: use when the workload has no viable local hardware path. Record this as a deliberate exception to local/remote symmetry.

Preflight the actual machine before deciding. Confirm architecture, OS, Docker daemon, accelerator, driver, disk, and available Python tooling. Use the Robium doctor if present; fall back to direct probes when it is not.

Keep the contract reproducible

  • Pin Python and dependency resolution with a lockfile, or pin the container base and build inputs. Avoid latest for reproducible applications.
  • Keep dependency declarations in one source of truth; do not preserve manual installation steps as hidden prerequisites.
  • Match container CUDA/runtime requirements to the target host driver rather than the developer laptop.
  • Design remote work as headless first. Use web visualization instead of making X forwarding part of the normal workflow.
  • Test the same entry command in the target environment and prove important hardware, file, device, network, and display assumptions.
  • Before claiming first-run reproducibility, run the complete setup from a clean project copy with the relevant package, model, and asset caches empty. A warm development checkout can hide broken fetch and bootstrap behavior.

Go deeper only when needed

  • Pure Python and lockfiles: uv patterns.
  • ROS/system images, build layout, and parity: Docker patterns.
  • NVIDIA passthrough and headless operation: GPU and remote.
  • Real-robot LAN, Wi-Fi, DDS/NAT, and Mac host issues: robot networking.
  • Workloads that can exist only on cloud GPUs: GPU cloud, then the relevant provider skill for provisioning.
  • macOS, Apple Silicon, arm64, and cold-build evidence: platform notes.
  • Use the bundled examples only as starting shapes; verify all tags and install steps against current upstream documentation.

Cross into integration when multiple modules, containers, or transports must be wired together. Cross into a deployment skill only after the image and runtime contract work locally or in an equivalent target environment.

Done

  • A fresh machine can reproduce the environment from committed inputs.
  • The same documented command starts the workload locally and remotely, except for named hardware flags.
  • GPU, devices, network, files, and display behavior are verified on the target.
  • Current details match official uv, Docker, ROS image, and NVIDIA Container Toolkit documentation.
Files (robium)
  • examples
    • Dockerfile.gpu-ml 2.3 KB · in bundle
    • Dockerfile.ros2 1.7 KB · in bundle
    • pyproject-uv.toml 1 KB
      # status: unverified
      # source: https://docs.astral.sh/uv/guides/projects/ (fetched via ctx7, astral-sh/uv)
      #
      # Minimal pyproject.toml for a pure-Python robium project managed with uv.
      # Use this shape when the project does NOT need ROS 2 or other system-level
      # dependencies; see the uv choice in skills/environments/SKILL.md. Generated
      # by `uv init` + `uv add`, then hand-trimmed for illustration; re-run
      # `uv add`/`uv lock` in a real project rather than copying this verbatim.
      
      [project]
      name = "robium-example-agent"
      version = "0.1.0"
      description = "Example pure-Python ML/robotics project managed with uv"
      readme = "README.md"
      requires-python = ">=3.11"
      dependencies = [
          "numpy>=1.26",
          "torch>=2.3",
      ]
      
      [dependency-groups]
      dev = [
          "pytest>=8.0",
          "ruff>=0.6",
      ]
      
      [tool.uv]
      # Include the `dev` group by default on `uv sync` / `uv run`, so CI and local
      # dev use the same environment without extra flags.
      default-groups = ["dev"]
      
      [build-system]
      requires = ["hatchling"]
      build-backend = "hatchling.build"
      
  • references
    • docker-patterns.md 5.6 KB
      # Docker patterns
      
      How to shape a Docker image for a robium project that needs ROS 2 or other
      system-level dependencies. This covers a *single environment's* Dockerfile shape; wiring
      multiple app modules together with compose is the `integration` skill's job;
      don't duplicate that here, cross-reference it.
      
      Sources: official ROS 2 images at
      [hub.docker.com/_/ros](https://hub.docker.com/_/ros) (fetched directly, not
      from memory: tags below were current at authoring time; re-verify before
      using), and the uv + Docker integration guide at
      [docs.astral.sh/uv/guides/integration/docker](https://docs.astral.sh/uv/guides/integration/docker/).
      
      ## Official ROS 2 image tags
      
      The official `ros` image on Docker Hub publishes, per distro, three variant
      tiers plus an OS-codename-suffixed form:
      
      - `ros:<distro>-ros-core`: minimal ROS 2 install.
      - `ros:<distro>-ros-base`: adds basic tools/libraries (the usual starting
        point for an application image).
      - `ros:<distro>-perception`: adds perception-related packages.
      - Each of the above also has an explicit OS-codename form, e.g.
        `ros:jazzy-ros-base-noble` / `ros:lyrical-ros-base-resolute`; prefer the
        explicit form when you want to pin the Ubuntu base as well as the ROS
        distro, for the strongest local/remote parity guarantee.
      
      Currently published distros include `jazzy` (Ubuntu Noble base) and
      `lyrical` (Ubuntu Resolute base, current LTS), among others (`humble`,
      `kilted`, `rolling`). Confirm the current set and exact tags at
      [hub.docker.com/_/ros](https://hub.docker.com/_/ros) before pinning one; this
      list changes as distros reach EOL and new ones ship. Desktop-variant images
      are not part of the official minimal set (kept lean/secure); if you need a
      desktop image, that's a deliberate, separate choice.
      
      There is no official `ros-desktop` reason to reach for the OSRF-hosted
      `osrf/ros2` images for a headless application container: official + minimal
      is the default; only deviate with a stated reason.
      
      ## Base pattern: ROS 2 image + uv for the Python layer
      
      Even inside a ROS 2 container, keep the "never pip install into system
      Python" directive: if the workspace has pure-Python glue code (a bridge
      script, a data-processing node, an ML inference node) with its own
      dependencies, manage those with uv rather than `pip install`ing them into the
      image's system Python.
      
      ```dockerfile
      FROM ros:jazzy-ros-base
      
      # Install uv by copying the binary from the official distroless image;
      # no pip/curl needed for this step.
      COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
      
      WORKDIR /workspace
      COPY ./src ./src
      COPY pyproject.toml uv.lock ./
      
      # Python-side deps for glue code, isolated in a project venv, not the
      # container's system Python.
      RUN uv sync --locked
      
      # ROS 2 workspace build (colcon ships in ros-base; add
      # python3-colcon-common-extensions via apt if a variant lacks it).
      RUN . /opt/ros/jazzy/setup.sh && colcon build --symlink-install
      ```
      
      See `examples/Dockerfile.ros2` for the full, runnable-shape version
      (non-root user, entrypoint that sources both the ROS 2 and workspace
      overlays).
      
      ## Multi-stage builds with uv (mixed / heavy-Python case)
      
      When the Python side is heavy (a training or inference stack with large
      dependencies like torch), use a proper multi-stage build so the final image
      doesn't carry uv's cache or build-only tooling:
      
      ```dockerfile
      FROM python:3.12-slim-trixie AS builder
      COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
      ENV UV_PYTHON_DOWNLOADS=0 UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
      WORKDIR /app
      RUN --mount=type=cache,target=/root/.cache/uv \
          --mount=type=bind,source=uv.lock,target=uv.lock \
          --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
          uv sync --locked --no-install-project
      COPY . /app
      RUN --mount=type=cache,target=/root/.cache/uv \
          uv sync --locked
      
      FROM python:3.12-slim-trixie
      COPY --from=builder /app/.venv /app/.venv
      ENV PATH="/app/.venv/bin:$PATH"
      WORKDIR /app
      COPY . /app
      CMD ["python", "train.py"]
      ```
      
      The `--mount=type=cache` on `/root/.cache/uv` speeds up rebuilds without
      baking the cache into any image layer; the two-step `sync` (deps first,
      then the project) maximizes Docker layer-cache hits when only application
      code changes. See `examples/Dockerfile.gpu-ml` for the GPU-base variant of
      this same shape.
      
      ## Non-root users
      
      Run the final container as a non-root user; it's both a security default and
      a parity aid (file permissions on mounted volumes behave the same locally and
      remotely):
      
      ```dockerfile
      RUN groupadd --system --gid 1000 robium \
       && useradd --system --gid 1000 --uid 1000 --create-home robium
      USER robium
      ```
      
      ## Local == remote parity, the Docker half
      
      These checks make the runtime contract in `SKILL.md` reproducible:
      
      - **Pin tags, not `latest`.** `ros:jazzy-ros-base-noble`, not `ros:jazzy`;
        ideally pin a digest (`@sha256:...`) for anything long-lived.
      - **`.dockerignore` your `.venv`, `build/`, `install/`, `log/`** (ROS 2
        colcon artifacts); building on a clean tree locally and remotely avoids
        "works because of stale local build state" bugs.
      - **Bake the lockfile in, don't `COPY` a `requirements.txt` generated
        ad-hoc.** `uv.lock` (or the ROS 2 `rosdep`-resolved package list) is the
        single source of truth referenced from both local and CI/remote builds.
      - **Build the same image for local dev and remote deployment**: a `docker
        build` + volume-mount for live-editing locally, the same image pushed and
        run remotely, rather than two divergent Dockerfiles.
      - **Compose/multi-service wiring is out of scope here**: once you have more
        than one container that needs to talk to each other, hand off to
        `integration` for the compose file and inter-node comms plan.
      
    • gpu-and-remote.md 5.1 KB
      # GPU passthrough and remote/headless environments
      
      How to get an NVIDIA GPU into a Docker container, and how to think about
      display/visualization once the project is running on a headless or remote
      machine. Sources: [NVIDIA Container Toolkit
      docs](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/)
      (install guide + sample workload page), fetched directly; re-verify package
      versions before use, they move frequently.
      
      ## GPU passthrough: nvidia-container-toolkit (Linux only)
      
      GPU access inside a Docker container requires the **NVIDIA Container
      Toolkit** installed on the *host*; this is a Linux-only requirement (the
      official install guide covers Linux distributions; there is no first-party
      macOS host path). See [platform notes](../PLATFORM-NOTES.md) for Robium's observed platform
      tradeoffs.
      
      **Install (Ubuntu/Debian host, apt):**
      
      ```bash
      # 1. Configure NVIDIA's package repository (see the current install guide
      #    for the exact repo-setup commands: they change; don't hardcode a key
      #    URL from memory).
      # 2. Install the toolkit:
      sudo apt-get install -y nvidia-container-toolkit
      # 3. Wire it into the Docker daemon:
      sudo nvidia-ctk runtime configure --runtime=docker
      sudo systemctl restart docker
      ```
      
      **Verify it works:**
      
      ```bash
      sudo docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi
      ```
      
      If `nvidia-smi` prints the host's GPU(s) from inside the container, the
      toolkit is wired up correctly. Do this verification step on any *new* remote
      host before assuming GPU workloads will just work there; it's a common
      source of "works locally, fails on the server" when the toolkit is missing
      or the Docker daemon wasn't restarted after configuration.
      
      **Running a GPU workload:**
      
      ```bash
      docker run --rm --gpus all my-gpu-image
      ```
      
      `--gpus all` is the flag that matters day to day; `--runtime=nvidia` above is
      mainly for the one-time verification (once `nvidia-ctk runtime configure`
      has run, `--gpus` works without needing to also set `--runtime` explicitly on
      every invocation, but check current NVIDIA docs if you see different
      behavior on a given Docker version).
      
      **CUDA base image, host driver compatibility:** the CUDA toolkit version
      baked into the container image must be supported by the host's installed GPU
      driver. Check the host's supported CUDA version with `nvidia-smi` (top-right
      of its output) before picking a CUDA base image tag; this is one of the
      sharpest local/remote parity failure modes: a dev laptop with a newer driver
      building an image whose CUDA version the remote server's older driver can't
      run. See `examples/Dockerfile.gpu-ml` for a concrete base-image example
      (marked `status: unverified`; re-check the tag against
      [hub.docker.com/r/nvidia/cuda](https://hub.docker.com/r/nvidia/cuda) before
      using it in a real project).
      
      **WSL2 note:** the official install guide referenced above documents Linux
      distributions only and doesn't cover WSL2 specifically; if a remote/dev
      target is Windows+WSL2, verify the current WSL2-specific GPU support path
      against NVIDIA's docs separately rather than assuming the Linux steps apply
      unchanged.
      
      ## Headless / remote display strategy
      
      The instinct when a container needs to show something is X11 forwarding
      (`-e DISPLAY=$DISPLAY`, mounting `/tmp/.X11-unix`, `xhost +local:docker`).
      That works for **local-Linux-only, single-user** development, but it breaks
      down fast:
      
      - It doesn't work at all from a headless remote server with no X server.
      - It's fragile over SSH (needs `-X`/`-Y` forwarding, a working X client
        chain, and generally poor performance for anything beyond simple 2D UI).
      - It doesn't work from macOS/Windows hosts without extra tooling (XQuartz,
        VcXsrv) that itself isn't containerized or reproducible.
      
      **Default instead to headless containers + web-based visualization.** Run
      the container with no display requirement at all, and expose whatever needs
      visualizing over a web UI that any browser (local or remote, any OS) can
      reach. This is exactly what the `foxglove` skill covers; route to it
      instead of building out X11 forwarding, especially for:
      
      - Any remote/cloud GPU server (the common case for training or
        Isaac Sim work).
      - Cross-platform teams where not everyone is on Linux.
      - CI or automated runs where no display exists at all.
      
      Reserve X11/Wayland forwarding for a narrow case: local Linux development,
      one user, and a tool that genuinely has no web-based alternative, and treat
      it as a local-dev convenience, not something the deployed/remote environment
      depends on.
      
      ## Local vs remote parity for GPU + display
      
      - **Don't let "works on my GPU laptop" silently mean "works on the remote
        GPU server."** Confirm both the CUDA-version-vs-driver compatibility above
        and that `nvidia-container-toolkit` is actually installed and configured
        on the remote host; it's easy to develop for weeks locally without ever
        hitting this gap.
      - **Don't build the visualization story around a display that only exists
        locally.** If any part of the workflow assumes a local X server, it will
        silently fail (or need a completely different setup) the moment the
        project moves to a remote/headless host. Default to `foxglove` from the
        start so there's nothing to redo.
      
    • gpu-cloud.md 2.8 KB
      # Cloud-GPU environment boundary and hyperscaler quota gates
      
      When a project needs a real NVIDIA GPU that the dev machine doesn't have
      (Isaac Sim / Isaac Lab, GR00T, heavy CUDA training), the environment is a
      cloud GPU that you drive as a thin client. This reference owns the environment
      boundary and hyperscaler quota context. Use the `runpod` skill for RunPod
      inventory, provisioning, storage, networking, diagnostics, billing, and
      cleanup. Battle-tested on go2-locomotion (Isaac Lab, 2026-07-26..28).
      
      ## The one genuine exception to virtual-first local==remote
      
      Robium's default is virtual-first with a local==remote parity guarantee.
      Some GPU apps have **no local mirror on a Mac at all**:
      
      - Isaac Sim / Isaac Lab (Omniverse Kit) needs NVIDIA RTX + CUDA on Linux or
        Windows; there is no macOS path, and Docker can't pass through a GPU the
        Mac doesn't have.
      - Same class: GR00T and other heavy-CUDA training stacks.
      
      For these, the Mac is a **thin SSH/browser client** and the sim/training
      runs entirely on a cloud GPU. State this explicitly in the architecture
      brief; it's the sanctioned exception to the local==remote rule, not a
      parity failure to fix.
      
      ## Choose a provider after defining the environment contract
      
      First pin the image/CUDA contract, GPU floor, storage, network exposure,
      budget, and acceptance test. Then compare live provider capacity and quota.
      Do not state stock, price, or time-to-first-GPU from memory. RunPod-specific
      selection and paid-compute gates live in the `runpod` skill.
      
      ### GCP GPU quota gotchas (if you must use GCP)
      
      - The binding quota is the **global `GPUS_ALL_REGIONS`** metric, not the
        per-region one (e.g. `NVIDIA_L4_GPUS`). GCP enforces both; the lower one
        binds, so a per-region grant is useless if the global is still 0.
      - A well-formed `GPUS-ALL-REGIONS` increase request can be **auto-denied in
        under a second** on a young project with no billing history; Google
        Support confirmed this is account-standing, not a malformed request.
        Budget 2+ days of lead time or choose a provider whose current account and
        capacity checks satisfy the workload.
      - Diagnose current quota programmatically via the Cloud Quotas API
        (`quotaPreferences`).
      
      ## Provider handoff
      
      - RunPod inventory, exact GPU/datacenter selection, network volumes, Pod
        creation, proxy/SSH behavior, interactive diagnostics, billing, and cleanup:
        use the `runpod` skill and verify its current official sources.
      - Google Cloud Run deployment: use the `cloud-run` skill.
      - Framework-specific cloud-image and runtime mechanics stay with the framework
        skill, such as `isaac-lab` or `lerobot`.
      
      The environment contract remains provider-independent: a pinned image or lock,
      explicit CUDA/architecture floor, no host-only paths, headless operation,
      durable evidence, and a documented exception when no local mirror exists.
      
    • robot-networking.md 4.8 KB
      # Robot networking for real-robot bring-up
      
      Getting a laptop (usually a Mac) onto a physical robot's network well enough
      to SSH in, reconfigure it, and run ROS 2 against it. This is the messy,
      pre-virtual layer: before any container or lockfile matters, you have to
      reach the robot at all. Battle-tested on a real TurtleBot 4 (tb4-teleop,
      2026-07-24/25). Everything here is about *host/LAN plumbing*; the parity
      mechanics for the environment that runs on top live in the other references.
      
      ## Headless first contact on no shared network
      
      When the robot ships headless and there's no shared Wi-Fi yet, cable a
      direct Ethernet link (USB-Ethernet on the Mac) and find the robot over
      IPv6 link-local:
      
      ```bash
      ping6 -c 4 ff02::1%en5      # all-nodes multicast on the direct iface
      ndp -a                       # read the neighbor table for fe80::... entries
      ssh ubuntu@fe80::...%en5     # SSH the link-local addr, scoped to the iface
      ```
      
      **IPv6 link-local is flaky for unicast SSH.** Multicast ping (`ff02::1`)
      answers reliably, but the `ndp -a` entries expire and drop under load;
      an `apt` install over that SSH will stall out. The stable fix is to add an
      IPv4 alias in the robot's own static subnet and SSH *that* instead. If the
      robot's eth0 is statically `192.168.185.3`:
      
      ```bash
      sudo ifconfig en5 inet 192.168.185.10 netmask 255.255.255.0 alias
      ssh ubuntu@192.168.185.3
      ```
      
      ## Reconfiguring the robot's Wi-Fi via netplan
      
      On Ubuntu the Wi-Fi config lives at `/etc/netplan/50-wifis.yaml`
      (NetworkManager renderer). The file is marked do-not-edit, but a direct
      edit followed by `netplan generate` / `netplan apply` works. Keep it
      `chmod 600`; netplan warns when the file is world-readable.
      
      Applying the change bounces eth0, which will kill the very SSH session you
      issued it from. Detach the apply so it survives the bounce:
      
      ```bash
      sudo chmod 600 /etc/netplan/50-wifis.yaml
      sudo netplan generate
      sudo nohup netplan apply >/tmp/netplan.log 2>&1 &
      ```
      
      With `autoconnect: yes` (NetworkManager default) the new Wi-Fi persists
      across reboots.
      
      ## DDS multicast, NAT, and where the bridge must run
      
      - **DDS multicast discovery does NOT cross NAT.** If you cable the Mac to a
        router's WAN port and turn on macOS Internet Sharing, the Mac ends up
        *behind* NAT and ROS 2 sees **zero topics**: discovery multicast never
        reaches the robot's LAN. The teleop host must sit on the robot's own LAN
        subnet, not behind a NAT boundary.
      - **In the 2026-07-24/25 TurtleBot 4 + Docker Desktop trial, the Linux VM did
        not discover DDS participants on the physical robot LAN.** Running the
        ROS↔browser bridge on the robot and connecting from the Mac over TCP was the
        validated resolution for that setup. Docker Desktop networking modes, RMWs,
        and LAN topologies change; probe bidirectional reachability and ROS discovery
        before generalizing this result or choosing bridge placement. See the
        foxglove skill for the bridge side.
      
      ## macOS Internet Sharing failure modes
      
      - **Sharing from an iPhone-hotspot uplink won't activate.** `bridge100`
        (192.168.2.1) never comes up. Worse, the half-on state *breaks the direct
        cable*: the robot still answers IPv6 multicast ping but unicast SSH times
        out. Turning Internet Sharing OFF restores the cable. (For a Mac cabled to
        a robot LAN losing its own internet, see [platform notes](../PLATFORM-NOTES.md), a separate
        issue.)
      
      ## DHCP and MAC-address gotchas on the shared SSID
      
      - **Two DHCP servers behind one SSID hand out different subnets on the same
        L2.** A Wi-Fi extender running its own DHCP will put the robot and the
        laptop on different subnets even though they share one SSID; they become
        mutually unreachable. Fix by pinning static IPs on both:
        - netplan: `dhcp4: false` plus explicit `addresses:`, `routes:`, and
          `nameservers:`.
        - or NetworkManager: `nmcli con mod <name> ipv4.method manual` (with
          `ipv4.addresses` / `ipv4.gateway` / `ipv4.dns`).
        Both persist across reboots.
      - **Wi-Fi MAC randomization hides the hardware MAC from the router's DHCP
        table.** The table shows a randomized `a8:e2:91:...` address, not the
        hardware `d8:3a:dd:...`. Don't try to find the robot by grepping for its
        hardware MAC; identify it by hostname / successful SSH, or sidestep the
        whole problem with static IPs.
      
      ## Validating a camera panel on a Mac (no V4L2)
      
      macOS Docker cannot pass a USB webcam into a container; V4L2 is
      Linux-only, so the container has no `/dev/video*` to bind. To exercise an
      MJPEG video panel locally on a Mac without the real camera, serve a synthetic
      multipart stream:
      
      - Serve a local `multipart/x-mixed-replace` source (e.g. an ffmpeg
        `testsrc`) that a browser `<img>` will render as a live MJPEG feed.
      - ffmpeg's built-in `-listen` HTTP server sends `application/octet-stream`,
        which a browser `<img>` will **not** render; you need a proper multipart
        server in front, not raw ffmpeg `-listen`.
      
    • uv-patterns.md 4.9 KB
      # uv patterns
      
      How to set up and run a pure-Python robium project with
      [uv](https://docs.astral.sh/uv/). This is the default for any project that
      doesn't need ROS 2 or other system-level dependencies; see the uv choice in
      `SKILL.md`. Sources: [uv docs](https://docs.astral.sh/uv/), fetched via the
      `ctx7` documentation tool (`astral-sh/uv`) rather than from memory; re-verify
      against current docs before relying on exact flag behavior.
      
      ## Project setup
      
      ```bash
      uv init my-project        # scaffolds pyproject.toml, a src/ layout, .gitignore
      cd my-project
      uv add numpy torch        # adds runtime dependencies, updates pyproject.toml + uv.lock
      uv add --dev pytest ruff  # adds a `dev` dependency group
      ```
      
      `pyproject.toml` after a couple of `uv add` calls looks like:
      
      ```toml
      [project]
      name = "my-project"
      version = "0.1.0"
      requires-python = ">=3.11"
      dependencies = [
          "numpy>=1.26",
          "torch>=2.3",
      ]
      
      [dependency-groups]
      dev = ["pytest>=8.0", "ruff>=0.6"]
      ```
      
      See `examples/pyproject-uv.toml` for a complete minimal file.
      
      ## Running things: always through `uv run`
      
      ```bash
      uv run python train.py
      uv run pytest
      uv run ruff check .
      ```
      
      `uv run` resolves and syncs the project's `.venv` automatically before
      running the command; there is no separate "activate the venv" step to
      forget or get wrong. This is why the key directive is "never `pip install`
      into system Python": `uv run` (and `uv sync`) already give you an isolated,
      reproducible environment for free, so there is no reason to fall back to a
      global install.
      
      If you do want an activated shell, `uv venv` creates `.venv` explicitly and
      you can `source .venv/bin/activate` as usual, but prefer `uv run` for
      scripts and CI since it doesn't depend on shell state.
      
      ## `uv sync` vs `uv pip install`
      
      - **`uv sync`** (project-mode) makes the environment match `pyproject.toml` +
        `uv.lock` exactly: installs missing packages *and* removes anything not
        declared. This is the reproducibility guarantee: `uv sync` on two different
        machines with the same lockfile produces the same environment.
      - **`uv pip install`** (pip-compatible mode) behaves like `pip install`:
        additive only, no lockfile awareness by default. Reach for it only for
        one-off/ad-hoc installs (e.g. pre-installing a build dependency like
        `torch`/`setuptools` before a package that needs it at build time), not as
        the primary install path for a project.
      
      For robium projects, default to `uv sync` / `uv run` as the primary workflow;
      treat `uv pip install` as an escape hatch, not the norm.
      
      ## Lockfiles
      
      Commit `uv.lock` to the repository. It pins every resolved dependency
      (including transitive ones) to an exact version, which is what makes "works
      on my machine" become "works everywhere"; this is the uv half of the
      local == remote runtime contract from `SKILL.md`. Re-run `uv lock` (or `uv
      sync`, which updates the lock as needed) after changing `pyproject.toml`, and
      commit the updated lockfile in the same change.
      
      ## Pinning the Python version
      
      ```bash
      uv python pin 3.11
      ```
      
      Writes a `.python-version` file; `uv run`/`uv sync` then provision and use
      that exact interpreter version (downloading it if needed, unless
      `UV_PYTHON_DOWNLOADS=0`). Pin explicitly rather than relying on "whatever
      Python happens to be on this machine"; another local/remote parity point.
      
      ## When `--system` / `UV_SYSTEM_PYTHON` is acceptable
      
      The "never `pip install` into system Python" directive has exactly one
      sanctioned exception: inside a **disposable container build stage**, where
      "system Python" means the container's own throwaway Python, not a host
      machine's. Two legitimate cases:
      
      - **CI runners that are themselves ephemeral** (a fresh container per job):
        setting `UV_SYSTEM_PYTHON=1` for the whole job lets `uv pip install` target
        the runner's Python directly, since there's no persistent host to pollute.
      - **A Docker build stage that installs directly into the image's Python**
        rather than creating a nested venv; acceptable *only* when that stage's
        entire filesystem is the deliverable (i.e., you're not also using that
        Python for anything else). Prefer the multi-stage venv pattern in
        `references/docker-patterns.md` when you have a choice; it keeps the
        Dockerfile identical in spirit to the non-Docker uv workflow.
      
      Never use `--system` on a developer's laptop or on a long-lived server's bare
      OS Python; that is exactly the case the directive exists to prevent.
      
      ## When to graduate from uv to Docker
      
      Stop trying to make uv alone carry a project once any of these become true:
      
      - The project needs ROS 2 or another apt/system package that isn't
        Python-installable.
      - The project depends on a specific OS/kernel feature (e.g. certain GPU
        driver interactions, real-time kernel patches).
      - You need the *exact same OS base*, not just the same Python packages,
        reproduced on another machine.
      
      At that point, move to `references/docker-patterns.md`, and keep using uv
      *inside* the container for the Python-dependency layer; the two are
      complementary, not alternatives, once you're in Docker.
      
  • evals.yaml 278 B
    triggers:
      positive:
        - phrase: should this Python robotics project use uv or Docker
        - phrase: make the local and remote CUDA environments reproducible
      negative:
        - phrase: find an available RunPod GPU in the same region as my volume
          expect: runpod
    tasks: []
    
  • PLATFORM-NOTES.md 2.2 KB
    # Environment platform notes
    
    These observations explain choices that can look arbitrary without their
    measured conditions. They are evidence, not universal performance claims.
    
    - **ROS 2 on macOS:** use Linux or a container for the normal supported ROS 2
      workflow. Gazebo by itself may run natively, but the full ROS integration
      inherits this boundary.
    
    - **Apple Silicon acceleration:** Docker Desktop does not pass Metal/MPS into a
      Linux container. In Robium's `manip-trial` and `vla-trial`, SmolVLA inference
      measured about 0.55 seconds per forward pass with host-native MPS and about 9
      seconds on CPU in Docker or Cloud Run, roughly a 17x difference. For an ML
      policy on a Mac, measure CPU latency before choosing Docker. A trivial host
      dependency such as `ffmpeg` may be a better documented exception than losing
      the accelerator.
    
    - **arm64 slim images:** a dependency without an arm64 manylinux wheel falls
      back to a source build. In the 2026-07-15 `manip-trial`, `pymunk` required a
      compiler and failed with `gcc` absent. Install the build toolchain in a build
      stage or choose a base that contains it; do not assume all architectures have
      the same wheels.
    
    - **GPU containers:** NVIDIA Container Toolkit is a Linux-host dependency.
      Docker on macOS cannot create an NVIDIA accelerator path. Some applications,
      including Isaac Sim/Lab and CUDA-heavy training, therefore use the Mac only
      as an SSH/browser client and run on a compatible remote GPU machine.
    
    - **Cold rebuild claims:** `docker compose down --rmi local` removes local
      images but not the BuildKit/buildx layer cache. Robium's 2026-07-11
      `nav-trial` used an additional builder-cache prune to test a genuinely cold
      build. Cache removal is destructive to build performance, so use it only when
      the test explicitly needs that evidence.
    
    - **Mac plus private robot LAN:** in the 2026-07-24 `tb4-teleop` setup, macOS
      preferred a wired no-internet robot LAN over Wi-Fi for the default route.
      Moving Wi-Fi above the USB LAN in Network Service Order restored internet
      while the robot subnet continued over the cable. Read
      [robot networking](references/robot-networking.md) before generalizing that
      topology.
    
  • SKILL.md 3.5 KB
    ---
    name: environments
    description: Set up reproducible robotics environments with uv, Docker, or GPU hosts.
    ---
    
    # Environments
    
    Define the runtime contract before writing around machine-specific accidents.
    Local and remote runs should differ only where the hardware genuinely differs.
    
    ## Choose the smallest sufficient boundary
    
    - **uv:** pure-Python robotics, ML, data, and tooling without required system
      packages. Commit the lockfile and run project commands through `uv run`.
    - **Docker:** ROS 2, native libraries, apt packages, an exact Linux userspace,
      or deployment as a container.
    - **Docker plus uv:** use Docker for the system layer and a project environment
      for substantial Python dependencies; do not turn the image's system Python
      into an untracked package set.
    - **Remote GPU host:** use when the workload has no viable local hardware path.
      Record this as a deliberate exception to local/remote symmetry.
    
    Preflight the actual machine before deciding. Confirm architecture, OS, Docker
    daemon, accelerator, driver, disk, and available Python tooling. Use the Robium
    doctor if present; fall back to direct probes when it is not.
    
    ## Keep the contract reproducible
    
    - Pin Python and dependency resolution with a lockfile, or pin the container
      base and build inputs. Avoid `latest` for reproducible applications.
    - Keep dependency declarations in one source of truth; do not preserve manual
      installation steps as hidden prerequisites.
    - Match container CUDA/runtime requirements to the target host driver rather
      than the developer laptop.
    - Design remote work as headless first. Use web visualization instead of making
      X forwarding part of the normal workflow.
    - Test the same entry command in the target environment and prove important
      hardware, file, device, network, and display assumptions.
    - Before claiming first-run reproducibility, run the complete setup from a
      clean project copy with the relevant package, model, and asset caches empty.
      A warm development checkout can hide broken fetch and bootstrap behavior.
    
    ## Go deeper only when needed
    
    - Pure Python and lockfiles: [uv patterns](references/uv-patterns.md).
    - ROS/system images, build layout, and parity: [Docker patterns](references/docker-patterns.md).
    - NVIDIA passthrough and headless operation: [GPU and remote](references/gpu-and-remote.md).
    - Real-robot LAN, Wi-Fi, DDS/NAT, and Mac host issues: [robot networking](references/robot-networking.md).
    - Workloads that can exist only on cloud GPUs: [GPU cloud](references/gpu-cloud.md),
      then the relevant provider skill for provisioning.
    - macOS, Apple Silicon, arm64, and cold-build evidence: [platform notes](PLATFORM-NOTES.md).
    - Use the bundled examples only as starting shapes; verify all tags and install
      steps against current upstream documentation.
    
    Cross into `integration` when multiple modules, containers, or transports must
    be wired together. Cross into a deployment skill only after the image and
    runtime contract work locally or in an equivalent target environment.
    
    ## Done
    
    - A fresh machine can reproduce the environment from committed inputs.
    - The same documented command starts the workload locally and remotely, except
      for named hardware flags.
    - GPU, devices, network, files, and display behavior are verified on the target.
    - Current details match official [uv](https://docs.astral.sh/uv/),
      [Docker](https://docs.docker.com/), ROS image, and
      [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/)
      documentation.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related