Split out of #483, whose "Frame audit" work item #485 does not do. #485 builds the mechanism and deliberately
leaves every shipped deployment unset: "Zero impact when unused — no ee_frame declared → bit-for-bit today's
behaviour. Every shipped deployment is left unset pending the per-checkpoint frame audit." This issue is that
audit, so the item does not vanish when #485 closes #483.
Background
After #485 every embodiment anchors on a frame named default: its model declares it, its driver reports
robot_state.ee_pose in it and accepts Cartesian commands in it. A checkpoint states the frame it speaks as
a Transform3D measured from default, and ChangeEEFrame(T) converts at the boundary — observations forward
(pose * T), commands back (pose * T⁻¹).
Two places declare it, and they are for different jobs:
- Training —
cfg.codecs.compose(ee_frame=T) re-expresses the dataset and stamps ee_frame into the
episode statics, so the checkpoint is trained in T.
- Serving —
openpi.server.pipeline(ee_frame=T) prepends ChangeEEFrame(T) outermost, so the wire and the
server-side codec see poses already in T.
Setting neither is correct for a checkpoint trained in default, and for any checkpoint that speaks joints.
Setting the wrong one is silent: the arm goes to the wrong place, nothing errors, the eval score is just worse.
What needs deciding
For each checkpoint we serve: does it speak default, or a transform from it?
| Deployment |
Pipeline |
Space |
ee_frame today |
Needs a decision? |
phail |
ee |
EE |
unset |
Yes — trained on the phail dataset under the flange convention; correct today only because default coincides with the Franka tool frame. #550 moves default to the flange and breaks that coincidence. |
sim_stack |
ee_flip_grip |
EE |
unset |
Yes — trained on sim recordings; the sim panda's default sits 45 mm from the FR3's along the approach axis (#485, "Known limit"). |
libero |
libero_pipe |
EE |
unset |
Yes, and blocked on #557 — LIBERO's gripper0_grip_site is 38.1 mm and 90° from what bundled_panda_model() calls default, so its episodes mislabel their poses before any checkpoint frame enters the picture. |
droid |
droid_pipe |
joints |
unset |
No — droid_obs carries joint_position and gripper_position only, action is joint_delta_action. No pose crosses the wire. |
droid_jointpos |
droid_jointpos_pipe |
joints |
unset |
No — same observation codec, absolute_joints_action. |
The bare pipelines with no checkpoint bound (ee, ee_joints, ee_traj, ee_joints_traj, joints_traj,
ee_flip_grip) inherit whatever the operator passes, so they need documentation rather than a value.
An EE-space DROID checkpoint is the case #483 was opened for and the one we do not currently serve. Serving
one means openpi.server.pipeline(ee_frame=models.DROID_EE_FRAME); there is no shipped config for it, so it is
a hand-wire that someone has to remember.
The other half: nothing enforces agreement
ChangeEEFrame.meta publishes ee_frame in the session handshake, so the rig is told what frame it is being
served in. Nothing compares that against what the checkpoint was trained in. #483 requirement 2 asks for
exactly this — "the checkpoint itself declares what frame it speaks; nobody re-states it at serving time" — and
today the serving config re-states it. A serving pipeline that declares the wrong transform, or omits one the
training run set, fails silently.
Deciding whether the checkpoint's stamped ee_frame should reach the server (via ModelSource.meta, which
already carries per-model handshake facts) and be checked against the pipeline's declaration is part of this
work.
Only OpenPI has the knob
positronic/vendors/lerobot/server.py:58 and positronic/vendors/gr00t/server.py:321 build their pipelines
with no ee_frame parameter at all. Any EE-space checkpoint served through either cannot declare a frame.
Work items
Split out of #483, whose "Frame audit" work item #485 does not do. #485 builds the mechanism and deliberately
leaves every shipped deployment unset: "Zero impact when unused — no
ee_framedeclared → bit-for-bit today'sbehaviour. Every shipped deployment is left unset pending the per-checkpoint frame audit." This issue is that
audit, so the item does not vanish when #485 closes #483.
Background
After #485 every embodiment anchors on a frame named
default: its model declares it, its driver reportsrobot_state.ee_posein it and accepts Cartesian commands in it. A checkpoint states the frame it speaks asa
Transform3Dmeasured fromdefault, andChangeEEFrame(T)converts at the boundary — observations forward(
pose * T), commands back (pose * T⁻¹).Two places declare it, and they are for different jobs:
cfg.codecs.compose(ee_frame=T)re-expresses the dataset and stampsee_frameinto theepisode statics, so the checkpoint is trained in
T.openpi.server.pipeline(ee_frame=T)prependsChangeEEFrame(T)outermost, so the wire and theserver-side codec see poses already in
T.Setting neither is correct for a checkpoint trained in
default, and for any checkpoint that speaks joints.Setting the wrong one is silent: the arm goes to the wrong place, nothing errors, the eval score is just worse.
What needs deciding
For each checkpoint we serve: does it speak
default, or a transform from it?ee_frametodayphaileedefaultcoincides with the Franka tool frame. #550 movesdefaultto the flange and breaks that coincidence.sim_stackee_flip_gripdefaultsits 45 mm from the FR3's along the approach axis (#485, "Known limit").liberolibero_pipegripper0_grip_siteis 38.1 mm and 90° from whatbundled_panda_model()callsdefault, so its episodes mislabel their poses before any checkpoint frame enters the picture.droiddroid_pipedroid_obscarriesjoint_positionandgripper_positiononly, action isjoint_delta_action. No pose crosses the wire.droid_jointposdroid_jointpos_pipeabsolute_joints_action.The bare pipelines with no checkpoint bound (
ee,ee_joints,ee_traj,ee_joints_traj,joints_traj,ee_flip_grip) inherit whatever the operator passes, so they need documentation rather than a value.An EE-space DROID checkpoint is the case #483 was opened for and the one we do not currently serve. Serving
one means
openpi.server.pipeline(ee_frame=models.DROID_EE_FRAME); there is no shipped config for it, so it isa hand-wire that someone has to remember.
The other half: nothing enforces agreement
ChangeEEFrame.metapublishesee_framein the session handshake, so the rig is told what frame it is beingserved in. Nothing compares that against what the checkpoint was trained in. #483 requirement 2 asks for
exactly this — "the checkpoint itself declares what frame it speaks; nobody re-states it at serving time" — and
today the serving config re-states it. A serving pipeline that declares the wrong transform, or omits one the
training run set, fails silently.
Deciding whether the checkpoint's stamped
ee_frameshould reach the server (viaModelSource.meta, whichalready carries per-model handshake facts) and be checked against the pipeline's declaration is part of this
work.
Only OpenPI has the knob
positronic/vendors/lerobot/server.py:58andpositronic/vendors/gr00t/server.py:321build their pipelineswith no
ee_frameparameter at all. Any EE-space checkpoint served through either cannot declare a frame.Work items
unset is right
ee_frameshould be carried inModelSource.metaand checkedagainst the serving pipeline's declaration
lerobotandgr00tpipelines the sameee_frameparameter, or state why they do not need onedefaultframe to the flange and migrate existing datasets #550 lands — it movesdefaultto the flange, which changes the correct value for everyEE-space checkpoint trained before it