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


Complete Code Repository

Find the complete, runnable example at:

/Users/wira/Desktop/system/igris-runtime/crates/igris-btree/examples/visualized_mission.rs