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 itsact(observation)method is used.
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_roomWhat 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 modulesmath,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 builtinsexec,eval,compile,__import__,open,getattr,setattr,globalsandlocals
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:
| Endpoint | Purpose |
|---|---|
GET /health | Reachability check (no token needed) |
POST /reset | Start an episode: {"scenario_id", "instruction", "seed"} |
POST /predict | An 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_roomNeeds 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.pyand call it withcurlorPolicyClient.botmanifold mock-server episode.jsonlstarts 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.