Class: Aidp::Execute::WorkLoopRunner

Inherits:
Object
  • Object
show all
Includes:
MessageDisplay
Defined in:
lib/aidp/execute/work_loop_runner.rb

Overview

Executes work loops for a single step using the fix-forward pattern Responsibilities:

  • Create initial PROMPT.md from templates and context
  • Loop: send PROMPT.md to agent, run tests/linters, check completion
  • Only send test/lint failures back to agent
  • Never rollback, only move forward through fixes
  • Track iteration count and state transitions
  • Record periodic checkpoints with metrics

Fix-Forward State Machine: READY → APPLY_PATCH → TEST → → DONE | FAIL → DIAGNOSE → NEXT_PATCH → READY

Constant Summary collapse

STATES =

State machine states

{
  ready: "READY",               # Ready to start new iteration
  apply_patch: "APPLY_PATCH",   # Agent applying changes
  test: "TEST",                 # Running tests and linters
  pass: "PASS",                 # Tests passed
  fail: "FAIL",                 # Tests failed
  diagnose: "DIAGNOSE",         # Analyzing failures
  next_patch: "NEXT_PATCH",     # Preparing next iteration
  done: "DONE"                  # Work complete
}.freeze
MAX_ITERATIONS =

Safety limit

50
CHECKPOINT_INTERVAL =

Record checkpoint every N iterations

5
STYLE_GUIDE_REMINDER_INTERVAL =

Re-inject LLM_STYLE_GUIDE every N iterations

5

Constants included from MessageDisplay

MessageDisplay::COLOR_MAP, MessageDisplay::CRITICAL_TYPES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MessageDisplay

#display_message, included, #message_display_prompt, #quiet_mode?

Constructor Details

#initialize(project_dir, provider_manager, config, options = {}) ⇒ WorkLoopRunner

Returns a new instance of WorkLoopRunner.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/aidp/execute/work_loop_runner.rb', line 56

def initialize(project_dir, provider_manager, config, options = {})
  @project_dir = project_dir
  @provider_manager = provider_manager
  @config = config
  @prompt = options[:prompt] || TTY::Prompt.new
  @prompt_manager = PromptManager.new(project_dir, config: config)
  @test_runner = Aidp::Harness::TestRunner.new(project_dir, config)
  @checkpoint = Checkpoint.new(project_dir)
  @checkpoint_display = CheckpointDisplay.new(prompt: @prompt)
  @guard_policy = GuardPolicy.new(project_dir, config.guards_config)
  @project_knowledge_manager = options[:project_knowledge_manager] || ProjectKnowledgeManager.new(project_dir)
  @work_context = {}
  @executed_tool_commands = []
  @agentic_execution_results = []
  @persistent_tasklist = PersistentTasklist.new(project_dir)
  @iteration_count = 0
  @step_name = nil
  @options = options
  @current_state = :ready
  @state_history = []
  @deterministic_runner = DeterministicUnits::Runner.new(project_dir)
  @unit_scheduler = nil

  # Initialize thinking depth manager for intelligent model selection
  # Issue #375: Enable autonomous mode by default for work loops
  require_relative "../harness/thinking_depth_manager"
  @thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(
    config,
    root_dir: @project_dir,
    autonomous_mode: true  # Issue #375: Work loops use autonomous mode
  )
  @consecutive_failures = 0
  @last_tier = nil
  @last_model = nil  # Issue #375: Track last used model

  # Initialize style guide selector for intelligent section selection
  @style_guide_selector = options[:style_guide_selector] || Aidp::StyleGuide::Selector.new(project_dir: project_dir)

  # FIX for issue #391: Initialize prompt evaluator for iteration threshold assessment
  @prompt_evaluator = options[:prompt_evaluator] || PromptEvaluator.new(config)
  @prompt_template_manager = options[:prompt_template_manager] || Prompts::PromptTemplateManager.new(project_dir: project_dir)

  # Initialize security adapter for Rule of Two enforcement
  @security_adapter = options[:security_adapter] || Aidp::Security::WorkLoopAdapter.new(project_dir: project_dir)
end

Instance Attribute Details

#checkpoint ⇒ Object (readonly)

Returns the value of attribute checkpoint.



49
50
51
# File 'lib/aidp/execute/work_loop_runner.rb', line 49

def checkpoint
  @checkpoint
end

#current_state ⇒ Object (readonly)

Returns the value of attribute current_state.



49
50
51
# File 'lib/aidp/execute/work_loop_runner.rb', line 49

def current_state
  @current_state
end

#guard_policy=(value) ⇒ Object (writeonly)

Sets the attribute guard_policy

Parameters:

  • value —

    the value to set the attribute guard_policy to.



50
51
52
# File 'lib/aidp/execute/work_loop_runner.rb', line 50

def guard_policy=(value)
  @guard_policy = value
end

#iteration_count ⇒ Object

Expose state for testability



48
49
50
# File 'lib/aidp/execute/work_loop_runner.rb', line 48

def iteration_count
  @iteration_count
end

#options ⇒ Object

Expose state for testability



48
49
50
# File 'lib/aidp/execute/work_loop_runner.rb', line 48

def options
  @options
end

#persistent_tasklist ⇒ Object

Expose state for testability



48
49
50
# File 'lib/aidp/execute/work_loop_runner.rb', line 48

def persistent_tasklist
  @persistent_tasklist
end

#project_dir ⇒ Object (readonly)

Returns the value of attribute project_dir.



49
50
51
# File 'lib/aidp/execute/work_loop_runner.rb', line 49

def project_dir
  @project_dir
end

#prompt_manager ⇒ Object

Returns the value of attribute prompt_manager.



49
50
51
# File 'lib/aidp/execute/work_loop_runner.rb', line 49

def prompt_manager
  @prompt_manager
end

#prompt_template_manager=(value) ⇒ Object (writeonly)

Sets the attribute prompt_template_manager

Parameters:

  • value —

    the value to set the attribute prompt_template_manager to.



50
51
52
# File 'lib/aidp/execute/work_loop_runner.rb', line 50

def prompt_template_manager=(value)
  @prompt_template_manager = value
end

#state_history ⇒ Object (readonly)

Returns the value of attribute state_history.



49
50
51
# File 'lib/aidp/execute/work_loop_runner.rb', line 49

def state_history
  @state_history
end

#step_name ⇒ Object

Expose state for testability



48
49
50
# File 'lib/aidp/execute/work_loop_runner.rb', line 48

def step_name
  @step_name
end

#style_guide_selector=(value) ⇒ Object (writeonly)

Sets the attribute style_guide_selector

Parameters:

  • value —

    the value to set the attribute style_guide_selector to.



50
51
52
# File 'lib/aidp/execute/work_loop_runner.rb', line 50

def style_guide_selector=(value)
  @style_guide_selector = value
end

#test_runner ⇒ Object (readonly)

Returns the value of attribute test_runner.



49
50
51
# File 'lib/aidp/execute/work_loop_runner.rb', line 49

def test_runner
  @test_runner
end

Instance Method Details

#execute_step(step_name, step_spec, context = {}) ⇒ Object

Execute a step using fix-forward work loop pattern Returns final result when step is complete Never rolls back - only moves forward through fixes



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/aidp/execute/work_loop_runner.rb', line 105

def execute_step(step_name, step_spec, context = {})
  @step_name = step_name
  @work_context = context
  @executed_tool_commands = []
  @agentic_execution_results = []
  @iteration_count = 0
  transition_to(:ready)

  Aidp.logger.info("work_loop", "Starting hybrid work loop execution", step: step_name, max_iterations: MAX_ITERATIONS)

  display_message("🔄 Starting hybrid work loop for step: #{step_name}", type: :info)
  display_message("  Flow: Deterministic ↔ Agentic with fix-forward core", type: :info)
  display_work_context(step_name, context)

  display_guard_policy_status
  display_pending_tasks

  @unit_scheduler = WorkLoopUnitScheduler.new(units_config, project_dir: @project_dir)
  base_context = context.dup

  loop do
    unit = @unit_scheduler.next_unit
    break unless unit

    if unit.deterministic?
      result = @deterministic_runner.run(unit.definition, reason: "scheduled by work loop")
      @unit_scheduler.record_deterministic_result(unit.definition, result)
      next
    end

    enriched_context = base_context.merge(
      deterministic_outputs: @unit_scheduler.deterministic_context,
      previous_agent_summary: @unit_scheduler.last_agentic_summary
    )

    agentic_payload = if unit.name == :decide_whats_next
      run_decider_agentic_unit(enriched_context)
    elsif unit.name == :diagnose_failures
      run_diagnose_agentic_unit(enriched_context)
    else
      run_primary_agentic_unit(step_spec, enriched_context)
    end

    @unit_scheduler.record_agentic_result(
      agentic_payload[:raw_result] || {},
      requested_next: agentic_payload[:requested_next],
      summary: agentic_payload[:summary],
      completed: agentic_payload[:completed]
    )

    return agentic_payload[:response] if agentic_payload[:terminate]
  end

  build_max_iterations_result
end