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
main.rs
sample_plugin.rhai
Cargo.toml
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
cd examples/rhai_hot_reload
While the program is running, edit sample_plugin.rhai:
fn execute(input) {
"[UPDATED] Modified: " + input.to_upper()
}
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
plugin . load ( & ctx ) . await ? ;
plugin . init_plugin () . await ? ;
Prepare plugin for execution
let result = plugin . execute ( input ) . await ? ;
Reload from file (hot reload)
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
Variables
Functions
Control Flow
Loops
let x = 42;
let name = "MoFA";
let is_active = true;
fn process(input) {
let result = input.to_upper();
"Processed: " + result
}
if value > 100 {
"high"
} else if value > 50 {
"medium"
} else {
"low"
}
let sum = 0;
for i in 0..10 {
sum += i;
}
sum
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
Syntax Error After Reload
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