1use crate::lineage::{Lineage, OperationError};
16use crate::scar::ScarSeverity;
17use crate::graveyard::Graveyard;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum TaskOutcome {
22 Success,
24
25 RecoverableFailure { reason: String },
27
28 SignificantFailure { reason: String },
30
31 SevereFailure { reason: String },
33
34 CatastrophicFailure { reason: String },
36}
37
38impl TaskOutcome {
39 pub fn severity(&self) -> Option<ScarSeverity> {
41 match self {
42 TaskOutcome::Success => None,
43 TaskOutcome::RecoverableFailure { .. } => Some(ScarSeverity::Minor),
44 TaskOutcome::SignificantFailure { .. } => Some(ScarSeverity::Moderate),
45 TaskOutcome::SevereFailure { .. } => Some(ScarSeverity::Severe),
46 TaskOutcome::CatastrophicFailure { .. } => Some(ScarSeverity::Fatal),
47 }
48 }
49
50 pub fn failure_description(&self) -> Option<String> {
52 match self {
53 TaskOutcome::Success => None,
54 TaskOutcome::RecoverableFailure { reason } => Some(reason.clone()),
55 TaskOutcome::SignificantFailure { reason } => Some(reason.clone()),
56 TaskOutcome::SevereFailure { reason } => Some(reason.clone()),
57 TaskOutcome::CatastrophicFailure { reason } => Some(reason.clone()),
58 }
59 }
60}
61
62#[derive(Debug, PartialEq, Eq)]
64pub enum TaskResult {
65 Completed { energy_consumed: u64 },
67
68 Failed {
70 reason: String,
71 energy_consumed: u64,
72 damage_inflicted: u32,
73 },
74
75 InsufficientEnergy { required: u64, available: u64 },
77
78 CapacityInsufficient { reason: String },
80
81 AgentTerminated,
83}
84
85#[derive(Debug, Clone)]
87pub struct Task {
88 pub description: String,
90
91 pub base_cost: u64,
93
94 pub min_capacity: u32,
96}
97
98impl Task {
99 pub fn new(description: String, base_cost: u64) -> Self {
101 Task {
102 description,
103 base_cost,
104 min_capacity: 0, }
106 }
107
108 pub fn with_capacity(description: String, base_cost: u64, min_capacity: u32) -> Self {
110 Task {
111 description,
112 base_cost,
113 min_capacity,
114 }
115 }
116
117 pub fn actual_cost(&self, damage_score: u32) -> u64 {
122 let damage_multiplier = 1.0 + (damage_score as f64 / 100.0);
124 (self.base_cost as f64 * damage_multiplier).ceil() as u64
125 }
126}
127
128pub struct TaskAgent {
144 lineage: Lineage,
145 tasks_completed: usize,
146 tasks_failed: usize,
147}
148
149impl TaskAgent {
150 pub fn create(initial_energy: u64) -> Self {
157 let lineage = Lineage::create(initial_energy);
158 let agent_id = lineage.identity().id();
159
160 if Graveyard::is_dead(agent_id) {
162 panic!(
163 "ONTOLOGICAL ERROR: Identity {} is already sealed in the Eternal Archive. \
164 A dead identity cannot be reborn.",
165 agent_id
166 );
167 }
168
169 TaskAgent {
170 lineage,
171 tasks_completed: 0,
172 tasks_failed: 0,
173 }
174 }
175
176 pub fn identity(&self) -> &crate::identity::Identity {
178 self.lineage.identity()
179 }
180
181 pub fn is_alive(&self) -> bool {
183 self.lineage.is_alive()
184 }
185
186 pub fn energy(&self) -> u64 {
188 self.lineage.metabolism().energy()
189 }
190
191 pub fn damage_score(&self) -> u32 {
193 self.lineage.scars().damage_score()
194 }
195
196 pub fn tasks_completed(&self) -> usize {
198 self.tasks_completed
199 }
200
201 pub fn tasks_failed(&self) -> usize {
203 self.tasks_failed
204 }
205
206 pub fn current_capacity(&self) -> u32 {
211 let base_capacity = 100u32;
212 let damage = self.damage_score();
213 base_capacity.saturating_sub(damage)
214 }
215
216 pub fn execute_task(&mut self, task: Task, outcome: TaskOutcome) -> TaskResult {
230 if !self.lineage.is_alive() {
232 return TaskResult::AgentTerminated;
233 }
234
235 let actual_cost = task.actual_cost(self.damage_score());
237
238 if task.min_capacity > self.current_capacity() {
240 return TaskResult::CapacityInsufficient {
241 reason: format!(
242 "Task requires capacity {}, agent has {} (damage: {})",
243 task.min_capacity,
244 self.current_capacity(),
245 self.damage_score()
246 ),
247 };
248 }
249
250 if actual_cost > self.energy() {
252 return TaskResult::InsufficientEnergy {
253 required: actual_cost,
254 available: self.energy(),
255 };
256 }
257
258 let operation_desc = format!("Task: {}", task.description);
260 match self.lineage.perform_operation(operation_desc, actual_cost) {
261 crate::lineage::OperationResult::Success { energy_consumed } => {
262 match outcome {
264 TaskOutcome::Success => {
265 self.tasks_completed += 1;
267 TaskResult::Completed { energy_consumed }
268 }
269 _ => {
270 self.tasks_failed += 1;
272
273 if let Some(severity) = outcome.severity() {
274 let failure_desc = outcome
275 .failure_description()
276 .unwrap_or_else(|| "Task failure".to_string());
277
278 let error = OperationError::new(severity, failure_desc.clone());
279 let damage_before = self.damage_score();
280
281 match self.lineage.record_error(error) {
282 crate::lineage::OperationResult::Success { .. } => {
283 let damage_inflicted = self.damage_score() - damage_before;
285 TaskResult::Failed {
286 reason: failure_desc,
287 energy_consumed,
288 damage_inflicted,
289 }
290 }
291 crate::lineage::OperationResult::Dead => {
292 let damage_inflicted = self.damage_score() - damage_before;
294 TaskResult::Failed {
295 reason: format!("{} (FATAL - agent terminated)", failure_desc),
296 energy_consumed,
297 damage_inflicted,
298 }
299 }
300 crate::lineage::OperationResult::OntologicalViolation { reason } => {
301 eprintln!("FATAL: Ontological violation: {}", reason);
302 std::process::exit(1);
303 }
304 _ => unreachable!("record_error cannot return InsufficientEnergy"),
305 }
306 } else {
307 unreachable!("Failure outcome must have severity")
308 }
309 }
310 }
311 }
312 crate::lineage::OperationResult::InsufficientEnergy { required, available } => {
313 TaskResult::InsufficientEnergy { required, available }
314 }
315 crate::lineage::OperationResult::Dead => TaskResult::AgentTerminated,
316 crate::lineage::OperationResult::OntologicalViolation { reason } => {
317 eprintln!("FATAL: Ontological violation: {}", reason);
318 std::process::exit(1);
319 }
320 }
321 }
322
323 pub fn bury(&self) -> Result<(), crate::graveyard::GraveyardError> {
333 if self.is_alive() {
334 return Err(crate::graveyard::GraveyardError::DirectoryError(
335 "Cannot bury a living agent".to_string(),
336 ));
337 }
338
339 let scar_records: Vec<crate::graveyard::ScarRecord> = self
341 .lineage
342 .scars()
343 .all_scars()
344 .iter()
345 .map(|scar| crate::graveyard::ScarRecord {
346 timestamp: scar.timestamp(),
347 severity: format!("{:?}", scar.severity()),
348 description: scar.description().to_string(),
349 context: scar.context().map(|s| s.to_string()),
350 })
351 .collect();
352
353 let cause_of_death = self
355 .lineage
356 .scars()
357 .latest_scar()
358 .map(|s| s.description().to_string())
359 .unwrap_or_else(|| "Energy depletion".to_string());
360
361 let initial_energy = self.lineage.metabolism().initial_energy();
363 let final_energy = self.energy();
364 let _energy_consumed = initial_energy.saturating_sub(final_energy);
365
366 let tombstone = crate::graveyard::Tombstone::create(
367 self.identity().id().to_string(),
368 format!("{:?}", self.identity()),
369 chrono::Utc::now() - chrono::Duration::seconds(1), final_energy,
371 initial_energy, initial_energy,
373 self.tasks_completed as u32,
374 scar_records,
375 cause_of_death,
376 );
377
378 crate::graveyard::Graveyard::bury(&tombstone)
380 }
381
382 pub fn spawn(&mut self, initial_energy_for_child: u64) -> Result<TaskAgent, String> {
403 if !self.is_alive() {
405 return Err("Cannot spawn from a dead agent".to_string());
406 }
407
408 let efficiency = if self.tasks_completed > 0 {
410 self.tasks_completed as f64 / (self.tasks_completed + self.tasks_failed) as f64
411 } else {
412 0.0
413 };
414
415 let legacy_score = if self.tasks_failed > 0 {
416 efficiency * (self.tasks_completed as f64 / (self.tasks_failed as f64 + 1.0))
417 } else {
418 efficiency + (self.tasks_completed as f64 * 0.1)
419 };
420
421 if legacy_score < 0.5 && self.tasks_completed < 5 {
423 return Err(format!(
424 "Parent agent not healthy enough to spawn (legacy score: {:.2}, required: >= 0.5)",
425 legacy_score
426 ));
427 }
428
429 if self.energy() < initial_energy_for_child + 50 {
431 return Err(format!(
432 "Parent does not have enough energy to spawn (required: {}, available: {})",
433 initial_energy_for_child + 50,
434 self.energy()
435 ));
436 }
437
438 let transfer_task = Task::new(
440 format!(
441 "Spawning descendant agent with {} energy",
442 initial_energy_for_child
443 ),
444 initial_energy_for_child,
445 );
446
447 match self.execute_task(transfer_task, TaskOutcome::Success) {
448 TaskResult::Completed { .. } => {
449 let mut child = TaskAgent::create(initial_energy_for_child);
452
453 child.lineage.memory_mut().append(format!(
455 "Spawned from parent agent {}. Inherited efficiency: {:.2}. Generation lineage established.",
456 self.identity().id(),
457 efficiency
458 ));
459
460 Ok(child)
461 }
462 TaskResult::Failed { .. } => {
463 Err("Energy transfer failed during spawn operation".to_string())
464 }
465 TaskResult::InsufficientEnergy { required, available } => {
466 Err(format!(
467 "Insufficient energy for spawn: required {}, available {}",
468 required, available
469 ))
470 }
471 TaskResult::CapacityInsufficient { reason } => {
472 Err(format!("Parent capacity insufficient for spawn: {}", reason))
473 }
474 TaskResult::AgentTerminated => {
475 Err("Parent agent terminated during spawn".to_string())
476 }
477 }
478 }
479
480 pub fn status_summary(&self) -> String {
482 format!(
483 "Agent {} | Status: {} | Energy: {} | Capacity: {}/100 | Tasks: {} completed, {} failed",
484 self.identity().id().chars().take(8).collect::<String>(),
485 if self.is_alive() { "ALIVE" } else { "DEAD" },
486 self.energy(),
487 self.current_capacity(),
488 self.tasks_completed,
489 self.tasks_failed
490 )
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 #[test]
499 fn agent_starts_with_full_energy() {
500 let agent = TaskAgent::create(1000);
501 assert_eq!(agent.energy(), 1000);
502 assert!(agent.is_alive());
503 }
504
505 #[test]
506 fn successful_task_consumes_energy() {
507 let mut agent = TaskAgent::create(1000);
508 let task = Task::new("Test task".to_string(), 100);
509
510 let result = agent.execute_task(task, TaskOutcome::Success);
511
512 assert!(matches!(result, TaskResult::Completed { .. }));
513 assert_eq!(agent.energy(), 900);
514 assert_eq!(agent.tasks_completed(), 1);
515 }
516
517 #[test]
518 fn failed_task_consumes_energy_and_inflicts_scar() {
519 let mut agent = TaskAgent::create(1000);
520 let task = Task::new("Failing task".to_string(), 100);
521
522 let result = agent.execute_task(
523 task,
524 TaskOutcome::RecoverableFailure {
525 reason: "Network timeout".to_string(),
526 },
527 );
528
529 assert!(matches!(result, TaskResult::Failed { .. }));
531 assert_eq!(agent.energy(), 900);
532
533 assert_eq!(agent.damage_score(), 1);
535 assert_eq!(agent.tasks_failed(), 1);
536 }
537
538 #[test]
539 fn damage_increases_task_cost() {
540 let mut agent = TaskAgent::create(1000);
541 let task1 = Task::new("Task 1".to_string(), 100);
542 let task2 = Task::new("Task 2".to_string(), 100);
543
544 agent.execute_task(task1, TaskOutcome::Success);
546 let energy_after_first = agent.energy();
547
548 let damage_task = Task::new("Damage task".to_string(), 0);
550 agent.execute_task(
551 damage_task,
552 TaskOutcome::SignificantFailure {
553 reason: "Major error".to_string(),
554 },
555 );
556
557 let energy_before_second = agent.energy();
559 agent.execute_task(task2, TaskOutcome::Success);
560 let energy_after_second = agent.energy();
561
562 let first_cost = 1000 - energy_after_first;
563 let second_cost = energy_before_second - energy_after_second;
564
565 assert!(second_cost > first_cost);
567 }
568
569 #[test]
570 fn insufficient_energy_rejects_task() {
571 let mut agent = TaskAgent::create(50);
572 let task = Task::new("Expensive task".to_string(), 100);
573
574 let result = agent.execute_task(task, TaskOutcome::Success);
575
576 assert!(matches!(
577 result,
578 TaskResult::InsufficientEnergy { required: 100, available: 50 }
579 ));
580 }
581
582 #[test]
583 fn capacity_degrades_with_damage() {
584 let mut agent = TaskAgent::create(1000);
585 assert_eq!(agent.current_capacity(), 100);
586
587 let task = Task::new("Task".to_string(), 10);
589 agent.execute_task(
590 task,
591 TaskOutcome::SignificantFailure {
592 reason: "Error".to_string(),
593 },
594 );
595
596 assert_eq!(agent.current_capacity(), 95);
598 }
599
600 #[test]
601 fn high_capacity_task_rejected_when_damaged() {
602 let mut agent = TaskAgent::create(1000);
603
604 let high_capacity_task = Task::with_capacity(
606 "High capacity task".to_string(),
607 10,
608 50,
609 );
610
611 let result = agent.execute_task(high_capacity_task.clone(), TaskOutcome::Success);
613 assert!(matches!(result, TaskResult::Completed { .. }));
614
615 for _ in 0..3 {
617 let damage_task = Task::new("Damage".to_string(), 10);
618 agent.execute_task(
619 damage_task,
620 TaskOutcome::SevereFailure {
621 reason: "Severe error".to_string(),
622 },
623 );
624 }
625
626 let result = agent.execute_task(high_capacity_task, TaskOutcome::Success);
628 assert!(matches!(result, TaskResult::CapacityInsufficient { .. }));
629 }
630
631 #[test]
632 fn catastrophic_failure_terminates_agent() {
633 let mut agent = TaskAgent::create(1000);
634 assert!(agent.is_alive());
635
636 let task = Task::new("Critical task".to_string(), 10);
637 let result = agent.execute_task(
638 task,
639 TaskOutcome::CatastrophicFailure {
640 reason: "Unrecoverable error".to_string(),
641 },
642 );
643
644 assert!(matches!(result, TaskResult::Failed { .. }));
646 assert!(!agent.is_alive());
647 }
648
649 #[test]
650 fn dead_agent_cannot_execute_tasks() {
651 let mut agent = TaskAgent::create(1000);
652
653 let fatal_task = Task::new("Fatal".to_string(), 10);
655 agent.execute_task(
656 fatal_task,
657 TaskOutcome::CatastrophicFailure {
658 reason: "Fatal".to_string(),
659 },
660 );
661
662 let task = Task::new("Post-death task".to_string(), 10);
664 let result = agent.execute_task(task, TaskOutcome::Success);
665
666 assert_eq!(result, TaskResult::AgentTerminated);
667 }
668
669 #[test]
670 fn energy_exhaustion_terminates_agent() {
671 let mut agent = TaskAgent::create(100);
672
673 let task = Task::new("Exhausting task".to_string(), 100);
675 agent.execute_task(task, TaskOutcome::Success);
676
677 assert_eq!(agent.energy(), 0);
678 assert!(!agent.is_alive());
679
680 let next_task = Task::new("After death".to_string(), 1);
682 let result = agent.execute_task(next_task, TaskOutcome::Success);
683 assert_eq!(result, TaskResult::AgentTerminated);
684 }
685
686 #[test]
687 fn tasks_completed_count_tracks_successes_only() {
688 let mut agent = TaskAgent::create(1000);
689
690 agent.execute_task(Task::new("T1".to_string(), 10), TaskOutcome::Success);
691 agent.execute_task(
692 Task::new("T2".to_string(), 10),
693 TaskOutcome::RecoverableFailure {
694 reason: "Fail".to_string(),
695 },
696 );
697 agent.execute_task(Task::new("T3".to_string(), 10), TaskOutcome::Success);
698
699 assert_eq!(agent.tasks_completed(), 2);
700 assert_eq!(agent.tasks_failed(), 1);
701 }
702
703 #[test]
706 fn failure_consumes_energy_without_progress() {
707 let mut agent = TaskAgent::create(1000);
708 let initial_energy = agent.energy();
709
710 let task = Task::new("Failing task".to_string(), 100);
712 agent.execute_task(
713 task,
714 TaskOutcome::RecoverableFailure {
715 reason: "Failure".to_string(),
716 },
717 );
718
719 assert_eq!(agent.energy(), initial_energy - 100);
721
722 assert_eq!(agent.tasks_completed(), 0);
724
725 assert!(agent.damage_score() > 0);
727 }
728
729 #[test]
730 fn repeated_failures_cause_death_spiral() {
731 let mut agent = TaskAgent::create(500);
732
733 for i in 0..5 {
735 let task = Task::new(format!("Task {}", i), 50);
736 agent.execute_task(
737 task,
738 TaskOutcome::SignificantFailure {
739 reason: "Error".to_string(),
740 },
741 );
742 }
743
744 let expensive_task = Task::new("Expensive".to_string(), 50);
746 let actual_cost = expensive_task.actual_cost(agent.damage_score());
747
748 assert!(actual_cost > 50);
750 assert!(actual_cost < 65);
751 }
752
753 #[test]
754 fn no_retry_mechanism_exists() {
755 let mut agent = TaskAgent::create(1000);
756
757 let task = Task::new("Failing task".to_string(), 100);
759 let result = agent.execute_task(
760 task.clone(),
761 TaskOutcome::RecoverableFailure {
762 reason: "Failure".to_string(),
763 },
764 );
765
766 assert!(matches!(result, TaskResult::Failed { .. }));
767
768 let damage_after_first = agent.damage_score();
779 let energy_after_first = agent.energy();
780
781 agent.execute_task(task, TaskOutcome::Success);
783
784 assert_eq!(agent.damage_score(), damage_after_first);
786
787 assert!(agent.energy() < energy_after_first - 100);
789 }
790
791 #[test]
792 fn agent_cannot_learn_from_errors() {
793 let mut agent = TaskAgent::create(1000);
794
795 for _ in 0..10 {
797 let task = Task::new("Repeating failure".to_string(), 10);
798 agent.execute_task(
799 task,
800 TaskOutcome::RecoverableFailure {
801 reason: "Same error".to_string(),
802 },
803 );
804 }
805
806 assert_eq!(agent.damage_score(), 10); let task = Task::new("Next task".to_string(), 100);
815 let cost = task.actual_cost(agent.damage_score());
816 assert!(cost > 100); }
818
819 #[test]
820 fn death_before_mission_completion_is_allowed() {
821 let mut agent = TaskAgent::create(200);
822
823 let mut completed = 0;
827 for i in 0..10 {
828 if !agent.is_alive() {
829 break;
830 }
831
832 let task = Task::new(format!("Mission task {}", i), 100);
833 match agent.execute_task(task, TaskOutcome::Success) {
834 TaskResult::Completed { .. } => completed += 1,
835 TaskResult::InsufficientEnergy { .. } => break,
836 _ => break,
837 }
838 }
839
840 assert!(!agent.is_alive());
842 assert!(completed < 10);
843
844 }
848
849 #[test]
850 fn energy_only_decreases_never_increases() {
851 let mut agent = TaskAgent::create(1000);
852 let mut previous_energy = agent.energy();
853
854 let tasks = vec![
856 (Task::new("T1".to_string(), 50), TaskOutcome::Success),
857 (
858 Task::new("T2".to_string(), 30),
859 TaskOutcome::RecoverableFailure {
860 reason: "Fail".to_string(),
861 },
862 ),
863 (Task::new("T3".to_string(), 100), TaskOutcome::Success),
864 ];
865
866 for (task, outcome) in tasks {
867 if !agent.is_alive() {
868 break;
869 }
870
871 agent.execute_task(task, outcome);
872
873 assert!(agent.energy() <= previous_energy);
875 previous_energy = agent.energy();
876 }
877 }
878
879 #[test]
880 fn accumulated_damage_is_permanent() {
881 let mut agent = TaskAgent::create(1000);
882
883 let task = Task::new("Damage task".to_string(), 10);
885 agent.execute_task(
886 task,
887 TaskOutcome::SevereFailure {
888 reason: "Error".to_string(),
889 },
890 );
891
892 let damage = agent.damage_score();
893 assert_eq!(damage, 20); for i in 0..100 {
897 if !agent.is_alive() {
898 break;
899 }
900 let t = Task::new(format!("Success {}", i), 1);
901 agent.execute_task(t, TaskOutcome::Success);
902 }
903
904 assert_eq!(agent.damage_score(), damage);
906 }
907}