Node Types Reference

Complete reference for all behavior tree node types with examples and use cases.


Composite Nodes

Composite nodes control the flow of execution through multiple children.

Sequence

Executes children in order. All must succeed.

API

pub struct Sequence {
    pub fn new(name: impl Into<String>) -> Self
    pub fn add_child(mut self, child: Box<dyn BTreeNode>) -> Self
}

Behavior

  • Ticks children left to right
  • Returns Success when all children succeed
  • Returns Failure immediately when a child fails
  • Returns Running when a child returns Running

Example

use igris_btree::prelude::*;

let mut sequence = Sequence::new("navigation_sequence")
    .add_child(Box::new(CheckBlackboard::new("check_power", "battery", 50)))
    .add_child(Box::new(SetBlackboard::new("start_nav", "status", "navigating")))
    .add_child(Box::new(ToolAction::new("move", "navigate", params)));

Use Cases

  • Multi-step procedures (setup → execute → cleanup)
  • Precondition chains (check battery → check sensors → start)
  • Sequential state machines
  • Assembly line operations

Tips

  • First child often checks preconditions
  • Last child often updates final state
  • Use Selector for error handling within sequences
  • Keep sequences focused (3-7 children ideal)

Selector

Tries children until one succeeds (fallback logic).

API

pub struct Selector {
    pub fn new(name: impl Into<String>) -> Self
    pub fn add_child(mut self, child: Box<dyn BTreeNode>) -> Self
}

Behavior

  • Ticks children left to right
  • Returns Success immediately when a child succeeds
  • Returns Failure when all children fail
  • Returns Running when a child returns Running

Example

use igris_btree::prelude::*;

let mut selector = Selector::new("navigation_methods")
    .add_child(Box::new(ToolAction::new("gps", "gps_navigate", params)))
    .add_child(Box::new(ToolAction::new("vision", "visual_nav", params)))
    .add_child(Box::new(ToolAction::new("fallback", "dead_reckoning", params)));

Use Cases

  • Fallback logic (try primary, then backup)
  • Priority lists (prefer fast, fall back to accurate)
  • Error recovery chains
  • Strategy selection

Tips

  • Order children by priority (best first)
  • Last child should be a reliable fallback
  • Use for graceful degradation
  • Combine with Retry for robust recovery

Parallel

Executes all children concurrently.

API

pub struct Parallel {
    pub fn new(name: impl Into<String>, policy: ParallelPolicy) -> Self
    pub fn add_child(mut self, child: Box<dyn BTreeNode>) -> Self
}

pub enum ParallelPolicy {
    RequireAll,  // All must succeed
    RequireOne,  // Any success is sufficient
}

Behavior

RequireAll Policy:

  • Ticks all children concurrently
  • Returns Success when all children succeed
  • Returns Failure when any child fails
  • Returns Running while any child is running

RequireOne Policy:

  • Ticks all children concurrently
  • Returns Success when any child succeeds
  • Returns Failure when all children fail
  • Returns Running while at least one is running

Example

use igris_btree::prelude::*;

// Monitor multiple sensors concurrently
let mut parallel = Parallel::new("sensor_monitoring", ParallelPolicy::RequireAll)
    .add_child(Box::new(ToolAction::new("camera", "monitor_camera", params)))
    .add_child(Box::new(ToolAction::new("lidar", "monitor_lidar", params)))
    .add_child(Box::new(ToolAction::new("imu", "monitor_imu", params)));

Use Cases

  • Multi-sensor monitoring (RequireAll)
  • Redundant systems (RequireOne)
  • Concurrent tasks (parallel execution)
  • Real-time monitoring with action

Tips

  • Use RequireAll for critical parallel tasks
  • Use RequireOne for redundant systems
  • Children should be independent (no shared mutable state)
  • Consider resource contention

Decorator Nodes

Decorators wrap a single child and modify its behavior.

Retry

Retries child on failure up to N times.

API

pub struct Retry {
    pub fn new(
        name: impl Into<String>,
        child: Box<dyn BTreeNode>,
        max_attempts: u32
    ) -> Self
}

Behavior

  • Ticks child normally
  • On Success: Returns Success
  • On Failure: Resets child, increments counter, retries
  • On max attempts reached: Returns Failure
  • On Running: Returns Running

Example

use igris_btree::prelude::*;

let retry = Retry::new(
    "retry_connection",
    Box::new(ToolAction::new("connect", "network_connect", params)),
    3  // Try up to 3 times
);

Use Cases

  • Network operations (connection retries)
  • Unreliable sensors (retry readings)
  • Transient failures (temporary glitches)
  • External service calls

Tips

  • Use reasonable max attempts (3-5 typical)
  • Combine with delay for backoff behavior
  • Don't retry on permanent failures
  • Log retry attempts for debugging

Timeout

Fails child if it exceeds time limit.

API

pub struct Timeout {
    pub fn new(
        name: impl Into<String>,
        child: Box<dyn BTreeNode>,
        duration: Duration
    ) -> Self
}

Behavior

  • Starts timer on first tick
  • Ticks child normally
  • If child returns terminal before timeout: Returns child's status
  • If timeout expires: Halts child, returns Failure

Example

use igris_btree::prelude::*;
use std::time::Duration;

let timeout = Timeout::new(
    "timed_navigation",
    Box::new(ToolAction::new("navigate", "nav_to_point", params)),
    Duration::from_secs(30)  // 30 second limit
);

Use Cases

  • Bounded operations (prevent hanging)
  • Real-time constraints (must complete in time)
  • Watchdog timers
  • Network timeouts

Tips

  • Set realistic timeouts (measure actual times)
  • Too short: false negatives
  • Too long: delays failure detection
  • Combine with Retry for robust behavior

Inverter

Inverts Success/Failure results.

API

pub struct Inverter {
    pub fn new(
        name: impl Into<String>,
        child: Box<dyn BTreeNode>
    ) -> Self
}

Behavior

  • SuccessFailure
  • FailureSuccess
  • RunningRunning (unchanged)
  • SkippedSkipped (unchanged)

Example

use igris_btree::prelude::*;

// Succeed if battery is NOT low
let inverter = Inverter::new(
    "battery_not_low",
    Box::new(CheckBlackboard::new("check_low", "battery_low", true))
);

Use Cases

  • Negating conditions ("not ready" → "ready")
  • Failure as success scenarios
  • Logic inversion in sequences
  • Guard clauses

Tips

  • Use sparingly (can confuse logic)
  • Name clearly (prefix with "not_" or "inverse_")
  • Document inverted logic
  • Consider if condition can be checked directly

Repeat

Repeats child N times or infinitely.

API

pub struct Repeat {
    pub fn new(
        name: impl Into<String>,
        child: Box<dyn BTreeNode>,
        count: Option<u32>  // None = infinite
    ) -> Self
}

Behavior

  • Ticks child until terminal
  • Resets child after terminal status
  • Increments counter
  • Returns Running until count reached (or forever if infinite)
  • Returns Success when count complete (finite only)

Example

use igris_btree::prelude::*;

// Patrol 5 times
let repeat_finite = Repeat::new(
    "patrol_5_times",
    Box::new(ToolAction::new("patrol", "patrol_route", params)),
    Some(5)
);

// Monitor forever
let repeat_infinite = Repeat::new(
    "continuous_monitor",
    Box::new(ToolAction::new("monitor", "check_sensors", params)),
    None
);

Use Cases

  • Patrol routes (fixed iterations)
  • Continuous monitoring (infinite)
  • Periodic checks
  • Loop behaviors

Tips

  • Finite: Use for fixed iterations
  • Infinite: Combine with executor max_ticks or deadline
  • Add delay between iterations if needed
  • Consider using Parallel for true continuous monitoring

ReplanOnFailure

LLM-powered automatic replanning when child fails.

API

pub struct ReplanOnFailure {
    pub fn new(
        name: impl Into<String>,
        child: Box<dyn BTreeNode>,
        task_key: impl Into<String>,
        plan_key: impl Into<String>,
        max_replans: u32
    ) -> Self
}

Behavior

  • Ticks child normally
  • On Success: Returns Success
  • On Failure:
    1. Reads task from blackboard[task_key]
    2. Asks LLM to generate recovery plan
    3. Parses and loads recovery subtree
    4. Executes recovery subtree
    5. Increments replan counter
  • On max replans reached: Returns Failure
  • On Running: Returns Running

Example

use igris_btree::prelude::*;

let adaptive = ReplanOnFailure::new(
    "adaptive_navigation",
    Box::new(ToolAction::new("nav", "navigate", params)),
    "navigation_task",  // Task description in blackboard
    "recovery_plan",    // Where to store recovery plan
    3                   // Max 3 replans
);

Use Cases

  • Adaptive recovery (unexpected obstacles)
  • Dynamic problem solving
  • Failure handling without exhaustive pre-planning
  • Novel situation adaptation

Tips

  • Requires LLM provider in context
  • Set reasonable max replans (1-3 typical)
  • Provide clear task descriptions
  • Consider costs of LLM calls
  • Use MockLlmProvider for testing

Action Nodes

Action nodes perform work and return immediate status.

SetBlackboard

Sets a key-value pair in the blackboard.

API

pub struct SetBlackboard {
    pub fn new(
        name: impl Into<String>,
        key: impl Into<String>,
        value: impl Serialize + Send + Sync + 'static
    ) -> Self
}

Behavior

  • Always returns Success immediately
  • Stores value in context.blackboard
  • Value is serialized to JSON internally

Example

use igris_btree::prelude::*;

let set = SetBlackboard::new("init_status", "status", "initializing");

// Different value types
let set_int = SetBlackboard::new("set_count", "count", 42);
let set_bool = SetBlackboard::new("set_ready", "ready", true);
let set_string = SetBlackboard::new("set_task", "task", "Navigate to warehouse");

Use Cases

  • Initialize state
  • Update progress markers
  • Store action results
  • Set configuration values

Tips

  • Use typed constants for keys
  • Document blackboard schema
  • Clean up temporary keys after use
  • Consider using structs for complex data

ToolAction

Executes a tool from the context's tool registry.

API

pub struct ToolAction {
    pub fn new(
        name: impl Into<String>,
        tool_name: impl Into<String>,
        parameters: serde_json::Value
    ) -> Self
}

Behavior

  • Looks up tool in context.tool_registry
  • Executes tool with parameters
  • Returns tool's result status
  • Returns Failure if tool not found

Example

use igris_btree::prelude::*;
use serde_json::json;

let tool = ToolAction::new(
    "navigate_to_warehouse",
    "navigate",
    json!({
        "destination": "warehouse",
        "speed": 1.0,
        "avoid_obstacles": true
    })
);

Use Cases

  • Robot navigation
  • Sensor readings
  • Actuator control
  • External system calls

Tips

  • Requires tool_registry in context
  • Validate parameters before execution
  • Handle tool errors gracefully
  • Use meaningful tool names
  • Document tool interfaces

Condition Nodes

Condition nodes check state without side effects.

CheckBlackboard

Checks if a blackboard key exists and optionally matches a value.

API

pub struct CheckBlackboard {
    pub fn new(
        name: impl Into<String>,
        key: impl Into<String>,
        expected: impl Serialize + Send + Sync + 'static
    ) -> Self
}

Behavior

  • Returns Success if key exists and equals expected value
  • Returns Failure if key doesn't exist or doesn't match
  • No side effects (read-only)

Example

use igris_btree::prelude::*;

// Check if ready flag is true
let check = CheckBlackboard::new("check_ready", "ready", true);

// Check if status is "complete"
let check_status = CheckBlackboard::new("check_status", "status", "complete");

// Check if count is 42
let check_count = CheckBlackboard::new("check_count", "count", 42);

Use Cases

  • Precondition checks
  • Guard clauses
  • State validation
  • Flow control decisions

Tips

  • Use at start of sequences (guard)
  • Combine with Selector for conditional logic
  • Name checks clearly (prefix with "check_")
  • Don't modify blackboard in condition nodes

LLM Nodes

LLM-powered nodes for adaptive behavior.

LLMPlannerNode

Generates a behavior tree plan using LLM.

API

pub struct LLMPlannerNode {
    pub fn new(
        name: impl Into<String>,
        task_key: impl Into<String>,
        output_key: impl Into<String>
    ) -> Self
}

Behavior

  • Reads task description from blackboard[task_key]
  • Constructs prompt for LLM
  • Calls LLM provider (bounded to 5s by igris-rt)
  • Parses LLM response as JSON tree
  • Stores plan in blackboard[output_key]
  • Returns Success if plan generated
  • Returns Failure on LLM error or invalid JSON
  • Retries up to 3 times on failure

Example

use igris_btree::prelude::*;

let planner = LLMPlannerNode::new(
    "mission_planner",
    "mission_description",  // Read task from here
    "execution_plan"        // Write plan here
);

// Requires context with LLM provider
let llm = Arc::new(MockLlmProvider::with_navigation_plan());
let context = BTreeContext::new().with_llm(llm);

Use Cases

  • Dynamic mission planning
  • Task decomposition
  • Adaptive strategy generation
  • Natural language task execution

Tips

  • Requires LLM provider in context
  • Provide clear, specific task descriptions
  • Validate generated plans before execution
  • Use MockLlmProvider for testing
  • Monitor LLM costs
  • Set reasonable timeouts

SubtreeLoader

Loads and executes a dynamically generated subtree.

API

pub struct SubtreeLoader {
    pub fn new(
        name: impl Into<String>,
        plan_key: impl Into<String>
    ) -> Self
}

Behavior

  • Reads JSON plan from blackboard[plan_key]
  • Parses JSON into behavior tree nodes
  • Ticks loaded subtree
  • Returns subtree's status
  • Returns Failure if plan not found or invalid

Example

use igris_btree::prelude::*;

// Use with LLMPlannerNode
let tree = Sequence::new("dynamic_execution")
    .add_child(Box::new(SetBlackboard::new(
        "set_task",
        "task",
        "Navigate to warehouse"
    )))
    .add_child(Box::new(LLMPlannerNode::new(
        "planner",
        "task",
        "plan"
    )))
    .add_child(Box::new(SubtreeLoader::new(
        "executor",
        "plan"
    )));

Use Cases

  • Execute LLM-generated plans
  • Dynamic behavior loading
  • Runtime tree modification
  • Adaptive execution

Tips

  • Always pair with LLMPlannerNode or manual plan creation
  • Validate plan format before loading
  • Handle parse errors gracefully
  • Consider security implications of dynamic code
  • Log loaded trees for debugging

Comparison Table

Node TypeChildrenSide EffectsLLM RequiredUse Case
SequenceMultipleVia childrenNoSequential steps
SelectorMultipleVia childrenNoFallback logic
ParallelMultipleVia childrenNoConcurrent tasks
RetrySingleVia childNoRetry on failure
TimeoutSingleVia childNoTime-bounded execution
InverterSingleVia childNoLogic inversion
RepeatSingleVia childNoLoop behavior
ReplanOnFailureSingleVia child + LLMYesAdaptive recovery
SetBlackboardNoneSets blackboardNoState updates
ToolActionNoneExecutes toolNoExternal actions
CheckBlackboardNoneNone (read-only)NoPreconditions
LLMPlannerNodeNoneSets blackboardYesDynamic planning
SubtreeLoaderDynamicVia loaded treeNoExecute plans

Node Selection Guide

When to Use Each Node

Sequence: Multi-step procedure where all steps must succeed Selector: Try multiple approaches until one works Parallel: Execute multiple tasks at the same time Retry: Temporary failures that might succeed on retry Timeout: Operations that could hang or take too long Inverter: Need opposite of a condition check Repeat: Execute same behavior multiple times or continuously ReplanOnFailure: Unknown failures that need adaptive recovery SetBlackboard: Update shared state ToolAction: Call external tools or systems CheckBlackboard: Guard clauses and preconditions LLMPlannerNode: Generate plans for novel situations SubtreeLoader: Execute dynamically generated behaviors


Next Steps


Summary

Igris BTree provides 13 node types across 5 categories:

  • 3 Composite nodes for control flow
  • 5 Decorator nodes for behavior modification
  • 2 Action nodes for work execution
  • 1 Condition node for state checking
  • 2 LLM nodes for adaptive intelligence

Choose the right nodes for your use case, combine them hierarchically, and leverage the hybrid model for robust, adaptive behaviors.