Skip to content

Behavior Trees

Hierarchical, reactive control for complex missions using behavior trees.

What is a Behavior Tree?

A behavior tree is a hierarchical structure for organizing complex behaviors:

Mission Root
├─ Sequence
│  ├─ CheckBattery
│  ├─ Submerge
│  ├─ NavigateToArea
│  └─ ExecuteSurvey
└─ Fallback
   └─ ReturnHome

Node Types

Composite Nodes

Sequence: Execute children in order (AND logic) - Succeeds if all children succeed - Fails if any child fails

Selector (Fallback): Try children until one succeeds (OR logic) - Succeeds if any child succeeds - Fails if all children fail

Parallel: Execute children simultaneously - Various success/failure policies

Decorator Nodes

Inverter: Flip success/failure

Repeat: Repeat child N times or until failure

Timeout: Fail if child exceeds time limit

Retry: Retry child on failure

Leaf Nodes (Actions)

Actual behaviors executed by vehicle: - GoToWaypoint - FollowPath - TakePhoto - Hover - etc.

Example: Pipeline Survey

from aqua.bt import Sequence, Fallback, Action, Repeat

survey_mission = Sequence([
    Action('CheckSystems'),
    Action('Submerge', depth=-5),
    Action('GoToWaypoint', target=[50, 0, -5]),
    Repeat(10, 
        Sequence([
            Action('FollowPath', path=pipeline_path),
            Action('TakePhoto'),
            Action('AdvanceAlongPath', distance=10)
        ])
    ),
    Fallback([
        Action('ReturnToStart'),
        Action('EmergencySurface')
    ])
])

Creating Custom Behaviors

from aqua.bt import Behavior, Status

class InspectObject(Behavior):
    def __init__(self, object_id):
        self.object_id = object_id

    def initialize(self):
        # Setup
        self.photos_taken = 0

    def update(self):
        # Execute behavior
        if self.photos_taken < 4:
            self.take_photo_at_angle(self.photos_taken * 90)
            self.photos_taken += 1
            return Status.RUNNING
        else:
            return Status.SUCCESS

    def terminate(self, status):
        # Cleanup
        pass

Reactive Behaviors

Behavior trees are inherently reactive:

patrol_tree = Sequence([
    Repeat(INFINITE,
        Fallback([
            # Check for threats
            Sequence([
                Condition('ThreatDetected'),
                Action('Investigate')
            ]),
            # Normal patrol
            Action('FollowPatrolRoute')
        ])
    )
])

Blackboard

Shared data structure for communication between behaviors:

blackboard = {
    'battery_level': 85,
    'current_waypoint': 3,
    'threats_detected': [],
    'mission_status': 'active'
}

# Behaviors read/write to blackboard
def check_battery(blackboard):
    if blackboard['battery_level'] < 20:
        return Status.FAILURE
    return Status.SUCCESS

Visualization

Behavior trees can be visualized in RViz or custom tools:

aqua-bt-visualizer --mission survey_mission.xml

XML Format

<BehaviorTree ID="SurveyMission">
  <Sequence>
    <Action ID="CheckSystems"/>
    <Action ID="Submerge" depth="-5"/>
    <Repeat num_cycles="10">
      <Sequence>
        <Action ID="FollowPath" path="pipeline"/>
        <Action ID="TakePhoto"/>
      </Sequence>
    </Repeat>
    <Action ID="Surface"/>
  </Sequence>
</BehaviorTree>

Best Practices

✓ Keep behaviors small and focused ✓ Use decorators for retries and timeouts ✓ Design for reactivity ✓ Test individual behaviors first ✓ Use blackboard for shared state

Next: Dynamic Replanning