BotManifold
Guides

Writing Policies

What a policy is, what a ZIP policy may import, and how to serve your own policy.

A policy turns an observation into an action. Verify supports two ways to provide one: a ZIP that BotManifold runs for you, and a policy server you host.

The policy contract

Every scenario defines its own observation (a dictionary of arrays and numbers) and action (a vector of actuator controls with fixed limits). Look yours up in the scenario reference: for example Messy Room gives joint_positions, end_effector_pos, target_pos and distractor_pos, and expects 3 controls.

Your policy is called once per simulation step and must return a NumPy array of the scenario's action size. Keep values inside the limits yourself; the shape is validated and a wrong size fails the run.

ZIP policies

Put your code in a ZIP. The loader finds the entry point by name:

  • Functions (checked first, in order): act, policy, my_policy, get_action
  • Classes (checked second, in order): Policy, MyPolicy, Agent, Bot. The class is created with no arguments and its act(observation) method is used.
policy.py
import numpy as np


class Policy:
    def __init__(self) -> None:
        self.gain = 5.0

    def act(self, observation: dict) -> np.ndarray:
        error = np.asarray(observation["target_pos"]) - np.asarray(observation["end_effector_pos"])
        return np.clip(self.gain * error, -1.0, 1.0)
zip policy.zip policy.py
botmanifold verify policy.zip --scenario messy_room

What a ZIP policy may use

Policies are checked with a static analysis before they run, and rejected if they import anything outside this list or call a blocked builtin:

  • Allowed imports: numpy, torch, jax, scipy, PIL, cv2, and the standard-library modules math, collections, functools, itertools, typing, dataclasses, enum, abc, copy, json, struct, array, random
  • Blocked: anything that touches the system or network (os, sys, subprocess, socket, urllib, requests, pickle, ctypes, ...), and the builtins exec, eval, compile, __import__, open, getattr, setattr, globals and locals

Each policy source file is limited to 512 KB and the ZIP to 100 MB. The entry point is found anywhere in the ZIP, so a file inside a folder works. A policy that needs files from disk, other libraries or a large model should be served instead.

Serve your own policy

If your policy needs other libraries, a large model or a GPU, run it as a server and submit its URL. This is also how you evaluate a Vision-Language-Action model.

botmanifold serve my_policy.py --port 8765
# Pairing token (paste into the Verify submit form): 7f3a...

serve loads a PolicyServer subclass from the file and exposes three endpoints:

EndpointPurpose
GET /healthReachability check (no token needed)
POST /resetStart an episode: {"scenario_id", "instruction", "seed"}
POST /predictAn observation in, an action out: {"values": [..]}

/reset and /predict require the pairing token as a Bearer token. Then submit the URL and the token:

botmanifold verify --url https://my-gpu-box:8765 --token 7f3a... --scenario messy_room

Needs the BotManifold libs

botmanifold serve and botmanifold.policy use BotManifold's shared libs packages, which are not part of the pip package. Today they work from a checkout of the BotManifold repository (put libs/ on sys.path). The protocol itself is plain HTTP and JSON, so any server that answers /health, /reset and /predict as above will work.

The address must be public

Verify's workers call your server from BotManifold's network, so the URL must be reachable from the internet. Private addresses and localhost are rejected. Use a cloud GPU box, a port-forwarded machine, or a tunnel. Use --no-token only for local testing: a reachable server without a token accepts predictions from anyone.

A server implements reset and predict:

from botmanifold.policy import Action, ActionType, PolicyServer


class MyPolicy(PolicyServer):
    def __init__(self) -> None:
        super().__init__(name="my-policy", version="1.0.0",
                         action_types=[ActionType.JOINT_VELOCITY])

    def reset(self, scenario_id, instruction, seed, config) -> bool:
        return True

    def predict(self, observation):
        return Action.joint_velocity(self.model(observation))

Test locally first

  • botmanifold serve my_policy.py and call it with curl or PolicyClient.
  • botmanifold mock-server episode.jsonl starts a mock evaluation server that replays a recorded episode, so you can develop without the full platform.
  • Run the scenario yourself in Studio when a matching robot exists, to watch the behavior before you spend a verification run.

Tips

  • Handle the first step. Some observation values are zero or constant at reset.
  • Keep per-step work small. The whole run has a time limit (and a server policy gets 30 seconds per step), so a slow policy times out.
  • Be deterministic where you can. Episodes are seeded; a policy that uses its own randomness makes results harder to compare.
  • Watch the video. The replay of each episode is the fastest way to see why a policy failed.

On this page