Quick Start
Build your first hybrid behavior tree in 10 minutes.
Prerequisites
- Rust 1.70+ installed
- Basic understanding of async/await in Rust
- Tokio runtime (added automatically)
Installation
Add igris-btree to your Cargo.toml:
[dependencies]
igris-btree = "0.1"
tokio = { version = "1", features = ["full"] }
anyhow = "1.0"
Your First Behavior Tree
Let's create a simple mission tree that sets some blackboard values in sequence.
Step 1: Create a New Project
cargo new my-btree-app
cd my-btree-app
Step 2: Write the Code
Create src/main.rs:
use igris_btree::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create execution context
let mut context = BTreeContext::new();
// Build a simple sequence
let mut tree = Sequence::new("simple_mission")
.add_child(Box::new(SetBlackboard::new("init", "status", "starting")))
.add_child(Box::new(SetBlackboard::new("work", "task", "processing")))
.add_child(Box::new(SetBlackboard::new("done", "status", "completed")));
// Execute the tree
let status = tree.tick(&mut context).await?;
println!("Tree status: {:?}", status);
println!("Status: {}", context.blackboard.get::<String>("status").await?);
println!("Task: {}", context.blackboard.get::<String>("task").await?);
Ok(())
}
Step 3: Run It
cargo run
Expected output:
Tree status: Success
Status: completed
Task: processing
Congratulations! You just executed your first behavior tree.
Understanding the Code
BTreeContext
The execution context holds shared state:
let mut context = BTreeContext::new();
- Blackboard: Shared key-value storage
- LLM Provider: Optional LLM integration (not used yet)
- Tool Registry: Optional tool execution (not used yet)
Sequence Node
Executes children in order:
let mut tree = Sequence::new("simple_mission")
.add_child(Box::new(/* first child */))
.add_child(Box::new(/* second child */))
.add_child(Box::new(/* third child */));
- If all children succeed → Returns
Success - If any child fails → Returns
Failureand stops - If a child returns
Running→ ReturnsRunning(continues next tick)
SetBlackboard Action
Sets a key-value pair in the blackboard:
SetBlackboard::new("node_name", "key", "value")
- Always returns
Successimmediately - Stores the value in the blackboard for other nodes to read
Adding Conditions
Let's add a condition check:
use igris_btree::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut context = BTreeContext::new();
// Pre-populate blackboard
context.blackboard.set("ready", true).await;
// Build tree with condition
let mut tree = Sequence::new("conditional_mission")
.add_child(Box::new(CheckBlackboard::new("check_ready", "ready", true)))
.add_child(Box::new(SetBlackboard::new("execute", "status", "running")));
let status = tree.tick(&mut context).await?;
println!("Status: {:?}", status); // Success
Ok(())
}
The CheckBlackboard node:
- Returns
Successif key exists and matches expected value - Returns
Failureotherwise
Adding Fallback Logic
Use a Selector for fallback behavior:
use igris_btree::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut context = BTreeContext::new();
// Try primary, fall back to secondary
let mut tree = Selector::new("mission_with_fallback")
.add_child(Box::new(
CheckBlackboard::new("try_primary", "primary_ready", true)
))
.add_child(Box::new(
SetBlackboard::new("use_secondary", "status", "using_fallback")
));
let status = tree.tick(&mut context).await?;
// Primary fails (key doesn't exist), so secondary executes
assert_eq!(status, NodeStatus::Success);
assert_eq!(
context.blackboard.get::<String>("status").await?,
"using_fallback"
);
Ok(())
}
Adding Decorators
Decorators modify child behavior:
Retry Decorator
use igris_btree::prelude::*;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut context = BTreeContext::new();
// Counter to simulate failure then success
let counter = Arc::new(Mutex::new(0));
let counter_clone = counter.clone();
// This would fail first time, succeed second time
// (In real code, use a proper failing node)
let mut tree = Retry::new(
"retry_action",
Box::new(SetBlackboard::new("action", "status", "done")),
3 // Max 3 attempts
);
let status = tree.tick(&mut context).await?;
println!("Status: {:?}", status);
Ok(())
}
Timeout Decorator
use igris_btree::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut context = BTreeContext::new();
let mut tree = Timeout::new(
"timed_action",
Box::new(SetBlackboard::new("action", "status", "done")),
Duration::from_secs(5) // 5 second timeout
);
let status = tree.tick(&mut context).await?;
println!("Status: {:?}", status);
Ok(())
}
Using the Executor
For automatic tick loop management, use BTreeExecutor:
use igris_btree::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create executor with limits
let executor = BTreeExecutor::new()
.with_max_ticks(100) // Max 100 ticks
.with_deadline(Duration::from_secs(30)) // 30 second deadline
.with_tracing(true); // Enable logging
// Build tree
let mut tree = Sequence::new("mission")
.add_child(Box::new(SetBlackboard::new("step1", "phase", "init")))
.add_child(Box::new(SetBlackboard::new("step2", "phase", "execute")))
.add_child(Box::new(SetBlackboard::new("step3", "phase", "complete")));
// Create context
let mut context = BTreeContext::new();
// Execute (automatic tick loop)
let result = executor.execute(&mut tree, &mut context).await?;
println!("Execution completed!");
println!(" Status: {:?}", result.status);
println!(" Ticks: {}", result.tick_count);
println!(" Duration: {:?}", result.duration);
Ok(())
}
The executor handles:
- Automatic tick loop until terminal status
- Max ticks enforcement
- Deadline enforcement
- Execution metadata collection
Adding LLM Intelligence
Now let's add adaptive LLM-powered planning:
use igris_btree::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create mock LLM provider for testing
let llm = Arc::new(MockLlmProvider::with_navigation_plan());
// Create context with LLM
let mut context = BTreeContext::new()
.with_llm(llm);
// Build hybrid tree
let mut tree = Sequence::new("hybrid_mission")
.add_child(Box::new(SetBlackboard::new(
"set_task",
"mission_task",
"Navigate to warehouse"
)))
.add_child(Box::new(LLMPlannerNode::new(
"planner",
"mission_task", // Read task from this blackboard key
"plan" // Write plan to this key
)))
.add_child(Box::new(SubtreeLoader::new(
"executor",
"plan" // Load and execute plan from this key
)));
// Execute
let executor = BTreeExecutor::new();
let result = executor.execute(&mut tree, &mut context).await?;
println!("Hybrid execution completed!");
println!(" Status: {:?}", result.status);
Ok(())
}
This tree:
- Sets a mission task in the blackboard
- Asks the LLM to generate a plan (JSON subtree)
- Loads and executes the LLM-generated plan
- Returns the final status
Complete Example
Here's a complete example combining everything:
use igris_btree::prelude::*;
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Setup
let llm = Arc::new(MockLlmProvider::with_navigation_plan());
let mut context = BTreeContext::new().with_llm(llm);
let executor = BTreeExecutor::new()
.with_max_ticks(50)
.with_deadline(Duration::from_secs(30))
.with_tracing(true);
// Build complex tree
let mut tree = Sequence::new("complete_mission")
.add_child(Box::new(SetBlackboard::new("init", "status", "starting")))
.add_child(Box::new(
Selector::new("main_logic")
.add_child(Box::new(
CheckBlackboard::new("check_plan", "manual_plan", true)
))
.add_child(Box::new(
Sequence::new("llm_planning")
.add_child(Box::new(SetBlackboard::new(
"set_task",
"task",
"Navigate warehouse"
)))
.add_child(Box::new(LLMPlannerNode::new(
"planner",
"task",
"plan"
)))
))
))
.add_child(Box::new(SubtreeLoader::new("executor", "plan")))
.add_child(Box::new(SetBlackboard::new("complete", "status", "done")));
// Execute
let result = executor.execute(&mut tree, &mut context).await?;
println!("\n=== Execution Complete ===");
println!("Status: {:?}", result.status);
println!("Ticks: {}", result.tick_count);
println!("Duration: {:?}", result.duration);
// Check blackboard
let status = context.blackboard.get::<String>("status").await?;
println!("Final status: {}", status);
Ok(())
}
Next Steps
Now that you've built your first behavior trees, explore these topics:
- Core Concepts - Deep dive into BTree fundamentals
- Node Types - Learn about all available nodes
- LLM Integration - Advanced LLM usage patterns
- Visualization - Monitor and debug your trees
- Runtime Execution - Advanced executor configuration
Common Patterns
Pattern 1: Check-Then-Act
Sequence::new("check_then_act")
.add_child(Box::new(CheckBlackboard::new("check", "ready", true)))
.add_child(Box::new(SetBlackboard::new("act", "status", "executing")))
Pattern 2: Try-With-Fallback
Selector::new("try_with_fallback")
.add_child(Box::new(/* primary action */))
.add_child(Box::new(/* fallback action */))
Pattern 3: Retry-With-Timeout
Retry::new(
"retry_wrapper",
Box::new(Timeout::new(
"timeout_wrapper",
Box::new(/* your action */),
Duration::from_secs(5)
)),
3
)
Pattern 4: Adaptive Recovery
ReplanOnFailure::new(
"adaptive_action",
Box::new(/* primary action */),
"task_description",
"recovery_plan",
3 // Max replans
)
Troubleshooting
Tree Never Completes
- Check for nodes that always return
Running - Use
with_max_ticks()to limit execution - Add timeout decorators to long-running actions
Blackboard Key Not Found
- Use
contains()to check beforeget() - Use
CheckBlackboardbefore actions that depend on keys - Initialize required keys before tree execution
LLM Planning Fails
- Check LLM provider is correctly configured
- Verify blackboard keys are set correctly
- Use
MockLlmProviderfor testing - Check LLM response format (must be valid JSON)
Tips
- Start simple: Build and test small trees before composing them
- Use the executor: Let
BTreeExecutorhandle the tick loop - Test with mocks: Use
MockLlmProviderbefore integrating real LLMs - Add logging: Enable tracing with
with_tracing(true) - Visualize: Use
TreeVisualizerto understand execution flow - Set limits: Always use
max_ticksanddeadlinein production
Ready to dive deeper? Continue to Core Concepts.