Adapter Examples

Run a tagged environment with a model that expects a different input and action format.

These examples show adapters at work: an environment tags its spaces, a model declares its own format, and RLMesh resolves the pairing. Both use the NumPy backend and need no GPU or simulator.

Smallest serve-and-run loop

One process serves a tagged environment and runs an adapted model against it. The runnable file is examples/python/adapters/serve_and_run.py.

uv run python examples/python/adapters/serve_and_run.py

The environment tags each observation and action component with a role. It also declares details the spaces cannot carry, such as rotation encoding and clipping range.

import rlmesh.adapters as adapt

ENV_TAGS = adapt.EnvTags(
    observation={
        "wrist_rgb": adapt.ImageTag(adapt.IMAGE_PRIMARY),
        "ee_pos": adapt.StateTag(adapt.EEF_POS),
        "ee_quat": adapt.StateTag(adapt.EEF_ROT, encoding="quat_xyzw"),
        "grip": adapt.StateTag(adapt.GRIPPER_POS),
        "goal": adapt.TextTag(adapt.INSTRUCTION),
    },
    action=adapt.Action(
        adapt.Actuator(adapt.ACTION_DELTA_POS, dim=3),
        adapt.Actuator(adapt.ACTION_DELTA_ROT, dim=3, encoding="axis_angle"),
        adapt.Actuator(adapt.ACTION_GRIPPER, dim=1, range=(-1.0, 1.0)),
        clip=(-1.0, 1.0),
    ),
)

The model declares what it expects: a 224×224 image, state with rot6d rotation, an instruction under its own key, and a rot6d action. The input dictionary becomes the prediction function’s payload.

MODEL_SPEC = adapt.ModelSpec(
    input={
        "image": adapt.Image(adapt.IMAGE_PRIMARY, size=224),
        "proprio": adapt.Concat(
            adapt.EEF_POS,
            adapt.State(adapt.EEF_ROT, encoding="rot6d"),
            adapt.GRIPPER_POS,
            container="list",
        ),
        "task": adapt.Text(adapt.INSTRUCTION),
    },
    output=adapt.Action(
        adapt.Actuator(adapt.ACTION_DELTA_POS, dim=3),
        adapt.Actuator(adapt.ACTION_DELTA_ROT, dim=6, encoding="rot6d"),
        adapt.Actuator(adapt.ACTION_GRIPPER, dim=1, range=(-1.0, 1.0)),
    ),
)

The prediction function sees the model’s declared format. It does not need to know the environment’s keys or rotation encoding.

def predict(payload: dict[str, Any]) -> Any:
    assert payload["image"].shape == (224, 224, 3)
    assert len(payload["proprio"]) == 10  # pos(3) + rot6d(6) + grip(1)
    return np.zeros(MODEL_SPEC.output.dim, dtype=np.float32)

The server publishes those tags in the environment contract. Model(spec=...).run(env) resolves the adapter from that contract and runs the episode.

import rlmesh
from rlmesh.numpy import Model, RemoteEnv

server = rlmesh.EnvServer(env, "127.0.0.1:0", tags=ENV_TAGS)
server.start()
client = RemoteEnv(server.address)

print(adapt.resolve_from_contract(client.env_contract, MODEL_SPEC).explain())
Model(predict, spec=MODEL_SPEC).run(client, episodes=1)

The script prints resolve_from_contract(...).explain() so you can inspect the chosen transformations: image resize, rotation conversion, instruction key remapping, and action clipping.

A project with many models and environments

The VLA adapter example keeps model specs and environment tags in separate modules. Its evaluator can run every model and environment pairing, or one chosen pair:

cd examples/python
uv run python -m vla_adapters.eval
uv run python -m vla_adapters.eval --model xvla --env simpler-bridge

Each model module declares a ModelSpec and a loader. Each environment module declares its spaces and EnvTags. The evaluator resolves compatible pairs without adding a hand-written adapter for every combination. The Metaworld example uses Split to tag slices of a flat observation vector; the same model specs also work with dictionary observations.

Some pairings need code beyond a declarative spec. The ACT example adds temporal ensembling with an AdapterBase subclass, and the evaluator can select an explicit pair override. See Escape Hatches for when and how to do that.

To connect the evaluator to a running environment server, pass its address:

uv run python -m vla_adapters.eval --model smolvla --env libero --address 127.0.0.1:5555

The Adapters guide explains the declarations, while the Adapter Reference covers the fields and conversion rules.