Skip to content

Your First Rhizomorph Behavior Tree

This tutorial will walk you through creating a simple Behavior Tree using Rhizomorph.

What You'll Build

You'll create a simple AI decision tree for a robot that needs to decide what to do.

Step 1: Import Rhizomorph

from mycorrhizal.rhizomorph.core import bt, Runner as BTRunner, Status
from pydantic import BaseModel
from typing import Optional

Step 2: Define Your Blackboard (State)

class RobotContext(BaseModel):
    battery_level: int = 100
    task_queue: list = []
    current_location: str = "home"
    charging_station: str = "home"

Step 3: Define the Behavior Tree

@bt.tree
class RobotAI:
    @bt.action
    async def check_battery(bb: RobotContext) -> Status:
        """Check if battery is low."""
        if bb.battery_level < 20:
            print(f"Battery low: {bb.battery_level}%")
            return Status.SUCCESS
        return Status.FAILURE

    @bt.action
    async def go_charge(bb: RobotContext) -> Status:
        """Navigate to charging station."""
        print(f"Going to charging station at {bb.charging_station}")
        bb.current_location = bb.charging_station
        bb.battery_level = 100
        print("Charged!")
        return Status.SUCCESS

    @bt.condition
    def has_tasks(bb: RobotContext) -> bool:
        """Check if there are tasks to do."""
        return len(bb.task_queue) > 0

    @bt.action
    async def do_task(bb: RobotContext) -> Status:
        """Perform the next task."""
        if not bb.task_queue:
            return Status.FAILURE
        task = bb.task_queue.pop(0)
        print(f"Doing task: {task}")
        bb.battery_level -= 10
        return Status.SUCCESS

    @bt.action
    async def idle(bb: RobotContext) -> Status:
        """Do nothing when no tasks."""
        print("Idling...")
        return Status.SUCCESS

    @bt.root
    @bt.selector
    def root():
        """Try battery check first, if that fails, do tasks."""
        yield check_battery
        yield has_tasks
        yield idle

    @bt.sequence
    def charging_sequence():
        """Sequence for charging: go_charge then done."""
        yield go_charge

    @bt.sequence
    def task_sequence():
        """Sequence for doing tasks."""
        yield has_tasks
        yield do_task

Step 4: Visualize Your Behavior Tree (Before Running!)

Rhizomorph lets you export your tree to a Mermaid diagram to visualize the decision logic:

# Create the tree
tree = RobotAI()

# Export to Mermaid diagram
mermaid = tree.to_mermaid()
print(mermaid)

This generates a visual representation of your behavior tree:

%%{init: {'layout':'elk'}}%%
flowchart TD
  N1["Selector<br/>root"]
  N1 --> N2
  N2((CONDITION<br/>is_low_battery))
  N1 --> N3
  N3((CONDITION<br/>has_tasks))
  N1 --> N4
  N4((ACTION<br/>idle))

This visualization reveals:

  • The decision flow and fallback logic
  • Which nodes run under what conditions
  • Whether all nodes are reachable
  • Potential issues in the tree structure

You can verify correctness before executing any behavior!

Step 5: Run the Behavior Tree

import asyncio

async def main():
    # Create blackboard with initial state
    bb = RobotContext(
        battery_level=50,
        task_queue=["clean", "cook", "clean"]
    )

    # Create and run the tree
    tree = RobotAI()
    runner = BTRunner(tree=tree, blackboard=bb)

    # Run for several ticks
    for i in range(10):
        print(f"\n--- Tick {i+1} ---")
        await runner.tick()
        if bb.battery_level <= 0:
            print("Battery depleted!")
            break

asyncio.run(main())

Expected Output

--- Tick 1 ---
Battery low: 50%
Idling...

--- Tick 2 ---
Battery low: 40%
Idling...

--- Tick 3 ---
Battery low: 30%
Idling...

--- Tick 4 ---
Battery low: 20%
Going to charging station at home
Charged!

--- Tick 5 ---
Charged!
Idling...

--- Tick 6 ---
Doing task: clean
...

Full Example

See the full example in the repository:

python examples/rhizomorph_example.py

Next Steps