Action spaces

Policy output is either joint angles (JointState) or end-effector poses (EEFPose); EVA always commands the robot in joint angles and runs IK for poses.

Two spaces

Observation and action are each described by a type string under inference_cfg. Switching is a config edit.

Space (type string)ContentsUse when
JointStateFlat list of joint angles, as the robot reads and writes them.Default. Policy outputs joint angles.
EEFPosePer-arm hand pose. EVA runs IK to produce joint angles.Policy outputs end-effector poses.

Both default to JointState:

python
inference_cfg = dict(
    obs_space=dict(type='JointState'),     # policy input
    action_space=dict(type='JointState'),  # policy output
    publish_rate=30,                          # joint commands per second (Hz)
)
publish_rate is the joint-command rate EVA sends to the robot. 30 Hz is the default; change it to match the robot's control loop.

Switch to end-effector mode

Set both type fields to EEFPose and declare the pose layout in a deploy config — copy a preset such as configs/01_deploy/<your-robot>/openpi_eef.py and launch with eva --config <that file>. The robot must ship an IK solver (see the note at the end).

python — deploy config
inference_cfg = dict(
    obs_space=dict(
        type='EEFPose',
        n_arms=<arms>,            # 1 or 2
        rotation='<rotation>',    # see table below
        include_gripper=True,
    ),
    action_space=dict(
        type='EEFPose',
        n_arms=<arms>,            # match obs_space
        rotation='<rotation>',    # match obs_space
        include_gripper=True,
    ),
)

rotation declares how orientation is encoded; the trailing number is its width.

rotationMeaningValues
quatQuaternion. Default.4
eulerRoll/pitch/yaw.3
rotvecRotation vector.3
rot6dSix-number form.6

The per-arm block is 3 (position) + rotation width + 1 (gripper) and repeats n_arms times. Set include_gripper=False if the policy emits no gripper value. Two arms with quat and grippers: (3+4+1) × 2 = 16 values per action.

IK solver required. Built-in robots with IK: agilex_AGILEX, dual_franka, agibot_g2, r1_lite, ur5e. Requesting EEF mode on a robot without IK fails with "EEF mode requires an IK solver but the robot does not provide one". Either run in JointState mode or add kinematics when adding the robot.

Canonical per-arm layout

EVA rewrites every EEF action into a single canonical layout before downstream processing, so later stages see one shape.

canonical per-arm layout
position (3)  +  orientation as quaternion (4)  +  gripper (1)  =  8 values per arm

The configured rotation is converted to a quaternion; position and gripper pass through. This happens automatically once action_space is EEFPose. The recorder writes the same 8-per-arm layout for EEF episodes.

Pose to joint command

The robot is always commanded in joint angles. Dispatch depends on action_space:

The IK solver is built once from the robot's kinematics and reused at publish_rate. FK is used in the reverse direction to populate pose values for the observation and recorder when EEF observations are requested without a pose sensor.

IK trajectory tracking

EVA's IK solver (PyRoki, on JAX and jaxls) converts a chunk frame by frame. Each frame is a small optimization chained to the previous frame.

Per-frame objectives:

The first frame is seeded with the current measured joint state. EVA does not drop unreachable frames: if tracking error exceeds tolerance, EVA logs a warning and dispatches the closest joint angles found. Repeated tracking warnings indicate the policy is asking for unreachable poses.

Two-arm robots are split into per-arm blocks, solved independently, and recombined. Extra body joints (e.g. fixed head and torso) stay frozen while arm joints update.

Real robot vs simulation

Solved joint angles go to one of two targets:

Console STEP mode previews a chunk in SIM and confirms it on REAL. Only joint angles at publish_rate leave EVA; poses do not.