| """ |
| AntiAtropos Curriculum Training. |
| |
| Defines progressive difficulty stages that the agent must pass before advancing. |
| Failed stages are retried with higher temperature for exploration. |
| |
| Each stage specifies: |
| - task: Which task to run |
| - max_steps: Episode length (shorter = easier) |
| - pass_threshold: Minimum composite score to advance |
| - temperature: Suggest LLM temperature for this stage |
| - description: Human-readable label |
| """ |
|
|
| from dataclasses import dataclass |
| from typing import List, Optional |
|
|
|
|
| @dataclass |
| class CurriculumStage: |
| """A single stage in the training curriculum.""" |
| task: str |
| max_steps: int |
| pass_threshold: float |
| temperature: float = 0.0 |
| description: str = "" |
| retries: int = 0 |
| max_retries: int = 3 |
|
|
| @property |
| def retry_temperature(self) -> float: |
| """Temperature increases with retries to encourage exploration.""" |
| if self.retries == 0: |
| return self.temperature |
| |
| return min(1.0, self.temperature + self.retries * 0.3) |
|
|
| @property |
| def should_skip(self) -> bool: |
| """Skip this stage if too many retries.""" |
| return self.retries >= self.max_retries |
|
|
|
|
| |
| CURRICULUM: List[CurriculumStage] = [ |
| CurriculumStage( |
| task="task-1", max_steps=40, pass_threshold=0.40, |
| temperature=0.0, description="Short ramp β learn basic scaling", |
| ), |
| CurriculumStage( |
| task="task-1", max_steps=60, pass_threshold=0.50, |
| temperature=0.0, description="Standard ramp β scale proactively", |
| ), |
| CurriculumStage( |
| task="task-1", max_steps=100, pass_threshold=0.55, |
| temperature=0.0, description="Full ramp β cost-aware scaling", |
| ), |
| CurriculumStage( |
| task="task-2", max_steps=40, pass_threshold=0.35, |
| temperature=0.0, description="Short fault β learn reroute/scale on failure", |
| ), |
| CurriculumStage( |
| task="task-2", max_steps=60, pass_threshold=0.45, |
| temperature=0.3, description="Standard fault β fast recovery", |
| ), |
| CurriculumStage( |
| task="task-3", max_steps=40, pass_threshold=0.35, |
| temperature=0.0, description="Short surge β protect VIP during spike", |
| ), |
| CurriculumStage( |
| task="task-3", max_steps=60, pass_threshold=0.45, |
| temperature=0.3, description="Standard surge β sustained VIP protection", |
| ), |
| |
| CurriculumStage( |
| task="task-1", max_steps=100, pass_threshold=0.55, |
| temperature=0.0, description="Final: full ramp at low temp", |
| ), |
| CurriculumStage( |
| task="task-2", max_steps=60, pass_threshold=0.50, |
| temperature=0.0, description="Final: fault recovery at low temp", |
| ), |
| CurriculumStage( |
| task="task-3", max_steps=60, pass_threshold=0.50, |
| temperature=0.0, description="Final: surge protection at low temp", |
| ), |
| ] |
|
|
|
|
| class CurriculumTracker: |
| """Tracks progress through the curriculum stages.""" |
|
|
| def __init__(self, stages: Optional[List[CurriculumStage]] = None): |
| self._stages = stages or CURRICULUM |
| self._current_idx: int = 0 |
|
|
| @property |
| def current(self) -> CurriculumStage: |
| return self._stages[self._current_idx] |
|
|
| @property |
| def current_index(self) -> int: |
| return self._current_idx |
|
|
| @property |
| def total_stages(self) -> int: |
| return len(self._stages) |
|
|
| @property |
| def is_complete(self) -> bool: |
| return self._current_idx >= len(self._stages) |
|
|
| def report_score(self, score: float) -> bool: |
| """Report a score for the current stage. Returns True if passed.""" |
| if score >= self.current.pass_threshold: |
| self._current_idx += 1 |
| return True |
| else: |
| self.current.retries += 1 |
| if self.current.should_skip: |
| self._current_idx += 1 |
| return False |
|
|
| def progress_summary(self) -> str: |
| stage = self.current |
| return ( |
| f"Stage {self._current_idx + 1}/{self.total_stages}: " |
| f"{stage.description} " |
| f"(task={stage.task}, max_steps={stage.max_steps}, " |
| f"threshold={stage.pass_threshold}, retries={stage.retries})" |
| ) |
|
|