Plugins
Solarian's core framework ships almost no batteries: no hardware drivers, no policies, no robot definitions. Everything that talks to real hardware or adds a capability lives in a standalone plugin package named solarian-<name>. This keeps the core slim and lets anyone publish and share extensions independently.
This page covers what a plugin is, the official ones to start from, how to install and use one, and how to write and publish your own. Browse every published package in the Plugin Directory.
What is a plugin?
A Solarian plugin is a normal, pip-installable Python package that follows two conventions:
- Its distribution name is prefixed
solarian-(e.g.solarian-feetech). - It imports under the matching snake_case module name (e.g.
solarian_feetech).
Beyond that, it just provides one or more Solarian classes — an Actuator, a Bus, a Module, a Robot, a Skill, a policy Trainer/PolicyServer, a dataset op, or a CLI command — and (optionally) advertises them through entry points so Solarian can discover them automatically.
There is no mandatory "register me" call. Direct imports always work: from solarian_feetech.actuator import FeetechServo. Entry points are what make a plugin discoverable — so solarian install, the AIController, and solarian train can find your classes by name without the user importing anything.
Kinds of plugins
A single package can ship any combination of these. The kind determines which files you write and which entry-point groups the classes register under.
- Name
Hardware — actuator / sensor- Description
A driver for a motor, servo, or sensor plus the Bus that speaks its protocol. Example:
solarian-feetech,solarian-dynamixel.
- Name
Module- Description
An opinionated Module that composes actuators/sensors into a ready-to-use unit (an arm, a gripper), usually with a bundled
templates/modules/config. Example:solarian-ur5,solarian-umi-slim.
- Name
Robot- Description
A full Robot definition wiring several modules together.
- Name
Skill- Description
A self-contained Skill behavior the AI Controller can orchestrate, auto-discovered by any
AIController. Example:solarian-umi-skill.
- Name
Policy- Description
A trainable model: a
Trainer(forsolarian train) and/or aPolicyServera skill calls over HTTP. Example:solarian-policy-diffusion. See Policies.
- Name
Dataset op- Description
A dataset op that
solarian datasetcan run on any project.
- Name
Command- Description
A custom
solariansubcommand. Example:solarian feetech-set-idshipped bysolarian-feetech.
Entry-point groups
Discovery is powered by standard Python entry points read via importlib.metadata. Each kind maps to one or more groups, and solarian create-plugin scaffolds the type in the second column:
| Kind | create-plugin type | Entry-point group(s) | Discovered by |
|---|---|---|---|
| Actuator | actuator | solarian.actuators, solarian.actuator_buses | config type: resolution |
| Sensor | sensor | solarian.sensors, solarian.sensor_buses | config type: resolution |
| Module | module | solarian.modules | solarian install |
| Robot | robot | solarian.robots | project wiring |
| Skill | (part of policy) | solarian.skills | AIController |
| Policy | policy | solarian.trainers, solarian.services, solarian.skills | solarian train, run-service |
| Service | (part of policy) | solarian.services | run-service |
| Dataset op | op | solarian.dataset_ops | solarian dataset |
| Command | command | solarian.commands | solarian CLI |
Core plugins
These are the official plugins most projects start from. Each name links to its page in the Plugin Directory, where you can also find community plugins and submit your own for review.
Actuators
- solarian-feetech — Feetech servos (STS3215, SCS) and the bus that talks to them, plus a
feetech-set-idcommand. - solarian-dynamixel — Dynamixel servos (XL330, XM430, and the rest) and the DynamixelBus.
- solarian-robstride — Robstride actuators and the bus that drives them.
Arms and grippers
- solarian-ur5 — Universal Robots UR5 arm module, in joint space and Cartesian space.
- solarian-so106 — SO-106 follower arm module.
- solarian-umi-slim — UMI slim gripper module, including the MuJoCo model used in simulation.
Skills and policies
- solarian-move-skill — Move to a named pose. Poses live in
conf/skills/move.yaml; add one there and start the skill with that name. - solarian-umi-skill — Run a UMI diffusion policy. Each policy is a yaml in
conf/; startUMISkillwith that filename. - solarian-policy-diffusion — Trains and serves the diffusion policy that
UMISkillcalls.
Installing a plugin
Plugins are ordinary pip packages, so the baseline is always pip install solarian-<name>. But most plugins need a config file and a reference in your code, so solarian install does three things:
solarian install feetech
- Installs the package into the current Python environment. A short name is prefixed, so
feetechinstallssolarian-feetech. PyPI is tried first; if the project is not published, the command looks forsolarian-feetech/in the current folder and then underplugins/. - Copies the default config into
conf/when the destination is absent. A file in the package'stemplates/directory keeps its folder and name (templates/skills/move.yaml→conf/skills/move.yaml,templates/modules/feetech.yaml→conf/modules/feetech.yaml). - Records the dependency — adds
solarian-feetech>=0.1.0to the project'spyproject.toml.
The Robot attribute has to match that filename. A second instance (a left arm and a right arm from one plugin) is a copy of the yaml under the new name.
Attach it to your Robot
solarian install never edits your Python — you import the class and attach it yourself:
from solarian.core import Robot
from solarian_ur5.module import UR5ArmModule
class MyRobot(Robot):
ur5 = UR5ArmModule() # reads conf/modules/ur5.yaml
Then edit conf/modules/ur5.yaml to set ports, servo IDs, frequency, and calibration paths for your hardware. See Project Configs.
From a local path
Pass a path to skip PyPI and install that directory editable:
solarian install plugins/solarian-umi-slim
Checking what's installed
Plugins are regular distributions, so normal tooling works:
pip show solarian-feetech # metadata + version
pip list | grep solarian- # every installed Solarian plugin
Using skills, policies and commands
Hardware and module plugins are used by attaching their classes to your Robot. The other kinds are picked up by the part of Solarian that runs them.
Skill plugins
Skill plugins use the same install. Any package that advertises a solarian.skills entry point is auto-discovered by every AIController — Solarian reads the entry points via importlib.metadata at startup and merges them into available_skills. You can also import the class and start it yourself:
from solarian_move_skill import MoveSkill
from solarian_umi_skill import UMISkill
MoveSkill(params={"pose": "home", "arm": "right_arm", "gripper": "right_gripper"})
UMISkill(params={"config": "cup_arrange.yaml"})
pose is any name under poses: in conf/skills/move.yaml, which solarian install solarian-move-skill creates from the plugin's template when the file is not already there. config is a yaml in conf/. solarian install solarian-umi-skill copies templates/umi.yaml to conf/skills/umi.yaml; copy that file for each policy.
Class-attribute skills you declare on your controller take priority over a discovered skill of the same name, so you can still override one by declaring it explicitly.
UMISkill reads the checkpoint and the rest of its parameters from the file you passed. arm, gripper, and camera live in that file too:
# conf/skills/cup_arrange.yaml — one UMI policy
policy_server_url: http://localhost:8001
server:
service: diffusion-policy
checkpoint: data/models/cup_wild_vit_l_1img.ckpt
port: 8001
arm: right_arm
gripper: right_gripper
camera: right_wrist_camera
Policy plugins
Policy plugins ship the trainer and the server a skill calls over HTTP, registered as a service. Install the plugin (with the extra you need), then use the CLI:
pip install "solarian-policy-diffusion[serve]" # torch, etc. for inference
# or [train] to train, [modal] for cloud training with solarian modal-train
# serve the checkpoint a skill conf names in its server: block
solarian run-service cup_arrange
# or any checkpoint, with the service directly
solarian run-service diffusion-policy --checkpoint <checkpoint.ckpt> --port 8001
Trainers register under solarian.trainers, so solarian train can select them by name (model.type=diffusion). See Training.
Command plugins
A plugin can add its own solarian subcommand via the solarian.commands entry-point group. Once the package is installed, the command shows up in solarian --help:
# shipped by solarian-feetech
solarian feetech-set-id
Mixing plugins in one module
Because each Actuator binds its own bus_class, a single Module can mix drivers from different plugins. Actuators that share a port share a bus instance; the rest each get their own:
from solarian.core.module import Module
from solarian_feetech.actuator import FeetechServo
from solarian_dynamixel.actuator import DynamixelServo
class MixedArmModule(Module):
shoulder = FeetechServo(id=1, model="sts3215")
elbow = FeetechServo(id=2, model="sts3215")
gripper = DynamixelServo(id=1, model="xl330-m077")
Creating a plugin
You can write a plugin by hand, but the fastest start is the built-in scaffolder, which lays down a correct package layout and wires the entry points for you:
solarian create-plugin <type> <name> [--force]
<type> is one of actuator, sensor, module, robot, command, policy, or op (see Entry-point groups for what each registers). <name> is the short name (feetech) or the full solarian-feetech — either way you get the same normalized package.
solarian create-plugin actuator feetech
This creates:
solarian-feetech/
├── .gitignore
├── pyproject.toml # wired with the right entry points
├── README.md
└── solarian_feetech/
├── __init__.py
├── actuator.py # <ClassPrefix>Servo stub
└── bus.py # <ClassPrefix>Bus stub
Re-run create-plugin with a different <type> and the same name to add a component type to an existing plugin. Base files (pyproject.toml, README.md, __init__.py) are never overwritten, and new entry points are merged idempotently — so create-plugin module feetech adds a module to solarian-feetech without clobbering the actuator you already have. Use --force to regenerate the component source stubs.
Then install it editable and implement the stubs:
cd solarian-feetech
pip install -e .
Naming convention
The scaffolder normalizes names so the three forms always line up:
| Input | Distribution | Import package | Class prefix |
|---|---|---|---|
feetech | solarian-feetech | solarian_feetech | Feetech |
umi-slim | solarian-umi-slim | solarian_umi_slim | UmiSlim |
Writing a hardware plugin
An actuator plugin ships two classes: the Actuator/Servo (declares its Data channels and binds a bus) and the Bus (speaks the wire protocol). This is the whole of solarian-feetech:
# solarian_feetech/actuator.py
from solarian.core.servo import Servo
from solarian.core.data import PosData, SpeedData
from solarian_feetech.bus import FeetechBus
class FeetechServo(Servo):
bus_class = FeetechBus # which bus talks to this hardware
pos = PosData() # a writable + readable position channel
speed = SpeedData()
The bus subclasses ActuatorBus (or SensorBus) and implements the bus contract — connect, disconnect, read, write, torque control, calibration:
# solarian_feetech/bus.py
from solarian.core.component_bus import ActuatorBus
class FeetechBus(ActuatorBus):
def connect(self): ...
def disconnect(self): ...
def read(self, data_type, names, normalize=True): ...
def write(self, values, normalize=True): ...
def enable_torque(self, names): ...
def disable_torque(self, names): ...
The generated pyproject.toml wires both:
[project.entry-points."solarian.actuators"]
feetech = "solarian_feetech.actuator:FeetechServo"
[project.entry-points."solarian.actuator_buses"]
feetech = "solarian_feetech.bus:FeetechBus"
Sensors
A sensor plugin has the same shape, using Sensor + SensorBus:
from solarian.core.sensor import Sensor
from solarian.core.data import ImageData
from solarian_camera.sensor_bus import CameraBus
class RGBCamera(Sensor):
bus_class = CameraBus
rgb_image = ImageData(shape=(480, 640, 3))
The bus implements read(ImageData, [...]) by grabbing a frame from V4L2 / AVFoundation / RealSense / whatever the sensor needs. It registers under solarian.sensors / solarian.sensor_buses.
Writing a module plugin
A module plugin composes actuators/sensors into a ready-to-use unit and ships a default config in templates/modules/. solarian-umi-slim is a parallel gripper driven by a single Feetech servo — note it depends on solarian-feetech rather than reimplementing the driver:
# solarian_umi_slim/module.py
from solarian.core.gripper_module import GripperConfig, GripperModule
from solarian.core.data import PosData
from solarian_feetech.actuator import FeetechServo
class UmiSlimConfig(GripperConfig):
aperture_closed_mm: float = 0.0 # straight-line fallback
aperture_open_mm: float = 85.0 # until a table is measured
class UmiSlimModule(GripperModule):
config_class = UmiSlimConfig
scalar_actuator = "gripper"
gripper = FeetechServo(model="sts3215") # one logical actuator
def initialize(self):
super().initialize()
self.disable_torque()
self.configure_components({"Operating_Mode": 0, "P_Coefficient": 32})
self.enable_torque()
def control_step(self):
... # per-tick control logic
Its pyproject.toml declares the module entry point and the dependency:
[project]
dependencies = ["solarian>=0.1.0", "solarian-feetech>=0.1.0"]
[project.entry-points."solarian.modules"]
umi-slim = "solarian_umi_slim.module:UmiSlimModule"
Bundle a default config
Ship the config under templates/, in the folder of the conf/ directory that reads it, so solarian install can seed it: templates/modules/<name>.yaml becomes conf/modules/<name>.yaml, a skill's templates/skills/move.yaml becomes conf/skills/move.yaml, and a service's templates/services/bbox_tracker.yaml becomes conf/services/bbox_tracker.yaml. Keep the module's tunables here:
# solarian_umi_slim/templates/modules/umi_slim.yaml
port: /dev/ttyUSB0
frequency: 30
aperture_closed_mm: 0.0
aperture_open_mm: 85.0
actuators:
gripper:
id: 1
port: ${port}
sim_joint: "gripper/left_manifold_to_rail"
sim_range_min: 0
sim_range_max: 0.0545
Ship data files (models, configs)
Non-Python files (config templates, MuJoCo models, meshes) must be declared as package data so they travel with the wheel. solarian-umi-slim ships its simulation model this way:
[tool.setuptools.package-data]
solarian_umi_slim = [
"templates/modules/*.yaml",
"assets/umi_slim.xml",
"assets/umi_slim_description/meshes/*.stl",
]
Bundling the MJCF inside the package means MJCF_PATH resolves from the installed location, so every project that installs the plugin gets the model with no copying. See Shipping the model inside a plugin.
Writing a skill or policy plugin
An AI plugin usually comes in two halves (they can be separate packages):
- A skill plugin — the behavior.
solarian-umi-skillships aUMISkillthat only calls a policy over HTTP. The yaml inparams["config"]says which checkpoint and which modules:
[project.entry-points."solarian.skills"]
umi = "solarian_umi_skill.skill:UMISkill"
- A policy plugin — the trainable model.
solarian-policy-diffusionships aTrainer(forsolarian train), aPolicyServer, and aServicethat registers the server sosolarian run-servicecan serve a checkpoint, with heavy ML deps kept behind optional extras so merely discovering the plugin doesn't pull in PyTorch:
[project.optional-dependencies]
serve = ["torch", "torchvision", "diffusers", "timm", ...]
train = ["torch", "accelerate", "wandb", ...]
[project.entry-points."solarian.trainers"]
diffusion = "solarian_policy_diffusion.trainer:DiffusionTrainer"
[project.entry-points."solarian.services"]
diffusion-policy = "solarian_policy_diffusion.service:DiffusionPolicyService"
solarian create-plugin policy <name> scaffolds the stubs at once — trainer.py, policy_server.py, service.py and skill.py — and registers the solarian.trainers, solarian.services and solarian.skills entry points. See Skills and Policies for the class contracts.
Writing a command plugin
A command plugin adds a solarian subcommand. Subclass BaseCommand, register it, and declare the solarian.commands entry point:
from omegaconf import DictConfig
from solarian.cli.base import BaseCommand, CommandRegistry
@CommandRegistry.register('feetech-set-id')
class SetIdCommand(BaseCommand):
category = "Utilities"
help = "Assign an ID to a Feetech servo."
def handle(self, cfg: DictConfig) -> None:
...
category is the heading solarian --help groups the command under. Set uses_config = False when it needs no project yaml. Override parse_args to accept positionals or flags: store the parsed values on self and read them from handle. The default forwards every token to Hydra as a key=value override.
[project.entry-points."solarian.commands"]
feetech-set-id = "solarian_feetech.commands.set_id:SetIdCommand"
See Authoring a custom command for config classes and lazy registration.
Publishing to PyPI
A plugin is a standard package, so publishing is standard too:
- Set a real
name(solarian-<name>),version, and metadata inpyproject.toml, and listsolarian>=0.1.0independencies. - Put your classes in the
solarian_<name>package and confirm the entry points point at them. - Declare any non-Python files under
[tool.setuptools.package-data]so they ship in the wheel. - Build and upload:
python -m build
twine upload dist/*
Users then install with solarian install solarian-<name>, and you can submit it to the Plugin Directory.
See also
- Plugin Directory — every published plugin, official and community.
- Modules — the class a module plugin subclasses, and the Component and Bus contracts a hardware plugin implements.
- Data classes — the
Datachannels a component declares. - CLI — full
installreference. - Project Configs — what
installcopies intoconf/.