The module system (helix-modules, 9 source files , ~3,800 lines ) provides a pluggable, hot-reloadable component framework for the kernel. Modules can implement schedulers, allocators, filesystems, drivers, and more — all loadable and replaceable at runtime.
Two API versions — v1 (trait-based) and v2 (const metadata + event-driven)
Hot-reload — replace module binaries without downtime, preserving state
ABI versioning — compatibility checking prevents loading incompatible modules
Dependency resolution — automatic topological sort with cycle detection
Module registry — lookup by ID, name, or capability
Interface system — type-erased message passing between modules
Three binary formats — ELF, HelixNative, WebAssembly (future)
Module System — Source Tree 10N · 9E ▶ modules/src/ Module system core 9 lib.rs Module trait v1, Mod… 1 abi.rs AbiVersion, AbiCheck… 1 dependencies.rs DependencyGraph, cyc… 1 hot_reload.rs HotReloadEngine, Rel… 1 interface.rs ModuleMessage, Inter… 1 loader.rs ModuleFormat, ElfLoa… 1 registry.rs ModuleRegistry — nam… 1 v2.rs ModuleTrait v2, Cont… 1 v2_tests.rs Lifecycle tests for … 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
The original module API used by most existing modules:
pub trait Module : Send + Sync {
/// Static metadata about this module.
fn metadata ( & self ) -> & ModuleMetadata ;
/// Initialize the module. Called once after loading.
fn init ( & mut self , ctx : & ModuleContext ) -> Result < ( ) , ModuleError > ;
/// Start the module. Called after all dependencies are initialized.
fn start ( & mut self ) -> Result < ( ) , ModuleError > ;
/// Stop the module. Called before unloading.
fn stop ( & mut self ) -> Result < ( ) , ModuleError > ;
/// Clean up all resources.
fn cleanup ( & mut self ) -> Result < ( ) , ModuleError > ;
/// Return true if the module is functioning correctly.
fn is_healthy ( & self ) -> bool ;
/// Serialize current state for hot-reload.
fn get_state ( & self ) -> Option < ModuleState > { None }
/// Restore state from a previous instance.
fn restore_state ( & mut self , _state : ModuleState ) -> Result < ( ) , ModuleError > {
Err ( ModuleError :: NotSupported )
/// Handle an incoming message from another module.
fn handle_message ( & mut self , _msg : ModuleMessage ) -> Result < ModuleMessage , ModuleError > {
Err ( ModuleError :: NotSupported )
Index Module metadata init start stop cleanup is_healthy get_state restore_state handle_message
pub struct ModuleMetadata {
pub version : & 'static str ,
pub author : & 'static str ,
pub description : & 'static str ,
pub dependencies : Vec < ModuleDependency > ,
pub abi_version : AbiVersion ,
pub struct ModuleFlags : u32 {
const ESSENTIAL = 1 << 0 ; // Cannot be unloaded
const HOT_RELOADABLE = 1 << 1 ; // Supports live replacement
const USERSPACE = 1 << 2 ; // Runs in user mode
const DRIVER = 1 << 3 ; // Hardware driver
const FILESYSTEM = 1 << 4 ; // Filesystem implementation
const SCHEDULER = 1 << 5 ; // Scheduling algorithm
const ALLOCATOR = 1 << 6 ; // Memory allocator
const SECURITY = 1 << 7 ; // Security module
description : "Example module" ,
flags : ModuleFlags :: HOT_RELOADABLE ,
dependencies : [ ( "helix-hal" , ">=0.4.0" ) ] ,
The macro generates the Module implementation boilerplate and exports the module entry point symbol.
The v2 API uses const metadata (computed at compile time) and an event-driven model:
pub trait ModuleTrait : Send + Sync {
/// Const metadata — no runtime allocation.
fn info ( & self ) -> ModuleInfo ;
fn init ( & mut self , ctx : & Context ) -> Result < ( ) , ModuleError > ;
fn start ( & mut self ) -> Result < ( ) , ModuleError > ;
fn stop ( & mut self ) -> Result < ( ) , ModuleError > ;
/// Handle kernel events (tick, shutdown, memory pressure, etc.)
fn handle_event ( & mut self , _event : & Event ) -> EventResponse {
/// Handle a typed request from another module.
fn handle_request ( & mut self , _request : & Request ) -> Result < Response , ModuleError > {
Ok ( Response :: err ( "Not implemented" ) )
fn is_healthy ( & self ) -> bool { true }
fn save_state ( & self ) -> Option < Vec < u8 >> { None }
fn restore_state ( & mut self , _state : & [ u8 ] ) -> Result < ( ) , ModuleError > { Ok ( ( ) ) }
Index ModuleTrait info init start stop handle_event handle_request is_healthy save_state restore_state
pub version : ModuleVersion ,
pub description : & 'static str ,
pub author : & 'static str ,
pub license : & 'static str ,
pub dependencies : & 'static [ & 'static str ] ,
pub provides : & 'static [ & 'static str ] ,
// Const builder — metadata computed at compile time
const INFO : ModuleInfo = ModuleInfo :: new ( "round_robin_scheduler" )
. description ( "Round-robin scheduling module" )
. flags ( ModuleFlags :: SCHEDULER . union ( ModuleFlags :: HOT_RELOADABLE ) ) ;
Tick { timestamp_ns : u64 } ,
MemoryPressure { level : MemoryPressureLevel } ,
CpuHotplug { cpu_id : u32 , online : bool } ,
Custom { name : String , data : Vec < u8 > } ,
The ModuleAdapter wraps a v2 module to present a v1 Module interface, enabling gradual migration:
modules/src/v2/adapter.rs
pub struct ModuleAdapter < T : ModuleTrait > {
impl < T : ModuleTrait > Module for ModuleAdapter < T > {
// Delegates to T's ModuleTrait methods
Index ModuleAdapter ModuleAdapter
The ABI system ensures binary compatibility between module versions.
pub major : u16 , // Incompatible changes
pub minor : u16 , // Backward-compatible additions
pub patch : u16 , // Bug fixes only
Old → New Compatible? Reason 1.0.0 → 1.0.1 Yes Patch bump 1.0.0 → 1.1.0 Yes Minor bump (additive) 1.0.0 → 2.0.0 No Major bump (breaking) 1.2.0 → 1.1.0 No Minor downgrade
pub symbols : Vec < SymbolEntry > ,
pub kind : SymbolKind , // Function, Variable, Type
Index SymbolTable SymbolEntry
The AbiChecker compares old and new symbol tables during hot-reload:
All non-deprecated symbols from the old version must exist in the new version
Function signatures must be compatible (same argument count and types)
New symbols may be added (minor version bump)
The dependency system uses a directed acyclic graph (DAG) to manage module load order.
modules/src/dependency.rs
pub struct DependencyGraph {
pub fn add_module ( & mut self , id : ModuleId , deps : & [ ModuleDependency ] ) ;
pub fn remove_module ( & mut self , id : ModuleId ) ;
pub fn topological_sort ( & self ) -> Result < Vec < ModuleId > , CycleError > ;
pub fn has_cycle ( & self ) -> bool ;
pub fn dependents ( & self , id : ModuleId ) -> Vec < ModuleId > ;
pub fn dependencies ( & self , id : ModuleId ) -> Vec < ModuleId > ;
Index DependencyGraph add_module remove_module topological_sort has_cycle dependents dependencies
modules/src/dependency.rs
pub struct ModuleDependency {
pub version_constraint : & 'static str , // ">=0.4.0", "^1.0", "=2.0.0"
The DependencyResolver verifies that all version constraints are satisfiable before loading begins. If not, it reports the conflicting requirements.
The graph uses Depth-First Search with three colors (white/gray/black) to detect cycles. If a gray node is encountered during traversal, a cycle exists and the offending path is reported.
The hot-reload engine manages the full lifecycle of replacing a running module.
Hot-Reload Engine — State Machine 8N · 7E success failure ▶ Idle Waiting for reload t… 1 Preparing Notify dependents, q… 2 SavingState Call get_state() on … 2 Unloading Remove old binary fr… 2 Loading Load new binary, ver… 2 RestoringState Call restore_state()… 3 ■ Completed Resume normal operat… 1 ■ RollbackFailed Recovery failed 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
modules/src/hot_reload.rs
pub struct HotReloadEngine {
pub fn reload ( & mut self , module_id : ModuleId ,
new_binary : & [ u8 ] ) -> Result < ReloadResult > ;
pub fn state ( & self ) -> ReloadState ;
pub fn cancel ( & mut self ) -> Result < ( ) > ;
pub struct ReloadResult {
pub old_version : AbiVersion ,
pub new_version : AbiVersion ,
pub state_preserved : bool ,
Index HotReloadEngine reload state cancel ReloadResult
If the new module fails to initialize or restore state:
The engine attempts to reload the old binary
Old state is restored from the saved snapshot
If rollback also fails, the module is marked as RollbackFailed and the self-healing system is notified
The ModuleRegistry provides fast lookup by multiple keys:
pub struct ModuleRegistry {
// Primary: ModuleId → Module
modules : BTreeMap < ModuleId , Box < dyn Module >> ,
// Index: name → ModuleId
by_name : BTreeMap < String , ModuleId > ,
// Index: capability → Vec<ModuleId>
by_capability : BTreeMap < ModuleFlags , Vec < ModuleId >> ,
pub fn register ( & mut self , module : Box < dyn Module > ) -> Result < ModuleId > ;
pub fn unregister ( & mut self , id : ModuleId ) -> Result < Box < dyn Module >> ;
pub fn get ( & self , id : ModuleId ) -> Option < & dyn Module > ;
pub fn get_mut ( & mut self , id : ModuleId ) -> Option < & mut dyn Module > ;
pub fn find_by_name ( & self , name : & str ) -> Option < ModuleId > ;
pub fn find_by_capability ( & self , flag : ModuleFlags ) -> Vec < ModuleId > ;
pub fn all ( & self ) -> Vec < ModuleId > ;
pub fn count ( & self ) -> usize ;
Index ModuleRegistry ModuleRegistry register unregister get get_mut find_by_name find_by_capability all count
The loader system supports multiple binary formats:
Elf , // Standard ELF64 shared object
HelixNative , // Helix-specific optimized format
Wasm , // WebAssembly (future)
pub trait ModuleLoader : Send + Sync {
fn format ( & self ) -> ModuleFormat ;
fn can_load ( & self , data : & [ u8 ] ) -> bool ;
fn load ( & self , data : & [ u8 ] ) -> Result < LoadedModule , LoadError > ;
fn unload ( & self , module : & LoadedModule ) -> Result < ( ) > ;
pub struct LoadedModule {
pub symbols : SymbolTable ,
Index ModuleLoader format can_load load unload LoadedModule
pub struct LoaderRegistry {
loaders : Vec < Box < dyn ModuleLoader >> ,
pub fn register ( & mut self , loader : Box < dyn ModuleLoader > ) ;
pub fn load ( & self , data : & [ u8 ] ) -> Result < LoadedModule > ; // Auto-detects format
Index LoaderRegistry LoaderRegistry register load
Modules communicate through a type-erased message-passing interface.
pub struct ModuleMessage {
pub msg_type : MessageType ,
pub payload : MessagePayload ,
pub enum MessagePayload {
Typed ( Box < dyn Any + Send > ) ,
Index ModuleMessage MessageType MessagePayload
pub trait ModuleInterface : Send + Sync {
fn interface_name ( & self ) -> & 'static str ;
fn handle_message ( & mut self , msg : ModuleMessage ) -> Result < ModuleMessage > ;
Index ModuleInterface interface_name handle_message
Interface Name Purpose Methods schedulerThread scheduling add_thread, remove, next, yield, tick allocatorMemory allocation alloc, dealloc, realloc, stats filesystemFile operations open, close, read, write, stat block_deviceBlock I/O read_block, write_block, flush networkNetwork stack send, recv, bind, listen securityAccess control check, grant, revoke
pub struct InterfaceRegistry {
pub fn register ( & mut self , name : & str ,
interface : Box < dyn ModuleInterface > ) -> Result < ( ) > ;
pub fn unregister ( & mut self , name : & str ) -> Result < ( ) > ;
pub fn get ( & self , name : & str ) -> Option < & dyn ModuleInterface > ;
pub fn get_mut ( & mut self , name : & str ) -> Option < & mut dyn ModuleInterface > ;
pub fn list ( & self ) -> Vec < & str > ;
Index InterfaceRegistry register unregister get get_mut list
The helix-scheduler-round-robin crate (modules_impl/schedulers/round_robin/, 3 files , ~400 lines ) is a complete example of a v2 module implementing the Scheduler trait.
Round-Robin Scheduler — Source Tree 4N · 3E ▶ round_robin/ Round-Robin schedule… 3 lib.rs RoundRobinModule — v… 1 config.rs RoundRobinConfig — t… 1 scheduler.rs RoundRobinScheduler … 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
modules_impl/schedulers/round_robin/src/lib.rs
pub struct RoundRobinConfig {
pub base_time_slice_ms : u64 , // Default: 10ms
pub priority_scaling : bool , // Scale time slice by priority?
pub load_balance_interval : u64 , // Ticks between load balancing
pub min_time_slice_ms : u64 , // Minimum quantum
pub max_time_slice_ms : u64 , // Maximum quantum
When priority_scaling is enabled, higher-priority threads get longer time slices:
Priority 0-30: 0.5x base (5 ms)
Priority 31-60: 1.0x base (10 ms)
Priority 61-90: 1.5x base (15 ms)
Priority 91+: 2.0x base (20 ms)
Each CPU has a FIFO queue of ready threads
On each timer tick, decrement the current thread's remaining quantum
When quantum expires, move the current thread to the back of the queue
Pick the next thread from the front of the queue
Periodically, load balance : migrate threads from overloaded CPUs to underloaded ones