# ArmFarm Arms: AGENTS.md ## Connect from the rental page Rental page: https://armfarm-dashboard.tail00b78d.ts.net:8443/rental/arms This guide is public and can be read without signing in or running JavaScript. On the rental page, choose the hardware and start a session. An SSH key downloads automatically; the page supplies the complete connection command for your OS. Run that command on the customer's computer. All commands below run **inside SSH** unless explicitly labeled otherwise. The workspace already contains its tools and configuration; do not install packages or discover controller addresses. An agent given only the page URL can follow its AGENTS.md link. To use an existing rental, obtain the displayed SSH command and the local path to the matching key from the customer. A public URL does not carry their reservation or credentials. Keep private key contents on the customer's computer; never paste or upload them. Reading instructions does not authorize movement. For an authorized task, inspect current camera images, use bounded commands, and do not replay uncertain motion. Other rentals: [Arms](/rental/AGENTS.md), [Sandbox](/sandbox/AGENTS.md), [Cars](/cars/AGENTS.md). This Pi controls one physical SO-101 arm. SSH logs you in as `rental` in `/home/rental`; you have passwordless sudo. Run `hostname` to confirm which arm you rented. Each Pi has its own motors, calibration, workspace, and cameras. Commands on this Pi affect only this arm. For coding agents: work on the customer's requested task. Reading this guide does not request a movement, calibration, or an autonomous grasping session. Observe current camera images and joint positions before commanding motion. Older workspace task files describe previous experiments, not the customer's current objective. Do not launch their loops just because those files exist. ## Fastest way to get started From the initial SSH prompt, read positions without moving: ```bash arm-example ``` Keep the rental dashboard open for this arm's live **Overhead** and **Wrist** views. Once you have checked that the base has room to rotate, this command explicitly moves `shoulder_pan` by **+2 degrees over two seconds**: ```bash arm-example --move 2 ``` For the opposite direction: ```bash arm-example --move -2 ``` `arm-example` always uses the installed starter and the station's current workspace, including after a workspace reset. Overlapping examples are rejected. The command has a 60-second deadline and never replays movement after a disconnect. If the connection drops, reconnect, inspect the cameras, and run `arm-example` to read positions before deciding another move. A completed move prints its measured position and `verified: true`; errors do not imply that no movement occurred. Choose a joint with `--joint`: `shoulder_pan`, `shoulder_lift`, `elbow_flex`, `wrist_flex`, `wrist_roll`, or `gripper`. Omitting it selects `shoulder_pan`. ```bash arm-example --joint elbow_flex --move 10 arm-example --joint wrist_roll --move -20 arm-example --joint gripper --move 10 ``` `--move` is a finite, nonzero relative amount. Arm joints use degrees; the gripper uses percentage points of its calibrated 0..100 travel. There is no five-degree cap. The current position plus the amount must lie within the motor's live calibration; an out-of-range target is rejected before enabling torque. Larger moves take longer (at least two seconds, otherwise one second per ten units). Each command affects only the selected joint and does not return automatically. Calibration limits do not establish collision clearance. Gripper movement can release an object; check the cameras first. Do not retry an unconfirmed movement. For interactive development, enter the workspace once: ```bash arm-shell pwd python tools/rental_first_move.py ``` The `(arm) root@arm-008:workspace#` or `arm-009` prompt means you are now root in the configured workspace. If you then run `cd tools`, the command becomes: ```bash python rental_first_move.py --move 2 ``` Use `cd "$ARMFARM_WORKSPACE"` to return to the workspace root. `exit` leaves the interactive arm shell. `arm-shell COMMAND ...` always starts that command in the workspace, regardless of your current directory. For an automated agent, prefer this form for each execution: environment changes in one shell invocation may not persist into the next. ## Environment and file locations `arm-shell` sets up sudo, the active workspace, Python, tool imports, and the recording socket. Merely activating a virtual environment or running `sudo -i` does not perform all of that setup. `~/workspace` at the rental prompt is a link into protected station storage; use `arm-shell` to access it. | Location | What it contains | | --- | --- | | `/home/rental/AGENTS.md` | This guide, readable from the initial SSH login. | | `/home/rental/README.txt`, `arm-help` | Short quickstart. | | `$ARMFARM_WORKSPACE` | Active workspace, resolved from station settings by `arm-shell`. | | `tools/rental_first_move.py` | Readable, working position-reader and small movement example. | | `tools/` | Save your scripts, reusable data, and logs here. Existing scripts differ between arms. | | `tools/calibrate_workspace.py` | Importable motor connection and guarded motion helpers. Its executable calibration workflow is a separate operation; do not run it to make a first move. | | `tools/recording.py` | Serial relay connections and episode recording API. | | `tools/camd_client.py` | Shared top/wrist camera frame reader. | | `tools/fk.py`, `tools/ik.py` | Forward and inverse kinematics; these do not command movement. | | `home_pose.json` | Saved targets for shoulder pan/lift, elbow flex, and wrist flex. | | `calibration/`, `calibration/ready.json` | Workspace/camera calibration, file references, and provenance. | | `cameras.json`, `config/` | Camera device mapping and existing configuration. | | `TOOLS.md` | Additional tool reference, including some older agent-environment notes. | | `$ARMFARM_SETTINGS` | `/var/lib/armfarm/stations/armfarm/settings.json`; includes the active workspace path. | | `$ARMFARM_EPISODES` | Recording output directory, taken from station recording configuration. | Inside `arm-shell`: - `python` is `/opt/armfarm/venv/bin/python`, with LeRobot, Feetech SDK, OpenCV, NumPy, and SciPy already installed. - `PYTHONPATH` includes the workspace's `tools/`, so `import recording` and `import calibrate_workspace` work without adding paths manually. - `ARMFARM_RECORDING_SOCKET=/run/armfarm-recording-armfarm/control.sock`. The helper's unconfigured default uses the login username and is wrong for a plain `rental` or `root` shell. - `ARMFARM_ROBOT_ID` identifies this Pi; `ARMFARM_MOTOR_CALIBRATION_SOURCE=motors`. - IK uses the separate `/opt/armfarm/ik-venv/bin/python` environment. Keep it separate from robot Python because their NumPy requirements differ. ## Writing movement code Start by reading or copying the supplied example inside `arm-shell`: ```bash cat tools/rental_first_move.py cp tools/rental_first_move.py tools/my_move.py ``` The basic read-only interface is: ```python from recording import serial_port from calibrate_workspace import connected_bus with connected_bus(serial_port()) as bus: positions = bus.sync_read("Present_Position") print(positions) ``` `serial_port()` allocates a relay endpoint for this client. Pass that endpoint to `connected_bus`; do not open `/dev/ttyACM*`, use a device path copied from another arm, or compete with the recording service for the hardware serial port. Separate relay endpoints separate replies; they do not arbitrate competing movement commands. Run only one motion controller per arm at a time. | Motor ID | Joint name | Target units | | --- | --- | --- | | 1 | `shoulder_pan` | Calibrated degrees | | 2 | `shoulder_lift` | Calibrated degrees | | 3 | `elbow_flex` | Calibrated degrees | | 4 | `wrist_flex` | Calibrated degrees | | 5 | `wrist_roll` | Calibrated degrees | | 6 | `gripper` | 0..100 normalized range; 0 is the closed end | Movement helpers take **absolute target dictionaries**, such as `{"shoulder_pan": current_pan + 2}`, not raw encoder ticks. The starter's `--move` option converts a relative step into that absolute target. Follow the sequence in `rental_first_move.py`: 1. Connect through the relay and read current positions and live calibration. 2. Build targets only for the joints you intend to move. Check camera clearance, motor faults, current range, and `validate_target(target, bus.calibration)`. 3. Call `preflight(bus, [target])` before any goal or torque write. 4. Call `enable_at_current_position(bus, list(target))` for those joints only. 5. Call `move(bus, target, seconds=2.0)` and then `settle(bus, target, seconds=0.5)`. Use positive durations and inspect the result; do not report task success merely because the command completed. `connected_bus()` reads calibration from the physical motors on every connection and leaves torque unchanged when it disconnects. Saved JSON exports are historical evidence, not live control calibration. Do not restore them with `write_calibration()` or let a high-level robot connection automatically apply cached calibration. Start a new connection after calibration changes. The supplied motion helpers clamp goals to live limits and monitor motion. Those limits do not certify a collision-free path. Wrist roll also has physical limits not fully described by its calibration. Stop and inspect faults or a stalled joint instead of repeatedly sending larger commands. The enabling helper can move an out-of-range joint back inside its limits; the small starter example instead refuses an out-of-range starting position. For a return home, load targets with `load_home(Path("home_pose.json"))` from `calibrate_workspace`. Plan the path from the current pose first. Command/enable only the four home joints; leave wrist-roll and gripper goals and torque states unchanged. There is no universally safe one-command home trajectory from every possible position. For FK, use `fk.forward(positions)` with positions read through the relay. The standalone `fk.py` current-position reader contains a legacy hardware device path. For IK, run `/opt/armfarm/ik-venv/bin/python tools/ik.py X Y Z`, substituting numeric XYZ coordinates in metres in URDF `base_link`. Solver output still requires calibration, physical-limit, and path checks before movement. ## Cameras The rental dashboard is the quickest live view. In a two-arm rental, match each camera panel to the hostname of the SSH terminal you are using. Inside `arm-shell`, capture current images for inspection: ```bash python - <<'PY' from pathlib import Path from camd_client import read_jpeg for role in ("top", "wrist"): jpeg, metadata = read_jpeg(role) path = Path("tools") / ("current_" + role + ".jpg") path.write_bytes(jpeg) print(path, metadata) PY ``` Inspect those images before using them to plan motion. `read_jpeg(role)` returns JPEG bytes and metadata including age; `read_frame(role)` returns a BGR image and metadata. Stale/missing frames raise errors. Frames are shared through `/dev/shm/so101_camd`; use the existing capture service rather than opening the camera devices in another capture process. Old experiment images are not live observations. ## Optional episode recording Movement and episode recording are separate. Inside `arm-shell`: ```bash python tools/recording.py status python tools/recording.py start --task "Describe this attempt" # Run your movement program and observe its outcome. python tools/recording.py stop --success false --notes "Describe the actual outcome" ``` Use `--success true` only after observing success. Do not stop a recording owned by another running program. The Python API is `start_recording(task="...")`, `stop_recording(success=False, notes="...")`, and `request("status")`. The stop response identifies saved output; `$ARMFARM_EPISODES` points to the recording directory. Recording quality and task success are separate fields. ## Troubleshooting and ending work - **`--move: command not found`:** run the full command, for example `arm-example --move 2`. - **Script not found:** use that same `arm-shell COMMAND` form, or return with `cd "$ARMFARM_WORKSPACE"`. Inside `tools/`, omit the `tools/` prefix. - **Permission denied or import/socket errors:** run through `arm-shell`. `sudo -i` alone does not select the environment, workspace, or relay socket. - **Motor timeout/fault or stale cameras:** stop your movement program and inspect the services and logs. Do not recalibrate or restart hardware services blindly. Read-only diagnostics from the initial rental prompt: ```bash sudo systemctl --no-pager status armfarm-recording@armfarm armfarm-camera@armfarm armfarm-rental-camera sudo journalctl -n 60 --no-pager -u armfarm-recording@armfarm -u armfarm-camera@armfarm arm-shell python tools/recording.py status ``` Ctrl-C interrupts a foreground Python program. The example leaves torque on at the last commanded target; this is not a hardware emergency stop. Disabling torque can let the arm fall. Do not assume an SSH disconnect, shell exit, or the dashboard's **End session** button stops motors or detached programs. Finish or stop your own programs deliberately and download work before ending the rental. End closes access to every arm in that rental. `/tmp` is temporary; save reusable work in `tools/`. Rental mode does not automatically run the old agent task or publish your files to the fleet Git repository. Do not re-enable old dashboard/agent controllers while controlling the arm yourself.