One evolution action suggested by the analysis LLM. ``target_skill_ids`` lists the parent skill(s) this action targets using **true skill_id** values (e.g. ``weather__imp_a1b2c3d4``): - FIX: exactly 1 parent (the skill to repair in-place) - DERIVED: 1+ parents (single parent → e
| 196 | |
| 197 | @dataclass |
| 198 | class EvolutionSuggestion: |
| 199 | """One evolution action suggested by the analysis LLM. |
| 200 | |
| 201 | ``target_skill_ids`` lists the parent skill(s) this action targets |
| 202 | using **true skill_id** values (e.g. ``weather__imp_a1b2c3d4``): |
| 203 | - FIX: exactly 1 parent (the skill to repair in-place) |
| 204 | - DERIVED: 1+ parents (single parent → enhance; multi → merge/fuse) |
| 205 | - CAPTURED: empty list (brand-new skill, no parents) |
| 206 | """ |
| 207 | |
| 208 | evolution_type: EvolutionType |
| 209 | target_skill_ids: List[str] = field(default_factory=list) # True skill_id(s) |
| 210 | category: Optional[SkillCategory] = None # Desired category of the result |
| 211 | direction: str = "" # Free-text: what to evolve / capture |
| 212 | |
| 213 | @property |
| 214 | def target_skill_id(self) -> str: |
| 215 | """Primary (or only) target skill_id. Empty string if none.""" |
| 216 | return self.target_skill_ids[0] if self.target_skill_ids else "" |
| 217 | |
| 218 | def to_dict(self) -> Dict[str, Any]: |
| 219 | return { |
| 220 | "type": self.evolution_type.value, |
| 221 | "target_skills": self.target_skill_ids, |
| 222 | # Keep legacy singular key for backward compat with stored analyses |
| 223 | "target_skill": self.target_skill_id, |
| 224 | "category": self.category.value if self.category else None, |
| 225 | "direction": self.direction, |
| 226 | } |
| 227 | |
| 228 | @classmethod |
| 229 | def from_dict(cls, data: Dict[str, Any]) -> "EvolutionSuggestion": |
| 230 | cat = None |
| 231 | if data.get("category"): |
| 232 | try: |
| 233 | cat = SkillCategory(data["category"]) |
| 234 | except ValueError: |
| 235 | pass |
| 236 | # Support both new list format and legacy single-string format |
| 237 | raw_targets = data.get("target_skills") |
| 238 | if isinstance(raw_targets, list): |
| 239 | targets = [t for t in raw_targets if t] |
| 240 | else: |
| 241 | legacy = data.get("target_skill", "") |
| 242 | targets = [legacy] if legacy else [] |
| 243 | return cls( |
| 244 | evolution_type=EvolutionType(data["type"]), |
| 245 | target_skill_ids=targets, |
| 246 | category=cat, |
| 247 | direction=data.get("direction", ""), |
| 248 | ) |
| 249 | |
| 250 | |
| 251 | # Task-level execution analysis (1 per task) |
no outgoing calls
no test coverage detected