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
Successwhen all children succeed - Returns
Failureimmediately when a child fails - Returns
Runningwhen a child returnsRunning
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
Successimmediately when a child succeeds - Returns
Failurewhen all children fail - Returns
Runningwhen a child returnsRunning
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
Successwhen all children succeed - Returns
Failurewhen any child fails - Returns
Runningwhile any child is running
RequireOne Policy:
- Ticks all children concurrently
- Returns
Successwhen any child succeeds - Returns
Failurewhen all children fail - Returns
Runningwhile 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: ReturnsSuccess - On
Failure: Resets child, increments counter, retries - On max attempts reached: Returns
Failure - On
Running: ReturnsRunning
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
Success→FailureFailure→SuccessRunning→Running(unchanged)Skipped→Skipped(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
Runninguntil count reached (or forever if infinite) - Returns
Successwhen 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: ReturnsSuccess - On
Failure:- Reads task from blackboard[task_key]
- Asks LLM to generate recovery plan
- Parses and loads recovery subtree
- Executes recovery subtree
- Increments replan counter
- On max replans reached: Returns
Failure - On
Running: ReturnsRunning
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
Successimmediately - 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
Failureif 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
Successif key exists and equals expected value - Returns
Failureif 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
Successif plan generated - Returns
Failureon 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
Failureif 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 Type | Children | Side Effects | LLM Required | Use Case |
|---|---|---|---|---|
| Sequence | Multiple | Via children | No | Sequential steps |
| Selector | Multiple | Via children | No | Fallback logic |
| Parallel | Multiple | Via children | No | Concurrent tasks |
| Retry | Single | Via child | No | Retry on failure |
| Timeout | Single | Via child | No | Time-bounded execution |
| Inverter | Single | Via child | No | Logic inversion |
| Repeat | Single | Via child | No | Loop behavior |
| ReplanOnFailure | Single | Via child + LLM | Yes | Adaptive recovery |
| SetBlackboard | None | Sets blackboard | No | State updates |
| ToolAction | None | Executes tool | No | External actions |
| CheckBlackboard | None | None (read-only) | No | Preconditions |
| LLMPlannerNode | None | Sets blackboard | Yes | Dynamic planning |
| SubtreeLoader | Dynamic | Via loaded tree | No | Execute 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
- LLM Integration - Deep dive into LLM-powered nodes
- Runtime Execution - Executor configuration
- Examples - Working code examples
- API Reference - Complete API docs
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.