Native SDK Development •4. Custom Runner

4. Custom Runner

Updated: October 12, 2026

1 Step 1: Copy the passive skeleton

Suppose the runner to be created is named demo_runner

  • Copy the Runner directory: src/runner/passive -> src/runner/demo
  • Copy the parameter class directory: src/data/_param/passive_param -> src/data/_param/demo_param
  • Copy the parameter configuration: assets/config/<robot>/passive/default.yaml -> assets/config/<robot>/demo/default.yaml
  • Quick reference table for key replacement parts:
Replacement Itempassive templatedemo exampleDescription
Parameter class name and default tagPassiveParam("passive")DemoParam("demo")Must be aligned with mode.yaml/param_tag
Parameter type in Runnerstd::shared_ptr<data::PassiveParam>std::shared_ptr<data::DemoParam>Includes the constructor and create<...>() in Enter()
Registration macro stringREGISTER_RUNNER(PassiveRunner, "passive_runner", kMotion)REGISTER_RUNNER(DemoRunner, "demo_runner", kMotion)Must be exactly identical to runner[].name in task_motion
Parameter configuration pathassets/config/<robot>/passive/default.yamlassets/config/<robot>/demo/default.yamlReferenced later in mode.yaml via scope

2 Step 2: Add a Runner in the Task

  1. Add the new tag -> scope in both assets/config/<robot>/mode.yaml (sim and robot)
YAML
- tag: demo
  scope: demo/default.yaml        # Consistent with the newly added parameter configuration path
  1. Add a task entry in task_motion/*.yaml or task_resident/*.yaml and mount it:
    • Assume the added demo_runner is a motion task
YAML
- motion: demo                    # task name
  period: 0.002                   # runner execution frequency
  runner:                         # list of runners required by this task
    - name: demo_runner           # newly added runner, name consistent with **REGISTER_RUNNER**
      enabled: true
      param_tag: sitting          # name consistent with tag in mode.yaml
  manual_transition:              # demo can only switch to motions in this list
    - passive
  key: [LB, X]                    # bind the remote control buttons required to enter this motion

3 Step 3: Extend the Parameter Class

  • Add LOAD_PARAM(...) fields as needed, and complete the read logic in Update().
  • When constraints are needed, you can add Check() (range, dimension, non-empty, etc.).

Example: if your Runner needs to load a model file (such as a policy network)

  1. Add a new field in the parameter YAML (e.g. policy_path):
YAML
policy_file: "demo/policies/policy.mnn"
  1. Add a new read field in the parameter class:
C++
class DemoParam : public BasicParam {
 public:
  DemoParam(std::string_view tag = "demo") : BasicParam(tag) {}

  std::string LOAD_PARAM(policy_file);

  void Update() override { LOAD_PARAM(policy_file); }
};
  1. Load the model in the Runner Enter():
C++
bool DemoRunner::Enter() {
  // Load parameters
  if (!param_tag_.empty()) {
    param_ = data::ParamManager::create<data::DemoParam>(param_tag_);
  }

  // Load model
  model_ = std::make_unique<math::MNNModel>(
      common::PathJoin(common::GlobalPathManager::GetInstance().GetConfigPath(), param_->policy_file));
  return true;
}
  1. Add a member in the Runner header file: std::unique_ptr<math::MNNModel> model_;

This forms the complete chain: YAML configuration -> Param LOAD_PARAM -> Runner Enter() takes effect

4 Step 4: Extend the Runner Logic

  • motion runner additionally maintains SetupContext() / TeardownContext() to manage global parameters
  • Continuously write output in Run() (GetMutableOutput().SetCommand(...))
  • Put the time-consuming reset logic in TryExit, and one-time cleanup/reset in Exit