Skip to main content

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}