Project Configs
Solarian uses Hydra for configuration. All YAML lives under your project's conf/ directory and is composed by the CLI based on which subcommand you run. The naming and layout follow simple conventions that bind YAML files to Python attributes.
The conf/ folder
conf/ is organized by who reads each file. Only the three process configs sit at the root; every other kind of config has a folder, and a plugin's templates/ mirrors those folders so solarian install seeds each file where its consumer looks:
conf/
├── robot.yaml # used by `solarian run-robot`
├── controller.yaml # used by `solarian run-controller`
├── server.yaml # used by `solarian run-server`
├── modules/<module>.yaml # a module, named by the robot's attribute
├── skills/<skill>.yaml # a skill's config
├── services/<service>.yaml # a service or processor config (bbox_tracker)
├── commands/
│ ├── record.yaml # used by `solarian record`
│ └── replay.yaml # used by `solarian replay`
├── training/<run>.yaml # one complete training run, for `solarian train`
├── calibrations/ # calibration results; cameras/<serial>.json
└── deployment/
└── localhost.yaml # relay address and device ports for one host layout
A config left at the root of conf/ is not read: looking for conf/modules/right_arm.yaml while conf/right_arm.yaml exists stops with Move conf/right_arm.yaml to conf/modules/right_arm.yaml.
Every CLI subcommand declares a config_name in solarian/cli/commands/__init__.py. When you run the command, Hydra loads conf/<config_name>.yaml; a name with a folder, such as commands/record, is a file in that folder. --conf conf/<folder>/<name> loads another file instead.
| Command | Config file |
|---|---|
run-robot | conf/robot.yaml |
run-controller | conf/controller.yaml |
run-server | conf/server.yaml |
record | conf/commands/record.yaml |
replay | conf/commands/replay.yaml |
train | none: --conf conf/training/<name> |
What is a config?
A config is a Hydra-composed YAML document validated by a Pydantic model. Composition lets you split shared values (like ZMQ port numbers) into reusable groups; validation catches typos before the robot starts moving.
For example, conf/robot.yaml:
connection: ${deployment.connection}
img_compression_quality: 70
fps: 30
${deployment.connection} pulls the connection: block out of the active deployment. After resolution the runtime config looks like:
img_compression_quality: 70
fps: 30
connection:
ip: 127.0.0.1
action_sender_port: 5555
action_receiver_port: 5556
state_sender_port: 5557
state_receiver_port: 5558
clock_sync_port: 5559
The result is validated against RobotConfig. If a field is missing or has the wrong type, Solarian prints a friendly error and refuses to start.
Module configs and the naming rule
Each Module attached to your Robot reads its own YAML file under conf/. The YAML filename must match the attribute name on your Robot subclass.
class MyRobot(Robot):
arm = ArmModule() # → conf/modules/arm.yaml
gripper = GripperModule() # → conf/modules/gripper.yaml
head_camera = CameraModule() # → conf/modules/head_camera.yaml
Each file is parsed against the module's config_class (default ModuleConfig):
# conf/modules/arm.yaml
actuator_port: 10.0.0.92
frequency: 120
actuators:
shoulder_pan:
id: 0
port: ${actuator_port}
shoulder_lift:
id: 1
port: ${actuator_port}
# ...
This same naming convention drives Observations and Actions: MyObservation.arm must be a ModuleObs whose fields reference MyRobot.arm.*.
This is the single most important convention in Solarian. Every consumer of a module — its YAML, its observation, its action, its action queue — finds the module by attribute name. Pick a clear, snake_case name once (e.g. arm, gripper_camera, left_leg) and stick with it.
Variable interpolation
Hydra supports ${var} interpolation. A common pattern is to declare a shared port at the top of a module config and reuse it for every actuator:
actuator_port: /dev/ttyUSB0
actuators:
joint_1:
port: ${actuator_port}
joint_2:
port: ${actuator_port}
Every file in conf/ can also reference ${deployment.*}, which is how host-specific values stay in one place. See below.
Deployments
A deployment is everything that changes with where your processes run: the relay's address and the device paths as seen from the host that owns the hardware. It lives in conf/deployment/<name>.yaml, and the scaffold ships with localhost.yaml:
connection:
ip: 127.0.0.1
action_sender_port: 5555
action_receiver_port: 5556
state_sender_port: 5557
state_receiver_port: 5558
clock_sync_port: 5559
actuator_port_arm: /dev/ttyUSB0
camera_port_wrist_camera: '0'
Every other file in conf/ reads these through ${deployment.*}, so moving a robot between machines means editing one file rather than duplicating the directory:
# conf/robot.yaml
connection: ${deployment.connection}
# conf/modules/arm.yaml
actuator_port: ${deployment.actuator_port_arm}
# conf/modules/wrist_camera.yaml
camera_port: ${deployment.camera_port_wrist_camera}
This works in all three kinds of config file — command, module and skill — even though only command configs go through Hydra composition.
Selecting a deployment
Add conf/deployment/pi.yaml with the Pi's device paths, then pick it per run or once per machine:
solarian run-robot --deployment pi # or -d pi
export SOLARIAN_DEPLOYMENT=pi # e.g. in the Pi's shell profile
The default is localhost. A name with no matching file is an error listing the available deployments.
Extending a deployment
A deployment can name another with extends: and set only what differs; its keys are merged over the other's, recursively:
# conf/deployment/solarian_base.yaml
extends: turing
bbox_tracker_url: https://acme--sb-1a2b3c4d5e6f-bbox-tracker-url.modal.run
The connection file Solarian Base writes works this way: it extends the profile the robot already uses with Base's relay and the URLs of the project's endpoints.
Server URLs and tokens
A conf that calls a server reads its URL from a deployment key, so one profile can point the project at servers on the local network and another at hosted ones:
# conf/services/bbox_tracker.yaml
server_url: ${deployment.bbox_tracker_url}
token: ${oc.select:deployment.solarian_base_endpoint_token,null}
server:
service: sam2-tracker
port: 8765
# conf/skills/umi_target.yaml
policy_server_url: ${deployment.umi_target_policy_server_url}
policy_server_token: ${oc.select:deployment.solarian_base_endpoint_token,null}
server:
service: diffusion-policy
checkpoint: ${oc.select:deployment.umi_target_policy_checkpoint,null}
port: 8003
${oc.select:deployment.KEY,null} falls back to null when the deployment does not set the key, here the bearer token only hosted endpoints require. The server: block says what solarian run-service and Solarian Base run for the conf; see Services.
A deployment describes the whole topology, not a single machine. If the robot runs on a Pi while you teleop from a laptop, one deployment file holds the Pi's paths for the robot's modules and the laptop's paths for the controller's modules. See Server for the full topology.
Hydra defaults composition
Hydra's defaults list determines the order in which configs are merged. record.yaml builds on the controller config this way:
defaults:
- controller # merge conf/controller.yaml
- _self_ # then apply the keys defined inline below
_self_ puts the inline keys last, so they override anything pulled in from groups.
Command-line overrides
Anything in the composed config can be overridden on the command line, no quoting required for top-level keys:
solarian run-robot fps=60
solarian record num_episodes=5 episode_time_s=15
Deployment values are not part of the composed config, so change the relay address by editing conf/deployment/<name>.yaml or pointing at another deployment with --deployment.
Overrides also work for +adding keys not present in the base config:
solarian train +precision.amp_enabled=true
These are forwarded straight to Hydra's compose(overrides=...).
Picking a different config file
The --conf flag chooses the config directory and (optionally) the config name within it:
# Use a different conf directory
solarian run-robot --conf conf_experiment
# Use a different config name (conf/robot_v2.yaml) in the default directory
solarian run-robot --conf conf/robot_v2
To run the same configs against different hardware, reach for --deployment rather than a second conf/ directory.
The CLI splits dir/name so the second form maps to conf/robot_v2.yaml.
Custom command configs
A custom CLI command registered via CommandRegistry.register_lazy(..., config_name="commands/my_command") automatically picks up conf/commands/my_command.yaml. If the command's get_config_class() returns a Pydantic model, the loaded config is validated against it. See CLI / Authoring a custom command.
See also
- Project Settings — the Python-side registry that complements the YAML side.
- Modules — the consumer of
conf/<module_name>.yaml. - Server — the connection profile used by all three nodes.
- CLI — the command/config-file mapping.