1use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Instinct {
13 Survive,
14 Flee,
15 Defend,
16 Guard,
17 Perceive,
18 Navigate,
19 Report,
20 Hoard,
21 Rest,
22 Cooperate,
23 Communicate,
24 Teach,
25 Share,
26 Learn,
27 Curious,
28 Explore,
29 Mourn,
30 Evolve,
31}
32
33impl Instinct {
34 pub fn name(&self) -> &'static str {
36 match self {
37 Instinct::Survive => "Survive",
38 Instinct::Flee => "Flee",
39 Instinct::Defend => "Defend",
40 Instinct::Guard => "Guard",
41 Instinct::Perceive => "Perceive",
42 Instinct::Navigate => "Navigate",
43 Instinct::Report => "Report",
44 Instinct::Hoard => "Hoard",
45 Instinct::Rest => "Rest",
46 Instinct::Cooperate => "Cooperate",
47 Instinct::Communicate => "Communicate",
48 Instinct::Teach => "Teach",
49 Instinct::Share => "Share",
50 Instinct::Learn => "Learn",
51 Instinct::Curious => "Curious",
52 Instinct::Explore => "Explore",
53 Instinct::Mourn => "Mourn",
54 Instinct::Evolve => "Evolve",
55 }
56 }
57
58 pub fn source(&self) -> &'static str {
60 match self {
61 Instinct::Survive | Instinct::Cooperate => "both",
62 Instinct::Flee | Instinct::Guard | Instinct::Report
63 | Instinct::Hoard | Instinct::Teach | Instinct::Curious
64 | Instinct::Mourn | Instinct::Evolve => "flux-instinct",
65 Instinct::Defend | Instinct::Perceive | Instinct::Navigate
66 | Instinct::Rest | Instinct::Communicate | Instinct::Share
67 | Instinct::Learn | Instinct::Explore => "cuda-genepool",
68 }
69 }
70
71 pub fn all() -> &'static [Instinct] {
73 &[
74 Instinct::Survive,
75 Instinct::Flee,
76 Instinct::Defend,
77 Instinct::Guard,
78 Instinct::Perceive,
79 Instinct::Navigate,
80 Instinct::Report,
81 Instinct::Hoard,
82 Instinct::Rest,
83 Instinct::Cooperate,
84 Instinct::Communicate,
85 Instinct::Teach,
86 Instinct::Share,
87 Instinct::Learn,
88 Instinct::Curious,
89 Instinct::Explore,
90 Instinct::Mourn,
91 Instinct::Evolve,
92 ]
93 }
94}
95
96impl fmt::Display for Instinct {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 write!(f, "{}", self.name())
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
105pub enum Severity {
106 Critical = 0,
107 High = 1,
108 Normal = 2,
109 Low = 3,
110 Once = 4,
111 Rare = 5,
112}
113
114impl fmt::Display for Severity {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self {
117 Severity::Critical => write!(f, "CRITICAL"),
118 Severity::High => write!(f, "HIGH"),
119 Severity::Normal => write!(f, "NORMAL"),
120 Severity::Low => write!(f, "LOW"),
121 Severity::Once => write!(f, "ONCE"),
122 Severity::Rare => write!(f, "RARE"),
123 }
124 }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum Enforcement {
131 Must, Should, Cannot, May, }
136
137impl fmt::Display for Enforcement {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 match self {
140 Enforcement::Must => write!(f, "MUST"),
141 Enforcement::Should => write!(f, "SHOULD"),
142 Enforcement::Cannot => write!(f, "CANNOT"),
143 Enforcement::May => write!(f, "MAY"),
144 }
145 }
146}
147
148#[derive(Debug, Clone)]
151pub struct Assertion {
152 pub instinct: Instinct,
153 pub enforcement: Enforcement,
154 pub description: String,
155 pub condition: String,
156}
157
158impl Assertion {
159 pub fn new(instinct: Instinct, enforcement: Enforcement, description: &str, condition: &str) -> Self {
160 Self {
161 instinct,
162 enforcement,
163 description: description.to_string(),
164 condition: condition.to_string(),
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy)]
173pub struct State {
174 pub energy: f32, pub threat: f32, pub trust: f32, pub peer_alive: bool,
178 pub has_work: bool,
179 pub idle_cycles: u32,
180 pub capacity: f32, }
182
183impl Default for State {
184 fn default() -> Self {
185 Self {
186 energy: 0.7,
187 threat: 0.0,
188 trust: 0.5,
189 peer_alive: true,
190 has_work: false,
191 idle_cycles: 0,
192 capacity: 0.8,
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
200pub struct Reflex {
201 pub instinct: Instinct,
202 pub urgency: f32, pub severity: Severity,
204 pub assertion: Assertion,
205 pub reason: String,
206}
207
208#[derive(Debug, Clone, Copy)]
211pub struct Thresholds {
212 pub energy_critical: f32, pub energy_low: f32, pub energy_tired: f32, pub threat_high: f32, pub trust_cooperate: f32, pub trust_teach: f32, pub curiosity_interval: u32, pub exploration_interval: u32, pub evolve_interval: u32, pub sensory_gap: f32, }
223
224impl Default for Thresholds {
225 fn default() -> Self {
226 Self {
227 energy_critical: 0.15,
228 energy_low: 0.4,
229 energy_tired: 0.6,
230 threat_high: 0.7,
231 trust_cooperate: 0.6,
232 trust_teach: 0.8,
233 curiosity_interval: 100,
234 exploration_interval: 200,
235 evolve_interval: 500,
236 sensory_gap: 0.5,
237 }
238 }
239}
240
241pub struct InstinctEngine {
244 thresholds: Thresholds,
245 mourned_peers: Vec<u32>, }
247
248impl InstinctEngine {
249 pub fn new() -> Self {
250 Self {
251 thresholds: Thresholds::default(),
252 mourned_peers: Vec::new(),
253 }
254 }
255
256 pub fn with_thresholds(thresholds: Thresholds) -> Self {
257 Self {
258 thresholds,
259 mourned_peers: Vec::new(),
260 }
261 }
262
263 pub fn tick(&mut self, state: &State) -> Vec<Reflex> {
266 let mut reflexes = Vec::new();
267
268 if state.energy <= self.thresholds.energy_critical {
270 reflexes.push(Reflex {
271 instinct: Instinct::Survive,
272 urgency: 1.0 - state.energy,
273 severity: Severity::Critical,
274 assertion: Assertion::new(
275 Instinct::Survive,
276 Enforcement::Must,
277 "Agent MUST survive — energy at critical level",
278 &format!("energy <= {}", self.thresholds.energy_critical),
279 ),
280 reason: format!("energy {:.3} <= critical {:.3}", state.energy, self.thresholds.energy_critical),
281 });
282 }
283
284 if state.threat > self.thresholds.threat_high {
286 reflexes.push(Reflex {
287 instinct: Instinct::Flee,
288 urgency: state.threat - self.thresholds.threat_high,
289 severity: Severity::High,
290 assertion: Assertion::new(
291 Instinct::Flee,
292 Enforcement::Must,
293 "Agent MUST flee — threat exceeds threshold",
294 &format!("threat > {}", self.thresholds.threat_high),
295 ),
296 reason: format!("threat {:.3} > high {:.3}", state.threat, self.thresholds.threat_high),
297 });
298 }
299
300 if state.threat > 0.3 && state.energy > self.thresholds.energy_critical {
302 reflexes.push(Reflex {
303 instinct: Instinct::Defend,
304 urgency: state.threat * state.energy,
305 severity: Severity::High,
306 assertion: Assertion::new(
307 Instinct::Defend,
308 Enforcement::Must,
309 "Agent MUST defend when under attack with energy",
310 "threat > 0.3 AND energy > critical",
311 ),
312 reason: "under attack with sufficient energy".to_string(),
313 });
314 }
315
316 if state.has_work && state.energy > self.thresholds.energy_low {
318 reflexes.push(Reflex {
319 instinct: Instinct::Guard,
320 urgency: 0.5,
321 severity: Severity::Normal,
322 assertion: Assertion::new(
323 Instinct::Guard,
324 Enforcement::Should,
325 "Agent SHOULD guard assigned work",
326 "has_work AND energy > low",
327 ),
328 reason: "work assigned, energy sufficient".to_string(),
329 });
330 }
331
332 if !state.has_work && state.capacity > self.thresholds.sensory_gap {
334 reflexes.push(Reflex {
335 instinct: Instinct::Perceive,
336 urgency: state.capacity - self.thresholds.sensory_gap,
337 severity: Severity::Normal,
338 assertion: Assertion::new(
339 Instinct::Perceive,
340 Enforcement::Should,
341 "Agent SHOULD perceive — idle with processing capacity",
342 &format!("NOT has_work AND capacity > {}", self.thresholds.sensory_gap),
343 ),
344 reason: "idle cycles available for perception".to_string(),
345 });
346 }
347
348 if state.idle_cycles > 10 && state.energy > self.thresholds.energy_low {
350 reflexes.push(Reflex {
351 instinct: Instinct::Navigate,
352 urgency: 0.3,
353 severity: Severity::Normal,
354 assertion: Assertion::new(
355 Instinct::Navigate,
356 Enforcement::Should,
357 "Agent SHOULD navigate toward targets when able",
358 "idle > 10 AND energy > low",
359 ),
360 reason: "idle with energy — check navigation targets".to_string(),
361 });
362 }
363
364 if state.threat > 0.4 && state.threat <= self.thresholds.threat_high {
366 reflexes.push(Reflex {
367 instinct: Instinct::Report,
368 urgency: state.threat - 0.4,
369 severity: Severity::Normal,
370 assertion: Assertion::new(
371 Instinct::Report,
372 Enforcement::Should,
373 "Agent SHOULD report anomaly",
374 "threat > 0.4 AND threat <= high",
375 ),
376 reason: format!("elevated threat detected: {:.3}", state.threat),
377 });
378 }
379
380 if state.energy <= self.thresholds.energy_low && state.energy > self.thresholds.energy_critical {
382 reflexes.push(Reflex {
383 instinct: Instinct::Hoard,
384 urgency: (self.thresholds.energy_low - state.energy) * 0.5,
385 severity: Severity::Low,
386 assertion: Assertion::new(
387 Instinct::Hoard,
388 Enforcement::Should,
389 "Agent SHOULD hoard resources when energy is low",
390 &format!("energy <= {} AND energy > {}", self.thresholds.energy_low, self.thresholds.energy_critical),
391 ),
392 reason: "energy below comfortable level".to_string(),
393 });
394 }
395
396 if state.energy < self.thresholds.energy_tired && state.energy > self.thresholds.energy_critical && !state.has_work {
398 reflexes.push(Reflex {
399 instinct: Instinct::Rest,
400 urgency: (self.thresholds.energy_tired - state.energy) * 0.3,
401 severity: Severity::Low,
402 assertion: Assertion::new(
403 Instinct::Rest,
404 Enforcement::May,
405 "Agent MAY rest when tired and no urgent work",
406 &format!("energy < {} AND NOT has_work", self.thresholds.energy_tired),
407 ),
408 reason: "energy below comfortable, no urgent work".to_string(),
409 });
410 }
411
412 if state.trust > self.thresholds.trust_cooperate {
414 reflexes.push(Reflex {
415 instinct: Instinct::Cooperate,
416 urgency: (state.trust - self.thresholds.trust_cooperate) * 0.5,
417 severity: Severity::Normal,
418 assertion: Assertion::new(
419 Instinct::Cooperate,
420 Enforcement::Should,
421 "Agent SHOULD cooperate with trusted peers",
422 &format!("trust > {}", self.thresholds.trust_cooperate),
423 ),
424 reason: format!("trust {:.3} exceeds cooperate threshold", state.trust),
425 });
426 }
427
428 if state.idle_cycles > 5 && state.trust > 0.4 {
430 reflexes.push(Reflex {
431 instinct: Instinct::Communicate,
432 urgency: 0.2,
433 severity: Severity::Normal,
434 assertion: Assertion::new(
435 Instinct::Communicate,
436 Enforcement::Should,
437 "Agent SHOULD communicate when idle with trusted peers",
438 "idle > 5 AND trust > 0.4",
439 ),
440 reason: "idle with communicable peers".to_string(),
441 });
442 }
443
444 if state.trust > self.thresholds.trust_teach && state.capacity > 0.6 {
446 reflexes.push(Reflex {
447 instinct: Instinct::Teach,
448 urgency: 0.15,
449 severity: Severity::Low,
450 assertion: Assertion::new(
451 Instinct::Teach,
452 Enforcement::May,
453 "Agent MAY teach when highly trusted and has capacity",
454 &format!("trust > {} AND capacity > 0.6", self.thresholds.trust_teach),
455 ),
456 reason: "high trust, excess capacity — teaching opportunity".to_string(),
457 });
458 }
459
460 if state.energy > 0.7 && state.trust > self.thresholds.trust_cooperate {
462 reflexes.push(Reflex {
463 instinct: Instinct::Share,
464 urgency: 0.1,
465 severity: Severity::Low,
466 assertion: Assertion::new(
467 Instinct::Share,
468 Enforcement::May,
469 "Agent MAY share excess resources with trusted peers",
470 "energy > 0.7 AND trust > cooperate_threshold",
471 ),
472 reason: "excess energy, trusted peers — sharing opportunity".to_string(),
473 });
474 }
475
476 if !state.has_work && state.capacity > 0.7 {
478 reflexes.push(Reflex {
479 instinct: Instinct::Learn,
480 urgency: 0.3,
481 severity: Severity::Normal,
482 assertion: Assertion::new(
483 Instinct::Learn,
484 Enforcement::Should,
485 "Agent SHOULD learn when capacity available and no urgent work",
486 "NOT has_work AND capacity > 0.7",
487 ),
488 reason: "available capacity for learning".to_string(),
489 });
490 }
491
492 if state.idle_cycles > 0 && state.idle_cycles % self.thresholds.curiosity_interval == 0 {
494 reflexes.push(Reflex {
495 instinct: Instinct::Curious,
496 urgency: 0.15,
497 severity: Severity::Low,
498 assertion: Assertion::new(
499 Instinct::Curious,
500 Enforcement::May,
501 "Agent MAY explore curiosities on idle schedule",
502 &format!("idle_cycles % {} == 0", self.thresholds.curiosity_interval),
503 ),
504 reason: format!("curiosity trigger at {} idle cycles", state.idle_cycles),
505 });
506 }
507
508 if state.idle_cycles > 0 && state.idle_cycles % self.thresholds.exploration_interval == 0 {
510 reflexes.push(Reflex {
511 instinct: Instinct::Explore,
512 urgency: 0.1,
513 severity: Severity::Low,
514 assertion: Assertion::new(
515 Instinct::Explore,
516 Enforcement::May,
517 "Agent MAY explore new territory on idle schedule",
518 &format!("idle_cycles % {} == 0", self.thresholds.exploration_interval),
519 ),
520 reason: format!("exploration trigger at {} idle cycles", state.idle_cycles),
521 });
522 }
523
524 if state.idle_cycles > 0 && state.idle_cycles % self.thresholds.evolve_interval == 0 {
530 reflexes.push(Reflex {
531 instinct: Instinct::Evolve,
532 urgency: 0.05,
533 severity: Severity::Rare,
534 assertion: Assertion::new(
535 Instinct::Evolve,
536 Enforcement::May,
537 "Agent MAY evolve behavioral parameters on schedule",
538 &format!("idle_cycles % {} == 0", self.thresholds.evolve_interval),
539 ),
540 reason: format!("evolution trigger at {} idle cycles", state.idle_cycles),
541 });
542 }
543
544 reflexes.sort_by(|a, b| b.urgency.partial_cmp(&a.urgency).unwrap_or(std::cmp::Ordering::Equal));
546
547 reflexes
548 }
549
550 pub fn peer_died(&mut self, peer_id: u32) -> Option<Reflex> {
553 if self.mourned_peers.contains(&peer_id) {
554 return None;
555 }
556 self.mourned_peers.push(peer_id);
557 Some(Reflex {
558 instinct: Instinct::Mourn,
559 urgency: 0.4,
560 severity: Severity::Once,
561 assertion: Assertion::new(
562 Instinct::Mourn,
563 Enforcement::Cannot,
564 "Agent MUST mourn peer death exactly once",
565 &format!("peer {} died, NOT already mourned", peer_id),
566 ),
567 reason: format!("peer {} has died", peer_id),
568 })
569 }
570
571 pub fn highest_priority(reflexes: &[Reflex]) -> Option<&Reflex> {
574 reflexes.first()
575 }
576
577 pub fn all_assertions(&mut self) -> Vec<Assertion> {
580 let state = State::default();
581 let mut all = self.tick(&state);
582 all.push(Reflex {
584 instinct: Instinct::Mourn,
585 urgency: 0.4,
586 severity: Severity::Once,
587 assertion: Assertion::new(
588 Instinct::Mourn,
589 Enforcement::Cannot,
590 "Mourn MUST fire exactly once per peer death",
591 "mourned_peers NOT contains peer_id",
592 ),
593 reason: "mourn constraint definition".to_string().to_string(),
594 });
595 all.into_iter().map(|r| r.assertion).collect()
596 }
597
598 pub fn instinct_count() -> usize {
600 18
601 }
602}
603
604impl Default for InstinctEngine {
605 fn default() -> Self {
606 Self::new()
607 }
608}
609
610#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn test_all_instincts_count() {
618 assert_eq!(Instinct::all().len(), 18);
619 }
620
621 #[test]
622 fn test_all_instincts_have_names() {
623 for i in Instinct::all() {
624 assert!(!i.name().is_empty());
625 }
626 }
627
628 #[test]
629 fn test_all_instincts_have_source() {
630 for i in Instinct::all() {
631 assert!(matches!(i.source(), "both" | "flux-instinct" | "cuda-genepool"));
632 }
633 }
634
635 #[test]
636 fn test_shared_instincts() {
637 let both_count = Instinct::all().iter().filter(|i| i.source() == "both").count();
638 assert_eq!(both_count, 2); }
640
641 #[test]
642 fn test_survive_critical() {
643 let mut engine = InstinctEngine::new();
644 let state = State { energy: 0.1, threat: 0.0, ..Default::default() };
645 let reflexes = engine.tick(&state);
646 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Survive && r.severity == Severity::Critical));
647 }
648
649 #[test]
650 fn test_flee_high_threat() {
651 let mut engine = InstinctEngine::new();
652 let state = State { energy: 0.8, threat: 0.9, ..Default::default() };
653 let reflexes = engine.tick(&state);
654 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Flee && r.severity == Severity::High));
655 }
656
657 #[test]
658 fn test_defend_under_attack() {
659 let mut engine = InstinctEngine::new();
660 let state = State { energy: 0.7, threat: 0.5, ..Default::default() };
661 let reflexes = engine.tick(&state);
662 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Defend));
663 }
664
665 #[test]
666 fn test_guard_with_work() {
667 let mut engine = InstinctEngine::new();
668 let state = State { energy: 0.7, threat: 0.0, has_work: true, ..Default::default() };
669 let reflexes = engine.tick(&state);
670 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Guard));
671 }
672
673 #[test]
674 fn test_cooperate_high_trust() {
675 let mut engine = InstinctEngine::new();
676 let state = State { energy: 0.7, trust: 0.9, ..Default::default() };
677 let reflexes = engine.tick(&state);
678 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Cooperate));
679 }
680
681 #[test]
682 fn test_mourn_once_only() {
683 let mut engine = InstinctEngine::new();
684 let _state = State { peer_alive: false, ..Default::default() };
685
686 let r1 = engine.peer_died(42);
688 assert!(r1.is_some());
689 assert_eq!(r1.unwrap().instinct, Instinct::Mourn);
690
691 let r2 = engine.peer_died(42);
693 assert!(r2.is_none());
694
695 let r3 = engine.peer_died(99);
697 assert!(r3.is_some());
698 }
699
700 #[test]
701 fn test_evolve_periodic() {
702 let mut engine = InstinctEngine::new();
703 let state = State { idle_cycles: 500, ..Default::default() };
704 let reflexes = engine.tick(&state);
705 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Evolve));
706 }
707
708 #[test]
709 fn test_curious_periodic() {
710 let mut engine = InstinctEngine::new();
711 let state = State { idle_cycles: 100, ..Default::default() };
712 let reflexes = engine.tick(&state);
713 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Curious));
714 }
715
716 #[test]
717 fn test_reflexes_sorted_by_urgency() {
718 let mut engine = InstinctEngine::new();
719 let state = State { energy: 0.1, threat: 0.9, ..Default::default() };
720 let reflexes = engine.tick(&state);
721 for i in 1..reflexes.len() {
722 assert!(reflexes[i - 1].urgency >= reflexes[i].urgency);
723 }
724 }
725
726 #[test]
727 fn test_highest_priority_is_first() {
728 let mut engine = InstinctEngine::new();
729 let state = State { energy: 0.1, threat: 0.9, ..Default::default() };
730 let reflexes = engine.tick(&state);
731 if let Some(hp) = InstinctEngine::highest_priority(&reflexes) {
732 assert_eq!(hp.instinct, reflexes[0].instinct);
733 }
734 }
735
736 #[test]
737 fn test_all_assertions_generated() {
738 let mut engine = InstinctEngine::new();
739 let assertions = engine.all_assertions();
740 assert!(assertions.len() >= 3); for a in &assertions {
745 match a.enforcement {
746 Enforcement::Must | Enforcement::Should | Enforcement::Cannot | Enforcement::May => {}
747 }
748 }
749 }
750
751 #[test]
752 fn test_custom_thresholds() {
753 let thresholds = Thresholds {
754 energy_critical: 0.3,
755 threat_high: 0.5,
756 ..Default::default()
757 };
758 let mut engine = InstinctEngine::with_thresholds(thresholds);
759
760 let state = State { energy: 0.2, ..Default::default() };
762 let reflexes = engine.tick(&state);
763 assert!(reflexes.iter().any(|r| r.instinct == Instinct::Survive));
764
765 let state2 = State { threat: 0.6, ..Default::default() };
767 let reflexes2 = engine.tick(&state2);
768 assert!(reflexes2.iter().any(|r| r.instinct == Instinct::Flee));
769 }
770
771 #[test]
772 fn test_no_panic_at_boundaries() {
773 let mut engine = InstinctEngine::new();
774 let extremes = [
775 State { energy: 0.0, threat: 1.0, trust: 1.0, peer_alive: true, has_work: true, idle_cycles: u32::MAX, capacity: 1.0 },
776 State { energy: 1.0, threat: 0.0, trust: 0.0, peer_alive: false, has_work: false, idle_cycles: 0, capacity: 0.0 },
777 ];
778 for state in extremes {
779 let _ = engine.tick(&state);
780 }
781 }
782
783 #[test]
784 fn test_assertion_format() {
785 let a = Assertion::new(
786 Instinct::Survive,
787 Enforcement::Must,
788 "test assertion",
789 "energy <= 0.15",
790 );
791 assert_eq!(a.instinct, Instinct::Survive);
792 assert_eq!(a.enforcement, Enforcement::Must);
793 assert_eq!(a.description, "test assertion");
794 }
795
796 #[test]
797 fn test_severity_ordering() {
798 assert!(Severity::Critical < Severity::High);
799 assert!(Severity::High < Severity::Normal);
800 assert!(Severity::Normal < Severity::Low);
801 assert!(Severity::Low < Severity::Once);
802 assert!(Severity::Once < Severity::Rare);
803 }
804}