Source code for atloop.orchestrator.phases.discover

"""DISCOVER phase implementation."""

import logging

from atloop.orchestrator.phases.base import BasePhase, PhaseContext, PhaseResult
from atloop.orchestrator.state_machine import Phase

logger = logging.getLogger(__name__)


[docs] class DiscoverPhase(BasePhase): """DISCOVER phase: Build context and prepare for planning."""
[docs] def execute(self, context: PhaseContext) -> PhaseResult: """ Execute DISCOVER phase. Args: context: Phase execution context Returns: Phase execution result """ logger.debug(f"[DiscoverPhase] Executing DISCOVER phase at step {context.step}") state = self.coordinator.state_manager.agent_state try: # Check if context_builder is initialized if self.coordinator.context_builder is None: logger.error("[DiscoverPhase] ContextBuilder not initialized") return PhaseResult( success=False, data={}, next_phase=Phase.FAIL, error="ContextBuilder not initialized", ) # Build memory summary logger.debug("[DiscoverPhase] Building memory summary") memory_config = getattr(self.coordinator.config, "memory", None) if memory_config: memory_summary_max_length = memory_config.summary_max_length logger.debug( f"[DiscoverPhase] Using memory config: max_length={memory_summary_max_length}" ) else: memory_summary_max_length = 64000 logger.debug( f"[DiscoverPhase] Using default memory summary max length: {memory_summary_max_length}" ) # Get formatted memory context using new interface # Format options are now loaded from MemoryConfig by default # Only override if specific customization is needed memory_context = state.memory.get_formatted_context( state=state, task_goal=self.coordinator.task_spec.goal, max_length=memory_summary_max_length, format_options=None, # Use defaults from MemoryConfig (single source of truth) tool_registry=self.coordinator.tool_runtime.registry, ) memory_summary = memory_context # Keep variable name for compatibility logger.debug( f"[DiscoverPhase] Memory summary length: {len(memory_summary)} chars (max: {memory_summary_max_length})" ) # Extract keywords logger.debug("[DiscoverPhase] Extracting keywords") keywords = self._extract_keywords() logger.debug(f"[DiscoverPhase] Extracted {len(keywords)} keywords: {keywords[:5]}") # Build context pack logger.debug("[DiscoverPhase] Building context pack") context_pack = self.coordinator.context_builder.build( goal=self.coordinator.task_spec.goal, recent_error=state.last_error.summary, current_diff=state.artifacts.current_diff, test_results=state.artifacts.test_results, verification_success=state.artifacts.verification_success, memory_summary=memory_summary, keywords=keywords, ) logger.debug( f"[DiscoverPhase] Context pack built: project_profile={context_pack.project_profile}" ) # Store context pack for PLAN phase self.coordinator.job_state.shared_data["context_pack"] = context_pack.to_string() logger.debug("[DiscoverPhase] Context pack stored in job_state") # Transition to PLAN logger.debug("[DiscoverPhase] Transitioning to PLAN phase") transition_result = self._transition(Phase.PLAN) if not transition_result: logger.error("[DiscoverPhase] Transition failed: DISCOVER -> PLAN") return PhaseResult( success=False, data={}, next_phase=Phase.FAIL, error="State transition failed: DISCOVER -> PLAN", ) self.coordinator.state_manager.update(phase="PLAN") logger.info("[DiscoverPhase] Successfully transitioned to PLAN phase") return PhaseResult( success=True, data={"context_pack": context_pack.to_string()}, next_phase=Phase.PLAN, ) except Exception as e: # Let Workflow handle the exception with unified error handling logger.error(f"[DiscoverPhase] DISCOVER phase exception: {e}") raise # Re-raise for Workflow to handle
def _extract_keywords(self) -> list[str]: """Extract keywords from state.""" keywords = [] state = self.coordinator.state_manager.agent_state # Extract from goal if self.coordinator.task_spec.goal: keywords.extend( self.coordinator.indexer.extract_keywords(self.coordinator.task_spec.goal) ) # Extract from error if state.last_error.summary: keywords.extend(self.coordinator.indexer.extract_keywords(state.last_error.summary)) return keywords[:10] # Limit to 10 keywords