Coverage for src / moai_adk / core / phase_optimized_hook_scheduler.py: 20.20%
302 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1"""
2Phase-Optimized Hook Scheduler
4Intelligent scheduling system for hooks based on development phases,
5token budget constraints, and performance requirements.
7Key Features:
8- Phase-aware hook scheduling
9- Token budget optimization
10- Dynamic priority adjustment
11- Performance-based scheduling
12- Dependency resolution
13- Resource management
14"""
16import asyncio
17import threading
18import time
19from dataclasses import dataclass, field
20from datetime import datetime
21from enum import Enum
22from typing import Any, Dict, List, Optional, Set
24from .jit_enhanced_hook_manager import (
25 HookEvent,
26 HookMetadata,
27 HookPriority,
28 JITEnhancedHookManager,
29 Phase,
30)
33class SchedulingStrategy(Enum):
34 """Hook scheduling strategies"""
35 PRIORITY_FIRST = "priority_first" # Execute highest priority hooks first
36 PERFORMANCE_FIRST = "performance_first" # Execute fastest hooks first
37 PHASE_OPTIMIZED = "phase_optimized" # Optimize for current phase
38 TOKEN_EFFICIENT = "token_efficient" # Minimize token usage
39 BALANCED = "balanced" # Balance all factors
42class SchedulingDecision(Enum):
43 """Scheduling decisions for hooks"""
44 EXECUTE = "execute" # Execute hook now
45 DEFER = "defer" # Defer to later
46 SKIP = "skip" # Skip execution
47 PARALLEL = "parallel" # Execute in parallel group
48 SEQUENTIAL = "sequential" # Execute sequentially
51@dataclass
52class HookSchedulingContext:
53 """Context for hook scheduling decisions"""
54 event_type: HookEvent
55 current_phase: Phase
56 user_input: str
57 available_token_budget: int
58 max_execution_time_ms: float
59 current_time: datetime = field(default_factory=datetime.now)
60 system_load: float = 0.5 # 0.0 (idle) to 1.0 (busy)
61 recent_performance: Dict[str, float] = field(default_factory=dict) # hook_path -> avg_time_ms
62 active_dependencies: Set[str] = field(default_factory=set)
65@dataclass
66class ScheduledHook:
67 """Hook scheduled for execution"""
68 hook_path: str
69 metadata: HookMetadata
70 priority_score: float
71 estimated_cost: int # Token cost
72 estimated_time_ms: float
73 scheduling_decision: SchedulingDecision
74 execution_group: Optional[int] = None # Group ID for parallel execution
75 dependencies: Set[str] = field(default_factory=set)
76 dependents: Set[str] = field(default_factory=set)
77 retry_count: int = 0
78 max_retries: int = 2
81@dataclass
82class SchedulingResult:
83 """Result of hook scheduling"""
84 scheduled_hooks: List[ScheduledHook]
85 execution_plan: List[List[ScheduledHook]] # Groups of hooks to execute
86 estimated_total_time_ms: float
87 estimated_total_tokens: int
88 skipped_hooks: List[ScheduledHook]
89 deferred_hooks: List[ScheduledHook]
90 scheduling_strategy: SchedulingStrategy
93@dataclass
94class ExecutionGroup:
95 """Group of hooks to execute together"""
96 group_id: int
97 execution_type: SchedulingDecision # PARALLEL or SEQUENTIAL
98 hooks: List[ScheduledHook]
99 estimated_time_ms: float
100 estimated_tokens: int
101 max_wait_time_ms: float
102 dependencies: Set[str] = field(default_factory=set)
105class PhaseOptimizedHookScheduler:
106 """
107 Intelligent scheduler for hooks with phase awareness and resource optimization
109 Provides optimal hook execution scheduling based on:
110 - Current development phase
111 - Token budget constraints
112 - Performance requirements
113 - Hook dependencies
114 - System load
115 """
117 def __init__(
118 self,
119 hook_manager: Optional[JITEnhancedHookManager] = None,
120 default_strategy: SchedulingStrategy = SchedulingStrategy.PHASE_OPTIMIZED,
121 max_parallel_groups: int = 3,
122 enable_adaptive_scheduling: bool = True
123 ):
124 """Initialize Phase-Optimized Hook Scheduler
126 Args:
127 hook_manager: JIT-Enhanced Hook Manager instance
128 default_strategy: Default scheduling strategy
129 max_parallel_groups: Maximum parallel execution groups
130 enable_adaptive_scheduling: Enable adaptive strategy selection
131 """
132 self.hook_manager = hook_manager or JITEnhancedHookManager()
133 self.default_strategy = default_strategy
134 self.max_parallel_groups = max_parallel_groups
135 self.enable_adaptive_scheduling = enable_adaptive_scheduling
137 # Performance tracking
138 self._scheduling_history: List[SchedulingResult] = []
139 self._performance_cache: Dict[str, Dict[str, float]] = {}
140 self._scheduling_lock = threading.Lock()
142 # Phase-specific optimization parameters
143 self._phase_parameters = self._initialize_phase_parameters()
145 # Adaptive strategy selection
146 self._strategy_performance: Dict[SchedulingStrategy, Dict[str, float]] = {
147 strategy: {
148 "success_rate": 1.0,
149 "avg_efficiency": 0.8,
150 "usage_count": 0
151 }
152 for strategy in SchedulingStrategy
153 }
155 def _initialize_phase_parameters(self) -> Dict[Phase, Dict[str, Any]]:
156 """Initialize phase-specific optimization parameters"""
157 return {
158 Phase.SPEC: {
159 "max_total_time_ms": 1000.0,
160 "token_budget_ratio": 0.3, # 30% of total budget
161 "priority_weights": {
162 HookPriority.CRITICAL: 1.0,
163 HookPriority.HIGH: 0.9,
164 HookPriority.NORMAL: 0.6,
165 HookPriority.LOW: 0.2
166 },
167 "prefer_parallel": False # Sequential for consistency
168 },
169 Phase.RED: {
170 "max_total_time_ms": 800.0,
171 "token_budget_ratio": 0.2, # 20% of total budget
172 "priority_weights": {
173 HookPriority.CRITICAL: 1.0,
174 HookPriority.HIGH: 1.0, # Testing is high priority
175 HookPriority.NORMAL: 0.8,
176 HookPriority.LOW: 0.3
177 },
178 "prefer_parallel": True # Parallel for faster test feedback
179 },
180 Phase.GREEN: {
181 "max_total_time_ms": 600.0,
182 "token_budget_ratio": 0.15, # 15% of total budget
183 "priority_weights": {
184 HookPriority.CRITICAL: 1.0,
185 HookPriority.HIGH: 0.8,
186 HookPriority.NORMAL: 0.7,
187 HookPriority.LOW: 0.4
188 },
189 "prefer_parallel": True # Parallel for faster implementation
190 },
191 Phase.REFACTOR: {
192 "max_total_time_ms": 1200.0,
193 "token_budget_ratio": 0.2, # 20% of total budget
194 "priority_weights": {
195 HookPriority.CRITICAL: 1.0,
196 HookPriority.HIGH: 0.9, # Code quality is important
197 HookPriority.NORMAL: 0.8,
198 HookPriority.LOW: 0.5
199 },
200 "prefer_parallel": False # Sequential for safety
201 },
202 Phase.SYNC: {
203 "max_total_time_ms": 1500.0,
204 "token_budget_ratio": 0.1, # 10% of total budget
205 "priority_weights": {
206 HookPriority.CRITICAL: 1.0,
207 HookPriority.HIGH: 0.7,
208 HookPriority.NORMAL: 0.9, # Documentation is important
209 HookPriority.LOW: 0.6
210 },
211 "prefer_parallel": False # Sequential for consistency
212 },
213 Phase.DEBUG: {
214 "max_total_time_ms": 500.0,
215 "token_budget_ratio": 0.05, # 5% of total budget
216 "priority_weights": {
217 HookPriority.CRITICAL: 1.0,
218 HookPriority.HIGH: 0.9, # Debug info is critical
219 HookPriority.NORMAL: 0.8,
220 HookPriority.LOW: 0.3
221 },
222 "prefer_parallel": True # Parallel for faster debugging
223 },
224 Phase.PLANNING: {
225 "max_total_time_ms": 800.0,
226 "token_budget_ratio": 0.25, # 25% of total budget
227 "priority_weights": {
228 HookPriority.CRITICAL: 1.0,
229 HookPriority.HIGH: 0.8,
230 HookPriority.NORMAL: 0.7,
231 HookPriority.LOW: 0.4
232 },
233 "prefer_parallel": False # Sequential for careful planning
234 }
235 }
237 async def schedule_hooks(
238 self,
239 event_type: HookEvent,
240 context: HookSchedulingContext,
241 strategy: Optional[SchedulingStrategy] = None
242 ) -> SchedulingResult:
243 """Schedule hooks for execution with optimization
245 Args:
246 event_type: Hook event type
247 context: Scheduling context with phase and constraints
248 strategy: Scheduling strategy (uses default if None)
250 Returns:
251 Scheduling result with execution plan
252 """
253 start_time = time.time()
255 # Select optimal strategy
256 selected_strategy = strategy or self._select_optimal_strategy(event_type, context)
258 # Get available hooks for event type
259 available_hooks = self.hook_manager._hooks_by_event.get(event_type, [])
261 # Create scheduled hooks with initial analysis
262 scheduled_hooks = await self._create_scheduled_hooks(
263 available_hooks, context, selected_strategy
264 )
266 # Filter and prioritize hooks
267 filtered_hooks = self._filter_hooks_by_constraints(scheduled_hooks, context)
268 prioritized_hooks = self._prioritize_hooks(filtered_hooks, context, selected_strategy)
270 # Resolve dependencies
271 dependency_resolved_hooks = self._resolve_dependencies(prioritized_hooks)
273 # Create execution groups
274 execution_groups = self._create_execution_groups(
275 dependency_resolved_hooks, context, selected_strategy
276 )
278 # Optimize execution order
279 optimized_groups = self._optimize_execution_order(execution_groups, context)
281 # Calculate estimates
282 total_time_ms = sum(group.estimated_time_ms for group in optimized_groups)
283 total_tokens = sum(group.estimated_tokens for group in optimized_groups)
285 # Create execution plan
286 execution_plan = [
287 [hook for hook in group.hooks if hook.scheduling_decision == SchedulingDecision.EXECUTE]
288 for group in optimized_groups
289 ]
291 # Separate skipped and deferred hooks
292 executed_hooks = [hook for group in optimized_groups for hook in group.hooks]
293 skipped_hooks = [hook for hook in scheduled_hooks if hook not in executed_hooks and
294 hook.scheduling_decision == SchedulingDecision.SKIP]
295 deferred_hooks = [hook for hook in scheduled_hooks if hook not in executed_hooks and
296 hook.scheduling_decision == SchedulingDecision.DEFER]
298 # Create scheduling result
299 result = SchedulingResult(
300 scheduled_hooks=scheduled_hooks,
301 execution_plan=execution_plan,
302 estimated_total_time_ms=total_time_ms,
303 estimated_total_tokens=total_tokens,
304 skipped_hooks=skipped_hooks,
305 deferred_hooks=deferred_hooks,
306 scheduling_strategy=selected_strategy
307 )
309 # Update scheduling history and performance
310 self._update_scheduling_history(result, time.time() - start_time)
311 self._update_strategy_performance(selected_strategy, result, context)
313 return result
315 def _select_optimal_strategy(
316 self,
317 event_type: HookEvent,
318 context: HookSchedulingContext
319 ) -> SchedulingStrategy:
320 """Select optimal scheduling strategy based on context"""
321 if not self.enable_adaptive_scheduling:
322 return self.default_strategy
324 # Strategy selection based on phase and constraints
325 if context.available_token_budget < 5000:
326 # Low token budget - prioritize token efficiency
327 return SchedulingStrategy.TOKEN_EFFICIENT
329 elif context.max_execution_time_ms < 500:
330 # Tight time constraint - prioritize performance
331 return SchedulingStrategy.PERFORMANCE_FIRST
333 elif context.system_load > 0.8:
334 # High system load - prioritize priority
335 return SchedulingStrategy.PRIORITY_FIRST
337 elif context.current_phase == Phase.SYNC:
338 # Documentation phase - use phase-optimized
339 return SchedulingStrategy.PHASE_OPTIMIZED
341 else:
342 # Use best performing strategy from history
343 best_strategy = self.default_strategy
344 best_efficiency = 0.0
346 for strategy, performance in self._strategy_performance.items():
347 if performance["usage_count"] > 0:
348 efficiency = (performance["success_rate"] * performance["avg_efficiency"])
349 if efficiency > best_efficiency:
350 best_efficiency = efficiency
351 best_strategy = strategy
353 return best_strategy
355 async def _create_scheduled_hooks(
356 self,
357 hook_paths: List[str],
358 context: HookSchedulingContext,
359 strategy: SchedulingStrategy
360 ) -> List[ScheduledHook]:
361 """Create scheduled hooks from hook paths with analysis"""
362 scheduled_hooks = []
364 for hook_path in hook_paths:
365 metadata = self.hook_manager._hook_registry.get(hook_path)
366 if not metadata:
367 continue
369 # Calculate priority score based on strategy
370 priority_score = self._calculate_priority_score(
371 metadata, context, strategy
372 )
374 # Estimate execution cost
375 estimated_cost = self._estimate_hook_cost(metadata, context)
376 estimated_time = self._estimate_hook_time(metadata, context)
378 # Make initial scheduling decision
379 scheduling_decision = self._make_initial_scheduling_decision(
380 metadata, context, estimated_cost, estimated_time
381 )
383 scheduled_hook = ScheduledHook(
384 hook_path=hook_path,
385 metadata=metadata,
386 priority_score=priority_score,
387 estimated_cost=estimated_cost,
388 estimated_time_ms=estimated_time,
389 scheduling_decision=scheduling_decision,
390 dependencies=metadata.dependencies.copy(),
391 dependents=set()
392 )
394 scheduled_hooks.append(scheduled_hook)
396 return scheduled_hooks
398 def _calculate_priority_score(
399 self,
400 metadata: HookMetadata,
401 context: HookSchedulingContext,
402 strategy: SchedulingStrategy
403 ) -> float:
404 """Calculate priority score for hook based on strategy"""
405 base_score = 0.0
407 if strategy == SchedulingStrategy.PRIORITY_FIRST:
408 # Priority-based scoring
409 phase_params = self._phase_parameters.get(context.current_phase, {})
410 priority_weights = phase_params.get("priority_weights", {})
411 base_score = priority_weights.get(metadata.priority, 0.5) * 100
413 elif strategy == SchedulingStrategy.PERFORMANCE_FIRST:
414 # Performance-based scoring (faster hooks get higher priority)
415 base_score = max(0, 100 - metadata.estimated_execution_time_ms / 10)
417 elif strategy == SchedulingStrategy.PHASE_OPTIMIZED:
418 # Phase relevance scoring
419 phase_relevance = metadata.phase_relevance.get(context.current_phase, 0.5)
420 base_score = phase_relevance * 100
422 # Add priority bonus
423 priority_bonus = (5 - metadata.priority.value) * 10
424 base_score += priority_bonus
426 elif strategy == SchedulingStrategy.TOKEN_EFFICIENT:
427 # Token efficiency scoring (lower token cost gets higher priority)
428 base_score = max(0, 100 - metadata.token_cost_estimate / 10)
430 else: # BALANCED
431 # Combine all factors
432 priority_score = (5 - metadata.priority.value) * 20
433 phase_score = metadata.phase_relevance.get(context.current_phase, 0.5) * 30
434 performance_score = max(0, 30 - metadata.estimated_execution_time_ms / 20)
435 token_score = max(0, 20 - metadata.token_cost_estimate / 50)
437 base_score = priority_score + phase_score + performance_score + token_score
439 # Apply success rate modifier
440 base_score *= metadata.success_rate
442 return base_score
444 def _estimate_hook_cost(
445 self,
446 metadata: HookMetadata,
447 context: HookSchedulingContext
448 ) -> int:
449 """Estimate token cost for hook execution"""
450 base_cost = metadata.token_cost_estimate
452 # Phase-based cost adjustment
453 phase_relevance = metadata.phase_relevance.get(context.current_phase, 0.5)
454 if phase_relevance < 0.3:
455 # Low relevance - increase cost to discourage execution
456 base_cost = int(base_cost * 2.0)
457 elif phase_relevance > 0.8:
458 # High relevance - reduce cost to encourage execution
459 base_cost = int(base_cost * 0.7)
461 # System load adjustment
462 if context.system_load > 0.8:
463 base_cost = int(base_cost * 1.3) # Increase cost under high load
465 return base_cost
467 def _estimate_hook_time(
468 self,
469 metadata: HookMetadata,
470 context: HookSchedulingContext
471 ) -> float:
472 """Estimate execution time for hook"""
473 base_time = metadata.estimated_execution_time_ms
475 # System load adjustment
476 load_factor = 1.0 + (context.system_load * 0.5) # Up to 1.5x slower under load
478 # Success rate adjustment (unreliable hooks might take longer due to retries)
479 reliability_factor = 2.0 - metadata.success_rate # 1.0 to 2.0
481 return base_time * load_factor * reliability_factor
483 def _make_initial_scheduling_decision(
484 self,
485 metadata: HookMetadata,
486 context: HookSchedulingContext,
487 estimated_cost: int,
488 estimated_time: float
489 ) -> SchedulingDecision:
490 """Make initial scheduling decision for hook"""
491 # Critical hooks always execute
492 if metadata.priority == HookPriority.CRITICAL:
493 return SchedulingDecision.EXECUTE
495 # Check constraints
496 if estimated_cost > context.available_token_budget:
497 return SchedulingDecision.SKIP
499 if estimated_time > context.max_execution_time_ms:
500 return SchedulingDecision.DEFER
502 # Phase relevance check
503 phase_relevance = metadata.phase_relevance.get(context.current_phase, 0.5)
504 if phase_relevance < 0.2:
505 return SchedulingDecision.SKIP
507 # Success rate check
508 if metadata.success_rate < 0.3:
509 return SchedulingDecision.DEFER
511 # System load check
512 if context.system_load > 0.9 and metadata.priority != HookPriority.HIGH:
513 return SchedulingDecision.DEFER
515 # Default to execution
516 return SchedulingDecision.EXECUTE
518 def _filter_hooks_by_constraints(
519 self,
520 scheduled_hooks: List[ScheduledHook],
521 context: HookSchedulingContext
522 ) -> List[ScheduledHook]:
523 """Filter hooks based on constraints"""
524 filtered_hooks = []
525 remaining_token_budget = context.available_token_budget
526 remaining_time_budget = context.max_execution_time_ms
528 # Sort by priority score (highest first)
529 sorted_hooks = sorted(scheduled_hooks, key=lambda h: h.priority_score, reverse=True)
531 for hook in sorted_hooks:
532 if hook.scheduling_decision != SchedulingDecision.EXECUTE:
533 filtered_hooks.append(hook)
534 continue
536 # Check if hook fits within constraints
537 if hook.estimated_cost <= remaining_token_budget and \
538 hook.estimated_time_ms <= remaining_time_budget:
539 filtered_hooks.append(hook)
540 remaining_token_budget -= hook.estimated_cost
541 remaining_time_budget -= hook.estimated_time_ms
542 else:
543 # Can't fit - mark as deferred
544 hook.scheduling_decision = SchedulingDecision.DEFER
545 filtered_hooks.append(hook)
547 return filtered_hooks
549 def _prioritize_hooks(
550 self,
551 scheduled_hooks: List[ScheduledHook],
552 context: HookSchedulingContext,
553 strategy: SchedulingStrategy
554 ) -> List[ScheduledHook]:
555 """Prioritize hooks for execution"""
556 # Separate by scheduling decision
557 execute_hooks = [h for h in scheduled_hooks if h.scheduling_decision == SchedulingDecision.EXECUTE]
558 other_hooks = [h for h in scheduled_hooks if h.scheduling_decision != SchedulingDecision.EXECUTE]
560 # Sort execute hooks by priority (descending)
561 execute_hooks.sort(key=lambda h: h.priority_score, reverse=True)
563 # Sort other hooks by priority (ascending - least important first for skipping)
564 other_hooks.sort(key=lambda h: h.priority_score)
566 return execute_hooks + other_hooks
568 def _resolve_dependencies(self, hooks: List[ScheduledHook]) -> List[ScheduledHook]:
569 """Resolve hook dependencies and adjust execution order"""
570 # Build dependency graph
571 for hook in hooks:
572 for dep_path in hook.dependencies:
573 # Find dependent hook
574 for other_hook in hooks:
575 if other_hook.hook_path == dep_path:
576 other_hook.dependents.add(hook.hook_path)
577 break
579 # Topological sort considering dependencies
580 resolved_hooks = []
581 remaining_hooks = hooks.copy()
583 while remaining_hooks:
584 # Find hooks with no unresolved dependencies
585 ready_hooks = [
586 h for h in remaining_hooks
587 if not any(dep in [rh.hook_path for rh in remaining_hooks] for dep in h.dependencies)
588 ]
590 if not ready_hooks:
591 # Circular dependency - break by priority
592 ready_hooks = sorted(remaining_hooks, key=lambda h: h.priority_score, reverse=True)
593 ready_hooks = [ready_hooks[0]]
595 # Add highest priority ready hook
596 next_hook = max(ready_hooks, key=lambda h: h.priority_score)
597 resolved_hooks.append(next_hook)
598 remaining_hooks.remove(next_hook)
600 return resolved_hooks
602 def _create_execution_groups(
603 self,
604 hooks: List[ScheduledHook],
605 context: HookSchedulingContext,
606 strategy: SchedulingStrategy
607 ) -> List[ExecutionGroup]:
608 """Create execution groups for optimal performance"""
609 groups = []
610 current_group = None
611 group_id = 0
613 # Get phase preferences
614 phase_params = self._phase_parameters.get(context.current_phase, {})
615 prefer_parallel = phase_params.get("prefer_parallel", True)
617 for hook in hooks:
618 if hook.scheduling_decision != SchedulingDecision.EXECUTE:
619 continue
621 # Determine execution type
622 execution_type = SchedulingDecision.PARALLEL if (prefer_parallel and hook.metadata.parallel_safe) else SchedulingDecision.SEQUENTIAL
624 # Create new group if needed
625 if (current_group is None or
626 current_group.execution_type != execution_type or
627 len(current_group.hooks) >= self.max_parallel_groups):
629 current_group = ExecutionGroup(
630 group_id=group_id,
631 execution_type=execution_type,
632 hooks=[],
633 estimated_time_ms=0.0,
634 estimated_tokens=0,
635 max_wait_time_ms=0.0,
636 dependencies=set()
637 )
638 groups.append(current_group)
639 group_id += 1
641 # Add hook to current group
642 current_group.hooks.append(hook)
643 current_group.estimated_time_ms += hook.estimated_time_ms
644 current_group.estimated_tokens += hook.estimated_cost
645 current_group.dependencies.update(hook.dependencies)
647 # Set max wait time for parallel groups
648 for group in groups:
649 if group.execution_type == SchedulingDecision.PARALLEL:
650 # For parallel groups, wait time is determined by slowest hook
651 group.max_wait_time_ms = max(h.estimated_time_ms for h in group.hooks)
652 else:
653 # For sequential groups, sum all hook times
654 group.max_wait_time_ms = group.estimated_time_ms
656 return groups
658 def _optimize_execution_order(
659 self,
660 groups: List[ExecutionGroup],
661 context: HookSchedulingContext
662 ) -> List[ExecutionGroup]:
663 """Optimize execution order of groups"""
664 if len(groups) <= 1:
665 return groups
667 # Sort groups by priority and execution type
668 def group_score(group: ExecutionGroup) -> float:
669 # Calculate average priority of hooks in group
670 avg_priority = sum(h.priority_score for h in group.hooks) / len(group.hooks)
672 # Parallel groups get bonus for speed
673 parallel_bonus = 10 if group.execution_type == SchedulingDecision.PARALLEL else 0
675 # Smaller groups get bonus for better time management
676 size_bonus = max(0, 5 - len(group.hooks))
678 return avg_priority + parallel_bonus + size_bonus
680 # Sort by score (descending)
681 optimized_groups = sorted(groups, key=group_score, reverse=True)
683 return optimized_groups
685 def _update_scheduling_history(self, result: SchedulingResult, planning_time_ms: float) -> None:
686 """Update scheduling history for learning"""
687 with self._scheduling_lock:
688 history_entry = {
689 "timestamp": datetime.now().isoformat(),
690 "strategy": result.scheduling_strategy.value,
691 "planning_time_ms": planning_time_ms,
692 "total_hooks": len(result.scheduled_hooks),
693 "executed_hooks": sum(len(group) for group in result.execution_plan),
694 "skipped_hooks": len(result.skipped_hooks),
695 "deferred_hooks": len(result.deferred_hooks),
696 "estimated_time_ms": result.estimated_total_time_ms,
697 "estimated_tokens": result.estimated_total_tokens
698 }
700 self._scheduling_history.append(history_entry)
702 # Keep history manageable
703 if len(self._scheduling_history) > 1000:
704 self._scheduling_history = self._scheduling_history[-500:]
706 def _update_strategy_performance(
707 self,
708 strategy: SchedulingStrategy,
709 result: SchedulingResult,
710 context: HookSchedulingContext
711 ) -> None:
712 """Update strategy performance metrics"""
713 performance = self._strategy_performance[strategy]
715 # Update usage count
716 performance["usage_count"] += 1
718 # Calculate efficiency (higher is better)
719 total_hooks = len(result.scheduled_hooks)
720 executed_hooks = sum(len(group) for group in result.execution_plan)
722 if total_hooks > 0:
723 execution_ratio = executed_hooks / total_hooks
725 # Consider resource utilization
726 time_efficiency = min(1.0, result.estimated_total_time_ms / context.max_execution_time_ms)
727 token_efficiency = min(1.0, result.estimated_total_tokens / max(1, context.available_token_budget))
729 # Overall efficiency
730 overall_efficiency = (execution_ratio * 0.5) + (time_efficiency * 0.3) + (token_efficiency * 0.2)
732 # Update rolling average
733 current_avg = performance["avg_efficiency"]
734 performance["avg_efficiency"] = (current_avg * 0.8) + (overall_efficiency * 0.2)
736 # Update success rate (assume successful if no critical errors)
737 # This would be updated with actual execution results
738 performance["success_rate"] = min(1.0, performance["success_rate"] * 0.95 + 0.05)
740 def get_scheduling_statistics(self) -> Dict[str, Any]:
741 """Get scheduling statistics and performance data"""
742 with self._scheduling_lock:
743 total_schedules = len(self._scheduling_history)
745 if total_schedules == 0:
746 return {
747 "total_schedules": 0,
748 "strategy_performance": {},
749 "recent_performance": []
750 }
752 # Calculate strategy statistics
753 strategy_stats = {}
754 for strategy, performance in self._strategy_performance.items():
755 if performance["usage_count"] > 0:
756 strategy_stats[strategy.value] = {
757 "usage_count": performance["usage_count"],
758 "success_rate": performance["success_rate"],
759 "avg_efficiency": performance["avg_efficiency"],
760 "recommendation_score": performance["success_rate"] * performance["avg_efficiency"]
761 }
763 # Get recent performance
764 recent_performance = self._scheduling_history[-10:] if total_schedules >= 10 else self._scheduling_history
766 return {
767 "total_schedules": total_schedules,
768 "strategy_performance": strategy_stats,
769 "recent_performance": recent_performance,
770 "recommended_strategy": max(strategy_stats.keys(),
771 key=lambda k: strategy_stats[k]["recommendation_score"]) if strategy_stats else None
772 }
774 def get_phase_optimization_insights(self, phase: Phase) -> Dict[str, Any]:
775 """Get insights for phase-specific optimization"""
776 phase_params = self._phase_parameters.get(phase, {})
778 # Analyze historical performance for this phase
779 phase_schedules = [
780 s for s in self._scheduling_history
781 if any(phase.value.lower() in s.get("context", "").lower() for phase in [phase])
782 ]
784 insights = {
785 "phase": phase.value,
786 "parameters": phase_params,
787 "historical_schedules": len(phase_schedules),
788 "optimization_recommendations": []
789 }
791 # Generate recommendations based on phase parameters
792 if phase_params.get("prefer_parallel", False):
793 insights["optimization_recommendations"].append(
794 "This phase benefits from parallel hook execution"
795 )
796 else:
797 insights["optimization_recommendations"].append(
798 "This phase prefers sequential hook execution for consistency"
799 )
801 if phase_params.get("token_budget_ratio", 0) > 0.2:
802 insights["optimization_recommendations"].append(
803 "This phase requires significant token budget - consider token-efficient scheduling"
804 )
806 # Add strategy recommendations
807 strategy_stats = self.get_scheduling_statistics()["strategy_performance"]
808 best_strategy = (
809 max(
810 strategy_stats.items(),
811 key=lambda x: x[1]["recommendation_score"]
812 ) if strategy_stats else None
813 )
815 if best_strategy:
816 insights["recommended_strategy"] = best_strategy[0]
817 insights["strategy_rationale"] = (
818 f"Best performance with {best_strategy[0]} strategy (score: {best_strategy[1]['recommendation_score']:.2f})"
819 )
821 return insights
824# Convenience functions for common scheduling operations
825async def schedule_session_start_hooks(
826 context: HookSchedulingContext,
827 strategy: Optional[SchedulingStrategy] = None
828) -> SchedulingResult:
829 """Schedule SessionStart hooks with phase optimization"""
830 scheduler = PhaseOptimizedHookScheduler()
831 return await scheduler.schedule_hooks(
832 HookEvent.SESSION_START,
833 context,
834 strategy
835 )
838async def schedule_pre_tool_hooks(
839 context: HookSchedulingContext,
840 strategy: Optional[SchedulingStrategy] = None
841) -> SchedulingResult:
842 """Schedule PreToolUse hooks with phase optimization"""
843 scheduler = PhaseOptimizedHookScheduler()
844 return await scheduler.schedule_hooks(
845 HookEvent.PRE_TOOL_USE,
846 context,
847 strategy
848 )
851def get_hook_scheduling_insights(phase: Phase) -> Dict[str, Any]:
852 """Get phase-specific hook scheduling insights"""
853 scheduler = PhaseOptimizedHookScheduler()
854 return scheduler.get_phase_optimization_insights(phase)
857if __name__ == "__main__":
858 # Example usage and testing
859 async def test_phase_optimized_scheduler():
860 scheduler = PhaseOptimizedHookScheduler()
862 # Create test context
863 context = HookSchedulingContext(
864 event_type=HookEvent.SESSION_START,
865 current_phase=Phase.SPEC,
866 user_input="Creating new specification for user authentication",
867 available_token_budget=10000,
868 max_execution_time_ms=1000.0
869 )
871 # Schedule hooks
872 result = await scheduler.schedule_hooks(HookEvent.SESSION_START, context)
874 print(f"Scheduled {len(result.execution_plan)} execution groups")
875 print(f"Estimated time: {result.estimated_total_time_ms:.1f}ms")
876 print(f"Estimated tokens: {result.estimated_total_tokens}")
877 print(f"Strategy: {result.scheduling_strategy.value}")
879 # Show insights
880 insights = scheduler.get_phase_optimization_insights(Phase.SPEC)
881 print("\nPhase insights for SPEC:")
882 for rec in insights.get("optimization_recommendations", []):
883 print(f" - {rec}")
885 # Show statistics
886 stats = scheduler.get_scheduling_statistics()
887 print("\nScheduling statistics:")
888 print(f" Total schedules: {stats['total_schedules']}")
889 if stats.get("recommended_strategy"):
890 print(f" Recommended strategy: {stats['recommended_strategy']}")
892 # Run test
893 asyncio.run(test_phase_optimized_scheduler())