A concurrent Rust library for building multi-agent systems with lock-free CPU/GPU hybrid execution.
multi-agent-engine is a library designed for building applications based on multi-agent systems (MAS), agent-based models (ABM), and agent-oriented programming (AOP) paradigms.
It provides a ready-to-use pipeline and tooling for generic use cases, with native support for multithreaded CPU execution and GPU acceleration.
While the engine excels at simulations, its architecture is general-purpose and can power any application requiring concurrent processing with responsive user interaction: games, real-time data pipelines, robotics controllers, and more.
The engine follows a multi-thread architecture that separates system logic from user interaction, enabling independent operation at different frequencies while maintaining efficient data synchronization.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Multi-Agent Engine β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β
β β Controller A β β Controller B β ... β Controller N β β
β β Thread (60 Hz) β β Thread (30 Hz) β β Thread (10 Hz) β β
β β β β β β β β
β β - User Input β β - Logging β β - Network I/O β β
β β - Rendering β β - Metrics β β - Remote Control β β
β β - UI Logic β β - Recording β β - Sync β β
β ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Config Channels (ArcSwap) β β
β β βββββββββββββ βββββββββββββ βββββββββββββ β β
β β β Config A β β Config B β ... β Config N β β β
β β βββββββββββββ βββββββββββββ βββββββββββββ β β
β β Controllers β System (independent configs) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β ββββββββββββββββββββ β
β β System Thread β β
β β (e.g., 30 Hz) β β
β β β β
β β - Agent Logic β β
β β - Physics β β
β β - Processing β β
β ββββββββββ¬ββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β State (ArcSwap) β β
β β System β Controllers β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Message Queues (per Controller) β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
β β β Controller A β β β Controller B β β β Controller N β β β β
β β β System β β System β β System β β β
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββBy default, the engine spawns dedicated threads for the System and each Controller. However, you can configure the engine to run either the System or one Controller on the engine's main thread. This is useful for:
- GUI frameworks that require the main thread for rendering.
- Reducing thread count in resource-constrained environments.
- Integration with existing event loops.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Thread Hosting Modes β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Mode 1: Default (all separate threads)
βββββββββββββββββββββββββββββββββββββββ
Engine Thread: idle/management only
Controller A: dedicated thread
Controller B: dedicated thread
System: dedicated thread
Mode 2: System on engine thread
βββββββββββββββββββββββββββββββββββββββ
Engine Thread: runs System
Controller A: dedicated thread
Controller B: dedicated thread
Mode 3: Controller on engine thread
βββββββββββββββββββββββββββββββββββββββ
Engine Thread: runs Controller A (e.g., GUI)
Controller B: dedicated thread
System: dedicated threadYou must implement two core traits to connect with the engine:
Handles user interaction, input processing, and visualization. Each Controller provides its own typed Config to the System.
trait Controller {
type Config;
type OutgoingMessage;
type IncomingMessage;
fn initialize(&mut self, initial_state: &State);
fn update(&mut self, state: &State) -> Option<Self::Config>;
fn handle_messages(&mut self, messages: Vec<Self::IncomingMessage>);
}Contains the core processing logic, agent behaviors, and computation. The System can read from multiple independent Configs.
trait System {
fn initialize(&mut self);
fn tick(&mut self, configs: &Configs) -> State;
fn handle_messages(&mut self, messages: Vec<ControllerMessage>);
}Each Controller defines and provides its own configuration type. The System reads these independently.
// Controller A's config
struct InputConfig {
// e.g., mouse_position, keyboard_state, selected_tool
}
// Controller B's config
struct SimulationConfig {
// e.g., time_scale, agent_count, environment_params
}
// Controller C's config
struct NetworkConfig {
// e.g., sync_rate, peer_list, connection_state
}Current system state, shared with all Controllers (System β Controllers).
struct State {
// User-defined fields
// e.g., agent_positions, environment_state, statistics
}The System and all Controllers operate in separate threads that never block each other. This design enables:
- Asynchronous execution: Each thread runs at its own frequency
- Responsive UI: Controllers remain responsive even during heavy processing
- Scalable performance: System can utilize multiple cores independently
- Modular design: Controllers can be added or removed without affecting others
Timeline Example (2 Controllers + System):
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ>
Controller A (60 Hz): |βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β
Controller B (20 Hz): |βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β
System (30 Hz): |βββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β
tick tick tick tick tick tick
β = frame/tick executionThreads share data through lock-free atomic swap (ArcSwap). Each Controller maintains its own Config channel, while all Controllers read from a shared State.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Flow Diagram β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Controller A Controller B Controller N
ββββββββββββ ββββββββββββ ββββββββββββ
WRITE WRITE WRITE
β β β
βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ
β Config A β β Config B β β Config N β
ββββββββββββ ββββββββββββ ββββββββββββ
β β β
ββββββββββββββββ¬βββββββ΄ββββββββββββββββββββββ
β
ArcSwap (per config)
β
βΌ
βββββββββββββββ
β System β β Reads configs selectively
βββββββββββββββ
β
WRITE
β
βΌ
βββββββββββββββ
β State β
βββββββββββββββ
β
ArcSwap (shared)
β
ββββββββββββββββΌβββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
READ READ READ
ββββββββββββ ββββββββββββ ββββββββββββ
β Ctrl A β β Ctrl B β ... β Ctrl N β
ββββββββββββ ββββββββββββ ββββββββββββAccess Patterns:
| Thread | Own Config | Other Configs | State |
|---|---|---|---|
| System | N/A | Read-only | Write-only |
| Controller A | Write-only | None | Read-only |
| Controller B | Write-only | None | Read-only |
| Controller N | Write-only | None | Read-only |
This design ensures:
- β No locks or mutexes needed for state sharing
- β Zero blocking between threads
- β Data race freedom through clear ownership
- β Independent configs per Controller with typed safety
In addition to shared data, threads communicate through typed message queues. Each Controller has its own bidirectional message channel with the System.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Message Communication β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Controller A System
ββββββββββββ ββββββ
ββββ [Outgoing Queue A] βββββββββββββ handle_messages()
β - UserAction β
β - ToolSelected βΌ
β - SpawnRequest Process & Execute
β β
β β
βββ [Incoming Queue A] ββββββββββββββββββββββ€
- SelectionResult β
- ActionFeedback β
β
Controller B β
ββββββββββββ β
ββββ [Outgoing Queue B] βββββββββββββββββββββ€
β - StartRecording β
β - SetMarker β
β β
βββ [Incoming Queue B] ββββββββββββββββββββββ€
- RecordingStats β
- FrameCaptured β
β
Controller N β
ββββββββββββ β
ββββ [Outgoing Queue N] βββββββββββββββββββββ€
β - SyncRequest β
β - PeerUpdate β
β β
βββ [Incoming Queue N] ββββββββββββββββββββββ
- SyncComplete
- PeerStatus// Controller A (UI) β System
enum UIControllerMessage {
Pause,
Resume,
Reset,
SpawnAgent { position: Vec3, agent_type: AgentType },
SelectEntity(EntityId),
}
// System β Controller A (UI)
enum UISystemMessage {
SelectionResult { entity: Option<EntityInfo> },
AgentSpawned { id: AgentId },
ActionFeedback { success: bool, message: String },
}
// Controller B (Metrics) β System
enum MetricsControllerMessage {
RequestSnapshot,
SetSamplingRate(f32),
}
// System β Controller B (Metrics)
enum MetricsSystemMessage {
Snapshot { tick: u64, data: SystemStats },
SamplingRateConfirmed(f32),
}The System supports flexible compute allocation between CPU and GPU, enabling efficient execution of agent behaviors and physics on appropriate hardware.
ββββββββββββββββββββββββββββββββββββββββββββββββ
β Multi-Layer Processing Pipeline β
ββββββββββββββββββββββββββββββββββββββββββββββββ
Input State
β
βΌ
ββββββ΄βββββ
β Layer 1 β β CPU: Agent Decision Making
β (CPU) β - Perception processing
ββββββ¬βββββ - Behavior selection
βΌ output1
ββββββ΄βββββ
β Layer 2 β β GPU: Spatial Queries
β (GPU) β - Neighbor detection
ββββββ¬βββββ - Collision detection
βΌ output2
ββββββ΄βββββ
β Layer 3 β β GPU: Physics Simulation
β (GPU) β - Force calculations
ββββββ¬βββββ - Position updates
βΌ output3
ββββββ΄βββββ
β Layer 4 β β CPU: Environment Update
β (CPU) β - Resource distribution
ββββββ¬βββββ - Event handling
βΌ output4
β
Final StateEach agent's behavior can be individually executed on GPU, enabling:
- Massive parallelization for homogeneous agent populations
- Efficient SIMD operations for agent computations
- Scalability to thousands or millions of agents
Agents: A1, A2, A3, A4, ..., An
CPU Execution GPU Execution
βββββββββββββ βββββββββββββ
A1 β compute ββββββββββββββββββββ
β β A1 A2 A3 A4 ... β
A2 β compute VS β β β β β β
β β Parallel Compute β
A3 β compute β β β β β β
β ββββββββββββββββββββ
...
(Sequential) (Parallel)// 1. Define your data structures
struct MyConfig {
/* ... */
}
struct MyState {
/* ... */
}
// 2. Define your message types
enum MyControllerMessage { /* ... */ }
enum MySystemMessage { /* ... */ }
// 3. Implement the required traits
struct MyController {
/* ... */
}
impl Controller for MyController {
type Config = MyConfig;
type OutgoingMessage = MyControllerMessage;
type IncomingMessage = MySystemMessage;
// ...
}
struct MySystem {
/* ... */
}
impl System for MySystem { /* ... */ }
fn main() {
// 4. Build and run the engine
let engine = MultiAgentEngine::builder()
.with_controller(MyController::new())
.with_system(MySystem::new())
.build();
engine.run();
}// Define multiple controllers with different responsibilities
struct RenderController {
/* ... */
}
struct MetricsController {
/* ... */
}
struct NetworkController {
/* ... */
}
// Each with its own Config type
struct RenderConfig {
/* ... */
}
struct MetricsConfig {
/* ... */
}
struct NetworkConfig {
/* ... */
}
fn main() {
let engine = MultiAgentEngine::builder()
.with_controller(RenderController::new())
.with_controller(MetricsController::new())
.with_controller(NetworkController::new())
.with_system(MySystem::new())
.build();
engine.run();
}fn main() {
// Run the render controller on the main thread (required by some GUI frameworks)
let engine = MultiAgentEngine::builder()
.with_controller_on_engine_thread(RenderController::new())
.with_controller(MetricsController::new())
.with_system(MySystem::new())
.build();
engine.run(); // RenderController runs here, others in spawned threads
}fn main() {
// Or run the System on the main thread
let engine = MultiAgentEngine::builder()
.with_controller(RenderController::new())
.with_controller(MetricsController::new())
.with_system_on_engine_thread(MySystem::new())
.build();
engine.run(); // System runs here, Controllers in spawned threads
}See LICENSE-APACHE and LICENSE-MIT for details.