- Robot Integration & Policy Execution: Connect robot hardware directly to hosted VLA models with
servo.connect(),servo.Policy(),servo.camera(), andservo.inspect(). - Resource Management & Administration: Manage fleet deployments, query available models, and register robots with
servo.Servo.
servo.Policy, servo.Session, servo.Observation, servo.ActionPrediction, servo.Servo, servo.Robot, servo.Model, servo.HostedDeployment, and servo.Fleet.
Preview. Theservo.connectAPI is currently in developer preview and not yet shipped in general availability. Early feedback and testing are welcome.connect/camera/inspect/gym_stepare imported explicitly fromservo.universalin the examples below — they don’t yet resolve asservo.connectetc.
Robot Integration API
servo.connect
Binds named model input and output slots to Python callables, ROS 2 topics, or hardware handles:
Parameters:
connect() runs in one of two mutually exclusive modes — pass model for a dedicated session, or fleet + robot to draw from pooled fleet capacity (see Robot Fleets):
model(str): The model identifier string (e.g."allenai/MolmoAct2-BimanualYAM") or a string constant fromservo.models(e.g.servo.models.MolmoAct2BimanualYAM). Mutually exclusive withfleet.fleet(str, optional): Fleet name fromsv.fleets.deploy(..., name=...). Selects the model implicitly — pass withrobot, notmodel.robot(str, optional withmodel, required withfleet): A stable robot name. The first call with a given name registers the robot in your organization; subsequent calls attach to that record. Omit entirely for an unregistered, standalone session.site,labels(str, dict[str, str], optional): Location and grouping metadata attached on first registration withrobot=; used bysv.fleets.deploy(selector=...).observe(dict[str, Source] | Callable[[], dict[str, Any]], optional withmock=True): Observation channel. Either a slot dictionary mapping model sensor names toservo.universal.Camerahandles (the type returned bycamera()), callables, or ROS topic strings, or a zero-argument callable returning the observation dictionary read in one call. Annotate a separate dictionary with a model-specificTypedDictwhen you want static slot-name checking.act(Callable[[np.ndarray], None | dict] | dict[str, str], optional withmock=True): Actuator sink accepting continuous target positions(N,) float32at the control rate (e.g. 30 Hz), or a dict of ROS topic strings. ReturnNonefor success or{"done": True}for early completion; raise to fault (triggers zero-velocity hold). Wrap an existing Gym env withservo.gym_step(env.step)rather than returning a raw step tuple.region(str, optional): Target compute region nearest to the physical robot (default:"us-west-2").ros_node(rclpy.node.Node, optional): Active ROS 2 node when binding topic strings.mock(bool, optional): Run a deterministic local fake policy instead of contacting a hosted endpoint (default:False). The exact MolmoAct2 development profile can synthesize Sources/Sinks automatically; other model identifiers require explicitobserveandactbindings. Mock mode validates local binding and control-loop behavior, not credentials or network transport.mock_action_dim(int, optional): Positive action-vector dimension for a mock model that has no exact local development profile. It is unnecessary for the built-in MolmoAct2 profile and invalid for non-mock sessions.
servo.Policy
Creates a high-performance functional policy callable for custom control loops:
env.step(action). instruction= is accepted as an alias for task=. Current limitation: unlike session.run()’s real background-prefetching control loop, policy(...) does not yet buffer or prefetch — every call is one real network round trip. Call it no faster than your model’s round-trip allows, or add your own buffering for a tighter loop.
servo.camera
Creates a persistent hardware camera handle opened via zero-copy V4L2 DMA-BUF:
servo.inspect
Inspects the sensory input schema a model expects for a given robot — the contract is a
property of the (model, robot) pairing, not the model alone, so robot is required:
Parameters:
model(str): The model identifier or hosted slug.robot(Robot | Embodiment | str): A registered robot or embodiment handle, anemb_*embodiment ID, or a unique cached embodiment name.
Returns:
ModelContract: The current flat compatibility projection. Its.inputspreserve basic type, shape, and data-type fields;.outputsis always{}today. It does not yet preserve the full server inference contract, including requiredness and action outputs. The current MolmoAct2 runtime contract publishes model-prepared camera inputs but not proprioceptive state metadata.
Exceptions:
servo.ValidationError: Raised byinspect()itself when the server can’t resolve an observation contract for this (model, robot) pair (e.g. no runtime installed for this family/embodiment yet).servo.universal.ContractMismatchError: Raised byservo.connect()when observed keys do not match an exact local development profile, or when a Source/Sink violates the runtime value contract.
Static Type Checking & Model Schemas
Servo provides a PEP 589TypedDict schema for its exact MolmoAct2 development profile. Apply the
type explicitly to an observation dictionary for mypy, pyright, and IDE completion; connect()
does not infer that schema from a model string.
RobotSession
Returned by servo.connect(). Not the same class as the real servo.Session returned by
sv.session(policy) (see Low-level inspection loop below) — that
one is a stepwise act()-plus-evidence-recording handle for a caller-driven loop;
RobotSession is a high-level rollout controller whose real (non-mock) .run() is sugar over
the robot.run(policy, seconds=...) verb.
session.run(task: str, timeout_s: float | None = None, max_steps: int | None = None, until: Callable[[], bool] | None = None) -> RunReport: Executes a bounded autonomous episode (rollout) at the model’s native rate (e.g. 30 Hz).session.pause() -> None: Halts action emission and commands zero-velocity holding targets to actuators.session.resume() -> None: Flushes stale frames, forces an IDR keyframe, prefetches a fresh chunk, and resumes 30 Hz policy execution.session.get_frame(camera_name: str) -> np.ndarray: Non-blocking preview (peek()) from the local shared-memory ring buffer.session.stop() -> None: Halts autonomous execution and tears down the session.session.listen() -> None: Blocks. Keepsobserve/actbindings live and reports readiness over the daemon’s control-plane connection; rollouts are then triggered byrobot.start()from another process (requiresrobot=— see Remote Start and Stop).
session.run() is not evidenced or recorded
server-side — no rollout record, no LeRobot dataset — because a bare connect(model, robot=...)
binding has no deployment to record against. connect() warns at construction time
(UserWarning) rather than silently dropping episode data; see ROBOT_INTEGRATION_SPEC.md.
RunReport
Returned by session.run():
report.status(str):"completed","timeout","fault","intervened", or"halted".report.error(str | None): Diagnostic details or exception trace ifreport.status == "fault", otherwiseNone.report.steps(int): Total control steps executed during the rollout.report.duration_s(float): Total elapsed wall-clock seconds.report.dataset_path(str | None): Reserved for a future recorded-episode path (in LeRobot format); alwaysNonetoday — see the current limitation noted above.report.telemetry(dict): Round-trip latency, prefetch jitter, and dropped-frame counts for the run.report.raise_for_status() -> None: Raisesservo.universal.ActuatorFaultErrorifstatus == "fault"orTimeoutErrorifstatus == "timeout".print(report)/repr(report): one-glance summary card (status, duration, steps, dataset path, cockpit replay link).
Client & Resource Namespaces
For multi-robot fleet administration, model querying, and hosted capacity provisioning:Models and deployments
servo deployment list, and stop unused capacity with servo deployment stop <deployment-id>.
Remote start and stop (preview)
For a robot bound locally withsession.listen() (see Remote Start and Stop), control it from any other process:
robot.start(task: str, timeout_s: float | None = None) -> RemoteRun: Non-blocking. Raisesservo.universal.RobotNotListeningif nothing is bound.RemoteRun.status(str):"running","completed","fault", etc.RemoteRun.pause() / .resume() / .stop() -> None: Proxysession.pause()/.resume()/.stop()on the listening robot.RemoteRun.wait(timeout_s: float | None = None) -> RunReport: Blocks until the rollout ends; sameRunReportshape as a localsession.run().
Low-level inspection loop
For integration debugging, step-by-step telemetry, or manual unit testing:robot.observe(): Captures camera frames and joint states adhering to the sensor contract.session.act(observation): Queries the hosted model and returns anActionPredictionchunk.robot.execute(prediction): Applies action-jump limits and streams joint targets to the local controller.