Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,54 @@ This script verifies in under 1 second that:
| **§1 Authorize** | API key authenticates → Convex picks a live Modal worker (us-west B200) → signs a 30-min HMAC session token |
| **§2 Worker health** | Authenticated GET to the Modal worker — proves the worker is up + reachable |

If you want the **closed-loop bimanual YAM demo** (arms move from camera observations):
### Closed-loop bimanual YAM (arms move from camera observations)

Everything runs from this repo — no monorepo clone, **no local GPU** (the model
runs on a Reflex cloud B200). Uses [`uv`](https://docs.astral.sh/uv/) so the
whole stack installs from a pinned lockfile in **one command** — same versions
that are verified working on real hardware (i2rt @ a known-good commit, Python
3.12, SDK 0.6.6).

```bash
git clone https://github.com/reflex-inc/reflex
cd reflex/sdk/python && pip install -e .
# 1. Install EVERYTHING (SDK[webrtc] + RealSense + i2rt, all pinned in uv.lock)
uv sync --extra yam

# 2. Auto-detect cameras → write yam_bimanual.yaml → check CAN. No hand-editing.
uv run yam-setup

# Connect to your YAM arms + 3 cameras via the cloud BASELINE worker
reflex connect --config ../../examples/yam_bimanual_molmoact2_BASELINE.yaml
# 3. CAN bring-up (one-time, needs sudo). yam-setup prints these if CAN is down:
sudo ip link set can0 up type can bitrate 1000000
sudo ip link set can1 up type can bitrate 1000000

# 4. Run. Defaults to dry_run (NO motion). Set REFLEX_YAM_APPLY=1 to move arms.
export REFLEX_API_KEY="rfx_..." # or: uv run reflex login
uv run yam-demo # Ctrl-C ONCE → safe-home
```

That's it — no version hunting, no editing serials by hand. `uv sync` reads
`uv.lock` so every machine gets the identical, hardware-verified dependency set.

What you need on the robot machine:

| Need | Notes |
|---|---|
| [`uv`](https://docs.astral.sh/uv/) | `curl -LsSf https://astral.sh/uv/install.sh \| sh`. It installs Python 3.12 + all deps for you |
| 2× YAM arms over CAN | `can0` + `can1` (socketcan) — `yam-setup` checks them + prints the bring-up |
| 3 RealSense cameras | 1× D435 (top) + 2× D405 (wrists) — `yam-setup` auto-detects serials |
| Reflex API key + balance | `export REFLEX_API_KEY=...` or `uv run reflex login` |

> **Why pinned?** i2rt's tip (1.1.2) has an fd=-1 CAN control bug; the lockfile
> pins the verified commit. dm-tree has no wheels on 3.13+ and lerobot needs
> ≥3.12 → Python is pinned to 3.12. You never have to know any of this — `uv
> sync` just gives you the working set.
>
> Prefer the old pip flow? `./setup_yam.sh all` still works (auto-picks a
> Python, pip-installs) — but `uv sync` is the reproducible path.

**Ctrl-C** ends the session and **safely homes both arms** (motors released — no
drop, no hold). Start with `mode: dry_run` in the yaml to verify the camera →
state → inference → action loop with the arms held still before applying motion.

**Pricing:** $10/hr × actual GPU-seconds (≈ $0.001 per 200ms inference call).
**Quality:** WebRTC + adaptive JPEG q=95 — visually lossless (PSNR 38.8 dB vs raw).
**Latency:** ~220 ms p50 RTT from residential WAN to us-west.
Expand Down
49 changes: 32 additions & 17 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
[project]
name = "reflex-quickstart"
version = "0.3.0"
version = "0.4.0"
description = "End-to-end quickstart for the Reflex Labs robotics fine-tune + inference API"
readme = "README.md"
requires-python = ">=3.12"
# Python 3.12 only: the bimanual-YAM path pulls i2rt → dm-env → dm-tree (no
# wheels on 3.13+), and lerobot (arm extra) requires >=3.12 — 3.12 is the
# single version that satisfies both. uv installs it for you automatically.
requires-python = ">=3.12,<3.13"
license = { text = "Apache-2.0" }
authors = [{ name = "Reflex Labs", email = "team@tryreflex.ai" }]
keywords = ["robotics", "vla", "fine-tuning", "inference", "reflex", "lerobot", "pi0.5"]
keywords = ["robotics", "vla", "fine-tuning", "inference", "reflex", "yam", "pi0.5"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Comment on lines +19 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Trove classifiers contradict requires-python.

requires-python = ">=3.12,<3.13" (line 9) restricts installation to Python 3.12, yet the classifiers still advertise 3.10 and 3.11. This is misleading package metadata and conflicts with the comment on lines 6–8.

🔧 Proposed fix
     "License :: OSI Approved :: Apache Software License",
     "Operating System :: POSIX :: Linux",
-    "Programming Language :: Python :: 3.10",
-    "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 19 - 20, The trove classifiers in pyproject.toml
conflict with the requires-python setting; remove or update the entries
"Programming Language :: Python :: 3.10" and "Programming Language :: Python ::
3.11" and replace/add a classifier for "Programming Language :: Python :: 3.12"
so the classifiers align with requires-python = ">=3.12,<3.13"; ensure no
classifiers advertise versions older than 3.12 and keep the classifiers list
consistent with the requires-python key.

"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: System :: Hardware",
]

# Default deps cover the training + inference smoke tests (quickstart*.py).
dependencies = [
# Reflex SDK — talks to the live API for training submission + status polling.
# 0.3.0 added the BYO-model surface used by quickstart_byom.py:
# client.models.import_from_hf / list / get / delete and
# client.keys.bind_model / unbind_model. 0.2.0 added 8 advanced
# training params (lora_alpha, lora_dropout, target_modules,
# warmup_steps, gradient_checkpointing, freeze_vision_encoder,
# dtype, save_freq) with server-side validation.
"reflex-sdk>=0.3.0",

# Image encoding for inference observations
"reflex-sdk>=0.6.6",
"opencv-python>=4.8",
"pillow>=10",
"numpy>=1.24",
]

[project.optional-dependencies]
# `uv sync --extra yam` installs EVERYTHING for the closed-loop bimanual YAM
# demo in one command: the WebRTC transport, RealSense, and the i2rt driver
# pinned to the hardware-verified commit (newer i2rt has an fd=-1 control bug).
yam = [
"reflex-sdk[webrtc]>=0.6.6",
"pyrealsense2>=2.54",
"i2rt",
]
Comment on lines +38 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Does pip honor the [tool.uv.sources] table in pyproject.toml when resolving optional-dependencies, or is it uv-specific?

💡 Result:

The [tool.uv.sources] table in pyproject.toml is specific to uv and is not honored by pip [1][2]. This table is a uv-only feature designed to allow developers to define alternative sources for dependencies (such as Git repositories, local paths, remote URLs, or specific indexes) while maintaining a standards-compliant project configuration that can still be published to PyPI [1][3][2]. Because pip adheres to standard PEP 621 metadata (found in the [project] table), it does not read or respect tables under [tool.uv]. If you use [tool.uv.sources] to define dependencies, those definitions are ignored by pip [1]. If you rely on these sources, any other tool used for development will require you to re-specify those dependencies using that tool's specific format [1].

Citations:


Fix yam so pip install .[yam] can resolve i2rt

project.optional-dependencies.yam includes a bare i2rt requirement, but the only source override is under [tool.uv.sources], which pip does not honor (it’s uv-specific). As a result, pip install .[yam] will try to resolve i2rt from PyPI and fail if it isn’t published. Use a PEP 508 direct URL/VCS reference for the pip path, or document that yam is uv-only and i2rt must be installed separately for pip.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 38 - 42, The optional dependency group
project.optional-dependencies.yam currently lists a bare "i2rt" which pip cannot
resolve because the only source override is under [tool.uv.sources]; update
pyproject.toml so pip can install .[yam] by replacing the bare "i2rt" in the yam
list with a PEP 508 direct reference (e.g., a VCS or direct URL) that points to
the i2rt package location, or alternatively add a comment/docstring in
pyproject.toml next to the yam group stating that i2rt is uv-only and must be
installed separately; search for the yam block and the i2rt entry to implement
the chosen fix (project.optional-dependencies.yam -> replace "i2rt" with a
VCS/URL spec or add documentation).

arm = [
# Required for §3 (closed-loop SO-101 control). Skip with SKIP_ARM=1
"lerobot>=0.5.2",
# lerobot 0.5.2 was yanked from PyPI; 0.5.1 is the latest available.
"lerobot>=0.5.0",
"feetech-servo-sdk>=1.0",
"deepdiff>=8.0",
]

# i2rt has no PyPI release — pull it from git, PINNED to the commit verified
# working on real YAM hardware (HAL + box). The tip of main (1.1.2) has an
# fd=-1 CAN control regression; do NOT unpin without re-verifying on hardware.
[tool.uv.sources]
i2rt = { git = "https://github.com/i2rt-robotics/i2rt.git", rev = "7b6d5016f05ca63f9ef0185b7143e63f2c7a5708" }

[project.urls]
Homepage = "https://app.tryreflex.ai"
Documentation = "https://docs.tryreflex.ai"
Expand All @@ -51,10 +61,15 @@ Issues = "https://github.com/reflex-inc/quickstart/issues"

[project.scripts]
reflex-quickstart = "quickstart:main"
# Bimanual YAM: one command each.
# uv run yam-setup → detect RealSense serials, write yam_bimanual.yaml, check CAN
# uv run yam-demo → setup + run the closed-loop demo (Ctrl-C = safe-home)
yam-setup = "yam_robot:setup_cli"
yam-demo = "yam_robot:demo_cli"

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
py-modules = ["quickstart", "quickstart_byom"]
py-modules = ["quickstart", "quickstart_byom", "yam_robot"]
126 changes: 126 additions & 0 deletions setup_yam.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# Reflex Labs — Bimanual YAM setup helper
#
# Gets a fresh Linux machine from nothing → ready to run closed-loop inference
# on two YAM arms. The inference model runs in the Reflex cloud (B200); this
# machine just needs the client + the robot driver + cameras.
#
# USAGE:
# ./setup_yam.sh install # install SDK + robot driver + RealSense
# ./setup_yam.sh can # bring up the CAN interfaces (needs sudo)
# ./setup_yam.sh cameras # list connected RealSense serials (for the yaml)
# ./setup_yam.sh check # verify everything is ready
# ./setup_yam.sh all # install + can + cameras + check
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

# Pin the audited-stable SDK. Bump deliberately after verifying on hardware.
REFLEX_SDK_VERSION="${REFLEX_SDK_VERSION:-0.6.6}"
CAN_BITRATE="${CAN_BITRATE:-1000000}"
CAN_INTERFACES="${CAN_INTERFACES:-can0 can1}"

bold() { printf '\033[1m%s\033[0m\n' "$*"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf ' \033[33m!\033[0m %s\n' "$*"; }
die() { printf ' \033[31m✗ %s\033[0m\n' "$*" >&2; exit 1; }

# Pick a SUPPORTED interpreter. i2rt's pinned dm-env==1.6 pulls dm-tree, which
# only has usable wheels for CPython 3.10–3.12 — on 3.13/3.14 it tries to
# cmake-build from source and fails. HAL runs 3.10. Honor $PYTHON if set,
# else probe 3.12 → 3.11 → 3.10, else fall back to python3 with a version gate.
pick_python() {
if [ -n "${PYTHON:-}" ]; then echo "$PYTHON"; return; fi
for cand in python3.12 python3.11 python3.10; do
command -v "$cand" >/dev/null 2>&1 && { echo "$cand"; return; }
done
echo "python3"
}
PY="$(pick_python)"

require_supported_python() {
"$PY" - <<'PYEOF' || die "Unsupported Python. Install 3.10–3.12 (e.g. 'pyenv install 3.12') and re-run with PYTHON=python3.12 ./setup_yam.sh ..."
import sys
maj, minor = sys.version_info[:2]
ok = maj == 3 and 10 <= minor <= 12
print(f" using {sys.executable} (Python {maj}.{minor}) — {'supported' if ok else 'UNSUPPORTED'}")
sys.exit(0 if ok else 1)
PYEOF
}

install() {
bold "Python interpreter"
require_supported_python

bold "Installing Reflex SDK (stable v${REFLEX_SDK_VERSION}) + WebRTC extras"
# [webrtc] pulls aiortc/av/msgpack/numpy/Pillow — required for target.kind=webrtc
"$PY" -m pip install --upgrade "reflex-sdk[webrtc]==${REFLEX_SDK_VERSION}"
ok "reflex-sdk[webrtc]==${REFLEX_SDK_VERSION}"

bold "Installing the i2rt YAM robot driver"
if ! "$PY" -c "import i2rt" 2>/dev/null; then
"$PY" -m pip install "i2rt @ git+https://github.com/i2rt-robotics/i2rt.git" \
|| warn "i2rt pip install failed — clone https://github.com/i2rt-robotics/i2rt and 'pip install -e .' manually"
Comment on lines +62 to +63
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the recommended immutable install target for i2rt(latest release tag or commit SHA) fromi2rt-robotics/i2rt for production-like robot setup scripts?

💡 Result:

I couldn’t find any official release tags or published “latest release” metadata for i2rt-robotics/i2rt in the sources available to me, so there’s no authoritative immutable install target I can quote (latest release tag or commit SHA) from i2rt-robotics/i2rt for production-like scripts. What the repository’s docs do provide is an installation approach that installs directly from the repo (i.e., effectively “whatever commit you checked out”), which is not immutable by itself: it shows cloning the repo and then installing it with uv pip in editable mode ("uv pip install -e."). [1] Recommended production-like approach (immutable target): 1) Pick a specific commit SHA from the i2rt-robotics/i2rt repo and pin to it in your scripts (e.g., by cloning with --branch/--single-branch only after you hard-pin the SHA, or by checking out the SHA). 2) Then run the same install steps as in the docs after checkout: "uv pip install -e." (or a non-editable install if your deployment prefers wheels/site-packages). If you want, tell me whether you’re okay with pinning to the current default branch HEAD (main) or you need an actual GitHub Release tag; I can then give you exact clone/checkout commands tailored to your preference—however, I still need a source that exposes the tag/SHA (or you can provide the desired tag/SHA).

Citations:


Pin the i2rt Git dependency to an immutable tag/commit.

setup_yam.sh installs i2rt from a floating VCS ref (pip install "i2rt @ git+https://github.com/i2rt-robotics/i2rt.git"), making bring-up non-reproducible and increasing supply-chain risk. Since there’s no easily discoverable “latest release” tag, pin to a specific commit SHA (e.g., via an I2RT_REF env var).

Suggested diff
+  I2RT_REF="${I2RT_REF:-<tag-or-commit>}"
   if ! "$PY" -c "import i2rt" 2>/dev/null; then
-    "$PY" -m pip install "i2rt @ git+https://github.com/i2rt-robotics/i2rt.git" \
+    "$PY" -m pip install "i2rt @ git+https://github.com/i2rt-robotics/i2rt.git@${I2RT_REF}" \
       || warn "i2rt pip install failed — clone https://github.com/i2rt-robotics/i2rt and 'pip install -e .' manually"
   fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@setup_yam.sh` around lines 37 - 38, The pip install uses an unfixed VCS ref;
make it reproducible by adding an I2RT_REF env var (with a sensible default
commit SHA) and update the install invocation to use
git+https://github.com/i2rt-robotics/i2rt.git@${I2RT_REF} so pip installs that
exact commit, and adjust the failure message in the warn call to mention the
pinned ref; locate the install line that references "$PY -m pip install "i2rt @
git+https://github.com/i2rt-robotics/i2rt.git"" and modify it to use ${I2RT_REF}
(and add a short comment or export for I2RT_REF near the top of the script).

fi
"$PY" -c "import i2rt" 2>/dev/null && ok "i2rt importable" || warn "i2rt not importable yet"

bold "Installing RealSense Python bindings (skip if you use USB webcams)"
"$PY" -m pip install pyrealsense2 || warn "pyrealsense2 failed — only needed for kind: realsense cameras"
ok "install step done"
}

can_up() {
bold "Bringing up CAN interfaces: ${CAN_INTERFACES} @ ${CAN_BITRATE} bps"
for iface in $CAN_INTERFACES; do
if ip link show "$iface" >/dev/null 2>&1; then
sudo ip link set "$iface" down 2>/dev/null || true
sudo ip link set "$iface" type can bitrate "$CAN_BITRATE"
sudo ip link set "$iface" up
ok "$iface up @ ${CAN_BITRATE}"
else
warn "$iface not present — check your CAN adapter / USB connection"
fi
done
ip -br link | grep -E "can[0-9]" || warn "no can interfaces visible"
}

cameras() {
bold "Connected RealSense devices (copy serials into yam_bimanual.yaml)"
if command -v rs-enumerate-devices >/dev/null 2>&1; then
rs-enumerate-devices -s 2>/dev/null || rs-enumerate-devices 2>/dev/null | grep -iE "serial|name" | head -20
else
"$PY" - <<'PYEOF' || warn "pyrealsense2 not installed — run ./setup_yam.sh install"
import pyrealsense2 as rs
ctx = rs.context()
devs = list(ctx.query_devices())
if not devs:
print(" (no RealSense devices found — check USB)")
for d in devs:
print(f" {d.get_info(rs.camera_info.name):32s} serial={d.get_info(rs.camera_info.serial_number)}")
PYEOF
fi
}

check() {
bold "Readiness check"
"$PY" -c "import sys; v=sys.version_info; ok=v[0]==3 and 10<=v[1]<=12; print(f' Python {v[0]}.{v[1]}', '(supported)' if ok else '(UNSUPPORTED — use 3.10–3.12)')" 2>/dev/null || warn "no usable python"
"$PY" -c "import reflex; print(' reflex-sdk', reflex.__version__)" 2>/dev/null || warn "reflex-sdk not installed — run ./setup_yam.sh install"
"$PY" -c "import aiortc, av, msgpack, numpy, PIL; print(' webrtc extras OK')" 2>/dev/null || warn "webrtc extras missing — pip install 'reflex-sdk[webrtc]'"
"$PY" -c "import i2rt; print(' i2rt OK')" 2>/dev/null || warn "i2rt missing"
ip -br link | grep -qE "can[0-9].*UP" && ok "a CAN interface is UP" || warn "no CAN interface UP — run ./setup_yam.sh can"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate all required CAN interfaces, not just one.

The current check passes if any can* is UP, but bimanual mode depends on both configured channels. This can report “ready” while one arm is unavailable.

Suggested diff
-  ip -br link | grep -qE "can[0-9].*UP" && ok "a CAN interface is UP" || warn "no CAN interface UP — run ./setup_yam.sh can"
+  missing_can=0
+  for iface in $CAN_INTERFACES; do
+    if ip -br link show "$iface" 2>/dev/null | grep -q "UP"; then
+      ok "$iface is UP"
+    else
+      warn "$iface is not UP — run ./setup_yam.sh can"
+      missing_can=1
+    fi
+  done
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 84-84: Note that A && B || C is not if-then-else. C may run when A is true.

(SC2015)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@setup_yam.sh` at line 84, The current one-liner using `ip -br link | grep -qE
"can[0-9].*UP"` only verifies that at least one CAN interface is UP; change the
check to validate each required interface (e.g., can0 and can1) is UP before
reporting success. Update the script logic around the existing `ip -br link`
check and the `ok`/`warn` calls so it explicitly tests both interfaces (or
iterates a list of required `can*` names) and only calls `ok "a CAN interface is
UP"` when all required CAN interfaces are UP, otherwise call `warn` with a clear
message about which interface(s) are down. Ensure you reference the same `ok`
and `warn` symbols so behavior and messaging remain consistent.

if [ -n "${REFLEX_API_KEY:-}" ]; then ok "REFLEX_API_KEY is set"; else warn "REFLEX_API_KEY not set — export it or run 'reflex login'"; fi
echo
bold "Next:"
echo " 1. ./setup_yam.sh cameras # get your 3 RealSense serials"
echo " 2. edit yam_bimanual.yaml # paste serials + set CAN channels + prompt"
echo " 3. reflex connect --config yam_bimanual.yaml # (start with mode: dry_run)"
}

case "${1:-all}" in
install) install ;;
can) can_up ;;
cameras) cameras ;;
check) check ;;
all) install; can_up; cameras; check ;;
*) echo "usage: $0 {install|can|cameras|check|all}"; exit 1 ;;
esac
Loading
Loading