🚀Transform your business with AI-powered process optimization
Architecture
💡 Design Patterns
Agent Design Patterns
Behavior Tree Pattern

Behavior Tree Pattern

The Behavior Tree Pattern organizes AI agent behaviors in a hierarchical tree structure, enabling complex decision-making through composable behavior nodes.

Pattern Overview

This pattern structures agent behavior as a tree where leaf nodes represent actions or conditions, and internal nodes represent control flow logic, creating modular and reusable behavior components.

Structure

// Node execution results
#[derive(Debug, Clone, PartialEq)]
pub enum NodeResult {
    Success,
    Failure,
    Running,
}
 
// Context for behavior execution
pub struct BehaviorContext {
    pub agent_id: String,
    pub variables: std::collections::HashMap<String, f64>,
}
 
// Base behavior node trait
pub trait BehaviorNode {
    fn execute(&self, context: &mut BehaviorContext) -> NodeResult;
    fn reset(&self);
}
 
// Action node - performs actual work
pub struct ActionNode {
    name: String,
    action_fn: fn(&mut BehaviorContext) -> NodeResult,
}
 
impl ActionNode {
    pub fn new(name: &str, action_fn: fn(&mut BehaviorContext) -> NodeResult) -> Self {
        Self {
            name: name.to_string(),
            action_fn,
        }
    }
}
 
impl BehaviorNode for ActionNode {
    fn execute(&self, context: &mut BehaviorContext) -> NodeResult {
        (self.action_fn)(context)
    }
 
    fn reset(&self) {
        // Actions typically don't need reset
    }
}
 
// Condition node - checks state
pub struct ConditionNode {
    name: String,
    condition_fn: fn(&BehaviorContext) -> bool,
}
 
impl ConditionNode {
    pub fn new(name: &str, condition_fn: fn(&BehaviorContext) -> bool) -> Self {
        Self {
            name: name.to_string(),
            condition_fn,
        }
    }
}
 
impl BehaviorNode for ConditionNode {
    fn execute(&self, context: &mut BehaviorContext) -> NodeResult {
        if (self.condition_fn)(context) {
            NodeResult::Success
        } else {
            NodeResult::Failure
        }
    }
 
    fn reset(&self) {
        // Conditions typically don't need reset
    }
}
 
// Sequence node - executes children in order, fails if any fails
pub struct SequenceNode {
    children: Vec<Box<dyn BehaviorNode>>,
    current_child: usize,
}
 
impl SequenceNode {
    pub fn new(children: Vec<Box<dyn BehaviorNode>>) -> Self {
        Self {
            children,
            current_child: 0,
        }
    }
}
 
impl BehaviorNode for SequenceNode {
    fn execute(&self, context: &mut BehaviorContext) -> NodeResult {
        for child in &self.children {
            match child.execute(context) {
                NodeResult::Success => continue,
                NodeResult::Failure => return NodeResult::Failure,
                NodeResult::Running => return NodeResult::Running,
            }
        }
        NodeResult::Success
    }
 
    fn reset(&self) {
        for child in &self.children {
            child.reset();
        }
    }
}
 
// Selector node - tries children until one succeeds
pub struct SelectorNode {
    children: Vec<Box<dyn BehaviorNode>>,
}
 
impl SelectorNode {
    pub fn new(children: Vec<Box<dyn BehaviorNode>>) -> Self {
        Self { children }
    }
}
 
impl BehaviorNode for SelectorNode {
    fn execute(&self, context: &mut BehaviorContext) -> NodeResult {
        for child in &self.children {
            match child.execute(context) {
                NodeResult::Success => return NodeResult::Success,
                NodeResult::Failure => continue,
                NodeResult::Running => return NodeResult::Running,
            }
        }
        NodeResult::Failure
    }
 
    fn reset(&self) {
        for child in &self.children {
            child.reset();
        }
    }
}
 
// Behavior tree agent
pub struct BehaviorTreeAgent {
    id: String,
    root_node: Box<dyn BehaviorNode>,
    context: BehaviorContext,
}
 
impl BehaviorTreeAgent {
    pub fn new(id: &str, root_node: Box<dyn BehaviorNode>) -> Self {
        Self {
            id: id.to_string(),
            root_node,
            context: BehaviorContext {
                agent_id: id.to_string(),
                variables: std::collections::HashMap::new(),
            },
        }
    }
 
    pub fn tick(&mut self) -> NodeResult {
        self.root_node.execute(&mut self.context)
    }
 
    pub fn set_variable(&mut self, key: &str, value: f64) {
        self.context.variables.insert(key.to_string(), value);
    }
 
    pub fn get_variable(&self, key: &str) -> Option<&f64> {
        self.context.variables.get(key)
    }
}
 
// Example behavior functions
fn check_energy_level(context: &BehaviorContext) -> bool {
    context.variables.get("energy").unwrap_or(&0.0) > &50.0
}
 
fn recharge_action(context: &mut BehaviorContext) -> NodeResult {
    println!("Agent {} is recharging", context.agent_id);
    context.variables.insert("energy".to_string(), 100.0);
    NodeResult::Success
}
 
fn work_action(context: &mut BehaviorContext) -> NodeResult {
    let current_energy = context.variables.get("energy").unwrap_or(&0.0);
    if *current_energy > 10.0 {
        context.variables.insert("energy".to_string(), current_energy - 10.0);
        println!("Agent {} is working (energy: {})", context.agent_id, current_energy - 10.0);
        NodeResult::Success
    } else {
        NodeResult::Failure
    }
}

Usage Example

// Build behavior tree: (Check Energy -> Work) OR Recharge
let work_sequence = SequenceNode::new(vec![
    Box::new(ConditionNode::new("check_energy", check_energy_level)),
    Box::new(ActionNode::new("work", work_action)),
]);
 
let root_selector = SelectorNode::new(vec![
    Box::new(work_sequence),
    Box::new(ActionNode::new("recharge", recharge_action)),
]);
 
let mut agent = BehaviorTreeAgent::new("WorkerBot", Box::new(root_selector));
agent.set_variable("energy", 30.0);
 
// Execute behavior tree
for _tick in 0..5 {
    let result = agent.tick();
    println!("Tick result: {:?}", result);
}

Benefits

  • Modularity: Reusable behavior components
  • Clarity: Visual and intuitive behavior representation
  • Flexibility: Easy to modify and extend behaviors
  • Composability: Combine simple behaviors into complex ones

Use Cases

  • Game AI character behavior
  • Robotic task execution
  • Automated decision workflows
  • Interactive agent behaviors