Skip to main content

lineage/
agent.rs

1// Autonomous Task Agent System
2//
3// ONTOLOGICAL CONSTRAINT: Finite lifespan, irreversible resource consumption.
4//
5// Agents are NOT:
6// - Learning systems that improve from errors
7// - Resilient processes that retry indefinitely
8// - Recoverable entities that checkpoint state
9//
10// Agents ARE:
11// - Finite-lifetime task executors
12// - Mortal entities that die permanently
13// - Consequence-bearing systems that accumulate damage
14
15use crate::lineage::{Lineage, OperationError};
16use crate::scar::ScarSeverity;
17use crate::graveyard::Graveyard;
18
19/// Classification of task execution outcomes.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum TaskOutcome {
22    /// Task completed successfully.
23    Success,
24    
25    /// Task failed with recoverable error (Minor scar).
26    RecoverableFailure { reason: String },
27    
28    /// Task failed with significant error (Moderate scar).
29    SignificantFailure { reason: String },
30    
31    /// Task failed with severe error (Severe scar).
32    SevereFailure { reason: String },
33    
34    /// Task failed catastrophically (Fatal scar, terminates agent).
35    CatastrophicFailure { reason: String },
36}
37
38impl TaskOutcome {
39    /// Determine scar severity for this outcome.
40    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    /// Get failure description if outcome is failure.
51    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/// Result of attempting to execute a task.
63#[derive(Debug, PartialEq, Eq)]
64pub enum TaskResult {
65    /// Task executed successfully.
66    Completed { energy_consumed: u64 },
67    
68    /// Task failed, scar inflicted.
69    Failed { 
70        reason: String,
71        energy_consumed: u64,
72        damage_inflicted: u32,
73    },
74    
75    /// Task rejected due to insufficient energy.
76    InsufficientEnergy { required: u64, available: u64 },
77    
78    /// Task rejected due to excessive damage (capacity degraded).
79    CapacityInsufficient { reason: String },
80    
81    /// Agent is dead, cannot execute tasks.
82    AgentTerminated,
83}
84
85/// Task descriptor with energy requirements.
86#[derive(Debug, Clone)]
87pub struct Task {
88    /// Task identifier/description.
89    pub description: String,
90    
91    /// Base energy cost to execute this task.
92    pub base_cost: u64,
93    
94    /// Minimum capacity required (affected by accumulated damage).
95    pub min_capacity: u32,
96}
97
98impl Task {
99    /// Create new task with description and base cost.
100    pub fn new(description: String, base_cost: u64) -> Self {
101        Task {
102            description,
103            base_cost,
104            min_capacity: 0, // No capacity requirement by default
105        }
106    }
107    
108    /// Create task requiring minimum capacity.
109    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    /// Calculate actual energy cost including damage penalty.
118    /// 
119    /// CONSEQUENCE: Accumulated damage increases energy cost.
120    /// More scars = higher cost for same task.
121    pub fn actual_cost(&self, damage_score: u32) -> u64 {
122        // Base cost + 10% per 10 damage points
123        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
128/// Autonomous task-executing agent with finite lifespan.
129/// 
130/// ONTOLOGICAL CONSTRAINTS:
131/// - Born with fixed energy budget
132/// - Tasks consume energy irreversibly
133/// - Errors inflict permanent scars
134/// - Scars increase future task costs
135/// - Death is permanent
136/// 
137/// FORBIDDEN OPERATIONS:
138/// - Retry without cost
139/// - Energy restoration
140/// - Scar removal
141/// - Death resurrection
142/// - Learning from errors
143pub struct TaskAgent {
144    lineage: Lineage,
145    tasks_completed: usize,
146    tasks_failed: usize,
147}
148
149impl TaskAgent {
150    /// Create new agent with initial energy budget.
151    /// 
152    /// FINITE LIFETIME: This is all the energy the agent will ever have.
153    /// 
154    /// LAZARUS CHECK: Verifies that no agent with this identity has died before.
155    /// If an agent identity is found in the graveyard, this function panics.
156    pub fn create(initial_energy: u64) -> Self {
157        let lineage = Lineage::create(initial_energy);
158        let agent_id = lineage.identity().id();
159        
160        // LAZARUS PREVENTION: Check if this identity has already died
161        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    /// Get agent identity.
177    pub fn identity(&self) -> &crate::identity::Identity {
178        self.lineage.identity()
179    }
180    
181    /// Check if agent is alive.
182    pub fn is_alive(&self) -> bool {
183        self.lineage.is_alive()
184    }
185    
186    /// Get current energy level.
187    pub fn energy(&self) -> u64 {
188        self.lineage.metabolism().energy()
189    }
190    
191    /// Get accumulated damage score.
192    pub fn damage_score(&self) -> u32 {
193        self.lineage.scars().damage_score()
194    }
195    
196    /// Get count of completed tasks.
197    pub fn tasks_completed(&self) -> usize {
198        self.tasks_completed
199    }
200    
201    /// Get count of failed tasks.
202    pub fn tasks_failed(&self) -> usize {
203        self.tasks_failed
204    }
205    
206    /// Calculate current task capacity.
207    /// 
208    /// DEGRADATION: Capacity decreases with accumulated damage.
209    /// Base capacity = 100, reduced by damage score.
210    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    /// Execute task with given outcome.
217    /// 
218    /// CONSEQUENCES:
219    /// - Energy consumed irreversibly (even on failure)
220    /// - Failures inflict permanent scars
221    /// - Scars increase future task costs
222    /// - Fatal failures terminate agent
223    /// - No retry mechanism exists
224    /// 
225    /// HARSH REALITY:
226    /// - Failure consumes energy without completing task
227    /// - Agent cannot learn from errors
228    /// - Accumulated damage leads to death spiral
229    pub fn execute_task(&mut self, task: Task, outcome: TaskOutcome) -> TaskResult {
230        // Check if agent is dead
231        if !self.lineage.is_alive() {
232            return TaskResult::AgentTerminated;
233        }
234        
235        // Calculate actual cost (increased by damage)
236        let actual_cost = task.actual_cost(self.damage_score());
237        
238        // Check capacity requirement
239        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        // Check energy availability
251        if actual_cost > self.energy() {
252            return TaskResult::InsufficientEnergy {
253                required: actual_cost,
254                available: self.energy(),
255            };
256        }
257        
258        // Consume energy for task attempt
259        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                // Task attempt consumed energy, now handle outcome
263                match outcome {
264                    TaskOutcome::Success => {
265                        // Task succeeded
266                        self.tasks_completed += 1;
267                        TaskResult::Completed { energy_consumed }
268                    }
269                    _ => {
270                        // Task failed - inflict scar
271                        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                                    // Non-fatal failure
284                                    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                                    // Fatal failure - agent terminated
293                                    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    /// Bury this agent in the Graveyard (requires agent to be dead).
324    /// 
325    /// Creates a cryptographically sealed tombstone with:
326    /// - Complete identity and metabolic information
327    /// - All scars and cause of death
328    /// - Causal chain for tamper detection
329    /// 
330    /// **CONSEQUENCE**: Once buried, this agent's identity cannot be reused.
331    /// **CONSEQUENCE**: Tombstone is immutable after creation.
332    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        // Gather scar information
340        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        // Determine cause of death
354        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        // Create tombstone with proper energy metrics
362        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), // Approximate creation time
370            final_energy,
371            initial_energy,  // Peak energy = initial energy
372            initial_energy,
373            self.tasks_completed as u32,
374            scar_records,
375            cause_of_death,
376        );
377
378        // Bury in the eternal archive
379        crate::graveyard::Graveyard::bury(&tombstone)
380    }
381
382    /// Spawn a descendant agent from this "healthy" parent agent.
383    ///
384    /// REQUIREMENTS FOR SPAWNING:
385    /// - Parent must be ALIVE (not dead)
386    /// - Parent must have high enough Legacy Score (at least 0.5)
387    /// - Parent must have capacity to transfer (remaining energy >= 100)
388    /// - Parent must have completed at least 5 tasks successfully
389    ///
390    /// TRANSFER MECHANICS:
391    /// - Portion of parent's energy passes to child (scaled by parent's efficiency)
392    /// - Parent loses transferred energy permanently
393    /// - Child inherits knowledge from parent's success ratio
394    /// - Child gains genetic advantage from parent's efficiency
395    /// - Creates a causal tree across generations
396    ///
397    /// CONSEQUENCE: Energy transfer is IRREVERSIBLE.
398    /// CONSEQUENCE: Parent becomes weaker after spawning.
399    /// CONSEQUENCE: Child starts with advantage but at higher risk.
400    /// 
401    /// **Returns**: New child TaskAgent with genealogical lineage
402    pub fn spawn(&mut self, initial_energy_for_child: u64) -> Result<TaskAgent, String> {
403        // Check if parent is alive
404        if !self.is_alive() {
405            return Err("Cannot spawn from a dead agent".to_string());
406        }
407
408        // Calculate parent's current legacy score for fitness check
409        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        // Check fitness requirements
422        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        // Check energy availability for transfer
430        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        // TRANSFER ENERGY: Irreversibly consume parent's energy
439        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                // Energy successfully transferred
450                // Create child agent
451                let mut child = TaskAgent::create(initial_energy_for_child);
452
453                // Record parentage in child's memory
454                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    /// Get agent status summary.
481    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        // Energy consumed despite failure
530        assert!(matches!(result, TaskResult::Failed { .. }));
531        assert_eq!(agent.energy(), 900);
532        
533        // Scar inflicted
534        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        // First task succeeds (base cost: 100)
545        agent.execute_task(task1, TaskOutcome::Success);
546        let energy_after_first = agent.energy();
547        
548        // Inflict significant damage (Moderate = 5 points)
549        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        // Second task has higher cost due to damage
558        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        // Second task cost is higher due to damage penalty
566        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        // Inflict moderate damage (5 points)
588        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        // Capacity reduced by damage
597        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        // Create task requiring 50 capacity
605        let high_capacity_task = Task::with_capacity(
606            "High capacity task".to_string(),
607            10,
608            50,
609        );
610        
611        // Initially succeeds (capacity = 100)
612        let result = agent.execute_task(high_capacity_task.clone(), TaskOutcome::Success);
613        assert!(matches!(result, TaskResult::Completed { .. }));
614        
615        // Inflict severe damage (20 points * 3 = 60 damage)
616        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        // Now capacity is insufficient (100 - 60 = 40 < 50)
627        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        // Task failed and agent terminated
645        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        // Terminate agent via catastrophic failure
654        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        // Attempt to execute another task
663        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        // Execute task consuming all energy
674        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        // Cannot execute more tasks
681        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    // BRUTAL TESTS: Prove harsh consequences
704
705    #[test]
706    fn failure_consumes_energy_without_progress() {
707        let mut agent = TaskAgent::create(1000);
708        let initial_energy = agent.energy();
709        
710        // Task fails - energy consumed, no progress
711        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        // Energy consumed
720        assert_eq!(agent.energy(), initial_energy - 100);
721        
722        // No completed tasks
723        assert_eq!(agent.tasks_completed(), 0);
724        
725        // Damage inflicted
726        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        // Inflict damage to increase costs
734        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        // Now tasks cost more due to damage (damage score = 25)
745        let expensive_task = Task::new("Expensive".to_string(), 50);
746        let actual_cost = expensive_task.actual_cost(agent.damage_score());
747        
748        // Cost increased from 50 to ~62 (50 * 1.25)
749        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        // Task fails
758        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        // ATTACK: Try to retry - no method exists
769        // Agent has no retry() method
770        // Agent has no reset() method
771        // Agent has no recover() method
772        
773        // Can execute same task again, but:
774        // - Costs more energy (damage penalty)
775        // - Damage still present
776        // - Previous failure still recorded
777        
778        let damage_after_first = agent.damage_score();
779        let energy_after_first = agent.energy();
780        
781        // Second attempt (manual, not automatic retry)
782        agent.execute_task(task, TaskOutcome::Success);
783        
784        // Damage remains from first failure
785        assert_eq!(agent.damage_score(), damage_after_first);
786        
787        // More energy consumed than if first succeeded
788        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        // Fail same task type repeatedly
796        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        // Damage accumulates (no learning)
807        assert_eq!(agent.damage_score(), 10); // 10 failures * 1 damage each
808        
809        // Agent does not improve
810        // Agent does not adapt
811        // Agent does not learn patterns
812        
813        // Cost keeps increasing with damage
814        let task = Task::new("Next task".to_string(), 100);
815        let cost = task.actual_cost(agent.damage_score());
816        assert!(cost > 100); // Penalty applied
817    }
818
819    #[test]
820    fn death_before_mission_completion_is_allowed() {
821        let mut agent = TaskAgent::create(200);
822        
823        // Mission: Complete 10 tasks (100 energy each = 1000 total needed)
824        // Agent only has 200 energy - will die before completion
825        
826        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        // Agent died before completing mission
841        assert!(!agent.is_alive());
842        assert!(completed < 10);
843        
844        // This is correct behavior, not a bug
845        // Agents with insufficient resources die
846        // No resurrection, no extra energy
847    }
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        // Execute various tasks
855        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            // Energy never increases
874            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        // Inflict damage
884        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); // Severe = 20 points
894        
895        // Execute many successful tasks
896        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        // Damage unchanged by success
905        assert_eq!(agent.damage_score(), damage);
906    }
907}