# Mecha10 Behavior Runtime
A unified behavior composition system for robotics and AI that enables building complex robot behaviors from simple, composable pieces.
## Overview
The behavior runtime provides a **tick-based execution model** where all behaviors implement a single `BehaviorNode` trait. This creates a uniform interface for:
- Custom Rust behaviors (complex logic, high performance)
- AI inference nodes (YOLO, LLMs, RL policies)
- Planning and navigation (A*, RRT, SLAM)
- Composition primitives (Sequence, Selector, Parallel)
## Quick Start
```rust
use mecha10_behavior_runtime::prelude::*;
// Define a custom behavior
#[derive(Debug)]
struct MyBehavior;
#[async_trait]
impl BehaviorNode for MyBehavior {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
// Your behavior logic here
println!("Hello from my behavior!");
Ok(NodeStatus::Success)
}
}
// Execute it
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let ctx = Context::new("my_node").await?;
let behavior = Box::new(MyBehavior);
let mut executor = BehaviorExecutor::new(behavior, 30.0); // 30 Hz
executor.init(&ctx).await?;
let (status, stats) = executor.run_until_complete(&ctx).await?;
println!("Completed with status: {} in {} ticks", status, stats.tick_count);
Ok(())
}
```
## Core Concepts
### BehaviorNode Trait
All behaviors implement this single trait:
```rust
#[async_trait]
pub trait BehaviorNode: Send + Sync + Debug {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus>;
fn name(&self) -> &str { /* ... */ }
async fn reset(&mut self) -> anyhow::Result<()> { /* ... */ }
async fn on_init(&mut self, ctx: &Context) -> anyhow::Result<()> { /* ... */ }
async fn on_terminate(&mut self, ctx: &Context) -> anyhow::Result<()> { /* ... */ }
}
```
### Node Status
Behaviors return one of three statuses:
- `NodeStatus::Success` - Behavior completed successfully
- `NodeStatus::Failure` - Behavior failed (irrecoverable error)
- `NodeStatus::Running` - Behavior is still executing
### Composition Primitives
#### Sequence Node
Executes children in order until one fails or all succeed.
```rust
let sequence = SequenceNode::new(vec![
Box::new(ApproachObject),
Box::new(GraspObject),
Box::new(LiftObject),
]);
```
#### Selector Node (Fallback)
Tries children until one succeeds or all fail.
```rust
let selector = SelectOrNode::new(vec![
Box::new(UseHighPrecisionSensor),
Box::new(UseLowPrecisionSensor),
Box::new(UseEstimatedPosition),
]);
```
#### Parallel Node
Executes all children concurrently.
```rust
let parallel = ParallelNode::new(
vec![
Box::new(MonitorObstacles),
Box::new(MoveToGoal),
Box::new(UpdateMap),
],
ParallelPolicy::RequireAll,
);
```
## Node Registry & Loading
The behavior runtime provides a powerful system for loading behavior trees from JSON configurations. This enables configuration-driven behavior and separation between behavior logic (Rust) and composition (JSON). Hot-reloading support is planned for future releases (see Hot Reload Status below).
### Registering Custom Nodes
Create a `NodeRegistry` and register your custom behavior types:
```rust
use mecha10_behavior_runtime::prelude::*;
// Define your custom behavior
#[derive(Debug)]
struct MoveToGoal {
speed: f64,
}
impl MoveToGoal {
fn from_config(config: serde_json::Value) -> anyhow::Result<Self> {
let speed = config.get("speed")
.and_then(|v| v.as_f64())
.unwrap_or(1.0);
Ok(Self { speed })
}
}
#[async_trait]
impl BehaviorNode for MoveToGoal {
async fn tick(&mut self, ctx: &Context) -> anyhow::Result<NodeStatus> {
// Your behavior logic here
Ok(NodeStatus::Success)
}
}
// Register it with the registry
let mut registry = NodeRegistry::new();
});
```
### Loading Behavior Trees
Use the `BehaviorLoader` to load behavior trees from JSON:
```rust
// Create a loader with your registry
let loader = BehaviorLoader::new(registry);
// Option 1: Load from a JSON string
let json = r#"{
"name": "my_behavior",
"root": {
"type": "node",
"node": "move_to_goal",
"config": { "speed": 2.0 }
}
}"#;
let behavior = loader.load_from_json(json)?;
// Option 2: Load from a file
let behavior = loader.load_from_file("behaviors/patrol.json")?;
// Option 3: Load from a parsed config
let config = BehaviorConfig::from_file("behaviors/patrol.json")?;
let behavior = loader.load(&config)?;
```
### Complete Workflow
Here's the complete workflow from registration to execution:
```rust
// 1. Create registry and register nodes
let mut registry = NodeRegistry::new();
});
registry.register("detect_obstacles", |config| {
Ok(Box::new(DetectObstacles::from_config(config)?))
});
// 2. Create loader and load behavior tree
let loader = BehaviorLoader::new(registry);
let behavior = loader.load_from_file("patrol_mission.json")?;
// 3. Create executor and run
let ctx = Context::new("robot").await?;
let mut executor = BehaviorExecutor::new(behavior, 30.0);
executor.init(&ctx).await?;
let (status, stats) = executor.run_until_complete(&ctx).await?;
println!("Completed with status: {} in {} ticks", status, stats.tick_count);
```
See `examples/load_and_execute.rs` for a complete working example.
## JSON Configuration
Behaviors can be defined in JSON for dynamic composition:
```json
{
"$schema": "https://mecha10.dev/schemas/behavior-composition-v1.json",
"name": "patrol_mission",
"root": {
"type": "sequence",
"children": [
{
"type": "node",
"node": "safety_check",
"config_ref": "safety"
},
{
"type": "selector",
"children": [
{
"type": "node",
"node": "detect_obstacles"
},
{
"type": "node",
"node": "wander"
}
]
}
]
},
"configs": {
"safety": { "max_speed": 1.0 }
}
}
```
Load and execute:
```rust
// Step 1: Create a registry and register your node types
let mut registry = NodeRegistry::new();
registry.register("safety_check", |config| {
Ok(Box::new(SafetyCheck::from_config(config)?))
});
registry.register("detect_obstacles", |config| {
Ok(Box::new(DetectObstacles::from_config(config)?))
});
});
// Step 2: Create a loader and load the behavior tree
let loader = BehaviorLoader::new(registry);
let behavior = loader.load_from_file("patrol_mission.json")?;
// Step 3: Execute with the behavior executor
let mut executor = BehaviorExecutor::new(behavior, 30.0);
let (status, stats) = executor.run_until_complete(&ctx).await?;
```
## Built-in Action Nodes
The runtime includes several built-in action nodes for common robotics tasks. Register them with:
```rust
use mecha10_behavior_runtime::register_builtin_actions;
let mut registry = NodeRegistry::new();
register_builtin_actions(&mut registry);
```
### WanderNode
Generates random movement commands for exploration:
```json
{
"type": "wander",
"config": {
"topic": "/motor/cmd_vel",
"linear_speed": 0.3,
"angular_speed": 0.5,
"change_interval_secs": 3.0
}
}
```
**Parameters:**
- `topic`: Topic to publish velocity commands (required)
- `linear_speed`: Forward speed in m/s (default: 0.3)
- `angular_speed`: Max turning speed in rad/s (default: 0.5)
- `change_interval_secs`: Seconds between direction changes (default: 3.0)
**Behavior:** Publishes constant linear velocity with periodically changing random angular velocity. Returns `Running` continuously.
### MoveNode
Executes a fixed velocity command for a specified duration:
```json
{
"type": "move",
"config": {
"topic": "/motor/cmd_vel",
"linear": 0.5,
"angular": 0.0,
"duration_secs": 2.0
}
}
```
**Parameters:**
- `topic`: Topic to publish velocity commands (required)
- `linear`: Forward/backward velocity in m/s (required)
- `angular`: Turning velocity in rad/s (required)
- `duration_secs`: Duration to execute (optional, null = forever)
**Behavior:** Publishes the specified velocity command. Returns `Running` while executing, `Success` when duration elapses. If no duration specified, runs forever.
### TimerNode
Waits for a specified duration before succeeding:
```json
{
"type": "timer",
"config": {
"duration_secs": 5.0
}
}
```
**Parameters:**
- `duration_secs`: Duration to wait in seconds (required)
**Behavior:** Returns `Running` until the duration elapses, then returns `Success`. Useful for delays in sequences.
### SensorCheckNode
Checks sensor conditions (placeholder for future implementation):
```json
{
"type": "sensor_check",
"config": {
"topic": "/sensors/distance",
"field": "value",
"operator": "less_than",
"threshold": 0.5
}
}
```
**Parameters:**
- `topic`: Sensor topic to subscribe to (required)
- `field`: Field name in sensor message (required)
- `operator`: Comparison operator - `less_than`, `greater_than`, `equal` (required)
- `threshold`: Comparison value (required)
**Current Status:** Returns `Success` immediately (stub). Full sensor subscription implementation pending.
## Seed Templates
The package includes production-ready behavior tree templates in the `seeds/` directory:
- **basic_navigation.json** - Simple waypoint navigation with battery check and path planning
- **obstacle_avoidance.json** - Reactive collision avoidance using sensor fusion
- **patrol_simple.json** - Basic patrol loop cycling through waypoints
- **idle_wander.json** - Random wandering for exploration and idle movement
You can use these templates as starting points for your own behavior trees or load them directly:
```rust
let loader = BehaviorLoader::new(registry);
let behavior = loader.load_from_file("seeds/basic_navigation.json")?;
```
### Creating Behavior Trees with the CLI
The Mecha10 CLI provides an interactive wizard for creating behavior trees:
```bash
# Interactive mode - wizard will guide you through template selection
mecha10 behaviors create
# Create with specific template
mecha10 behaviors create --name my_behavior --template navigation
# List available templates
mecha10 behaviors list
# Validate an existing behavior tree
mecha10 behaviors validate behaviors/my_behavior.json
```
## Configuration Validation
The runtime provides comprehensive validation for behavior trees before execution:
```rust
use mecha10_behavior_runtime::{validate_behavior_config, NodeRegistry, register_builtin_actions};
use serde_json::json;
let mut registry = NodeRegistry::new();
register_builtin_actions(&mut registry);
let config = json!({
"name": "wander_behavior",
"root": {
"type": "sequence",
"children": [
{
"type": "wander",
"config": {
"topic": "/motor/cmd_vel",
"linear_speed": 0.5
}
}
]
}
});
let result = validate_behavior_config(&config, ®istry)?;
if !result.valid {
for error in &result.errors {
eprintln!("Error: {}", error);
}
}
for warning in &result.warnings {
eprintln!("Warning: {}", warning);
}
```
**Validation Checks:**
- Required fields (`name`, `root`)
- Node types are registered or are composition nodes
- Tree structure is well-formed
- Children arrays exist for composition nodes
- Schema reference is present (warning if missing)
- Empty children arrays (warning)
**ValidationResult:**
```rust
pub struct ValidationResult {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
```
## Environment-Specific Configuration
Behavior trees support a three-tier configuration hierarchy for environment-specific overrides:
1. **Template** (required): `behaviors/wander.json` - Base behavior tree
2. **Common** (optional): `configs/common/behaviors/wander.json` - Shared overrides
3. **Environment** (optional): `configs/dev/behaviors/wander.json` - Environment-specific overrides
Configurations are deep-merged, with later tiers overriding earlier ones.
### Loading Environment-Specific Configs
```rust
use mecha10_behavior_runtime::{load_behavior_config, detect_project_root, get_current_environment};
// Auto-detect project root (walks up to find mecha10.json)
let project_root = detect_project_root()?;
// Get environment from MECHA10_ENVIRONMENT or default to "dev"
let environment = get_current_environment();
// Load with environment-specific overrides
let config = load_behavior_config("wander", &project_root, &environment).await?;
```
### Example: Environment Overrides
**Base Template** (`behaviors/wander.json`):
```json
{
"name": "wander_behavior",
"root": {
"type": "wander",
"config": {
"topic": "/motor/cmd_vel",
"linear_speed": 0.3,
"angular_speed": 0.5,
"change_interval_secs": 3.0
}
}
}
```
**Development Override** (`configs/dev/behaviors/wander.json`):
```json
{
"root": {
"config": {
"linear_speed": 0.1,
"angular_speed": 0.2
}
}
}
```
**Production Override** (`configs/production/behaviors/wander.json`):
```json
{
"root": {
"config": {
"linear_speed": 0.5,
"angular_speed": 0.8,
"change_interval_secs": 5.0
}
}
}
```
**Result:** In development, the robot moves slowly (0.1 m/s linear). In production, it moves faster (0.5 m/s linear) with wider turns.
### Deep Merge Behavior
The merge is recursive for nested objects:
```rust
// Base
{ "a": 1, "b": { "x": 1, "y": 2 } }
// Override
{ "b": { "y": 3, "z": 4 }, "c": 5 }
// Result
{ "a": 1, "b": { "x": 1, "y": 3, "z": 4 }, "c": 5 }
```
Non-object values are replaced entirely.
## Composition Patterns
### Pattern 1: Sequential Execution (Sequence Node)
Use sequences when tasks must be performed in order, and all must succeed:
```json
{
"type": "sequence",
"children": [
{ "type": "node", "node": "check_battery" },
{ "type": "node", "node": "plan_path" },
{ "type": "node", "node": "follow_path" },
{ "type": "node", "node": "check_arrival" }
]
}
```
**Behavior**: Executes children left-to-right. If any child fails, the sequence fails immediately. All children must succeed for the sequence to succeed.
**Use cases**: Multi-step tasks, initialization sequences, workflows
### Pattern 2: Fallback/Priority (Selector Node)
Use selectors when you have multiple strategies and want to try them in priority order:
```json
{
"type": "selector",
"children": [
{ "type": "node", "node": "use_high_precision_sensor" },
{ "type": "node", "node": "use_low_precision_sensor" },
{ "type": "node", "node": "use_estimated_position" }
]
}
```
**Behavior**: Tries children left-to-right. If a child succeeds, the selector succeeds immediately. If all children fail, the selector fails.
**Use cases**: Fallback strategies, priority-based behavior selection, fault tolerance
### Pattern 3: Concurrent Execution (Parallel Node)
Use parallel nodes when tasks can run simultaneously:
```json
{
"type": "parallel",
"policy": "require_all",
"children": [
{ "type": "node", "node": "monitor_obstacles" },
{ "type": "node", "node": "move_to_goal" },
{ "type": "node", "node": "update_map" }
]
}
```
**Behavior**: Executes all children concurrently. Success/failure depends on the policy:
- `require_all`: All children must succeed
- `require_one`: At least one child must succeed
- `require_n(N)`: At least N children must succeed
**Use cases**: Monitoring + action, sensor fusion, multi-tasking
### Pattern 4: Subsumption Architecture
Layered behavior with priority suppression (high-priority behaviors override low-priority ones):
```json
{
"type": "selector",
"name": "subsumption_layers",
"children": [
{
"type": "sequence",
"name": "emergency_layer",
"children": [
{ "type": "node", "node": "detect_emergency" },
{ "type": "node", "node": "emergency_stop" }
]
},
{
"type": "sequence",
"name": "avoidance_layer",
"children": [
{ "type": "node", "node": "detect_obstacles" },
{ "type": "node", "node": "avoid_obstacles" }
]
},
{
"type": "sequence",
"name": "navigation_layer",
"children": [
{ "type": "node", "node": "check_goal" },
{ "type": "node", "node": "navigate_to_goal" }
]
},
{ "type": "node", "node": "idle" }
]
}
```
**Use cases**: Safety-critical systems, reactive robotics, behavior hierarchies
See `packages/behavior-patterns/seeds/safety_subsumption.json` for a complete example.
### Pattern 5: Ensemble/Fusion
Multiple models or sensors providing input for a single decision:
```json
{
"type": "sequence",
"children": [
{ "type": "node", "node": "capture_sensor_data" },
{
"type": "parallel",
"policy": "require_all",
"children": [
{ "type": "node", "node": "yolo_detector" },
{ "type": "node", "node": "mobilenet_detector" },
{ "type": "node", "node": "depth_detector" }
]
},
{ "type": "node", "node": "fuse_detections" },
{ "type": "node", "node": "make_decision" }
]
}
```
**Use cases**: Sensor fusion, multi-model AI, robust perception
See `packages/behavior-patterns/seeds/multi_model_ensemble.json` for a complete example.
## Execution Engine
The `BehaviorExecutor` manages tick-based execution:
```rust
let executor = BehaviorExecutor::new(behavior, 30.0) // 30 Hz tick rate
.with_max_ticks(1000); // Optional timeout
// Run until completion
let (status, stats) = executor.run_until_complete(&ctx).await?;
// Or run for a fixed duration
let (status, stats) = executor.run_for_duration(&ctx, Duration::from_secs(10)).await?;
```
Execution statistics:
```rust
println!("Ticks: {}", stats.tick_count);
println!("Duration: {:?}", stats.total_duration);
println!("Avg tick: {:?}", stats.avg_tick_duration);
println!("Min/Max: {:?}/{:?}", stats.min_tick_duration, stats.max_tick_duration);
```
## Architecture
### Philosophy
- **Behavior Logic** = Rust code (complex logic, high performance)
- **Behavior Composition** = JSON config (simple orchestration, will support hot-reloading)
- **Configuration** = JSON with validation (parameters only)
### Benefits
- **Unified Interface**: Everything is a BehaviorNode
- **Composable**: Mix and match behaviors freely
- **Type-Safe**: Rust's type system ensures correctness
- **Performance**: Zero-cost abstractions, compiled Rust
- **Debuggable**: Clear execution model with stats
## Integration with Mecha10
This package is part of the Mecha10 framework and integrates with:
- **mecha10-core**: Provides `Context` for messaging and state
- **AI nodes** (planned): YOLO, LLMs, RL policies
- **Planning nodes** (planned): A*, RRT, SLAM
- **Dashboard**: Real-time behavior visualization
## Status
**Current Status**: Phase 5 Complete - Config Loading & Validation ✅
Implemented:
- ✅ Core `BehaviorNode` trait
- ✅ `NodeStatus` enum
- ✅ Composition primitives (Sequence, Selector, Parallel)
- ✅ JSON configuration types with JSON Schema
- ✅ Tick-based execution engine
- ✅ Execution statistics
- ✅ Node registry for dynamic node instantiation
- ✅ Behavior loader for loading trees from JSON
- ✅ Integration examples and documentation
- ✅ Production-ready seed templates (6 templates)
- ✅ CLI wizard for creating behavior trees
- ✅ Comprehensive composition pattern documentation
- ✅ JSON Schema for validation and IDE support
- ✅ **Built-in action nodes** (WanderNode, MoveNode, TimerNode, SensorCheckNode)
- ✅ **Configuration validation** (validate_behavior_config with detailed errors/warnings)
- ✅ **Environment-specific configuration** (three-tier: template → common → environment)
- ✅ **BehaviorExecutor node** (mecha10-nodes-behavior-executor for integration)
- ✅ **Integration tests** (14 tests for action nodes and config validation)
Next Steps (Priority 7 - see TODOS.md):
- [ ] Behavior tree catalog service (REST API + PostgreSQL)
- [ ] AI node library (Vision, Language, Speech)
- [ ] Planning nodes (Path planning, SLAM, OpenCV)
- [ ] Dashboard monitoring interface
- [ ] Hot-reload support for behavior trees
## Hot Reload Status
**Current**: The behavior runtime supports loading behavior trees from JSON files at startup. You can modify JSON files and restart your nodes to load updated behavior trees.
**Planned**: Automatic hot-reloading of behavior trees while nodes are running. This will enable:
- File system watching for JSON behavior tree changes
- Validation of new behavior trees before applying
- Seamless tree swapping without node restart
- State preservation for stateful nodes (optional)
- Rollback on reload errors
**Timeline**: Hot-reload functionality is tracked in TODOS.md under P2 (Medium Priority) tasks.
**Current Workflow**:
1. Edit your behavior tree JSON file
2. Stop your running node (Ctrl+C)
3. Restart the node with `mecha10 run <node-name>`
4. The node will load the updated behavior tree
## Testing
The package currently builds successfully. Integration tests require a running Redis instance (provided by mecha10-core Context).
```bash
cargo build -p mecha10-behavior-runtime
cargo test -p mecha10-behavior-runtime
```
## License
MIT
## See Also
- [AI Features Documentation](../../docs/AI_FEATURES.md)
- [TODOS.md](../../TODOS.md) - Priority 7: AI Native