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
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
| Field | Type | Default | Description |
|---|---|---|---|
name | str | — | Task name |
description | str | — | Task description |
actions | list[ActionConfig] | [] | Action list, executed in order |
goals | list[GoalConfig] | [] | Final goal list; all must pass for task success |
retry_config | RetryConfig | null | Global 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
| Field | Type | Default | Description |
|---|---|---|---|
stereotype | str | — | Action type (e.g. pick / place / move / grasp / navigate) |
name | str | — | Action name |
description | str | — | Action description |
robot_name | str | — | Robot instance name |
goal_config | GoalConfig | null | Inline validation goal after action (optional) |
visualization_config | VisualizationConfig | — | Visualization toggles (planner path, EE pose, etc.) |
wait_robot_stopped | bool | false | Wait for robot to stop before executing |
wait_robot_stopped_timeout | float | 3.0 | Wait-for-stop timeout (seconds) |
save_snapshot | bool | false | Save scene snapshot before action |
snapshot_dir | str | null | Snapshot output directory |
retry_config | RetryConfig | null | Per-action retry config (overrides task-level) |
extra_parameters | dict | {} | 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)
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
stereotype | str | — | yes | Goal type — must be one of pose / on_top / inside / grasped / articulated_joint_state |
name | str | "no_name_provided" | yes | Goal name (shown in success / failure logs — pick descriptive names for easy diagnosis) |
description | str | "no_description_provided" | yes | Free-form description |
extra_parameters | dict | {} | no | User-defined extra params; not consumed by built-in goals |
Three places goals can be used
| Slot | Cardinality | When checked | Purpose |
|---|---|---|---|
action.goal_config | single, optional | immediately after that action's execute() | Per-step validation; failure can trigger retry |
task.goals | list, optional | after all actions complete | Final task success criterion |
benchmark.goals | list, per episode | end of each benchmark episode | Headless 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
| Field | Type | Default | Description |
|---|---|---|---|
position_tolerance | float | null | Position tolerance in meters; null skips position check (must be ≥ 0) |
rotation_tolerance | float | null | Rotation tolerance in radians; null skips rotation check (must be ≥ 0) |
position_offset | list[3] | [0, 0, 0] | Offset applied to pose B in B's local frame |
rotation_offset | list[4] | [1, 0, 0, 0] | Quaternion offset (wxyz) applied to B |
pose_A_source | enum | "ee" | Pose A source: ee / spawnable / pose |
pose_A_params | dict | {} | Pose A lookup parameters (depends on source) |
pose_B_source | enum | "spawnable" | Pose B source |
pose_B_params | dict | {} | Pose B lookup parameters |
pose_*_params schema by source:
| source | required params | optional params | Notes |
|---|---|---|---|
ee | robot_name: str | arm_name: str | End-effector pose; specify arm_name for multi-arm ModularRobot; frame is forced to world |
spawnable | spawnable_name: str | — | Scene entity pose; mobile robots are auto-handled via get_robot_navigation_pose |
pose | depends on type | type field (quaternion / rotation_matrix / homogeneous_matrix / euler_xyz / rot6d) | Literal pose, parsed by Pose.from_data |
Logic:
- Resolve pose A and pose B (both in world frame)
- Build offset pose from
position_offset+rotation_offset(treated as relative to B's local frame) - Compute
offset_pose_B = pose_B * offset_pose - Position check: Euclidean distance
|pose_A.position - offset_pose_B.position| ≤ position_tolerance - Rotation check: geodesic angle error
≤ rotation_tolerance - If both tolerances are
null, the goal is always satisfied (only info log printed) — easy mistake
Example 1: EE reaches door handle
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
- 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_offsetis in B's local frame, so it rotates with B. For world-frame offsets, use a fixed-pose B or a literalposerotation_toleranceis in radians, not degreesrotation_offsetdefault[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
| Field | Type | Default | Description |
|---|---|---|---|
object_A_name | str | — | Required. Object expected on top |
object_B_name | str | — | Required. Reference object below |
max_horizontal_offset | float | 0.01 | Horizontal center-to-center distance tolerance (m); only used when horizontal_check_mode: center_distance |
max_vertical_offset | float | 0.02 | Vertical gap tolerance (m) = A bottom - B top |
horizontal_check_mode | enum | "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_offsetis ignoredcenter_distance— XY distance between A and B centers ≤max_horizontal_offset(suits "small block centered on big block")
Logic:
- Fetch world AABBs of both objects
delta_z = min(corners_A.z) - max(corners_B.z); fail if >max_vertical_offset(A floating too high)delta_z < 0(A interpenetrating B) does NOT fail- Horizontal check per the mode
Example:
- 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_xyonly 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
| Field | Type | Default | Description |
|---|---|---|---|
object_A_name | str | — | Required. Object expected inside |
object_B_name | str | — | Required. Container |
max_offset | float | 0.0 | Single-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:
- 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 = 0is 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
| Field | Type | Default | Description |
|---|---|---|---|
robot_name | str | — | Required. Robot expected to be holding the object |
arm_name | str | null | Required for multi-arm ModularRobot; omit for single-arm |
object_name | str | — | Required. Object expected to be grasped |
max_distance | float | 0.08 | Max EE-to-object-center distance in meters; must be > 0 |
Logic: |EE_world_pos - object_world_pos| ≤ max_distance → grasped.
Example:
- 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_distancedefault 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_distancemust 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
| Field | Type | Default | Description |
|---|---|---|---|
object_name | str | — | Required. Articulated object name |
joint_positions | dict[str, float] | {} | Required and non-empty. Joint name → target position |
tolerance | float | 0.001 | Per-joint absolute tolerance (joint native unit); must be ≥ 0 |
Logic:
- Query the listed joints' current positions on
object_name - Any joint where
|actual - target| > tolerancecauses failure - Failure message lists each violator (actual / target / error / tolerance)
Example:
# 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_positionsmust 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
toleranceis 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
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
| Field | Type | Default | Description |
|---|---|---|---|
rules | list[RetryRule] | [] | Retry rules evaluated top-to-bottom; first match wins. Empty = no retry |
RetryRule fields
| Field | Type | Default | Description |
|---|---|---|---|
condition | enum | all | Failure type triggering this rule: all / goal_not_satisfied / execution_failed |
reason_pattern | str | null | Substring match against failure message (e.g. distinguish IK_FAIL vs COLLISION); null = match any |
retry_times | int | 0 | Max retry count for this rule |
retry_from | str | null | Action name to jump back to; null = retry self |
rollback_state | bool | false | Restore the snapshot taken before the retry-target action |
Retry execution flow
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):
| Field | Type | Description |
|---|---|---|
attempt | int | Current retry attempt (1-indexed) |
max_retries | int | Max retry count |
triggered_by | str | Name of the action whose failure triggered the retry |
triggered_by_reason | str | Failure reason text |
retry_from | str | Action name jumped back to |
rollback_state | bool | Whether snapshot rollback was performed |
Retry config example
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
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