Task configuration

The task block declaratively defines a complete task flow — a sequence of actions executed in order, validated by a set of goal conditions at the end. TaskManager converts the config into an executable Task instance at runtime.

Source: fastsim/task/task_cfg.py (TaskConfig), fastsim/task/task.py (Task)

Execution semantics

text
Task.execute()
  │
  ├─ for action in actions:
  │     action.act()                        ← run the action
  │     action.goal.is_satisfied()          ← check inline goal
  │     [failure?] → retry mechanism / interactive debug ← see RetryConfig
  │
  └─ for goal in goals:
        goal.is_satisfied()                 ← final acceptance

All actions are executed in list order. Each action may carry an inline goal_config validated immediately after the action runs; task.goals are the final acceptance conditions evaluated after all actions complete.

TaskConfig fields

FieldTypeDefaultDescription
namestrTask name
descriptionstrTask description
actionslist[ActionConfig][]Action list, executed in order
goalslist[GoalConfig][]Final goal list; all must pass for task success
retry_configRetryConfignullGlobal retry config applied to all actions (each action can override with its own retry_config)

ActionConfig fields

Each action uses its stereotype to bind to a concrete behavior (e.g. pick, place, move, navigate), instantiated and executed by ActionController.execute_action().

Source: fastsim/extensions/common/action/action.py

FieldTypeDefaultDescription
stereotypestrAction type (e.g. pick / place / move / grasp / navigate)
namestrAction name
descriptionstrAction description
robot_namestrRobot instance name
goal_configGoalConfignullInline validation goal after action (optional)
visualization_configVisualizationConfigVisualization toggles (planner path, EE pose, etc.)
wait_robot_stoppedboolfalseWait for robot to stop before executing
wait_robot_stopped_timeoutfloat3.0Wait-for-stop timeout (seconds)
save_snapshotboolfalseSave scene snapshot before action
snapshot_dirstrnullSnapshot output directory
retry_configRetryConfignullPer-action retry config (overrides task-level)
extra_parametersdict{}Stereotype-specific extra params (interpreted by the Action subclass)

GoalConfig fields

GoalConfig is typed by stereotype, e.g. pose (EE pose reached), on_top (object stacked), inside (object inside container), grasped (grasp success).

Source: fastsim/extensions/common/goal/goal.py

Common fields (shared by all goals)

FieldTypeDefaultRequiredDescription
stereotypestryesGoal type — must be one of pose / on_top / inside / grasped / articulated_joint_state
namestr"no_name_provided"yesGoal name (shown in success / failure logs — pick descriptive names for easy diagnosis)
descriptionstr"no_description_provided"yesFree-form description
extra_parametersdict{}noUser-defined extra params; not consumed by built-in goals

Three places goals can be used

SlotCardinalityWhen checkedPurpose
action.goal_configsingle, optionalimmediately after that action's execute()Per-step validation; failure can trigger retry
task.goalslist, optionalafter all actions completeFinal task success criterion
benchmark.goalslist, per episodeend of each benchmark episodeHeadless evaluation pass/fail

Failure handling: After each action runs, the system first checks action.action_fail (execution exception); if execution succeeded, it checks action.goal_config. Both kinds of failure feed into the RetryConfig system — distinguish them via RetryRule.condition (execution_failed vs goal_not_satisfied).

Unit conventions

  • Distances / position tolerances: meters
  • Rotation angles / rotation tolerances: radians (NOT degrees — common bug)
  • Quaternions: wxyz order (NOT xyzw — common bug)
  • Articulated joint positions: native joint unit (revolute = radians, prismatic = meters)

PoseGoal — pose target (stereotype: pose)

The most general goal: checks whether two poses are within tolerance. A "pose" can be a robot end-effector, a scene entity, or an inline literal pose.

Source: fastsim/extensions/common/goal/goals/pose_goal.py

FieldTypeDefaultDescription
position_tolerancefloatnullPosition tolerance in meters; null skips position check (must be ≥ 0)
rotation_tolerancefloatnullRotation tolerance in radians; null skips rotation check (must be ≥ 0)
position_offsetlist[3][0, 0, 0]Offset applied to pose B in B's local frame
rotation_offsetlist[4][1, 0, 0, 0]Quaternion offset (wxyz) applied to B
pose_A_sourceenum"ee"Pose A source: ee / spawnable / pose
pose_A_paramsdict{}Pose A lookup parameters (depends on source)
pose_B_sourceenum"spawnable"Pose B source
pose_B_paramsdict{}Pose B lookup parameters

pose_*_params schema by source:

sourcerequired paramsoptional paramsNotes
eerobot_name: strarm_name: strEnd-effector pose; specify arm_name for multi-arm ModularRobot; frame is forced to world
spawnablespawnable_name: strScene entity pose; mobile robots are auto-handled via get_robot_navigation_pose
posedepends on typetype field (quaternion / rotation_matrix / homogeneous_matrix / euler_xyz / rot6d)Literal pose, parsed by Pose.from_data

Logic:

  1. Resolve pose A and pose B (both in world frame)
  2. Build offset pose from position_offset + rotation_offset (treated as relative to B's local frame)
  3. Compute offset_pose_B = pose_B * offset_pose
  4. Position check: Euclidean distance |pose_A.position - offset_pose_B.position| ≤ position_tolerance
  5. Rotation check: geodesic angle error ≤ rotation_tolerance
  6. If both tolerances are null, the goal is always satisfied (only info log printed) — easy mistake

Example 1: EE reaches door handle

yaml
goals:
  - stereotype: pose
    name: ee_at_handle
    description: EE within 1cm and 5deg of door handle (5cm above)
    position_tolerance: 0.01      # 1cm
    rotation_tolerance: 0.0872    # ~5° in radians
    position_offset: [0.0, 0.0, 0.05]    # 5cm above handle in handle's local frame
    rotation_offset: [1.0, 0.0, 0.0, 0.0]
    pose_A_source: ee
    pose_A_params:
      robot_name: panda
      # arm_name: left_arm     # for multi-arm ModularRobot
    pose_B_source: spawnable
    pose_B_params:
      spawnable_name: door_handle

Example 2: Object reaches a literal world pose

yaml
  - stereotype: pose
    name: cube_at_target
    position_tolerance: 0.02
    rotation_tolerance: null      # skip rotation check
    pose_A_source: spawnable
    pose_A_params:
      spawnable_name: red_cube
    pose_B_source: pose
    pose_B_params:
      type: quaternion
      position: [0.5, 0.2, 0.1]
      rotation: [1.0, 0.0, 0.0, 0.0]

Pitfalls:

  • position_offset is in B's local frame, so it rotates with B. For world-frame offsets, use a fixed-pose B or a literal pose
  • rotation_tolerance is in radians, not degrees
  • rotation_offset default [1, 0, 0, 0] is the identity quaternion (wxyz)
  • If both tolerances are null → goal is always true; double-check your YAML

OnTopGoal — stack target (stereotype: on_top)

Checks whether object A is stacked on top of object B. Uses AABB bounding boxes for vertical + horizontal validation.

Source: fastsim/extensions/common/goal/goals/on_top_goal.py

FieldTypeDefaultDescription
object_A_namestrRequired. Object expected on top
object_B_namestrRequired. Reference object below
max_horizontal_offsetfloat0.01Horizontal center-to-center distance tolerance (m); only used when horizontal_check_mode: center_distance
max_vertical_offsetfloat0.02Vertical gap tolerance (m) = A bottom - B top
horizontal_check_modeenum"inside_xy"Horizontal check mode: center_distance / inside_xy

horizontal_check_mode details:

  • inside_xy — A's center (XY) must fall within B's XY bbox (default; suits "cup on plate" with a large surface); max_horizontal_offset is ignored
  • center_distance — XY distance between A and B centers ≤ max_horizontal_offset (suits "small block centered on big block")

Logic:

  1. Fetch world AABBs of both objects
  2. delta_z = min(corners_A.z) - max(corners_B.z); fail if > max_vertical_offset (A floating too high)
  3. delta_z < 0 (A interpenetrating B) does NOT fail
  4. Horizontal check per the mode

Example:

yaml
  - stereotype: on_top
    name: cup_on_plate
    object_A_name: red_cup
    object_B_name: dinner_plate
    max_vertical_offset: 0.02         # cup may float up to 2cm
    horizontal_check_mode: inside_xy  # cup center must be inside plate

  - stereotype: on_top
    name: small_block_centered
    object_A_name: small_cube
    object_B_name: big_cube
    max_vertical_offset: 0.005
    max_horizontal_offset: 0.01       # ≤ 1cm center offset
    horizontal_check_mode: center_distance

Pitfalls:

  • Bounding boxes are world-axis-aligned (AABB) — tilted objects produce inflated AABBs
  • inside_xy only checks A's center — half of A hanging off the edge still passes
  • No lower bound on delta_z — physically clipping objects pass

InsideGoal — containment target (stereotype: inside)

Checks whether object A is fully inside object B's AABB (strict container constraint).

Source: fastsim/extensions/common/goal/goals/inside_goal.py

FieldTypeDefaultDescription
object_A_namestrRequired. Object expected inside
object_B_namestrRequired. Container
max_offsetfloat0.0Single-axis poke-out tolerance (m); default = strict containment

Logic: All 8 corners of A's AABB must lie within B's AABB (each axis expanded by max_offset). Any corner exceeding any axis fails.

Example:

yaml
  - stereotype: inside
    name: ball_in_box
    object_A_name: tennis_ball
    object_B_name: cardboard_box
    max_offset: 0.005   # allow 5mm protrusion per axis

Pitfalls:

  • Strict AABB containment — a pencil sticking out of a cup fails
  • Rotated containers have inflated AABBs, possibly causing unexpected passes
  • max_offset = 0 is sensitive to float jitter; leave a few mm slack for stability

GraspedGoal — grasp success target (stereotype: grasped)

Heuristically checks grasp success via EE-to-object-center distance (not contact-based).

Source: fastsim/extensions/common/goal/goals/grasped_goal.py

FieldTypeDefaultDescription
robot_namestrRequired. Robot expected to be holding the object
arm_namestrnullRequired for multi-arm ModularRobot; omit for single-arm
object_namestrRequired. Object expected to be grasped
max_distancefloat0.08Max EE-to-object-center distance in meters; must be > 0

Logic: |EE_world_pos - object_world_pos| ≤ max_distance → grasped.

Example:

yaml
  - stereotype: grasped
    name: hold_apple
    robot_name: panda
    object_name: red_apple
    max_distance: 0.08

  # multi-arm modular robot
  - stereotype: grasped
    name: left_hand_holds_cup
    robot_name: dual_arm_robot
    arm_name: left_arm
    object_name: white_cup
    max_distance: 0.06

Pitfalls:

  • Distance-based heuristic — doesn't read gripper state or contacts; an object falling near the EE still "passes"
  • max_distance default 8cm is generous; tighten for small objects, loosen for bulky ones
  • Distance is EE origin ↔ object center — long objects held at one end may have far-away centers
  • max_distance must be strictly > 0

ArticulatedJointStateGoal — articulated joint state (stereotype: articulated_joint_state, NEW)

Checks whether specific joints of an articulated object reached target positions. Useful for "is the door open 90°?" or "is the drawer pulled out 20cm?".

Source: fastsim/extensions/common/goal/goals/articulated_joint_state_goal.py

FieldTypeDefaultDescription
object_namestrRequired. Articulated object name
joint_positionsdict[str, float]{}Required and non-empty. Joint name → target position
tolerancefloat0.001Per-joint absolute tolerance (joint native unit); must be ≥ 0

Logic:

  1. Query the listed joints' current positions on object_name
  2. Any joint where |actual - target| > tolerance causes failure
  3. Failure message lists each violator (actual / target / error / tolerance)

Example:

yaml
  # single joint: cabinet door fully open
  - stereotype: articulated_joint_state
    name: door_open
    object_name: cabinet_left
    joint_positions:
      left_door_hinge: 1.5708   # 90° = π/2 radians
    tolerance: 0.05             # ~3° slack

  # multi-joint: drawer + door
  - stereotype: articulated_joint_state
    name: cabinet_fully_configured
    object_name: kitchen_cabinet
    joint_positions:
      drawer_slide: 0.20        # prismatic, meters
      door_hinge:   0.0         # revolute, radians (closed)
    tolerance: 0.01

Pitfalls:

  • joint_positions must be non-empty — empty dict raises a validation error
  • Joint names must be exact USD/URDF names; misspelling triggers a length-mismatch failure
  • Units are joint-native: revolute = radians, prismatic = meters
  • tolerance is a single scalar for all joints; for per-joint tolerances split into multiple goals
  • Only works on articulated spawnables; rigid objects fail at the controller layer

Full goal example in action + task

yaml
task:
  name: pick_and_place
  retry_config:
    rules:
      - condition: all
        retry_times: 2

  actions:
    - stereotype: pick
      name: pick_cube
      robot_name: arm
      arm_name: main_arm
      # action-level goal_config: single goal, checked immediately after pick
      # failure can trigger retry per RetryConfig rules
      goal_config:
        stereotype: grasped
        name: grasp_check
        robot_name: arm
        object_name: red_cube
        max_distance: 0.05

    - stereotype: place
      name: place_cube
      robot_name: arm
      arm_name: main_arm
      goal_config:
        stereotype: pose
        name: ee_at_drop
        position_tolerance: 0.02
        rotation_tolerance: null
        pose_A_source: ee
        pose_A_params: { robot_name: arm, arm_name: main_arm }
        pose_B_source: spawnable
        pose_B_params: { spawnable_name: tray }

  # task-level goals: list, checked sequentially after all actions; all must pass for task success
  goals:
    - stereotype: on_top
      name: cube_on_tray
      object_A_name: red_cube
      object_B_name: tray
      max_vertical_offset: 0.02
      horizontal_check_mode: inside_xy

Retry mechanism (RetryConfig)

FastSim's task system has a built-in automatic retry mechanism. When an action fails or its goal check fails, the system can automatically jump back to a specified action and re-execute, without manual intervention.

RetryConfig fields

FieldTypeDefaultDescription
ruleslist[RetryRule][]Retry rules evaluated top-to-bottom; first match wins. Empty = no retry

RetryRule fields

FieldTypeDefaultDescription
conditionenumallFailure type triggering this rule: all / goal_not_satisfied / execution_failed
reason_patternstrnullSubstring match against failure message (e.g. distinguish IK_FAIL vs COLLISION); null = match any
retry_timesint0Max retry count for this rule
retry_fromstrnullAction name to jump back to; null = retry self
rollback_stateboolfalseRestore the snapshot taken before the retry-target action

Retry execution flow

text
action runs
    │
    ├─ success → continue to next action
    │
    └─ failure
        │
        ├─ Walk RetryConfig.rules top-to-bottom; first rule matching
        │   condition + reason_pattern wins.
        │
        ├─ retry_count < rule.retry_times?
        │   ├─ no  → task fails ("after N retries")
        │   └─ yes → execute retry
        │
        └─ Retry execution
            ├─ Compute jump target (rule.retry_from or current action)
            ├─ Reset failure flags within the jump-back range
            ├─ If rollback_state, restore the snapshot saved at jump target
            ├─ Truncate execution result records back to the jump point
            └─ Resume execution from the jump point

Retry context (RetryContext)

While a retry is active, each action's execution result carries a RetryContext (observable via task_observer):

FieldTypeDescription
attemptintCurrent retry attempt (1-indexed)
max_retriesintMax retry count
triggered_bystrName of the action whose failure triggered the retry
triggered_by_reasonstrFailure reason text
retry_fromstrAction name jumped back to
rollback_stateboolWhether snapshot rollback was performed

Retry config example

yaml
task:
  name: pick_and_place_with_retry
  retry_config:
    rules:
      - condition: all
        retry_times: 2

  actions:
    - stereotype: release
      name: open_gripper
      robot_name: arm
      arm_name: main_arm

    - stereotype: pick
      name: pick_cube
      robot_name: arm
      arm_name: main_arm
      retry_config:
        rules:
          - condition: goal_not_satisfied
            retry_from: open_gripper       # jump back to open_gripper
            retry_times: 3
            rollback_state: true           # restore physics state before retry
      goal_config:
        stereotype: grasped
        name: grasp_check
        robot_name: arm
        object_name: red_cube

    - stereotype: place
      name: place_cube
      robot_name: arm
      arm_name: main_arm

Full example

yaml
task:
  name: pick_and_place
  description: Pick the red cube and place it on the tray
  retry_config:
    rules:
      - condition: all
        retry_times: 2

  actions:
    - stereotype: pick
      name: pick_cube
      description: Pick up the red cube
      robot_name: arm
      arm_name: main_arm
      wait_robot_stopped: true
      goal_config:
        stereotype: grasped
        name: grasp_check
        robot_name: arm
        object_name: cube
      visualization_config:
        enable_target_bounding_box_visualization: true

    - stereotype: place
      name: place_cube_on_tray
      description: Place the cube onto the tray
      robot_name: arm
      arm_name: main_arm

  goals:
    - stereotype: on_top
      name: cube_on_tray
      description: The cube is on the tray
      object_A_name: cube
      object_B_name: tray
      max_horizontal_offset: 0.01
      max_vertical_offset: 0.02