Skip to content

One-command bimanual YAM quickstart (uv sync + uv run yam-demo)#5

Open
AndresNinou wants to merge 3 commits into
mainfrom
andres-bimanual-yam-quickstart
Open

One-command bimanual YAM quickstart (uv sync + uv run yam-demo)#5
AndresNinou wants to merge 3 commits into
mainfrom
andres-bimanual-yam-quickstart

Conversation

@AndresNinou
Copy link
Copy Markdown
Collaborator

@AndresNinou AndresNinou commented Jun 1, 2026

Makes the closed-loop bimanual YAM demo a uv-native, one-command experience with a pinned lockfile as institutional memory of the hardware-verified dependency set.

uv sync --extra yam   # installs the WHOLE stack from uv.lock (one command)
uv run yam-setup      # auto-detects RealSense serials → writes config, checks CAN
uv run yam-demo       # closed-loop demo (Ctrl-C ONCE = safe-home)

Why this matters (the landmines, now codified so nobody rediscovers them):

  • uv.lock pins i2rt to the verified commit 7b6d501 — tip 1.1.2 has an fd=-1 CAN control bug (lost hours to this on real hardware)
  • Python pinned to 3.12 — i2rt/dm-tree has no wheels on 3.13+, lerobot needs ≥3.12
  • SDK pinned 0.6.6 — 0.6.5 crashes on connect
  • config is auto-derived (camera serials detected, CAN probed) — no hand-editing

Verified: uv lock resolves 165 pkgs, uv sync --extra yam installs the full stack, all imports work (reflex 0.6.6, aiortc, pyrealsense2, i2rt@7b6d501). setup_yam.sh kept as the pip fallback.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a repo-local closed-loop bimanual YAM workflow with automated camera detection, CAN readiness checks, and demo/run CLI commands for safe dry-run and optional motion.
  • Documentation

    • Rewrote README with step-by-step local setup, prerequisites, pinned-dependency rationale, and safe-start guidance (default dry_run, Ctrl-C homing).
  • Chores

    • Introduced optional WebRTC and RealSense dependencies and bumped package/version metadata to support the new workflow.

Previously the only path to the real arm demo was 'go clone the reflex
monorepo and find an example yaml'. This makes the public quickstart
self-contained: a fresh Linux machine with 2 YAM arms can go from zero to
driving the arms against the cloud model without cloning anything else.

Adds:
  - yam_bimanual.yaml    templated closed-loop config (14-DOF, 3 cameras,
                         WebRTC fast path, region-auto worker selection).
                         Three clearly-marked EDIT spots: CAN channels,
                         task prompt, camera serials. Defaults to mode that
                         drives the arms but documents dry_run for first run.
  - setup_yam.sh         one-shot helper: install (SDK[webrtc] pinned to the
                         audited-stable 0.6.6 + i2rt + pyrealsense2), CAN
                         bring-up, RealSense serial discovery, readiness check.
  - pyproject [yam]      extra pinning reflex-sdk[webrtc]>=0.6.6 + pyrealsense2.
                         Note: 0.6.5 had a connect-time crash; 0.6.6 is the
                         hardware-verified floor.
  - README               replaces the 'clone the monorepo' block with the
                         self-contained 3-step flow + requirements table +
                         the safe-home / dry-run-first safety guidance.

Verified: yaml + toml parse, setup_yam.sh shellcheck-clean, check subcommand
runs. Camera kinds (realsense/v4l2/shm) and connector fields match the SDK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a repo-local workflow for bimanual YAM closed-loop inference: updated docs and packaging, a Bash setup script to install and probe hardware, a runtime YAML template, and a Python CLI to detect cameras, write config, check CAN, and run reflex connect (default dry-run).

Changes

Bimanual YAM Closed-Loop Setup

Layer / File(s) Summary
Documentation and packaging
README.md, pyproject.toml
README adds a repo-local bimanual YAM section and step-by-step commands; pyproject.toml bumps version, narrows Python range, raises reflex-sdk minimum, adds a yam extra (reflex-sdk[webrtc], pyrealsense2, i2rt), and adds yam-setup/yam-demo console scripts.
Setup automation: install, hardware, and readiness
setup_yam.sh
Bash script with interpreter selection and strict options; install() installs pinned reflex-sdk[webrtc], conditionally installs i2rt from git and pyrealsense2; can_up() brings up CAN interfaces; cameras() enumerates RealSense devices; check() validates prerequisites; CLI dispatcher supports install, can, cameras, check, all.
Runtime configuration template
yam_bimanual.yaml
YAML template for closed-loop sessions: execution mode, max steps, WebRTC DataChannel inference target, dual-arm hardware block (CAN channels, rates, gravity/home), chunking/apply-horizon parameters, and three camera inputs (RealSense serial placeholders with alternatives).
CLI runtime: camera detection, config, CAN probe, demo
yam_robot.py
Python CLI detects RealSense cameras, categorizes models/serials, writes yam_bimanual.yaml, probes can0/can1, provides setup_cli() and demo_cli(), and invokes reflex connect with a dry-run safety default unless REFLEX_YAM_APPLY=1.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: enabling a one-command quickstart workflow for bimanual YAM using uv sync and uv run yam-demo, which is the core feature added across all modified and new files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch andres-bimanual-yam-quickstart

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with 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.

Inline comments:
In `@pyproject.toml`:
- Around line 45-53: The dependency declaration for reflex-sdk with the [webrtc]
extra currently uses an open lower bound ("reflex-sdk[webrtc]>=0.6.6") which
contradicts the comment that 0.6.6 is the audited-stable baseline; update the
pyproject.toml dependency entry for "reflex-sdk[webrtc]" to pin it to the
audited release (e.g., "reflex-sdk[webrtc]==0.6.6") or alternatively add a
conservative upper bound (e.g., "reflex-sdk[webrtc]>=0.6.6,<0.7.0") so the
package cannot float to unvalidated versions while preserving the documented
guarantee.

In `@setup_yam.sh`:
- 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.
- Around line 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).

In `@yam_bimanual.yaml`:
- Around line 17-19: The configuration currently defaults mode: apply_actions
which risks accidental live arm motion; change the default to mode: dry_run and
update the inline comment around the mode setting (the "mode" key and its
comment) to clearly state that apply_actions is an explicit opt-in for live
actuation and that dry_run is the safe default for quickstarts and first runs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bb529b2b-84a4-49b5-913f-c564bd886ce9

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce9c43 and 98150fd.

📒 Files selected for processing (4)
  • README.md
  • pyproject.toml
  • setup_yam.sh
  • yam_bimanual.yaml

Comment thread setup_yam.sh
Comment on lines +37 to +38
"$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"
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).

Comment thread setup_yam.sh
"$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.

Comment thread yam_bimanual.yaml
Comment on lines +17 to +19
mode: apply_actions # apply_actions = drive the arms.
# Set to dry_run to test the full pipeline with
# NO arm motion (recommended for first run).
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 | 🔴 Critical | ⚡ Quick win

Use dry_run as the default mode to prevent accidental arm motion.

For a public quickstart, defaulting to apply_actions is unsafe. The config should require explicit opt-in for live actuation.

Suggested diff
-mode: apply_actions          # apply_actions = drive the arms.
+mode: dry_run                # safer default; switch to apply_actions intentionally.
                              # Set to dry_run to test the full pipeline with
                              # NO arm motion (recommended for first run).
📝 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
mode: apply_actions # apply_actions = drive the arms.
# Set to dry_run to test the full pipeline with
# NO arm motion (recommended for first run).
mode: dry_run # safer default; switch to apply_actions intentionally.
# Set to dry_run to test the full pipeline with
# NO arm motion (recommended for first run).
🤖 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 `@yam_bimanual.yaml` around lines 17 - 19, The configuration currently defaults
mode: apply_actions which risks accidental live arm motion; change the default
to mode: dry_run and update the inline comment around the mode setting (the
"mode" key and its comment) to clearly state that apply_actions is an explicit
opt-in for live actuation and that dry_run is the safe default for quickstarts
and first runs.

AndresNinou and others added 2 commits June 1, 2026 15:08
…all)

Ran the full quickstart install end-to-end in clean venvs. Findings:

  ✓ reflex-sdk[webrtc]==0.6.6 installs clean
  ✓ i2rt 1.1.2 installs from the PUBLIC git URL (no auth) on Python 3.10
  ✓ connector imports resolve: get_yam_robot + GripperType.LINEAR_4310
  ✓ get_yam_robot accepts the connector's exact kwargs (channel,
    gripper_type, zero_gravity_mode) — API compatible across i2rt 0.0.1→1.1.2
  ✓ YamBimanualConnector loads with safe default (motors release on stop)
  ✗ FAILS on Python 3.13/3.14: i2rt's dm-env→dm-tree has no usable wheel and
    cmake-builds from source, which errors. A fresh box whose default python3
    is 3.14 would hit this.

Hardening:
  - setup_yam.sh auto-picks python3.12/3.11/3.10, hard-refuses unsupported
    versions with an actionable message (PYTHON=python3.12 ./setup_yam.sh ...)
  - check subcommand now reports the Python version + supported/unsupported
  - README + pyproject [yam] document the 3.10–3.12 requirement and the
    exact public i2rt git install line

Verified: the 3.10 venv check goes fully green (reflex-sdk 0.6.6, webrtc
extras, i2rt all import); 3.14 is correctly rejected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make the bimanual-YAM demo a uv-native, one-command experience with a pinned
lockfile as institutional memory of the hardware-verified dependency set:

  uv sync --extra yam   # installs the WHOLE stack from uv.lock (one command)
  uv run yam-setup      # auto-detects RealSense serials → writes config, checks CAN
  uv run yam-demo       # runs the closed-loop demo (Ctrl-C ONCE = safe-home)

Key decisions (so nobody rediscovers these landmines):
  - uv.lock pins i2rt to the VERIFIED commit 7b6d501 (tip 1.1.2 has an fd=-1
    CAN control bug) + SDK 0.6.6 + 165 transitive deps
  - requires-python = 3.12 only: i2rt/dm-tree has no wheels on 3.13+, lerobot
    needs >=3.12 → 3.12 is the single satisfying version
  - relax arm extra lerobot>=0.5.2 → >=0.5.0 (0.5.2 was yanked from PyPI)

yam_robot.py: config is DERIVED, not hand-edited — enumerates cameras + probes
CAN for you (D435→top, 2×D405→wrists). Defaults to dry_run; REFLEX_YAM_APPLY=1
to move the arms. setup_yam.sh kept as the pip fallback.

Verified: uv lock resolves (165 pkgs), uv sync --extra yam installs the full
stack, all imports work (reflex 0.6.6, aiortc, pyrealsense2, i2rt@7b6d501).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AndresNinou AndresNinou changed the title From Andres: self-contained bimanual YAM closed-loop quickstart One-command bimanual YAM quickstart (uv sync + uv run yam-demo) Jun 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
setup_yam.sh (1)

89-90: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the RealSense fallback against pipefail exits.

With set -euo pipefail, this branch aborts cameras/all if rs-enumerate-devices -s fails and the fallback grep finds no serial|name lines. On a fresh machine that turns a diagnostic step into a hard stop instead of a warning.

Suggested diff
   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
+    if ! rs-enumerate-devices -s 2>/dev/null; then
+      if ! rs-enumerate-devices 2>/dev/null | grep -iE "serial|name" | head -20; then
+        warn "no RealSense devices found — check USB / librealsense install"
+      fi
+    fi
   else
🤖 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 89 - 90, The RealSense fallback can cause the
script to abort under set -euo pipefail if rs-enumerate-devices -s fails and the
grep returns no matches; update the fallback line that uses rs-enumerate-devices
and grep so the whole command cannot trigger a pipefail exit (for example,
ensure the fallback pipeline ends with a safe no-op like "|| true" or wrap it in
a grouped command that appends "|| true") so that the if-branch (the
rs-enumerate-devices check) becomes a warning-only path instead of aborting the
script.
♻️ Duplicate comments (2)
setup_yam.sh (2)

110-110: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate every configured CAN interface, not just one.

Bimanual mode depends on both channels in CAN_INTERFACES, and yam_robot.py refuses to run unless all of them are UP. This check can still print success with only one arm reachable, which makes ./setup_yam.sh check a false positive.

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"
+  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"
+    fi
+  done
🤖 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 110, The current health check only looks for any UP CAN
device; change it to iterate over the configured CAN_INTERFACES in setup_yam.sh
and verify each interface is UP; for each name in CAN_INTERFACES (the same
variable used by yam_robot.py) run a per-interface check (e.g., via ip -br link
show <iface>) and collect failures, print a single ok "all CAN interfaces are
UP" only if none failed, otherwise warn listing the down/missing interfaces and
advise running ./setup_yam.sh can. Ensure you reference CAN_INTERFACES when
building the list so the check matches yam_robot.py's requirements.

60-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin and enforce the hardware-verified i2rt revision.

pyproject.toml pins i2rt to 7b6d5016f05ca63f9ef0185b7143e63f2c7a5708 because newer revisions regress CAN control, but this fallback still installs HEAD and skips reinstall whenever any i2rt import succeeds. That makes ./setup_yam.sh install non-reproducible and can leave a known-bad driver in place while check() still reports success.

Suggested diff
+# Match the hardware-verified i2rt revision from pyproject.toml.
+I2RT_REF="${I2RT_REF:-7b6d5016f05ca63f9ef0185b7143e63f2c7a5708}"
+
   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"
-  fi
+  "$PY" -m pip install --upgrade --force-reinstall \
+    "i2rt @ git+https://github.com/i2rt-robotics/i2rt.git@${I2RT_REF}" \
+    || warn "i2rt pip install failed for ${I2RT_REF} — clone https://github.com/i2rt-robotics/i2rt and 'pip install -e .' manually"
   "$PY" -c "import i2rt" 2>/dev/null && ok "i2rt importable" || warn "i2rt not importable yet"
#!/bin/bash
set -euo pipefail

echo "setup_yam.sh:"
sed -n '60,65p' setup_yam.sh

echo
echo "pyproject.toml:"
sed -n '39,45p' pyproject.toml
🤖 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 60 - 65, The script installs i2rt unpinned and
skips reinstall whenever any import succeeds; change it to pin to the verified
commit and force a reinstall if the installed package is not that commit: define
a variable I2RT_COMMIT="7b6d5016f05ca63f9ef0185b7143e63f2c7a5708", change the
pip target to "$PY" -m pip install "i2rt @
git+https://github.com/i2rt-robotics/i2rt.git@$I2RT_COMMIT" and replace the
current conditional with a check that imports i2rt and inspects its installed
version/metadata (e.g., via python -c "import pkg_resources, sys;
v=pkg_resources.get_distribution('i2rt').version; sys.exit(0 if
v=='$I2RT_COMMIT' else 1)") and if the check fails run pip install --upgrade
--force-reinstall against the pinned commit; keep the existing ok/warn messages
but base them on the pinned-commit check rather than any import.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@pyproject.toml`:
- Around line 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.
- Around line 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).

In `@yam_robot.py`:
- Around line 60-94: The CONFIG_TEMPLATE currently injects raw values into
placeholders like "{prompt}", "{top}", "{left}", and "{right}" which can break
YAML when values contain quotes, newlines or backslashes; when you format
CONFIG_TEMPLATE (where CONFIG_TEMPLATE is used) serialize/escape those string
values first (e.g., use yaml.safe_dump or json.dumps for each value) and pass
the serialized results into the template instead of raw DEFAULT_PROMPT and
camera serials so the inserted strings are valid YAML scalars and won't break
parsing.
- Line 26: The module currently parses REFLEX_YAM_MAX_STEPS at import time into
MAX_STEPS which will raise an unhandled ValueError if the env var is
non-integer; move or change this to defensive parsing: stop performing
int(os.environ.get(...)) at module import and instead parse inside setup_cli()
(or wrap the current parse in a try/except), validate the value, and emit a
clear user-facing error message or fallback to the default when parsing fails;
update references to MAX_STEPS accordingly (either keep MAX_STEPS but assign it
after safe parsing in setup_cli, or introduce a getter that returns the
validated integer) so importing yam_robot.py no longer crashes.
- Around line 44-57: The current assignment of left/right cameras is
non-deterministic because ctx.query_devices() ordering can change; update the
discovery logic (the loop over ctx.query_devices() that builds d435 and d405 and
the final return mapping) to deterministically pick which D405 is "left" vs
"right" — for example, gather device info into a list of dicts including serial
and a stable identifier (preferably rs.camera_info.physical_port via
dev.get_info(rs.camera_info.physical_port) if available, otherwise use serial),
sort the D405 entries by that stable identifier, then return {"top": d435[0],
"left": sorted_d405[0]["serial"], "right": sorted_d405[1]["serial"]}; ensure you
still validate counts as before and fall back to sorting by serial string if
physical_port is missing.

---

Outside diff comments:
In `@setup_yam.sh`:
- Around line 89-90: The RealSense fallback can cause the script to abort under
set -euo pipefail if rs-enumerate-devices -s fails and the grep returns no
matches; update the fallback line that uses rs-enumerate-devices and grep so the
whole command cannot trigger a pipefail exit (for example, ensure the fallback
pipeline ends with a safe no-op like "|| true" or wrap it in a grouped command
that appends "|| true") so that the if-branch (the rs-enumerate-devices check)
becomes a warning-only path instead of aborting the script.

---

Duplicate comments:
In `@setup_yam.sh`:
- Line 110: The current health check only looks for any UP CAN device; change it
to iterate over the configured CAN_INTERFACES in setup_yam.sh and verify each
interface is UP; for each name in CAN_INTERFACES (the same variable used by
yam_robot.py) run a per-interface check (e.g., via ip -br link show <iface>) and
collect failures, print a single ok "all CAN interfaces are UP" only if none
failed, otherwise warn listing the down/missing interfaces and advise running
./setup_yam.sh can. Ensure you reference CAN_INTERFACES when building the list
so the check matches yam_robot.py's requirements.
- Around line 60-65: The script installs i2rt unpinned and skips reinstall
whenever any import succeeds; change it to pin to the verified commit and force
a reinstall if the installed package is not that commit: define a variable
I2RT_COMMIT="7b6d5016f05ca63f9ef0185b7143e63f2c7a5708", change the pip target to
"$PY" -m pip install "i2rt @
git+https://github.com/i2rt-robotics/i2rt.git@$I2RT_COMMIT" and replace the
current conditional with a check that imports i2rt and inspects its installed
version/metadata (e.g., via python -c "import pkg_resources, sys;
v=pkg_resources.get_distribution('i2rt').version; sys.exit(0 if
v=='$I2RT_COMMIT' else 1)") and if the check fails run pip install --upgrade
--force-reinstall against the pinned commit; keep the existing ok/warn messages
but base them on the pinned-commit check rather than any import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2fe4791b-67ed-4d97-a543-0786eb449829

📥 Commits

Reviewing files that changed from the base of the PR and between 98150fd and cab1f64.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • README.md
  • pyproject.toml
  • setup_yam.sh
  • yam_robot.py

Comment thread pyproject.toml
Comment on lines +19 to +20
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
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.

Comment thread pyproject.toml
Comment on lines +38 to +42
yam = [
"reflex-sdk[webrtc]>=0.6.6",
"pyrealsense2>=2.54",
"i2rt",
]
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).

Comment thread yam_robot.py
# Safety: default to dry_run (full pipeline, NO arm motion). Set
# REFLEX_YAM_APPLY=1 to actually drive the arms.
APPLY = os.environ.get("REFLEX_YAM_APPLY", "0") == "1"
MAX_STEPS = int(os.environ.get("REFLEX_YAM_MAX_STEPS", "200"))
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

Import-time env parsing can crash the CLI with an unhandled ValueError (Line [26]).

If REFLEX_YAM_MAX_STEPS is set to a non-integer, module import fails before setup_cli() can print actionable guidance. Parse defensively and return a user-facing error.

Suggested fix
-MAX_STEPS = int(os.environ.get("REFLEX_YAM_MAX_STEPS", "200"))
+def _read_max_steps() -> int:
+    raw = os.environ.get("REFLEX_YAM_MAX_STEPS", "200")
+    try:
+        value = int(raw)
+    except ValueError as exc:
+        raise RuntimeError(
+            f"REFLEX_YAM_MAX_STEPS must be an integer, got: {raw!r}"
+        ) from exc
+    if value < 0:
+        raise RuntimeError("REFLEX_YAM_MAX_STEPS must be >= 0")
+    return value
+
+MAX_STEPS = _read_max_steps()
🤖 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 `@yam_robot.py` at line 26, The module currently parses REFLEX_YAM_MAX_STEPS at
import time into MAX_STEPS which will raise an unhandled ValueError if the env
var is non-integer; move or change this to defensive parsing: stop performing
int(os.environ.get(...)) at module import and instead parse inside setup_cli()
(or wrap the current parse in a try/except), validate the value, and emit a
clear user-facing error message or fallback to the default when parsing fails;
update references to MAX_STEPS accordingly (either keep MAX_STEPS but assign it
after safe parsing in setup_cli, or introduce a getter that returns the
validated integer) so importing yam_robot.py no longer crashes.

Comment thread yam_robot.py
Comment on lines +44 to +57
for dev in ctx.query_devices():
name = dev.get_info(rs.camera_info.name)
serial = dev.get_info(rs.camera_info.serial_number)
if "D435" in name:
d435.append(serial)
elif "D405" in name:
d405.append(serial)
if not d435 or len(d405) < 2:
raise RuntimeError(
f"expected 1×D435 + 2×D405, found {len(d435)}×D435 + {len(d405)}×D405. "
"Check USB connections (lsusb | grep RealSense). If a camera is "
"wedged, fully unplug all 3 for 30s and replug."
)
return {"top": d435[0], "left": d405[0], "right": d405[1]}
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

Camera role assignment is non-deterministic across re-enumeration (Line [57]).

ctx.query_devices() ordering is not guaranteed; assigning left/right by index can silently flip wrist cameras between runs. For bimanual control, this can invert observations and degrade behavior.

Suggested fix
@@
-    return {"top": d435[0], "left": d405[0], "right": d405[1]}
+    d435 = sorted(d435)
+    d405 = sorted(d405)
+    return {"top": d435[0], "left": d405[0], "right": d405[1]}
📝 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
for dev in ctx.query_devices():
name = dev.get_info(rs.camera_info.name)
serial = dev.get_info(rs.camera_info.serial_number)
if "D435" in name:
d435.append(serial)
elif "D405" in name:
d405.append(serial)
if not d435 or len(d405) < 2:
raise RuntimeError(
f"expected 1×D435 + 2×D405, found {len(d435)}×D435 + {len(d405)}×D405. "
"Check USB connections (lsusb | grep RealSense). If a camera is "
"wedged, fully unplug all 3 for 30s and replug."
)
return {"top": d435[0], "left": d405[0], "right": d405[1]}
for dev in ctx.query_devices():
name = dev.get_info(rs.camera_info.name)
serial = dev.get_info(rs.camera_info.serial_number)
if "D435" in name:
d435.append(serial)
elif "D405" in name:
d405.append(serial)
if not d435 or len(d405) < 2:
raise RuntimeError(
f"expected 1×D435 + 2×D405, found {len(d435)}×D435 + {len(d405)}×D405. "
"Check USB connections (lsusb | grep RealSense). If a camera is "
"wedged, fully unplug all 3 for 30s and replug."
)
d435 = sorted(d435)
d405 = sorted(d405)
return {"top": d435[0], "left": d405[0], "right": d405[1]}
🧰 Tools
🪛 Ruff (0.15.15)

[warning] 52-56: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 53-53: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?

(RUF001)


[warning] 53-53: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?

(RUF001)


[warning] 53-53: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?

(RUF001)


[warning] 53-53: String contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?

(RUF001)

🤖 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 `@yam_robot.py` around lines 44 - 57, The current assignment of left/right
cameras is non-deterministic because ctx.query_devices() ordering can change;
update the discovery logic (the loop over ctx.query_devices() that builds d435
and d405 and the final return mapping) to deterministically pick which D405 is
"left" vs "right" — for example, gather device info into a list of dicts
including serial and a stable identifier (preferably
rs.camera_info.physical_port via dev.get_info(rs.camera_info.physical_port) if
available, otherwise use serial), sort the D405 entries by that stable
identifier, then return {"top": d435[0], "left": sorted_d405[0]["serial"],
"right": sorted_d405[1]["serial"]}; ensure you still validate counts as before
and fall back to sorting by serial string if physical_port is missing.

Comment thread yam_robot.py
Comment on lines +60 to +94
CONFIG_TEMPLATE = """\
# AUTO-GENERATED by `uv run yam-setup` — camera serials detected from hardware.
mode: {mode} # dry_run = NO arm motion; apply_actions = arms move
max_steps: {max_steps} # finite → attempts the task then auto safe-homes
control_period_s: 0.2
stop_on_error: true
pipeline_inference: false

target:
kind: webrtc # cloud, ~90ms server / ~220ms RTT. No url: the SDK
timeout_s: 60 # picks the nearest healthy worker (region-aware).
connect_timeout_s: 60
state_field: "state"
instruction_field: "prompt"
img_size: 256
extra_payload:
num_steps: 5

hardware:
kind: yam_bimanual
config:
left: {{ channel: can1, gripper: linear_4310 }}
right: {{ channel: can0, gripper: linear_4310 }}
hz: 25
zero_gravity_mode: false
instruction: "{prompt}"
home_duration_s: 4.0
chunk_boundary_max_delta: 0.05
chunk_apply_horizon: 0

cameras:
top: {{ kind: realsense, serial: "{top}", width: 640, height: 480, fps: 30 }}
left: {{ kind: realsense, serial: "{left}", width: 640, height: 480, fps: 30 }}
right: {{ kind: realsense, serial: "{right}", width: 640, height: 480, fps: 30 }}
"""
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

Unescaped YAML interpolation can break config generation (Line [85], Line [91]-Line [93]).

DEFAULT_PROMPT (env-controlled) is inserted directly into YAML with quotes, but embedded quotes/newlines/backslashes will produce invalid YAML or unintended structure. Please escape/serialize string values before formatting.

Suggested fix
+import json
@@
-CONFIG_TEMPLATE = """\
+CONFIG_TEMPLATE = """\
@@
-    instruction: "{prompt}"
+    instruction: {prompt}
@@
-  top:   {{ kind: realsense, serial: "{top}",   width: 640, height: 480, fps: 30 }}
-  left:  {{ kind: realsense, serial: "{left}",  width: 640, height: 480, fps: 30 }}
-  right: {{ kind: realsense, serial: "{right}", width: 640, height: 480, fps: 30 }}
+  top:   {{ kind: realsense, serial: {top},   width: 640, height: 480, fps: 30 }}
+  left:  {{ kind: realsense, serial: {left},  width: 640, height: 480, fps: 30 }}
+  right: {{ kind: realsense, serial: {right}, width: 640, height: 480, fps: 30 }}
 """
@@
         CONFIG_TEMPLATE.format(
             mode="apply_actions" if APPLY else "dry_run",
             max_steps=MAX_STEPS,
-            prompt=DEFAULT_PROMPT,
-            **serials,
+            prompt=json.dumps(DEFAULT_PROMPT),
+            top=json.dumps(serials["top"]),
+            left=json.dumps(serials["left"]),
+            right=json.dumps(serials["right"]),
         )
     )

Also applies to: 97-105

🤖 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 `@yam_robot.py` around lines 60 - 94, The CONFIG_TEMPLATE currently injects raw
values into placeholders like "{prompt}", "{top}", "{left}", and "{right}" which
can break YAML when values contain quotes, newlines or backslashes; when you
format CONFIG_TEMPLATE (where CONFIG_TEMPLATE is used) serialize/escape those
string values first (e.g., use yaml.safe_dump or json.dumps for each value) and
pass the serialized results into the template instead of raw DEFAULT_PROMPT and
camera serials so the inserted strings are valid YAML scalars and won't break
parsing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant