"""UpdateStep — runs the SkillManager, which mutates the skillbook in place.""" from __future__ import annotations from ..core.context import ACEStepContext from ..core.insight_source import InsightSource, infer_trace_identity from ..core.skillbook import Skillbook from ..protocols import SkillManagerLike class UpdateStep: """Run the agentic SkillManager against the current reflection. The SkillManager mutates the real :class:`Skillbook` directly through its tools (``add_skill`` / ``update_skill`` / ``remove_skill`` / ``tag_skill``). By the time this step returns the skillbook already reflects the changes — ``ctx.skill_manager_output`` is a post-hoc audit log, not a plan to apply. ``max_workers = 1`` because the SM reads the current skillbook state and mutates it; concurrent calls would see stale state and race on writes. """ requires = frozenset({"reflections", "skillbook"}) provides = frozenset({"skill_manager_output"}) max_workers = 1 def __init__(self, skill_manager: SkillManagerLike, skillbook: Skillbook) -> None: self.skill_manager = skill_manager self.skillbook = skillbook def __call__(self, ctx: ACEStepContext) -> ACEStepContext: progress = f"Epoch {ctx.epoch}/{ctx.total_epochs}" if ctx.total_steps is not None: progress += f", sample {ctx.step_index}/{ctx.total_steps}" sample = getattr(ctx, "sample", None) sample_question = getattr(sample, "question", "") or "" sample_context = getattr(sample, "context", "") or "" question_context = "" if isinstance(ctx.trace, dict): q = str(ctx.trace.get("question", "") or "") c = str(ctx.trace.get("context", "") or "") question_context = f"{q}\n{c}".strip() if c else q elif sample_question: question_context = ( f"{sample_question}\n{sample_context}".strip() if sample_context else sample_question ) identity = infer_trace_identity( trace=ctx.trace, sample=sample, metadata=ctx.metadata, ) reflection = ctx.reflections[0] source = InsightSource( trace_uid=identity.trace_uid or "", source_system=identity.source_system, trace_id=identity.trace_id, display_name=identity.display_name, sample_question=sample_question or None, epoch=ctx.epoch, error_identification=reflection.error_identification or None, learning_text=reflection.key_insight or None, ) output = self.skill_manager.update_skills( reflections=ctx.reflections, skillbook=self.skillbook, question_context=question_context, progress=progress, source=source, injected_skill_ids=ctx.injected_skill_ids, ) return ctx.replace(skill_manager_output=output.update)