transfer()
Measure what a policy loses moving to a new embodiment — source and target rates with intervals, and the clusters that opened.
Policies are trained on one body and deployed on another, and the distance
between those two numbers is where deployments die. roborama.transfer()
measures it: the same policy on the same task, on the embodiment it was
trained on and the embodiment you intend to ship — both with n and intervals,
plus the failure clusters that exist only on the new body.
The call
import roborama # reads ROBORAMA_API_KEY from the environment
policy = roborama.Policy.checkpoint(hf="acme/skill-v4", runtime="openpi")
gap = roborama.transfer(
policy=policy,
source="aloha-bimanual@fw3.1", # the embodiment it was trained on
target="g1-edu-pro@fw2.3",
task="fold_towel@v2",
)
print(gap.report())
# source: n=380 rate=0.942 ci95=(0.914, 0.961)
# target: n=340 rate=0.715 ci95=(0.665, 0.760)
# failure clusters opened in transfer: [grasp_slip, wrist_singularity]Reading the gap
The canonical fold_towel@v2 gap: on the source, aloha-bimanual@fw3.1 — the
rig the policy was trained on — the rate is 0.942 (n=380, ci95 0.914–0.961).
On the target, g1-edu-pro@fw2.3, it is 0.715 (n=340, ci95 0.665–0.760). The
intervals are nowhere near overlapping: a 22.7-point drop that no amount of
sampling luck explains. That is the real number to plan a deployment around —
not the source rate that made the demo look ready.
What opened in transfer
The gap report does more than subtract two rates. It names the failure
clusters that were absent or negligible on the source and material on the
target — here, grasp_slip and wrist_singularity — which turns "the policy
got worse" into a work list. The target's dex3-1 hands present different
contact geometry than the source's grippers, and grasp_slip is that
difference biting; wrist_singularity is the target's arm kinematics hitting
configurations the source arms never encountered on the same trajectories.
Each cluster links to its episodes, so the diagnosis comes with video, MCAP
telemetry, and commanded-versus-executed traces rather than a hunch.
Closing the gap
The episodes are also the remedy. Target-embodiment failures export in
training-ready formats — run.export(format="lerobot"), or "rlds" — subject
to your own data posture, since train_on_failures guards our use of them,
not yours. Fine-tune on target-embodiment data, re-run transfer(), and
watch the two intervals converge; when they are close enough to argue about,
graduate to matrix() and put the new embodiment
in the release grid.
The full workflow — measure, fine-tune, re-measure, and when to stop — is the transfer gap guide.
Where next
- Transfer gap guide — the end-to-end workflow.
- compare() — paired A/B when both candidates run on one embodiment.
- Data contract — export formats and ownership.