strange_loop/nano_agent/
scheduler.rs

1//! Nano-agent scheduler with budget enforcement
2
3use super::{NanoAgent, NanoBus, TickResult, NanoMetrics, SchedulerTopology, rdtsc, spin};
4use std::time::{Instant, Duration};
5use std::collections::VecDeque;
6
7/// Configuration for the nano-scheduler
8pub struct SchedulerConfig {
9    pub topology: SchedulerTopology,
10    pub run_duration_ns: u128,
11    pub tick_duration_ns: u128,
12    pub max_agents: usize,
13    pub bus_capacity: usize,
14    pub enable_tracing: bool,
15}
16
17impl Default for SchedulerConfig {
18    fn default() -> Self {
19        Self {
20            topology: SchedulerTopology::RoundRobin,
21            run_duration_ns: 1_000_000_000, // 1 second
22            tick_duration_ns: 1_000_000,     // 1ms per macro-tick
23            max_agents: 1024,
24            bus_capacity: 65536,
25            enable_tracing: false,
26        }
27    }
28}
29
30/// Agent wrapper with budget and metrics
31struct AgentSlot {
32    agent: Box<dyn NanoAgent>,
33    budget_ns: u128,
34    priority: u32,
35    metrics: NanoMetrics,
36}
37
38/// Nano-scheduler for agent orchestration
39pub struct NanoScheduler {
40    agents: Vec<AgentSlot>,
41    bus: NanoBus,
42    config: SchedulerConfig,
43    start_time: Instant,
44    traces: VecDeque<AgentTrace>,
45}
46
47/// Trace entry for agent execution
48#[derive(Debug, Clone)]
49pub struct AgentTrace {
50    pub agent_name: &'static str,
51    pub timestamp_ns: u128,
52    pub result: TickResult,
53}
54
55impl NanoScheduler {
56    /// Create a new scheduler with configuration
57    pub fn new(config: SchedulerConfig) -> Self {
58        let bus = NanoBus::new(config.bus_capacity);
59        Self {
60            agents: Vec::with_capacity(config.max_agents),
61            bus,
62            config,
63            start_time: Instant::now(),
64            traces: VecDeque::with_capacity(10000),
65        }
66    }
67
68    /// Register an agent with its budget
69    pub fn register<A: NanoAgent + 'static>(&mut self, agent: A) {
70        let budget_ns = agent.budget_ns();
71        let slot = AgentSlot {
72            agent: Box::new(agent),
73            budget_ns,
74            priority: 0,
75            metrics: NanoMetrics::new(),
76        };
77        self.agents.push(slot);
78    }
79
80    /// Register an agent with custom priority
81    pub fn register_with_priority<A: NanoAgent + 'static>(
82        &mut self,
83        agent: A,
84        priority: u32,
85    ) {
86        let budget_ns = agent.budget_ns();
87        let slot = AgentSlot {
88            agent: Box::new(agent),
89            budget_ns,
90            priority,
91            metrics: NanoMetrics::new(),
92        };
93        self.agents.push(slot);
94    }
95
96    /// Get the number of registered agents
97    pub fn agent_count(&self) -> usize {
98        self.agents.len()
99    }
100
101    /// Add an agent using a boxed trait object
102    pub fn add_agent(&mut self, agent: Box<dyn NanoAgent>) {
103        let budget_ns = agent.budget_ns();
104        let slot = AgentSlot {
105            agent,
106            budget_ns,
107            priority: 0,
108            metrics: NanoMetrics::new(),
109        };
110        self.agents.push(slot);
111    }
112
113    /// Run the scheduler for the configured duration
114    pub fn run(mut self) -> SchedulerStats {
115        let start = Instant::now();
116        let mut now_ns = || start.elapsed().as_nanos();
117        let run_budget = self.config.run_duration_ns;
118
119        // Sort agents by priority if needed
120        if matches!(self.config.topology, SchedulerTopology::Priority) {
121            self.agents.sort_by_key(|a| std::cmp::Reverse(a.priority));
122        }
123
124        let mut total_ticks = 0u64;
125        let mut total_cycles = 0u64;
126        let mut budget_violations = 0u64;
127
128        // Main scheduler loop
129        while now_ns() < run_budget {
130            let macro_tick_start = now_ns();
131
132            // Execute all agents based on topology
133            match self.config.topology {
134                SchedulerTopology::RoundRobin => {
135                    self.execute_round_robin(|| now_ns(), &mut total_ticks, &mut total_cycles, &mut budget_violations);
136                }
137                SchedulerTopology::Priority => {
138                    self.execute_priority(|| now_ns(), &mut total_ticks, &mut total_cycles, &mut budget_violations);
139                }
140                SchedulerTopology::Hierarchical => {
141                    self.execute_hierarchical(|| now_ns(), &mut total_ticks, &mut total_cycles, &mut budget_violations);
142                }
143                SchedulerTopology::Mesh => {
144                    self.execute_mesh(|| now_ns(), &mut total_ticks, &mut total_cycles, &mut budget_violations);
145                }
146                SchedulerTopology::Quantum => {
147                    self.execute_quantum(|| now_ns(), &mut total_ticks, &mut total_cycles, &mut budget_violations);
148                }
149            }
150
151            // Busy wait to fill the macro tick
152            while now_ns() - macro_tick_start < self.config.tick_duration_ns {
153                spin();
154            }
155        }
156
157        // Collect final statistics
158        SchedulerStats {
159            total_ticks,
160            total_cycles,
161            budget_violations,
162            runtime_ns: start.elapsed().as_nanos(),
163            agent_count: self.agents.len(),
164            traces: if self.config.enable_tracing {
165                self.traces.into()
166            } else {
167                Vec::new()
168            },
169        }
170    }
171
172    /// Execute agents in round-robin fashion
173    fn execute_round_robin(
174        &mut self,
175        mut now_ns: impl FnMut() -> u128,
176        total_ticks: &mut u64,
177        total_cycles: &mut u64,
178        budget_violations: &mut u64,
179    ) {
180        for slot in &mut self.agents {
181            let tick_start_ns = now_ns();
182            let tick_start_tsc = rdtsc();
183
184            // Execute agent tick
185            let bus = self.bus.clone_bus();
186            let result = slot.agent.tick(tick_start_ns, &bus);
187
188            // Measure execution time
189            let tick_cycles = rdtsc() - tick_start_tsc;
190            let tick_duration_ns = now_ns() - tick_start_ns;
191
192            // Check budget violation
193            if tick_duration_ns > slot.budget_ns {
194                *budget_violations += 1;
195                slot.metrics.budget_violations.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
196            }
197
198            // Update metrics
199            slot.metrics.record_tick(&result);
200            *total_ticks += 1;
201            *total_cycles += tick_cycles;
202
203            // Record trace if enabled
204            if self.config.enable_tracing {
205                self.traces.push_back(AgentTrace {
206                    agent_name: slot.agent.name(),
207                    timestamp_ns: tick_start_ns,
208                    result,
209                });
210
211                // Keep trace buffer bounded
212                if self.traces.len() > 100000 {
213                    self.traces.pop_front();
214                }
215            }
216
217            // Busy wait to enforce budget
218            while now_ns() - tick_start_ns < slot.budget_ns {
219                spin();
220            }
221        }
222    }
223
224    /// Execute agents by priority
225    fn execute_priority(
226        &mut self,
227        mut now_ns: impl FnMut() -> u128,
228        total_ticks: &mut u64,
229        total_cycles: &mut u64,
230        budget_violations: &mut u64,
231    ) {
232        // Agents already sorted by priority
233        self.execute_round_robin(now_ns, total_ticks, total_cycles, budget_violations);
234    }
235
236    /// Execute agents in hierarchical tree structure
237    fn execute_hierarchical(
238        &mut self,
239        mut now_ns: impl FnMut() -> u128,
240        total_ticks: &mut u64,
241        total_cycles: &mut u64,
242        budget_violations: &mut u64,
243    ) {
244        // For now, fallback to round-robin
245        // TODO: Implement tree-based delegation
246        self.execute_round_robin(now_ns, total_ticks, total_cycles, budget_violations);
247    }
248
249    /// Execute agents in mesh topology
250    fn execute_mesh(
251        &mut self,
252        mut now_ns: impl FnMut() -> u128,
253        total_ticks: &mut u64,
254        total_cycles: &mut u64,
255        budget_violations: &mut u64,
256    ) {
257        // For now, fallback to round-robin
258        // TODO: Implement peer-to-peer coordination
259        self.execute_round_robin(now_ns, total_ticks, total_cycles, budget_violations);
260    }
261
262    /// Execute agents with quantum superposition scheduling
263    fn execute_quantum(
264        &mut self,
265        mut now_ns: impl FnMut() -> u128,
266        total_ticks: &mut u64,
267        total_cycles: &mut u64,
268        budget_violations: &mut u64,
269    ) {
270        // For now, fallback to round-robin
271        // TODO: Implement quantum scheduling with superposition
272        self.execute_round_robin(now_ns, total_ticks, total_cycles, budget_violations);
273    }
274}
275
276/// Statistics from a scheduler run
277#[derive(Debug)]
278pub struct SchedulerStats {
279    pub total_ticks: u64,
280    pub total_cycles: u64,
281    pub budget_violations: u64,
282    pub runtime_ns: u128,
283    pub agent_count: usize,
284    pub traces: Vec<AgentTrace>,
285}
286
287impl SchedulerStats {
288    /// Calculate average nanoseconds per tick
289    pub fn avg_ns_per_tick(&self) -> f64 {
290        if self.total_ticks == 0 {
291            0.0
292        } else {
293            self.runtime_ns as f64 / self.total_ticks as f64
294        }
295    }
296
297    /// Calculate average cycles per tick
298    pub fn avg_cycles_per_tick(&self) -> f64 {
299        if self.total_ticks == 0 {
300            0.0
301        } else {
302            self.total_cycles as f64 / self.total_ticks as f64
303        }
304    }
305
306    /// Calculate budget violation rate
307    pub fn violation_rate(&self) -> f64 {
308        if self.total_ticks == 0 {
309            0.0
310        } else {
311            self.budget_violations as f64 / self.total_ticks as f64
312        }
313    }
314}