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.



53
54
55
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
# File 'lib/aidp/execute/work_loop_runner.rb', line 53

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)
  @work_context = {}
  @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
  require_relative "../harness/thinking_depth_manager"
  @thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config, root_dir: @project_dir)
  @consecutive_failures = 0
  @last_tier = nil

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

  # 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

#checkpointObject (readonly)

Returns the value of attribute checkpoint.



46
47
48
# File 'lib/aidp/execute/work_loop_runner.rb', line 46

def checkpoint
  @checkpoint
end

#current_stateObject (readonly)

Returns the value of attribute current_state.



46
47
48
# File 'lib/aidp/execute/work_loop_runner.rb', line 46

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.



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

def guard_policy=(value)
  @guard_policy = value
end

#iteration_countObject

Expose state for testability



45
46
47
# File 'lib/aidp/execute/work_loop_runner.rb', line 45

def iteration_count
  @iteration_count
end

#optionsObject

Expose state for testability



45
46
47
# File 'lib/aidp/execute/work_loop_runner.rb', line 45

def options
  @options
end

#persistent_tasklistObject

Expose state for testability



45
46
47
# File 'lib/aidp/execute/work_loop_runner.rb', line 45

def persistent_tasklist
  @persistent_tasklist
end

#project_dirObject (readonly)

Returns the value of attribute project_dir.



46
47
48
# File 'lib/aidp/execute/work_loop_runner.rb', line 46

def project_dir
  @project_dir
end

#prompt_managerObject

Returns the value of attribute prompt_manager.



46
47
48
# File 'lib/aidp/execute/work_loop_runner.rb', line 46

def prompt_manager
  @prompt_manager
end

#state_historyObject (readonly)

Returns the value of attribute state_history.



46
47
48
# File 'lib/aidp/execute/work_loop_runner.rb', line 46

def state_history
  @state_history
end

#step_nameObject

Expose state for testability



45
46
47
# File 'lib/aidp/execute/work_loop_runner.rb', line 45

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.



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

def style_guide_selector=(value)
  @style_guide_selector = value
end

#test_runnerObject (readonly)

Returns the value of attribute test_runner.



46
47
48
# File 'lib/aidp/execute/work_loop_runner.rb', line 46

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



92
93
94
95
96
97
98
99
100
101
102
103
104
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
# File 'lib/aidp/execute/work_loop_runner.rb', line 92

def execute_step(step_name, step_spec, context = {})
  @step_name = step_name
  @work_context = context
  @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