From bc3c0b0915bdd5b349ee917a29f679b8be026a48 Mon Sep 17 00:00:00 2001 From: gaolebaigao <128270856+gaolebaigao@users.noreply.github.com> Date: Fri, 28 Aug 2026 15:12:41 +0800 Subject: [PATCH 1/2] fix(embodiments): un-hardcode verify script, finish RTC horizon integer migration - verify_embodiment_structure.sh hard-coded the expected preset list and has been failing since quadcopter shipped; validate whatever list_presets() returns instead. - emit_embodiment_presets.py and configs/embodiments/ still stored the legacy fractional rtc_execution_horizon; re-running the emitter would regress the migrated integer values in the package presets, and every from-source load emitted a deprecation warning. Sync all copies to the migrated integer counts (franka/ur5: 25, so100: 12). - docs/embodiment_schema.md: fix shipped preset count (4, quadcopter included), canonical preset location, and the rtc_execution_horizon field semantics + cross-field rules to match schema.json/validate.py (ADR 2026-04-25 decision #8). No behavior change beyond the verify script now passing. Prep for #69 (the Unitree Z1 arm preset will be the 5th preset). --- configs/embodiments/franka.json | 2 +- configs/embodiments/so100.json | 2 +- configs/embodiments/ur5.json | 2 +- docs/embodiment_schema.md | 9 +++++---- scripts/emit_embodiment_presets.py | 12 +++++++++--- scripts/verify_embodiment_structure.sh | 13 ++++++++----- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/configs/embodiments/franka.json b/configs/embodiments/franka.json index bac9a729..6d7f60d5 100644 --- a/configs/embodiments/franka.json +++ b/configs/embodiments/franka.json @@ -82,7 +82,7 @@ "control": { "frequency_hz": 20.0, "chunk_size": 50, - "rtc_execution_horizon": 0.5 + "rtc_execution_horizon": 25 }, "constraints": { "max_ee_velocity": 1.0, diff --git a/configs/embodiments/so100.json b/configs/embodiments/so100.json index 2985e408..b35c751b 100644 --- a/configs/embodiments/so100.json +++ b/configs/embodiments/so100.json @@ -76,7 +76,7 @@ "control": { "frequency_hz": 15.0, "chunk_size": 30, - "rtc_execution_horizon": 0.4 + "rtc_execution_horizon": 12 }, "constraints": { "max_ee_velocity": 0.5, diff --git a/configs/embodiments/ur5.json b/configs/embodiments/ur5.json index 4fa2e1f7..97236ab7 100644 --- a/configs/embodiments/ur5.json +++ b/configs/embodiments/ur5.json @@ -82,7 +82,7 @@ "control": { "frequency_hz": 20.0, "chunk_size": 50, - "rtc_execution_horizon": 0.5 + "rtc_execution_horizon": 25 }, "constraints": { "max_ee_velocity": 1.0, diff --git a/docs/embodiment_schema.md b/docs/embodiment_schema.md index ed3db766..53f81ff0 100644 --- a/docs/embodiment_schema.md +++ b/docs/embodiment_schema.md @@ -1,6 +1,6 @@ # Embodiment config schema (v1) -`tether serve --embodiment ` reads a per-robot config from `configs/embodiments/.json`. Three presets ship out of the box: **franka**, **so100**, **ur5**. You can also point at a custom config with `--custom-embodiment-config `. +`tether serve --embodiment ` loads a per-robot config by name. Four presets ship out of the box: **franka**, **so100**, **ur5**, **quadcopter**. The canonical preset files live inside the package at `src/tether/embodiments/presets/.json`; the copies in `configs/embodiments/` are editable dev fallbacks (checked only if the in-package presets are missing). You can also point at a custom config with `--custom-embodiment-config `. The authoritative schema is at `src/tether/embodiments/schema.json` (JSON Schema draft-07). This doc is a human-readable mirror — if they drift, the JSON is canonical. @@ -83,10 +83,11 @@ List of camera streams the model expects (1–8 cameras). |---|---|---|---| | `frequency_hz` | float | 0–1000 (exclusive 0) | Robot control loop rate. | | `chunk_size` | int | 1–200 | Actions in a single inference chunk. | -| `rtc_execution_horizon` | float | 0–5.0 (exclusive 0) | Seconds of chunk to execute before requesting next inference. | +| `rtc_execution_horizon` | int | 1–`chunk_size` | Integer count of actions to lock during RTC replan. Legacy fractional values (0 < v < 1) auto-migrate to `int(v × chunk_size)` at load with a one-time deprecation warning; schema v2 will reject them. | -**Cross-field rule (warning, not blocking):** -- `frequency_hz × rtc_execution_horizon ≥ 1` (else `rtc-horizon-too-short` warning — RTC degenerates if the horizon is shorter than one control step). +**Cross-field rules (warnings, not blocking):** +- `rtc_execution_horizon < 1` (else `rtc-horizon-too-short` warning — RTC degenerates below one action) +- `rtc_execution_horizon > chunk_size` (else `rtc-horizon-exceeds-chunk` warning — can't lock more actions than the chunk holds) ## `constraints` diff --git a/scripts/emit_embodiment_presets.py b/scripts/emit_embodiment_presets.py index 6437401a..d61a248a 100644 --- a/scripts/emit_embodiment_presets.py +++ b/scripts/emit_embodiment_presets.py @@ -70,7 +70,9 @@ "control": { "frequency_hz": 20.0, "chunk_size": 50, - "rtc_execution_horizon": 0.5, + # Integer count of actions locked during replan (ADR 2026-04-25 #8 — + # the legacy fractional form auto-migrates with a deprecation warning). + "rtc_execution_horizon": 25, }, "constraints": { "max_ee_velocity": 1.0, @@ -118,7 +120,9 @@ # TECHNICAL_PLAN line 1987. Customers with desktop GPU can override. "frequency_hz": 15.0, "chunk_size": 30, - "rtc_execution_horizon": 0.4, + # Integer count of actions locked during replan (ADR 2026-04-25 #8 — + # the legacy fractional form auto-migrates with a deprecation warning). + "rtc_execution_horizon": 12, }, "constraints": { "max_ee_velocity": 0.5, @@ -165,7 +169,9 @@ # UR native is ~125 Hz; we run at 20 Hz to match our action chunk cadence "frequency_hz": 20.0, "chunk_size": 50, - "rtc_execution_horizon": 0.5, + # Integer count of actions locked during replan (ADR 2026-04-25 #8 — + # the legacy fractional form auto-migrates with a deprecation warning). + "rtc_execution_horizon": 25, }, "constraints": { # UR5 rated for 1.0 m/s; conservative cap at 1.2 with collision check on diff --git a/scripts/verify_embodiment_structure.sh b/scripts/verify_embodiment_structure.sh index 17d5870b..ed49b837 100755 --- a/scripts/verify_embodiment_structure.sh +++ b/scripts/verify_embodiment_structure.sh @@ -2,9 +2,10 @@ # Sanity check for per-embodiment configs (B.1). # # Verifies: -# - 3 preset JSON files exist at configs/embodiments/{franka,so100,ur5}.json +# - the arm preset JSON files exist at configs/embodiments/{franka,so100,ur5}.json # - each parses as valid JSON -# - the embodiments package imports + load_preset works for all 3 +# - the embodiments package imports and every shipped package preset +# (whatever list_presets() returns — not a hard-coded list) loads + validates # # Run from repo root: # bash scripts/verify_embodiment_structure.sh @@ -41,9 +42,11 @@ from tether.embodiments import EmbodimentConfig, list_presets from tether.embodiments.validate import validate_embodiment_config presets = list_presets() -expected = ["franka", "so100", "ur5"] -if presets != expected: - print(f" ✗ list_presets() returned {presets}, expected {expected}") +# Don't hard-code the expected preset list — it already drifted once when +# quadcopter shipped, and hard-coding it here would break again on the +# next preset. Validate whatever the package actually ships. +if not presets: + print(" ✗ list_presets() returned no presets — package presets dir missing?") sys.exit(1) for name in presets: From 7fd27687a742cdebaa8e23b29f0fccc04535d6ca Mon Sep 17 00:00:00 2001 From: gaolebaigao <128270856+gaolebaigao@users.noreply.github.com> Date: Fri, 28 Aug 2026 15:30:53 +0800 Subject: [PATCH 2/2] fix(embodiments): restore omission detection in verify script; correct migration formula in docs Address Codex review feedback on #308: - verify_embodiment_structure.sh validated whatever list_presets() returned, so an accidentally dropped preset file (e.g. quadcopter, which has no configs/ dev copy) passed the preflight silently. Keep a required-minimum set checked as a SUBSET of what ships: omissions fail the check, while new presets still validate automatically without editing the script. - schema.json description and docs/embodiment_schema.md documented the legacy fractional rtc_execution_horizon migration as int(v x chunk_size) (truncation), but EmbodimentConfig.from_dict() rounds: round(v x chunk_size), minimum 1. Document the actual behavior (v=0.25, chunk_size=30 migrates to 8, not 7). No behavior change; the shipped presets' migrated values are unaffected. --- docs/embodiment_schema.md | 2 +- scripts/verify_embodiment_structure.sh | 15 ++++++++++----- src/tether/embodiments/schema.json | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/embodiment_schema.md b/docs/embodiment_schema.md index 53f81ff0..e0731d58 100644 --- a/docs/embodiment_schema.md +++ b/docs/embodiment_schema.md @@ -83,7 +83,7 @@ List of camera streams the model expects (1–8 cameras). |---|---|---|---| | `frequency_hz` | float | 0–1000 (exclusive 0) | Robot control loop rate. | | `chunk_size` | int | 1–200 | Actions in a single inference chunk. | -| `rtc_execution_horizon` | int | 1–`chunk_size` | Integer count of actions to lock during RTC replan. Legacy fractional values (0 < v < 1) auto-migrate to `int(v × chunk_size)` at load with a one-time deprecation warning; schema v2 will reject them. | +| `rtc_execution_horizon` | int | 1–`chunk_size` | Integer count of actions to lock during RTC replan. Legacy fractional values (0 < v < 1) auto-migrate to `round(v × chunk_size)` (minimum 1) at load with a one-time deprecation warning; schema v2 will reject them. | **Cross-field rules (warnings, not blocking):** - `rtc_execution_horizon < 1` (else `rtc-horizon-too-short` warning — RTC degenerates below one action) diff --git a/scripts/verify_embodiment_structure.sh b/scripts/verify_embodiment_structure.sh index ed49b837..6c329798 100755 --- a/scripts/verify_embodiment_structure.sh +++ b/scripts/verify_embodiment_structure.sh @@ -42,11 +42,16 @@ from tether.embodiments import EmbodimentConfig, list_presets from tether.embodiments.validate import validate_embodiment_config presets = list_presets() -# Don't hard-code the expected preset list — it already drifted once when -# quadcopter shipped, and hard-coding it here would break again on the -# next preset. Validate whatever the package actually ships. -if not presets: - print(" ✗ list_presets() returned no presets — package presets dir missing?") +# Two checks, not one: a required-minimum SUBSET (catches an accidentally +# dropped preset file — an equality check broke when quadcopter shipped, +# but no check at all lets packaging omissions pass silently) plus dynamic +# validation of everything else that ships. New presets validate +# automatically in the loop below; add them to REQUIRED when they should +# be omission-protected too. +REQUIRED = {"franka", "so100", "ur5", "quadcopter"} +missing = REQUIRED - set(presets) +if missing: + print(f" ✗ required package presets missing: {sorted(missing)}") sys.exit(1) for name in presets: diff --git a/src/tether/embodiments/schema.json b/src/tether/embodiments/schema.json index 3ffba914..8e3e6edc 100644 --- a/src/tether/embodiments/schema.json +++ b/src/tether/embodiments/schema.json @@ -168,7 +168,7 @@ "type": "number", "minimum": 0, "maximum": 1000, - "description": "Integer count of actions to lock during RTC replan. Per ADR 2026-04-25-auto-calibration-architecture decision #8. Legacy fractional values (0 < value < 1) auto-migrate to int(value * chunk_size) at load time with a one-time deprecation warning. Schema v2 will reject fractional values." + "description": "Integer count of actions to lock during RTC replan. Per ADR 2026-04-25-auto-calibration-architecture decision #8. Legacy fractional values (0 < value < 1) auto-migrate to round(value * chunk_size), minimum 1, at load time with a one-time deprecation warning. Schema v2 will reject fractional values." } } },