Skip to main content

Overview

This example demonstrates MoFA’s runtime plugin system using Rhai scripts. It showcases:
  • Loading Rhai plugins from external files
  • Hot-reloading scripts when files change
  • Executing updated plugins without restart
  • Plugin lifecycle management
  • File watching and automatic reload

What You’ll Learn

  • Creating Rhai plugins for runtime logic
  • Implementing hot-reload functionality
  • Plugin lifecycle (load, init, execute, unload)
  • File watching patterns
  • Dynamic business logic updates

Prerequisites

  • Rust 1.75 or higher
  • Understanding of plugin patterns
  • Basic knowledge of Rhai syntax

Architecture

Source Code

use mofa_sdk::plugins::{AgentPlugin, PluginContext, RhaiPlugin};
use std::path::PathBuf;
use tokio::time;
use tracing::{info, warn, Level};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_max_level(Level::INFO)
        .init();

    info!("=== Rhai Plugin Hot Reload Example ===\n");

    // Path to plugin
    let plugin_path = PathBuf::from("sample_plugin.rhai");
    if !plugin_path.exists() {
        warn!("⚠️  Run from examples/rhai_hot_reload directory!");
        warn!("Directory: {}", std::env::current_dir()?.display());
        return Ok(());
    }

    // Create plugin from file
    let mut plugin = RhaiPlugin::from_file(
        "my_hot_plugin", 
        &plugin_path
    ).await?;

    // Initialize plugin
    let ctx = PluginContext::new("test_agent");
    plugin.load(&ctx).await?;
    plugin.init_plugin().await?;

    // Example input
    let input = "Test message from agent";

    // Execute plugin multiple times with reload checks
    for i in 0..10 {
        // Check if file changed and reload
        match check_and_reload(&mut plugin, &plugin_path).await {
            Ok(true) => info!("🔄 Plugin reloaded!"),
            Ok(false) => info!("✅ Plugin unchanged"),
            Err(e) => warn!("⚠️  Error checking/reloading: {}", e),
        }

        // Execute the plugin
        let result = plugin.execute(input.to_string()).await?;
        info!("Execution result {}: {}", i + 1, result);

        // Wait for 2 seconds
        time::sleep(time::Duration::from_secs(2)).await;
    }

    // Cleanup
    plugin.unload().await?;

    info!("\n=== Example Complete ===");
    Ok(())
}

async fn check_and_reload(
    plugin: &mut RhaiPlugin,
    path: &PathBuf,
) -> Result<bool, Box<dyn std::error::Error>> {
    // Get current file modification time
    let current_mod = std::fs::metadata(path)?
        .modified()?
        .duration_since(std::time::UNIX_EPOCH)?
        .as_secs();

    // Check if plugin needs reload
    if plugin.last_modified() != current_mod {
        plugin.reload().await?;
        Ok(true)
    } else {
        Ok(false)
    }
}

Running the Example

2
cd examples/rhai_hot_reload
3
Run the Example
4
cargo run
5
Modify the Plugin
6
While the program is running, edit sample_plugin.rhai:
7
fn execute(input) {
    "[UPDATED] Modified: " + input.to_upper()
}
8
Save the file and watch the output change!

Expected Output

=== Rhai Plugin Hot Reload Example ===

✅ Plugin unchanged
Execution result 1: Echo: Test message from agent
✅ Plugin unchanged
Execution result 2: Echo: Test message from agent
🔄 Plugin reloaded!
Execution result 3: [UPDATED] Modified: TEST MESSAGE FROM AGENT
✅ Plugin unchanged
Execution result 4: [UPDATED] Modified: TEST MESSAGE FROM AGENT

=== Example Complete ===

Key Concepts

Rhai Plugin System

Rhai plugins provide runtime programmability:
// Create from file
let plugin = RhaiPlugin::from_file("plugin_id", path).await?;

// Create from string
let script = r#"
    fn execute(input) {
        "Result: " + input
    }
"#;
let plugin = RhaiPlugin::from_string("plugin_id", script).await?;

Plugin Lifecycle

1
Load
2
plugin.load(&ctx).await?;
3
Load plugin into memory
4
Initialize
5
plugin.init_plugin().await?;
6
Prepare plugin for execution
7
Execute
8
let result = plugin.execute(input).await?;
9
Run plugin logic
10
Reload
11
plugin.reload().await?;
12
Reload from file (hot reload)
13
Unload
14
plugin.unload().await?;
15
Cleanup and free resources

Hot Reload Pattern

// Check file modification time
let metadata = fs::metadata(&path)?;
let modified = metadata.modified()?
    .duration_since(UNIX_EPOCH)?
    .as_secs();

// Compare with plugin's last modified time
if plugin.last_modified() != modified {
    // Reload plugin
    plugin.reload().await?;
    info!("Plugin reloaded!");
}

Rhai Scripting Guide

Basic Syntax

let x = 42;
let name = "MoFA";
let is_active = true;

Built-in Functions

// String operations
let upper = text.to_upper();
let lower = text.to_lower();
let len = text.len();
let contains = text.contains("search");

// Array operations
let arr = [1, 2, 3, 4, 5];
let filtered = arr.filter(|x| x > 2);
let mapped = arr.map(|x| x * 2);
let sum = arr.reduce(|sum, x| sum + x, 0);

// JSON operations
let obj = #{ name: "MoFA", version: 1 };
let json_str = to_json(obj);
let parsed = parse_json(json_str);

Advanced Examples

Complex Plugin Logic

// Advanced processing plugin
fn execute(input) {
    // Parse JSON input
    let data = parse_json(input);
    
    // Extract fields
    let action = data["action"];
    let params = data["params"];
    
    // Route based on action
    if action == "process" {
        process_data(params)
    } else if action == "analyze" {
        analyze_data(params)
    } else {
        "Unknown action: " + action
    }
}

fn process_data(params) {
    let items = params["items"];
    let results = [];
    
    for item in items {
        if item > 0 {
            results.push(item * 2);
        }
    }
    
    to_json(#{ 
        status: "success",
        results: results,
        count: results.len()
    })
}

fn analyze_data(params) {
    let values = params["values"];
    let sum = values.reduce(|s, v| s + v, 0);
    let avg = sum / values.len();
    
    to_json(#{
        sum: sum,
        average: avg,
        count: values.len()
    })
}

Stateful Plugin

// Global state (persists across executions)
let counter = 0;
let history = [];

fn execute(input) {
    counter += 1;
    history.push(input);
    
    to_json(#{
        execution: counter,
        input: input,
        history_size: history.len()
    })
}

Common Use Cases

Business Rules

Hot-reload business logic without restarts

Compliance Rules

Update regulatory rules dynamically

Feature Flags

Control features via scripts

Data Transformation

Modify transformation logic on-the-fly

File Watching

For production use, implement proper file watching:
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use tokio::sync::mpsc;

let (tx, mut rx) = mpsc::channel(10);

let mut watcher = RecommendedWatcher::new(
    move |res: Result<Event, _>| {
        if let Ok(event) = res {
            let _ = tx.blocking_send(event);
        }
    },
    Config::default()
)?;

watcher.watch(&plugin_path, RecursiveMode::NonRecursive)?;

tokio::spawn(async move {
    while let Some(event) = rx.recv().await {
        if matches!(event.kind, EventKind::Modify(_)) {
            info!("File changed, reloading...");
            if let Err(e) = plugin.reload().await {
                warn!("Reload failed: {}", e);
            }
        }
    }
});

Troubleshooting

Problem: Plugin fails after hot reloadSolution: Validate Rhai syntax before reload:
let script_content = fs::read_to_string(&path)?;
if rhai::Engine::new().compile(&script_content).is_err() {
    warn!("Syntax error, keeping old version");
    return Ok(false);
}
plugin.reload().await?;
Problem: Plugin loses state after reloadSolution: Persist state externally:
// Before reload
let state = plugin.get_state().await?;

// Reload
plugin.reload().await?;

// Restore state
plugin.set_state(state).await?;
Problem: Plugin file path incorrectSolution: Use absolute paths:
let path = std::env::current_dir()?
    .join("sample_plugin.rhai");

Best Practices

Security Consideration: Rhai scripts run with full access. Only load trusted scripts in production.
Performance: Rhai is interpreted. For performance-critical paths, use compiled plugins (Rust/WASM).
Version Control: Keep plugin scripts in version control alongside your codebase.

Next Steps

WASM Plugins

Compile-time plugins for performance

Financial Compliance

Real-world hot-reload use case

Plugin Guide

Complete plugin system guide

Rhai Reference

Rhai scripting API reference

Build docs developers (and LLMs) love