Class: Aidp::Harness::ThinkingDepthManager
- Inherits:
-
Object
- Object
- Aidp::Harness::ThinkingDepthManager
- Includes:
- MessageDisplay
- Defined in:
- lib/aidp/harness/thinking_depth_manager.rb
Overview
Manages thinking depth tier selection and escalation Integrates with CapabilityRegistry and Configuration to select appropriate models
Constant Summary collapse
- MAX_TIER_HISTORY_SIZE =
Configuration constants for tier management
100- MAX_COMMENT_LENGTH =
2000- MAX_REASONING_DISPLAY_LENGTH =
100- DEFAULT_CONFIDENCE =
0.7
Constants included from MessageDisplay
MessageDisplay::COLOR_MAP, MessageDisplay::CRITICAL_TYPES
Instance Attribute Summary collapse
-
#configuration ⇒ Object
readonly
Returns the value of attribute configuration.
-
#escalation_count ⇒ Object
readonly
Get escalation attempt count.
-
#model_attempts ⇒ Object
readonly
Issue #375: Model attempt tracking for intelligent escalation Structure: { tier => { provider => { model => { attempts: n, failed: bool } } } }.
-
#registry ⇒ Object
readonly
Returns the value of attribute registry.
Instance Method Summary collapse
-
#apply_autonomous_tier_cap(tier) ⇒ String
Apply autonomous mode tier cap if active Returns the tier capped at autonomous_max_tier when in autonomous mode.
-
#autonomous_max_tier ⇒ Object
Get maximum tier for autonomous operations (issue #375).
-
#autonomous_max_tier=(tier) ⇒ Object
Set autonomous max tier for this session.
-
#autonomous_mode? ⇒ Boolean
Check if in autonomous mode.
-
#available_models_for_tier(provider:) ⇒ Array<String>
Get all available models for current tier and provider.
-
#base_max_tier ⇒ Object
Get the base maximum tier (from session override or config, ignoring autonomous mode) This is the "raw" max tier before autonomous mode restrictions are applied.
-
#can_escalate? ⇒ Boolean
Check if we can escalate to next tier.
-
#current_tier ⇒ Object
Get current tier (defaults to config default_tier if not set).
-
#current_tier=(tier) ⇒ Object
Set current tier (validates against max_tier).
-
#de_escalate_tier(reason: nil) ⇒ Object
De-escalate to next lower tier Returns new tier or nil if already at minimum.
-
#default_tier ⇒ Object
Get default tier from configuration.
-
#denylist_model(model) ⇒ Object
Add model to denylist.
-
#determine_tier_from_comment(comment_text:, provider_manager:, labels: []) ⇒ Hash
Determine appropriate tier from issue/PR comment content using ZFC.
-
#disable_autonomous_mode ⇒ Object
Disable autonomous mode (restores normal max tier).
-
#enable_autonomous_mode ⇒ Object
Enable autonomous mode (restricts max tier, enables model-level tracking) Should be called when entering watch mode or work loops.
-
#escalate_tier(reason: nil) ⇒ Object
Escalate to next higher tier Returns new tier or nil if already at max.
-
#escalate_tier_intelligent(provider:, reason: nil) ⇒ String?
Escalate tier with intelligent model tracking (issue #375) Only escalates if all models in current tier have been tried.
-
#initialize(configuration, registry: nil, root_dir: nil, autonomous_mode: false) ⇒ ThinkingDepthManager
constructor
A new instance of ThinkingDepthManager.
-
#max_tier ⇒ Object
Get effective maximum tier (applies autonomous mode restrictions) Issue #375: In autonomous mode, respects autonomous_max_tier.
-
#max_tier=(tier) ⇒ Object
Set maximum tier for this session (temporary override).
-
#model_attempt_count(provider:, model:) ⇒ Object
Get attempts for a specific model.
-
#model_attempts_summary ⇒ Object
Get summary of model attempts in current tier.
-
#model_denylisted?(model) ⇒ Boolean
Check if model is denylisted.
-
#model_failed?(provider:, model:) ⇒ Boolean
Check if a model has been marked as failed.
-
#models_configured_for_tier?(provider:) ⇒ Boolean
Check if any models are configured for the current tier and provider.
-
#permission_for_current_tier ⇒ Object
Get permission level for current tier.
-
#recommend_tier_for_complexity(complexity_score) ⇒ Object
Get tier recommendation based on complexity score (0.0-1.0).
-
#record_model_attempt(provider:, model:, success:) ⇒ Object
Record an attempt with a specific model.
-
#reset_model_tracking ⇒ Object
Reset model tracking (call when changing tiers or starting new work) Clears current tier's data for consistency; preserves other tiers' history for analysis.
-
#reset_to_default ⇒ Object
Reset to default tier.
-
#select_model_for_tier(tier = nil, provider: nil) ⇒ Object
Select best model for current tier and provider Returns [provider_name, model_name, model_data] or nil.
-
#select_next_model(provider:) ⇒ String?
Select the next model to try in current tier Issue #375: Tries all models before escalating, respects min attempts per model.
-
#should_escalate_on_complexity?(context) ⇒ Boolean
Check if should escalate based on complexity thresholds.
-
#should_escalate_on_failures?(failure_count) ⇒ Boolean
Check if should escalate based on failure count.
-
#should_escalate_tier?(provider:) ⇒ Hash
Check if we should escalate tier based on model exhaustion Issue #375: Requires minimum total attempts and trying all models first.
-
#tier_for_model(provider, model) ⇒ Object
Get tier for a specific model.
-
#tier_history ⇒ Object
Get tier change history.
-
#tier_info(tier) ⇒ Object
Get information about a specific tier.
-
#tier_override_for(key) ⇒ Object
Check if tier override exists for skill/template.
Methods included from MessageDisplay
#display_message, included, #message_display_prompt, #quiet_mode?
Constructor Details
#initialize(configuration, registry: nil, root_dir: nil, autonomous_mode: false) ⇒ ThinkingDepthManager
Returns a new instance of ThinkingDepthManager.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 38 def initialize(configuration, registry: nil, root_dir: nil, autonomous_mode: false) @configuration = configuration @registry = registry || CapabilityRegistry.new(root_dir: root_dir || configuration.project_dir) @current_tier = nil @session_max_tier = nil @tier_history = [] @escalation_count = 0 # Issue #375: Track model attempts for intelligent escalation @model_attempts = {} @total_attempts_in_tier = 0 @autonomous_mode = autonomous_mode @model_denylist = [] # Models to skip (e.g., denylisted by user) Aidp.log_debug("thinking_depth_manager", "Initialized", default_tier: default_tier, max_tier: max_tier, autonomous_max_tier: autonomous_max_tier, autonomous_mode: autonomous_mode) end |
Instance Attribute Details
#configuration ⇒ Object (readonly)
Returns the value of attribute configuration.
32 33 34 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 32 def configuration @configuration end |
#escalation_count ⇒ Object (readonly)
Get escalation attempt count
778 779 780 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 778 def escalation_count @escalation_count end |
#model_attempts ⇒ Object (readonly)
Issue #375: Model attempt tracking for intelligent escalation Structure: { tier => { provider => { model => { attempts: n, failed: bool } } } }
36 37 38 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 36 def model_attempts @model_attempts end |
#registry ⇒ Object (readonly)
Returns the value of attribute registry.
32 33 34 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 32 def registry @registry end |
Instance Method Details
#apply_autonomous_tier_cap(tier) ⇒ String
Apply autonomous mode tier cap if active Returns the tier capped at autonomous_max_tier when in autonomous mode
145 146 147 148 149 150 151 152 153 154 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 145 def apply_autonomous_tier_cap(tier) return tier unless @autonomous_mode auto_max = autonomous_max_tier if @registry.compare_tiers(auto_max, tier) < 0 auto_max else tier end end |
#autonomous_max_tier ⇒ Object
Get maximum tier for autonomous operations (issue #375)
93 94 95 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 93 def autonomous_max_tier @session_autonomous_max_tier || configuration.autonomous_max_tier end |
#autonomous_max_tier=(tier) ⇒ Object
Set autonomous max tier for this session
98 99 100 101 102 103 104 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 98 def autonomous_max_tier=(tier) validate_tier!(tier) @session_autonomous_max_tier = tier Aidp.log_info("thinking_depth_manager", "Autonomous max tier updated", new: tier) end |
#autonomous_mode? ⇒ Boolean
Check if in autonomous mode
88 89 90 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 88 def autonomous_mode? @autonomous_mode end |
#available_models_for_tier(provider:) ⇒ Array<String>
Get all available models for current tier and provider
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 304 def available_models_for_tier(provider:) tier = current_tier # First try user-configured models configured = configuration.models_for_tier(tier, provider) if configured.any? # Filter out denylisted models return configured.reject { |m| model_denylisted?(m) } end # Fall back to catalog models model_name, _data = @registry.best_model_for_tier(tier, provider) return [] unless model_name [model_name].reject { |m| model_denylisted?(m) } end |
#base_max_tier ⇒ Object
Get the base maximum tier (from session override or config, ignoring autonomous mode) This is the "raw" max tier before autonomous mode restrictions are applied
131 132 133 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 131 def base_max_tier @session_max_tier || configuration.max_tier end |
#can_escalate? ⇒ Boolean
Check if we can escalate to next tier
193 194 195 196 197 198 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 193 def can_escalate? next_tier = @registry.next_tier(current_tier) return false unless next_tier @registry.compare_tiers(next_tier, max_tier) <= 0 end |
#current_tier ⇒ Object
Get current tier (defaults to config default_tier if not set)
107 108 109 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 107 def current_tier @current_tier || default_tier end |
#current_tier=(tier) ⇒ Object
Set current tier (validates against max_tier)
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 112 def current_tier=(tier) validate_tier!(tier) old_tier = current_tier @current_tier = enforce_max_tier(tier) if @current_tier != tier Aidp.log_warn("thinking_depth_manager", "Tier capped at max", requested: tier, applied: @current_tier, max: max_tier) end if @current_tier != old_tier log_tier_change(old_tier, @current_tier, "manual_set") end end |
#de_escalate_tier(reason: nil) ⇒ Object
De-escalate to next lower tier Returns new tier or nil if already at minimum
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 228 def de_escalate_tier(reason: nil) prev_tier = @registry.previous_tier(current_tier) unless prev_tier Aidp.log_debug("thinking_depth_manager", "Cannot de-escalate", current: current_tier) return nil end old_tier = current_tier @current_tier = prev_tier @escalation_count = [@escalation_count - 1, 0].max log_tier_change(old_tier, prev_tier, reason || "de-escalation") Aidp.log_info("thinking_depth_manager", "De-escalated tier", from: old_tier, to: prev_tier, reason: reason) prev_tier end |
#default_tier ⇒ Object
Get default tier from configuration
174 175 176 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 174 def default_tier configuration.default_tier end |
#denylist_model(model) ⇒ Object
Add model to denylist
291 292 293 294 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 291 def denylist_model(model) @model_denylist << model unless @model_denylist.include?(model) Aidp.log_debug("thinking_depth_manager", "Model denylisted", model: model) end |
#determine_tier_from_comment(comment_text:, provider_manager:, labels: []) ⇒ Hash
Determine appropriate tier from issue/PR comment content using ZFC
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 523 def determine_tier_from_comment(comment_text:, provider_manager:, labels: []) # Check for explicit tier labels first (fast path) tier_from_labels = extract_tier_from_labels(labels) if tier_from_labels tier = tier_from_labels reasoning = "Explicit tier label found: #{tier_from_labels}" # In autonomous mode, cap at autonomous_max_tier (same as ZFC path) if @autonomous_mode && @registry.compare_tiers(tier, autonomous_max_tier) > 0 original_tier = tier tier = autonomous_max_tier reasoning += " (capped from #{original_tier} due to autonomous mode)" Aidp.log_debug("thinking_depth_manager", "Label tier capped for autonomous mode", original: original_tier, capped_to: tier) end return { tier: tier, confidence: 1.0, reasoning: reasoning, source: "label" } end # Use ZFC to determine tier from comment content determine_tier_via_zfc(comment_text, provider_manager) rescue => e Aidp.log_warn("thinking_depth_manager", "ZFC tier determination failed, using default", error: e., error_class: e.class.name) # Return conservative default on error { tier: "mini", confidence: 0.5, reasoning: "ZFC determination failed, using conservative default", source: "fallback" } end |
#disable_autonomous_mode ⇒ Object
Disable autonomous mode (restores normal max tier)
82 83 84 85 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 82 def disable_autonomous_mode @autonomous_mode = false Aidp.log_debug("thinking_depth_manager", "Autonomous mode disabled") end |
#enable_autonomous_mode ⇒ Object
Enable autonomous mode (restricts max tier, enables model-level tracking) Should be called when entering watch mode or work loops
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 61 def enable_autonomous_mode @autonomous_mode = true reset_model_tracking # Cap current tier at autonomous max if needed if @registry.compare_tiers(current_tier, autonomous_max_tier) > 0 old_tier = current_tier @current_tier = autonomous_max_tier log_tier_change(old_tier, @current_tier, "autonomous_mode_cap") Aidp.log_info("thinking_depth_manager", "Tier capped for autonomous mode", old: old_tier, new: @current_tier, autonomous_max: autonomous_max_tier) end Aidp.log_debug("thinking_depth_manager", "Autonomous mode enabled", max_tier: autonomous_max_tier) end |
#escalate_tier(reason: nil) ⇒ Object
Escalate to next higher tier Returns new tier or nil if already at max
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 202 def escalate_tier(reason: nil) unless can_escalate? Aidp.log_warn("thinking_depth_manager", "Cannot escalate", current: current_tier, max: max_tier, reason: reason) return nil end old_tier = current_tier new_tier = @registry.next_tier(current_tier) @current_tier = new_tier @escalation_count += 1 log_tier_change(old_tier, new_tier, reason || "escalation") Aidp.log_info("thinking_depth_manager", "Escalated tier", from: old_tier, to: new_tier, reason: reason, count: @escalation_count) new_tier end |
#escalate_tier_intelligent(provider:, reason: nil) ⇒ String?
Escalate tier with intelligent model tracking (issue #375) Only escalates if all models in current tier have been tried
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 451 def escalate_tier_intelligent(provider:, reason: nil) escalation_check = should_escalate_tier?(provider: provider) unless escalation_check[:should_escalate] Aidp.log_debug("thinking_depth_manager", "Intelligent escalation blocked", reason: escalation_check[:reason], details: escalation_check) return nil end # Reset model tracking for new tier old_tier = current_tier new_tier = escalate_tier(reason: reason || escalation_check[:reason]) if new_tier attempts_before_reset = @total_attempts_in_tier reset_model_tracking Aidp.log_info("thinking_depth_manager", "Intelligent tier escalation", from: old_tier, to: new_tier, reason: reason || escalation_check[:reason], total_attempts_in_old_tier: attempts_before_reset) end new_tier end |
#max_tier ⇒ Object
Get effective maximum tier (applies autonomous mode restrictions) Issue #375: In autonomous mode, respects autonomous_max_tier
137 138 139 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 137 def max_tier apply_autonomous_tier_cap(base_max_tier) end |
#max_tier=(tier) ⇒ Object
Set maximum tier for this session (temporary override)
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 157 def max_tier=(tier) validate_tier!(tier) old_max = max_tier @session_max_tier = tier # If current tier exceeds new max, cap it if @registry.compare_tiers(current_tier, tier) > 0 self.current_tier = tier end Aidp.log_info("thinking_depth_manager", "Max tier updated", old: old_max, new: tier, current: current_tier) end |
#model_attempt_count(provider:, model:) ⇒ Object
Get attempts for a specific model
279 280 281 282 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 279 def model_attempt_count(provider:, model:) tier = current_tier @model_attempts.dig(tier, provider, model, :attempts) || 0 end |
#model_attempts_summary ⇒ Object
Get summary of model attempts in current tier
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 491 def model_attempts_summary tier = current_tier tier_attempts = @model_attempts[tier] || {} summary = { tier: tier, total_attempts: @total_attempts_in_tier, providers: {} } tier_attempts.each do |provider, models| summary[:providers][provider] = models.map do |model, data| { model: model, attempts: data[:attempts], failed: data[:failed] } end end summary end |
#model_denylisted?(model) ⇒ Boolean
Check if model is denylisted
297 298 299 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 297 def model_denylisted?(model) @model_denylist.include?(model) end |
#model_failed?(provider:, model:) ⇒ Boolean
Check if a model has been marked as failed
285 286 287 288 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 285 def model_failed?(provider:, model:) tier = current_tier @model_attempts.dig(tier, provider, model, :failed) || false end |
#models_configured_for_tier?(provider:) ⇒ Boolean
Check if any models are configured for the current tier and provider
325 326 327 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 325 def models_configured_for_tier?(provider:) available_models_for_tier(provider: provider).any? end |
#permission_for_current_tier ⇒ Object
Get permission level for current tier
773 774 775 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 773 def configuration.(current_tier) end |
#recommend_tier_for_complexity(complexity_score) ⇒ Object
Get tier recommendation based on complexity score (0.0-1.0)
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 735 def recommend_tier_for_complexity(complexity_score) tier = @registry.recommend_tier_for_complexity(complexity_score) # Cap at max_tier if @registry.compare_tiers(tier, max_tier) > 0 Aidp.log_debug("thinking_depth_manager", "Recommended tier capped", recommended: tier, complexity: complexity_score, capped_to: max_tier) return max_tier end Aidp.log_debug("thinking_depth_manager", "Recommended tier", tier: tier, complexity: complexity_score) tier end |
#record_model_attempt(provider:, model:, success:) ⇒ Object
Record an attempt with a specific model
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 257 def record_model_attempt(provider:, model:, success:) tier = current_tier @model_attempts[tier] ||= {} @model_attempts[tier][provider] ||= {} @model_attempts[tier][provider][model] ||= {attempts: 0, failed: false, last_attempt_at: nil} @model_attempts[tier][provider][model][:attempts] += 1 @model_attempts[tier][provider][model][:last_attempt_at] = Time.now @model_attempts[tier][provider][model][:failed] = !success @total_attempts_in_tier += 1 Aidp.log_debug("thinking_depth_manager", "Recorded model attempt", tier: tier, provider: provider, model: model, success: success, total_attempts: @model_attempts[tier][provider][model][:attempts], tier_total: @total_attempts_in_tier) end |
#reset_model_tracking ⇒ Object
Reset model tracking (call when changing tiers or starting new work) Clears current tier's data for consistency; preserves other tiers' history for analysis
480 481 482 483 484 485 486 487 488 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 480 def reset_model_tracking tier = current_tier # Ensure hash exists then clear it for consistency with counter reset @model_attempts[tier] ||= {} @model_attempts[tier].clear @total_attempts_in_tier = 0 Aidp.log_debug("thinking_depth_manager", "Model tracking reset for tier", tier: tier) end |
#reset_to_default ⇒ Object
Reset to default tier
179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 179 def reset_to_default old_tier = current_tier @current_tier = nil @session_max_tier = nil @escalation_count = 0 Aidp.log_info("thinking_depth_manager", "Reset to default", old: old_tier, new: current_tier) current_tier end |
#select_model_for_tier(tier = nil, provider: nil) ⇒ Object
Select best model for current tier and provider Returns [provider_name, model_name, model_data] or nil
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 567 def select_model_for_tier(tier = nil, provider: nil) tier ||= current_tier validate_tier!(tier) provider_has_no_tiers = provider && configuration.configured_tiers(provider).empty? provider_has_catalog_models = provider && !@registry.models_for_provider(provider).empty? if provider_has_no_tiers && !provider_has_catalog_models Aidp.log_info("thinking_depth_manager", "No configured tiers for provider, deferring to provider auto model selection", requested_tier: tier, provider: provider) return [provider, nil, {auto_model: true, reason: "provider_has_no_tiers"}] end # First, try to get models from user's configuration for this tier and provider if provider configured_models = configuration.models_for_tier(tier, provider) if configured_models.any? # Use first configured model for this provider and tier model_name = configured_models.first # Check if model is deprecated and try to upgrade require_relative "ruby_llm_registry" unless defined?(Aidp::Harness::RubyLLMRegistry) llm_registry = Aidp::Harness::RubyLLMRegistry.new if llm_registry.model_deprecated?(model_name, provider) Aidp.log_warn("thinking_depth_manager", "Configured model is deprecated", tier: tier, provider: provider, model: model_name) # Try to find replacement replacement = llm_registry.find_replacement_model(model_name, provider: provider) if replacement Aidp.log_info("thinking_depth_manager", "Auto-upgrading to non-deprecated model", tier: tier, provider: provider, old_model: model_name, new_model: replacement) model_name = replacement else # Try next model in config list non_deprecated = configured_models.find { |m| !llm_registry.model_deprecated?(m, provider) } if non_deprecated Aidp.log_info("thinking_depth_manager", "Using alternate configured model", tier: tier, provider: provider, skipped: model_name, selected: non_deprecated) model_name = non_deprecated else Aidp.log_warn("thinking_depth_manager", "All configured models deprecated, falling back to catalog", tier: tier, provider: provider) # Fall through to catalog selection model_name = nil end end end if model_name Aidp.log_debug("thinking_depth_manager", "Selected model from user config", tier: tier, provider: provider, model: model_name) return [provider, model_name, {}] end end # Provider specified but has no models for this tier in config # Try catalog for the specified provider before switching providers Aidp.log_debug("thinking_depth_manager", "Provider has no configured models for tier, trying catalog", tier: tier, provider: provider) # Continue to catalog-based selection below (will try specified provider first) else # No provider specified - this should not happen in normal flow # Log warning and fall through to catalog-based selection Aidp.log_warn("thinking_depth_manager", "select_model_for_tier called without provider", tier: tier) end # Fall back to catalog-based selection if no models in user config # If provider specified, try to find model for that provider in catalog if provider model_name, model_data = @registry.best_model_for_tier(tier, provider) if model_name Aidp.log_debug("thinking_depth_manager", "Selected model from catalog", tier: tier, provider: provider, model: model_name) return [provider, model_name, model_data] end # Per issue #323: Don't return nil here - let fallback logic handle missing tiers end # Try all providers in catalog if provider switching is allowed if configuration.allow_provider_switch_for_tier? providers_to_try = provider ? (@registry.provider_names - [provider]) : @registry.provider_names providers_to_try.each do |prov_name| model_name, model_data = @registry.best_model_for_tier(tier, prov_name) if model_name Aidp.log_info("thinking_depth_manager", "Selected model from catalog (alternate provider)", tier: tier, original_provider: provider, selected_provider: prov_name, model: model_name) return [prov_name, model_name, model_data] end end end # No model found for requested tier - try fallback to other tiers # Per issue #323: fallback events log at debug level Aidp.log_debug("thinking_depth_manager", "tier_not_found_trying_fallback", tier: tier, provider: provider) result = try_fallback_tiers(tier, provider) # If no model found after fallback, defer to provider auto model selection # This allows providers to select their own model when no explicit tier config exists # Per issue #323: log at debug level, don't constrain model selection if result.nil? && provider Aidp.log_debug("thinking_depth_manager", "no_model_for_tier_deferring_to_provider", requested_tier: tier, provider: provider, reason: provider_has_no_tiers ? "provider_has_no_tiers" : "tier_not_configured") return [provider, nil, {auto_model: true, reason: provider_has_no_tiers ? "provider_has_no_tiers" : "tier_not_configured"}] end unless result # This path should only be reached when no provider is specified # Enhanced error message with discovery hints display_enhanced_tier_error(tier, provider) Aidp.log_error("thinking_depth_manager", "No model found for tier or fallback tiers", tier: tier, provider: provider) end result end |
#select_next_model(provider:) ⇒ String?
Select the next model to try in current tier Issue #375: Tries all models before escalating, respects min attempts per model
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 333 def select_next_model(provider:) tier = current_tier models = available_models_for_tier(provider: provider) if models.empty? Aidp.log_debug("thinking_depth_manager", "No models configured for tier", tier: tier, provider: provider, reason: "no_models_configured") return nil end min_attempts = configuration.min_attempts_per_model # First pass: find any model with under-min-attempts (must reach min before retry) # This ensures every model gets minimum attempts before we consider retrying models.each do |model| attempts = model_attempt_count(provider: provider, model: model) if attempts < min_attempts Aidp.log_debug("thinking_depth_manager", "Selected under-min-attempts model", model: model, attempts: attempts, min_required: min_attempts) return model end end # Second pass: retry models that have met min attempts (if retry enabled) # Prioritize non-failed models first, then retry failed models if configuration.retry_failed_models? # First try non-failed models that have met min attempts models.each do |model| attempts = model_attempt_count(provider: provider, model: model) if attempts >= min_attempts && !model_failed?(provider: provider, model: model) Aidp.log_debug("thinking_depth_manager", "Selected non-failed model for retry", model: model, attempts: attempts) return model end end # Then retry previously failed models that have met min attempts models.each do |model| attempts = model_attempt_count(provider: provider, model: model) if attempts >= min_attempts && model_failed?(provider: provider, model: model) Aidp.log_debug("thinking_depth_manager", "Retrying previously failed model", model: model, attempts: attempts) return model end end end # All models exhausted in this tier Aidp.log_debug("thinking_depth_manager", "All models exhausted in tier", tier: tier, models_tried: models.size) nil end |
#should_escalate_on_complexity?(context) ⇒ Boolean
Check if should escalate based on complexity thresholds
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 792 def should_escalate_on_complexity?(context) thresholds = configuration.escalation_complexity_threshold return false if thresholds.empty? files_changed = context[:files_changed] || 0 modules_touched = context[:modules_touched] || 0 exceeds_threshold = false if thresholds[:files_changed] && files_changed >= thresholds[:files_changed] exceeds_threshold = true end if thresholds[:modules_touched] && modules_touched >= thresholds[:modules_touched] exceeds_threshold = true end if exceeds_threshold Aidp.log_debug("thinking_depth_manager", "Complexity check", files: files_changed, modules: modules_touched, exceeds: exceeds_threshold) end exceeds_threshold end |
#should_escalate_on_failures?(failure_count) ⇒ Boolean
Check if should escalate based on failure count
786 787 788 789 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 786 def should_escalate_on_failures?(failure_count) threshold = configuration.escalation_fail_attempts failure_count >= threshold end |
#should_escalate_tier?(provider:) ⇒ Hash
Check if we should escalate tier based on model exhaustion Issue #375: Requires minimum total attempts and trying all models first
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 397 def should_escalate_tier?(provider:) return {should_escalate: false, reason: "not_autonomous"} unless @autonomous_mode min_total = configuration.min_total_attempts_before_escalation min_per_model = configuration.min_attempts_per_model models = available_models_for_tier(provider: provider) # Check if we have any models below minimum attempts threshold models_below_min = models.select do |model| model_attempt_count(provider: provider, model: model) < min_per_model end if models_below_min.any? return { should_escalate: false, reason: "models_below_min_attempts", remaining_count: models_below_min.size } end # Check minimum total attempts # Relax if tier lacks sufficient models (each model needs min 2 tries) effective_min_total = [min_total, models.size * min_per_model].min if @total_attempts_in_tier < effective_min_total return { should_escalate: false, reason: "below_min_attempts", current: @total_attempts_in_tier, required: effective_min_total } end # Check if all models have failed # Only escalate if ALL models have failed - don't escalate just because min attempts reached # if some models are still succeeding all_failed = models.all? { |m| model_failed?(provider: provider, model: m) } if all_failed return { should_escalate: true, reason: "all_models_failed", total_attempts: @total_attempts_in_tier } end {should_escalate: false, reason: "continue_current_tier"} end |
#tier_for_model(provider, model) ⇒ Object
Get tier for a specific model
714 715 716 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 714 def tier_for_model(provider, model) @registry.tier_for_model(provider, model) end |
#tier_history ⇒ Object
Get tier change history
781 782 783 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 781 def tier_history @tier_history.dup end |
#tier_info(tier) ⇒ Object
Get information about a specific tier
719 720 721 722 723 724 725 726 727 728 729 730 731 732 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 719 def tier_info(tier) validate_tier!(tier) { tier: tier, priority: @registry.tier_priority(tier), next_tier: @registry.next_tier(tier), previous_tier: @registry.previous_tier(tier), available_models: @registry.models_by_tier(tier), at_max: tier == max_tier, at_min: @registry.previous_tier(tier).nil?, can_escalate: can_escalate_to?(tier) } end |
#tier_override_for(key) ⇒ Object
Check if tier override exists for skill/template
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 |
# File 'lib/aidp/harness/thinking_depth_manager.rb', line 754 def tier_override_for(key) override = configuration.tier_override_for(key) return nil unless override validate_tier!(override) # Cap at max_tier if @registry.compare_tiers(override, max_tier) > 0 Aidp.log_warn("thinking_depth_manager", "Override tier exceeds max", key: key, override: override, max: max_tier) return max_tier end override end |