feagi_npu_plasticity/executor.rs
1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Plasticity Executor Abstraction Layer
5//!
6//! Provides backend-agnostic execution models for plasticity computation:
7//! - AsyncPlasticityExecutor: For std environments (CPU, CUDA, WebGPU)
8//! - SyncPlasticityExecutor: For no_std environments (RTOS, embedded) [Future]
9//!
10//! This abstraction allows FEAGI to run plasticity optimally on different backends
11//! without code duplication or runtime overhead.
12
13use crate::memory_stats_cache::MemoryStatsCache;
14use crate::service::{PlasticityCommand, PlasticityConfig, PlasticityService};
15use std::sync::{Arc, Mutex};
16use tracing::{debug, warn};
17
18/// Trait for executing plasticity computation
19///
20/// Implementations of this trait define how plasticity is executed on different backends.
21/// - Async implementations run plasticity in a background thread (std environments)
22/// - Sync implementations run plasticity inline in burst loop (no_std environments)
23pub trait PlasticityExecutor: Send + Sync {
24 /// Notify the executor of a completed burst
25 ///
26 /// # Arguments
27 /// * `timestep` - The timestep/burst count that just completed
28 fn notify_burst(&self, timestep: u64);
29
30 /// Drain pending commands from the executor
31 ///
32 /// Commands are generated by plasticity computation and need to be
33 /// applied to the NPU (e.g., inject memory neurons to FCL).
34 ///
35 /// # Returns
36 /// Vector of plasticity commands to execute
37 fn drain_commands(&self) -> Vec<PlasticityCommand>;
38
39 /// Register a memory area with the plasticity system
40 ///
41 /// # Arguments
42 /// * `area_idx` - Cortical area index
43 /// * `area_name` - Human-readable name for the area
44 /// * `temporal_depth` - Number of historical timesteps to consider
45 /// * `upstream_areas` - Cortical indices of areas that project to this memory area
46 /// * `lifecycle_config` - Optional lifecycle configuration for memory neurons
47 fn register_memory_area(
48 &self,
49 area_idx: u32,
50 area_name: String,
51 temporal_depth: u32,
52 upstream_areas: Vec<u32>,
53 lifecycle_config: Option<crate::MemoryNeuronLifecycleConfig>,
54 );
55
56 /// Start the executor (for async implementations)
57 ///
58 /// Default implementation is no-op (for sync implementations).
59 fn start(&mut self) {}
60
61 /// Stop the executor (for async implementations)
62 ///
63 /// Default implementation is no-op (for sync implementations).
64 fn stop(&mut self) {}
65
66 /// Check if the executor is running
67 fn is_running(&self) -> bool {
68 false // Default for sync implementations
69 }
70}
71
72/// Async plasticity executor for std environments
73///
74/// Runs plasticity computation in a background thread, allowing the burst loop
75/// to continue processing while patterns are detected and memory neurons created.
76///
77/// This is optimal for CPU/GPU backends where:
78/// - FireLedger is CPU-resident
79/// - Plasticity computation can overlap with GPU burst processing
80/// - Thread overhead is acceptable
81pub struct AsyncPlasticityExecutor {
82 /// The plasticity service instance
83 service: Arc<Mutex<Option<PlasticityService>>>,
84
85 /// Memory stats cache (shared with health check)
86 memory_stats_cache: MemoryStatsCache,
87
88 /// Configuration for the plasticity service
89 _config: PlasticityConfig,
90
91 /// Running state
92 running: bool,
93}
94
95impl AsyncPlasticityExecutor {
96 /// Create a new async plasticity executor
97 pub fn new(
98 config: PlasticityConfig,
99 memory_stats_cache: MemoryStatsCache,
100 npu: Arc<feagi_npu_burst_engine::TracingMutex<feagi_npu_burst_engine::DynamicNPU>>,
101 ) -> Self {
102 let service = PlasticityService::new(config.clone(), memory_stats_cache.clone(), npu);
103
104 Self {
105 service: Arc::new(Mutex::new(Some(service))),
106 memory_stats_cache,
107 _config: config,
108 running: false,
109 }
110 }
111
112 /// Get the memory stats cache
113 pub fn get_memory_stats_cache(&self) -> MemoryStatsCache {
114 self.memory_stats_cache.clone()
115 }
116
117 pub fn enqueue_commands_for_test(&self, commands: Vec<crate::PlasticityCommand>) {
118 if let Some(service) = self.service.lock().unwrap().as_ref() {
119 service.enqueue_commands_for_test(commands);
120 }
121 }
122}
123
124impl PlasticityExecutor for AsyncPlasticityExecutor {
125 fn notify_burst(&self, timestep: u64) {
126 // trace!("[PLASTICITY-EXEC] 🔔 notify_burst({}) called", timestep);
127 if let Some(service) = self.service.lock().unwrap().as_ref() {
128 service.notify_burst(timestep);
129 } else {
130 warn!("[PLASTICITY-EXEC] ⚠️ Service is None, cannot notify");
131 }
132 }
133
134 fn drain_commands(&self) -> Vec<PlasticityCommand> {
135 if let Some(service) = self.service.lock().unwrap().as_ref() {
136 let drained = service.drain_commands();
137 if !drained.is_empty() {
138 debug!(
139 target: "plasticity",
140 "[PLASTICITY-EXEC] Drained {} command(s) from executor",
141 drained.len()
142 );
143 for command in &drained {
144 match command {
145 PlasticityCommand::RegisterMemoryNeuron {
146 neuron_id,
147 area_idx,
148 ..
149 } => {
150 debug!(
151 target: "plasticity",
152 "[PLASTICITY-EXEC] RegisterMemoryNeuron area={} neuron_id={}",
153 area_idx,
154 neuron_id
155 );
156 }
157 PlasticityCommand::MemoryNeuronConvertedToLtm {
158 neuron_id,
159 area_idx,
160 ..
161 } => {
162 debug!(
163 target: "plasticity",
164 "[PLASTICITY-EXEC] MemoryNeuronConvertedToLtm area={} neuron_id={}",
165 area_idx,
166 neuron_id
167 );
168 }
169 PlasticityCommand::InjectMemoryNeuronToFCL {
170 neuron_id,
171 area_idx,
172 is_reactivation,
173 replay_frames,
174 ..
175 } => {
176 debug!(
177 target: "plasticity",
178 "[PLASTICITY-EXEC] InjectMemoryNeuronToFCL area={} neuron_id={} reactivation={} replay_frames={}",
179 area_idx,
180 neuron_id,
181 is_reactivation,
182 replay_frames.len()
183 );
184 }
185 PlasticityCommand::UpdateWeightsDelta { .. } => {}
186 PlasticityCommand::UpdateStateCounters { .. } => {}
187 }
188 }
189 }
190 drained
191 } else {
192 Vec::new()
193 }
194 }
195
196 fn register_memory_area(
197 &self,
198 area_idx: u32,
199 area_name: String,
200 temporal_depth: u32,
201 upstream_areas: Vec<u32>,
202 lifecycle_config: Option<crate::MemoryNeuronLifecycleConfig>,
203 ) {
204 if let Some(service) = self.service.lock().unwrap().as_mut() {
205 service.register_memory_area(
206 area_idx,
207 area_name,
208 temporal_depth,
209 upstream_areas,
210 lifecycle_config,
211 );
212 }
213 }
214
215 fn start(&mut self) {
216 if self.running {
217 tracing::warn!(target: "plasticity", "⚠️ PlasticityExecutor already running");
218 return;
219 }
220
221 if let Some(service) = self.service.lock().unwrap().as_ref() {
222 tracing::info!(target: "plasticity", "🚀 Initializing AsyncPlasticityExecutor...");
223 service.start();
224 self.running = true;
225 tracing::info!(target: "plasticity",
226 "✅ AsyncPlasticityExecutor started successfully - ready to monitor memory areas and process STDP"
227 );
228 } else {
229 tracing::error!(target: "plasticity", "❌ Failed to start PlasticityExecutor - service not initialized");
230 }
231 }
232
233 fn stop(&mut self) {
234 if !self.running {
235 return;
236 }
237
238 if let Some(service) = self.service.lock().unwrap().as_ref() {
239 service.stop();
240 self.running = false;
241 tracing::info!("Stopped async plasticity executor");
242 }
243 }
244
245 fn is_running(&self) -> bool {
246 self.running
247 }
248}
249
250// ============================================================================
251// FUTURE: Sync Executor for no_std Environments
252// ============================================================================
253
254/// Sync plasticity executor for no_std environments
255///
256/// **NOT YET IMPLEMENTED** - Placeholder for future embedded/RTOS support.
257///
258/// This would run plasticity computation synchronously in the burst loop,
259/// blocking until pattern detection and memory neuron creation completes.
260///
261/// Optimal for embedded environments where:
262/// - No std::thread available
263/// - Memory is constrained (no separate thread stack)
264/// - Deterministic timing is critical
265///
266/// To implement:
267/// 1. Create no_std-compatible PlasticityService variant
268/// 2. Use spin::Mutex or critical_section::Mutex
269/// 3. Call compute_plasticity() directly in execute()
270#[cfg(not(feature = "std"))]
271pub struct SyncPlasticityExecutor {
272 // TODO: Implement for embedded support
273 _marker: core::marker::PhantomData<()>,
274}
275
276#[cfg(not(feature = "std"))]
277impl PlasticityExecutor for SyncPlasticityExecutor {
278 fn notify_burst(&self, _timestep: u64) {
279 unimplemented!("SyncPlasticityExecutor not yet implemented");
280 }
281
282 fn drain_commands(&self) -> Vec<PlasticityCommand> {
283 unimplemented!("SyncPlasticityExecutor not yet implemented");
284 }
285
286 fn register_memory_area(
287 &self,
288 _area_idx: u32,
289 _area_name: String,
290 _temporal_depth: u32,
291 _upstream_areas: Vec<u32>,
292 _lifecycle_config: Option<crate::MemoryNeuronLifecycleConfig>,
293 ) {
294 unimplemented!("SyncPlasticityExecutor not yet implemented");
295 }
296}