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

1""" 

2Phase-Optimized Hook Scheduler 

3 

4Intelligent scheduling system for hooks based on development phases, 

5token budget constraints, and performance requirements. 

6 

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""" 

15 

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 

23 

24from .jit_enhanced_hook_manager import ( 

25 HookEvent, 

26 HookMetadata, 

27 HookPriority, 

28 JITEnhancedHookManager, 

29 Phase, 

30) 

31 

32 

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 

40 

41 

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 

49 

50 

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) 

63 

64 

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 

79 

80 

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 

91 

92 

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) 

103 

104 

105class PhaseOptimizedHookScheduler: 

106 """ 

107 Intelligent scheduler for hooks with phase awareness and resource optimization 

108 

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 """ 

116 

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 

125 

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 

136 

137 # Performance tracking 

138 self._scheduling_history: List[SchedulingResult] = [] 

139 self._performance_cache: Dict[str, Dict[str, float]] = {} 

140 self._scheduling_lock = threading.Lock() 

141 

142 # Phase-specific optimization parameters 

143 self._phase_parameters = self._initialize_phase_parameters() 

144 

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 } 

154 

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 } 

236 

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 

244 

245 Args: 

246 event_type: Hook event type 

247 context: Scheduling context with phase and constraints 

248 strategy: Scheduling strategy (uses default if None) 

249 

250 Returns: 

251 Scheduling result with execution plan 

252 """ 

253 start_time = time.time() 

254 

255 # Select optimal strategy 

256 selected_strategy = strategy or self._select_optimal_strategy(event_type, context) 

257 

258 # Get available hooks for event type 

259 available_hooks = self.hook_manager._hooks_by_event.get(event_type, []) 

260 

261 # Create scheduled hooks with initial analysis 

262 scheduled_hooks = await self._create_scheduled_hooks( 

263 available_hooks, context, selected_strategy 

264 ) 

265 

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) 

269 

270 # Resolve dependencies 

271 dependency_resolved_hooks = self._resolve_dependencies(prioritized_hooks) 

272 

273 # Create execution groups 

274 execution_groups = self._create_execution_groups( 

275 dependency_resolved_hooks, context, selected_strategy 

276 ) 

277 

278 # Optimize execution order 

279 optimized_groups = self._optimize_execution_order(execution_groups, context) 

280 

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) 

284 

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 ] 

290 

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] 

297 

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 ) 

308 

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) 

312 

313 return result 

314 

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 

323 

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 

328 

329 elif context.max_execution_time_ms < 500: 

330 # Tight time constraint - prioritize performance 

331 return SchedulingStrategy.PERFORMANCE_FIRST 

332 

333 elif context.system_load > 0.8: 

334 # High system load - prioritize priority 

335 return SchedulingStrategy.PRIORITY_FIRST 

336 

337 elif context.current_phase == Phase.SYNC: 

338 # Documentation phase - use phase-optimized 

339 return SchedulingStrategy.PHASE_OPTIMIZED 

340 

341 else: 

342 # Use best performing strategy from history 

343 best_strategy = self.default_strategy 

344 best_efficiency = 0.0 

345 

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 

352 

353 return best_strategy 

354 

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 = [] 

363 

364 for hook_path in hook_paths: 

365 metadata = self.hook_manager._hook_registry.get(hook_path) 

366 if not metadata: 

367 continue 

368 

369 # Calculate priority score based on strategy 

370 priority_score = self._calculate_priority_score( 

371 metadata, context, strategy 

372 ) 

373 

374 # Estimate execution cost 

375 estimated_cost = self._estimate_hook_cost(metadata, context) 

376 estimated_time = self._estimate_hook_time(metadata, context) 

377 

378 # Make initial scheduling decision 

379 scheduling_decision = self._make_initial_scheduling_decision( 

380 metadata, context, estimated_cost, estimated_time 

381 ) 

382 

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 ) 

393 

394 scheduled_hooks.append(scheduled_hook) 

395 

396 return scheduled_hooks 

397 

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 

406 

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 

412 

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) 

416 

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 

421 

422 # Add priority bonus 

423 priority_bonus = (5 - metadata.priority.value) * 10 

424 base_score += priority_bonus 

425 

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) 

429 

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) 

436 

437 base_score = priority_score + phase_score + performance_score + token_score 

438 

439 # Apply success rate modifier 

440 base_score *= metadata.success_rate 

441 

442 return base_score 

443 

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 

451 

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) 

460 

461 # System load adjustment 

462 if context.system_load > 0.8: 

463 base_cost = int(base_cost * 1.3) # Increase cost under high load 

464 

465 return base_cost 

466 

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 

474 

475 # System load adjustment 

476 load_factor = 1.0 + (context.system_load * 0.5) # Up to 1.5x slower under load 

477 

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 

480 

481 return base_time * load_factor * reliability_factor 

482 

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 

494 

495 # Check constraints 

496 if estimated_cost > context.available_token_budget: 

497 return SchedulingDecision.SKIP 

498 

499 if estimated_time > context.max_execution_time_ms: 

500 return SchedulingDecision.DEFER 

501 

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 

506 

507 # Success rate check 

508 if metadata.success_rate < 0.3: 

509 return SchedulingDecision.DEFER 

510 

511 # System load check 

512 if context.system_load > 0.9 and metadata.priority != HookPriority.HIGH: 

513 return SchedulingDecision.DEFER 

514 

515 # Default to execution 

516 return SchedulingDecision.EXECUTE 

517 

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 

527 

528 # Sort by priority score (highest first) 

529 sorted_hooks = sorted(scheduled_hooks, key=lambda h: h.priority_score, reverse=True) 

530 

531 for hook in sorted_hooks: 

532 if hook.scheduling_decision != SchedulingDecision.EXECUTE: 

533 filtered_hooks.append(hook) 

534 continue 

535 

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) 

546 

547 return filtered_hooks 

548 

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] 

559 

560 # Sort execute hooks by priority (descending) 

561 execute_hooks.sort(key=lambda h: h.priority_score, reverse=True) 

562 

563 # Sort other hooks by priority (ascending - least important first for skipping) 

564 other_hooks.sort(key=lambda h: h.priority_score) 

565 

566 return execute_hooks + other_hooks 

567 

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 

578 

579 # Topological sort considering dependencies 

580 resolved_hooks = [] 

581 remaining_hooks = hooks.copy() 

582 

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 ] 

589 

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]] 

594 

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) 

599 

600 return resolved_hooks 

601 

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 

612 

613 # Get phase preferences 

614 phase_params = self._phase_parameters.get(context.current_phase, {}) 

615 prefer_parallel = phase_params.get("prefer_parallel", True) 

616 

617 for hook in hooks: 

618 if hook.scheduling_decision != SchedulingDecision.EXECUTE: 

619 continue 

620 

621 # Determine execution type 

622 execution_type = SchedulingDecision.PARALLEL if (prefer_parallel and hook.metadata.parallel_safe) else SchedulingDecision.SEQUENTIAL 

623 

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): 

628 

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 

640 

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) 

646 

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 

655 

656 return groups 

657 

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 

666 

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) 

671 

672 # Parallel groups get bonus for speed 

673 parallel_bonus = 10 if group.execution_type == SchedulingDecision.PARALLEL else 0 

674 

675 # Smaller groups get bonus for better time management 

676 size_bonus = max(0, 5 - len(group.hooks)) 

677 

678 return avg_priority + parallel_bonus + size_bonus 

679 

680 # Sort by score (descending) 

681 optimized_groups = sorted(groups, key=group_score, reverse=True) 

682 

683 return optimized_groups 

684 

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 } 

699 

700 self._scheduling_history.append(history_entry) 

701 

702 # Keep history manageable 

703 if len(self._scheduling_history) > 1000: 

704 self._scheduling_history = self._scheduling_history[-500:] 

705 

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] 

714 

715 # Update usage count 

716 performance["usage_count"] += 1 

717 

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) 

721 

722 if total_hooks > 0: 

723 execution_ratio = executed_hooks / total_hooks 

724 

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)) 

728 

729 # Overall efficiency 

730 overall_efficiency = (execution_ratio * 0.5) + (time_efficiency * 0.3) + (token_efficiency * 0.2) 

731 

732 # Update rolling average 

733 current_avg = performance["avg_efficiency"] 

734 performance["avg_efficiency"] = (current_avg * 0.8) + (overall_efficiency * 0.2) 

735 

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) 

739 

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) 

744 

745 if total_schedules == 0: 

746 return { 

747 "total_schedules": 0, 

748 "strategy_performance": {}, 

749 "recent_performance": [] 

750 } 

751 

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 } 

762 

763 # Get recent performance 

764 recent_performance = self._scheduling_history[-10:] if total_schedules >= 10 else self._scheduling_history 

765 

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 } 

773 

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, {}) 

777 

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 ] 

783 

784 insights = { 

785 "phase": phase.value, 

786 "parameters": phase_params, 

787 "historical_schedules": len(phase_schedules), 

788 "optimization_recommendations": [] 

789 } 

790 

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 ) 

800 

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 ) 

805 

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 ) 

814 

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 ) 

820 

821 return insights 

822 

823 

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 ) 

836 

837 

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 ) 

849 

850 

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) 

855 

856 

857if __name__ == "__main__": 

858 # Example usage and testing 

859 async def test_phase_optimized_scheduler(): 

860 scheduler = PhaseOptimizedHookScheduler() 

861 

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 ) 

870 

871 # Schedule hooks 

872 result = await scheduler.schedule_hooks(HookEvent.SESSION_START, context) 

873 

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}") 

878 

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}") 

884 

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']}") 

891 

892 # Run test 

893 asyncio.run(test_phase_optimized_scheduler())