Core Interfaces#

When you wire a new robot or primitive into RPent, you implement the interfaces below. Walkthroughs: Add a Robot or Simulator, Add an Action Primitive. Repo layout: Architecture and Execution.

Robot Entry#

After you add robots/<robot>/, the package __init__.py re-exports two functions implemented in robot_spec.py for main.py to call:

def get_robot_spec() -> RobotSpec: ...
def get_toolkit(
    *,
    runtime_kwargs,
    dashboard_events: DashboardEventSink,
    config: RunConfig,
): ...

get_robot_spec returns a RobotSpec. You supply:

Field / hook

What you provide

name

Robot name for --robot.

prompts

A PromptBundle with system and user prompt factories (see robots/<robot>/prompt_bundle.py).

dashboard

Optional Dashboard description. None disables Dashboard control for the robot. Otherwise, the spec defines its task command and fields, runtime components, and frame channels.

add_cli_args

Register this robot’s CLI flags (e.g. --suite, --env-endpoint).

parse_config

Validate args and return RunConfig; set at least recipe_tag, output_dir, and prompt_vars for prompt templating.

init_runtime

Start or attach to all runtime components, or to the component names in the optional selection, and build runtime_kwargs for them. The normal CLI passes None; the Dashboard passes explicit shared and unique subsets derived from its spec. A DashboardEventSink reports status.

get_toolkit usually passes runtime_kwargs into your robot subclass; dashboard_events and config are supplied by the active runner. It must construct a MemoryManager (rooted at the configured config.prompt_vars["memory_dir"], falling back to get_memory_dir(robot_name) when unset) and pass it to the toolkit. Memory access permissions are configured on the manager. Robots that need extra toolkit arguments may declare them as keyword-only parameters; LIBERO additionally uses mode, attempts_per_session, and state_output_dir.

Reference: robots/libero/robot_spec.py.

Planner#

Most users pick a built-in api, claude_code, or codex planner — see Planner Configuration. Only custom planners need rpent.planner.base.Planner:

def solve(
    self,
    *,
    system_prompt: str,
    user_message: str,
    toolkit: Toolkit,
    max_turns: int,
    input_queue=None,
    dashboard_interaction=None,
) -> PlannerResult: ...

Contract: adapt the declarations from toolkit.list_tools() for the model; dispatch each call via toolkit.execute_tool(name, input_dict); feed results back to the model; return PlannerResult on the finish tool or when turns are exhausted.

Toolkit#

Subclass Toolkit in robots/<robot>/toolkit.py and register robot tools with add_tool:

def add_tool(self, tool: Tool, *, replace: bool = False) -> None: ...

Declare functions or methods with @tool. Type annotations and Google-style docstrings provide parameter schemas and descriptions; @tool(readonly=True) allows concurrent readonly calls and skips post-action state capture. Handlers return ToolResult(data=..., images=...): data holds the JSON payload, including error or _finish when needed, and images holds PNG bytes.

The base class already registers common file tools; call super().__init__() then add_tool for robot tools. Per-step state and view_env_state are in Add an Action Primitive.

Tools without readonly=True run exclusively, in admission order. Waiting exclusive calls take priority over new readonly calls. Exclusive execution includes observation capture and Dashboard publication. A readonly handler that updates shared caches must synchronize those updates itself. EnvState.save serializes artifact writes.

cancel_active_and_wait() pauses new calls, signals queued and active calls, and waits for their cleanup. Long-running handlers check raise_if_cancelled() at safe boundaries. After the planner has also drained its old requests, resume_calls() accepts new calls. close() permanently closes admission; subclasses call super().close() before saving recordings or releasing resources.

Inter-process Communication#

Relevant when attaching to existing servers or writing env_server / vla_server.

Client endpoints — expose in add_cli_args and parse in the applicable normal-CLI or Dashboard runtime hook:

[protocol://]host:port    # defaults to http when protocol is omitted

Common flags: --env-endpoint, --vla-endpoint. The default http sends JSON over POST /call, encoding NumPy arrays as {"__ndarray__": <base64>, "dtype": ..., "shape": ...} and NumPy scalars as {"__npscalar__": <value>, "dtype": ...} so dtypes survive the round trip; switch to socket for large or history-stacked nested-NumPy observations to move length-prefixed pickle frames and skip repeated JSON encoding. Pickle is unsafe on untrusted input, so only point socket at trusted endpoints.

Environment and VLA clients should normally subclass BaseEnvClient and BaseVLAClient; their servers should subclass BaseEnvFacade and BaseVLAFacade and register extension routes through _register_rpc. The bases provide common routing and locking on top of RpcFacade. Subclass RpcFacade directly only for a service type without a specialized base. Do not implement healthz or shutdown in application subclasses.

Details are in the env_server / vla_server sections of Add a Robot or Simulator.