Simple Mission Example
Complete working example of a basic warehouse navigation mission using hybrid behavior trees.
Overview
This example demonstrates:
- Building a multi-step sequence
- Using LLM for dynamic planning
- Executing with BTreeExecutor
- Monitoring with TreeVisualizer
- Complete error handling
Complete Code
use igris_btree::prelude::*;
use igris_btree::visualizer::{TreeVisualizer, VisualizerConfig};
use std::sync::Arc;
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Initialize logging
tracing_subscriber::fmt::init();
println!("š Warehouse Navigation Mission\n");
// Step 1: Create LLM provider (mock for this example)
let llm = Arc::new(MockLlmProvider::with_navigation_plan());
// Step 2: Create execution context
let mut context = BTreeContext::new().with_llm(llm);
// Step 3: Configure visualizer
let viz_config = VisualizerConfig {
enabled: true,
max_trace_entries: 50,
max_replan_events: 10,
export_frequency: 0, // Every tick
compute_diffs: true,
blackboard_filter: None,
};
let visualizer = Arc::new(TreeVisualizer::with_config(viz_config));
// Step 4: Create executor
let executor = BTreeExecutor::new()
.with_max_ticks(100)
.with_deadline(Duration::from_secs(30))
.with_tracing(true)
.with_visualizer(visualizer.clone());
// Step 5: Build mission tree
let mut mission = Sequence::new("warehouse_mission")
// Initialize mission
.add_child(Box::new(SetBlackboard::new(
"init_status",
"status",
"initializing",
)))
// Check prerequisites
.add_child(Box::new(Sequence::new("prerequisites")
.add_child(Box::new(SetBlackboard::new(
"set_battery",
"battery_level",
95,
)))
.add_child(Box::new(CheckBlackboard::new(
"check_battery",
"battery_level",
95,
)))
))
// Set mission task
.add_child(Box::new(SetBlackboard::new(
"set_mission",
"mission_task",
"Navigate to warehouse section A3, pick up box #42, and return to dock",
)))
// LLM planning phase
.add_child(Box::new(Sequence::new("planning_phase")
.add_child(Box::new(SetBlackboard::new(
"planning_status",
"status",
"planning",
)))
.add_child(Box::new(LLMPlannerNode::new(
"llm_planner",
"mission_task",
"navigation_plan",
)))
))
// Execute the plan
.add_child(Box::new(Sequence::new("execution_phase")
.add_child(Box::new(SetBlackboard::new(
"executing_status",
"status",
"executing",
)))
.add_child(Box::new(SubtreeLoader::new(
"plan_executor",
"navigation_plan",
)))
))
// Complete mission
.add_child(Box::new(SetBlackboard::new(
"complete_status",
"status",
"completed",
)));
// Step 6: Execute mission
println!("š Executing mission...\n");
let result = executor.execute(&mut mission, &mut context).await?;
// Step 7: Display results
println!("\nā
Mission Execution Complete!\n");
println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
println!("Status: {:?}", result.status);
println!("Ticks: {}", result.tick_count);
println!("Duration: {:?}", result.duration);
if result.cancelled {
println!("ā ļø Execution was cancelled");
}
if result.max_ticks_reached {
println!("ā ļø Max ticks limit reached");
}
if result.deadline_exceeded {
println!("ā ļø Deadline exceeded");
}
if !result.errors.is_empty() {
println!("ā ļø Errors: {:?}", result.errors);
}
// Step 8: Display blackboard state
println!("\nš Final Blackboard State:");
println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
if let Ok(status) = context.blackboard.get::<String>("status").await {
println!(" Status: {}", status);
}
if let Ok(task) = context.blackboard.get::<String>("mission_task").await {
println!(" Mission: {}", task);
}
if let Ok(battery) = context.blackboard.get::<i32>("battery_level").await {
println!(" Battery: {}%", battery);
}
// Step 9: Export visualization snapshot
let snapshot = visualizer
.export_snapshot(&mission, &context, result.tick_count)
.await?;
println!("\nš Execution Metrics:");
println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
println!(" Total Ticks: {}", snapshot.metrics.total_ticks);
println!(" Tick Rate: {:.2} ticks/sec", snapshot.metrics.avg_tick_rate);
println!(" Replans: {}", snapshot.metrics.total_replans);
println!(" Failure Rate: {:.2}%", snapshot.metrics.failure_rate * 100.0);
println!(" LLM Latency: {:.2}ms", snapshot.metrics.avg_llm_latency_ms);
// Step 10: Display execution trace
println!("\nš Execution Trace (last 10 entries):");
println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
for entry in snapshot.execution_trace.iter().take(10) {
println!(
" Tick {}: {} ({:?}) - {:.2}ms",
entry.tick,
entry.node_name,
entry.status,
entry.duration_ms
);
}
// Step 11: Display tree structure
println!("\nš³ Tree Structure:");
println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
print_tree(&snapshot.root, 0);
// Step 12: Save snapshot to file
let json = serde_json::to_string_pretty(&snapshot)?;
std::fs::write("mission_snapshot.json", &json)?;
println!("\nš¾ Snapshot saved to: mission_snapshot.json");
println!("\n⨠Mission complete!\n");
Ok(())
}
/// Helper function to print tree structure
fn print_tree(node: &igris_btree::visualizer::NodeSnapshot, indent: usize) {
let indent_str = " ".repeat(indent);
let status_icon = match node.status {
NodeStatus::Success => "ā
",
NodeStatus::Failure => "ā",
NodeStatus::Running => "ā³",
NodeStatus::Skipped => "āļø ",
};
println!(
"{}{} {} ({}) - Ticks: {}, Avg: {:.2}ms",
indent_str,
status_icon,
node.name,
node.node_type,
node.stats.tick_count,
node.stats.avg_execution_ms
);
for child in &node.children {
print_tree(child, indent + 1);
}
}
Expected Output
š Warehouse Navigation Mission
š Executing mission...
ā
Mission Execution Complete!
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Status: Success
Ticks: 8
Duration: 12.5ms
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š Final Blackboard State:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Status: completed
Mission: Navigate to warehouse section A3, pick up box #42, and return to dock
Battery: 95%
š Execution Metrics:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Total Ticks: 8
Tick Rate: 640.00 ticks/sec
Replans: 0
Failure Rate: 0.00%
LLM Latency: 0.00ms
š Execution Trace (last 10 entries):
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Tick 1: init_status (Success) - 0.05ms
Tick 2: set_battery (Success) - 0.03ms
Tick 3: check_battery (Success) - 0.02ms
Tick 4: set_mission (Success) - 0.04ms
Tick 5: planning_status (Success) - 0.03ms
Tick 6: llm_planner (Success) - 5.20ms
Tick 7: executing_status (Success) - 0.03ms
Tick 8: plan_executor (Success) - 2.10ms
š³ Tree Structure:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā
warehouse_mission (Sequence) - Ticks: 8, Avg: 1.56ms
ā
init_status (SetBlackboard) - Ticks: 1, Avg: 0.05ms
ā
prerequisites (Sequence) - Ticks: 2, Avg: 0.03ms
ā
set_battery (SetBlackboard) - Ticks: 1, Avg: 0.03ms
ā
check_battery (CheckBlackboard) - Ticks: 1, Avg: 0.02ms
ā
set_mission (SetBlackboard) - Ticks: 1, Avg: 0.04ms
ā
planning_phase (Sequence) - Ticks: 2, Avg: 2.62ms
ā
planning_status (SetBlackboard) - Ticks: 1, Avg: 0.03ms
ā
llm_planner (LLMPlannerNode) - Ticks: 1, Avg: 5.20ms
ā
execution_phase (Sequence) - Ticks: 2, Avg: 1.07ms
ā
executing_status (SetBlackboard) - Ticks: 1, Avg: 0.03ms
ā
plan_executor (SubtreeLoader) - Ticks: 1, Avg: 2.10ms
ā
complete_status (SetBlackboard) - Ticks: 1, Avg: 0.03ms
š¾ Snapshot saved to: mission_snapshot.json
⨠Mission complete!
How It Works
Phase 1: Initialization
.add_child(Box::new(SetBlackboard::new(
"init_status",
"status",
"initializing",
)))
Sets the initial status in the blackboard.
Phase 2: Prerequisites
.add_child(Box::new(Sequence::new("prerequisites")
.add_child(Box::new(SetBlackboard::new("set_battery", "battery_level", 95)))
.add_child(Box::new(CheckBlackboard::new("check_battery", "battery_level", 95)))
))
- Sets battery level
- Verifies it meets requirements
- If check fails, entire sequence fails
Phase 3: Mission Setup
.add_child(Box::new(SetBlackboard::new(
"set_mission",
"mission_task",
"Navigate to warehouse section A3, pick up box #42, and return to dock",
)))
Stores the mission description for LLM planning.
Phase 4: LLM Planning
.add_child(Box::new(LLMPlannerNode::new(
"llm_planner",
"mission_task",
"navigation_plan",
)))
- Reads task from blackboard
- Generates navigation plan using LLM
- Stores plan in blackboard
Phase 5: Plan Execution
.add_child(Box::new(SubtreeLoader::new(
"plan_executor",
"navigation_plan",
)))
- Loads the LLM-generated plan
- Parses JSON into behavior tree
- Executes the dynamically loaded tree
Phase 6: Completion
.add_child(Box::new(SetBlackboard::new(
"complete_status",
"status",
"completed",
)))
Updates status to completed.
Customization
Add Error Handling
.add_child(Box::new(
Selector::new("robust_execution")
.add_child(Box::new(SubtreeLoader::new("plan_executor", "navigation_plan")))
.add_child(Box::new(SetBlackboard::new("fallback", "status", "failed")))
))
Add Retry Logic
.add_child(Box::new(
Retry::new(
"retry_execution",
Box::new(SubtreeLoader::new("plan_executor", "navigation_plan")),
3
)
))
Add Timeout
use std::time::Duration;
.add_child(Box::new(
Timeout::new(
"timed_execution",
Box::new(SubtreeLoader::new("plan_executor", "navigation_plan")),
Duration::from_secs(10)
)
))
Add Adaptive Recovery
.add_child(Box::new(
ReplanOnFailure::new(
"adaptive_execution",
Box::new(SubtreeLoader::new("plan_executor", "navigation_plan")),
"mission_task",
"recovery_plan",
3
)
))
Real LLM Integration
To use a real LLM instead of mock:
// Replace MockLlmProvider with real implementation
use your_llm_crate::OllamaProvider;
let llm = Arc::new(OllamaProvider::new(
"http://localhost:11434",
"phi3"
));
See LLM Integration for provider implementations.
Testing
Run the example:
cargo run --example simple_mission
Run with trace logging:
RUST_LOG=debug cargo run --example simple_mission
Next Steps
- Adaptive Planning Example - Advanced LLM usage
- Parallel Execution Example - Concurrent behaviors
- Production Deployment - Deploy to production
Complete Code Repository
Find the complete, runnable example at:
/Users/wira/Desktop/system/igris-runtime/crates/igris-btree/examples/visualized_mission.rs