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..e0731d58 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 `round(v × chunk_size)` (minimum 1) 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..6c329798 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,16 @@ 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}") +# 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." } } },