Class: RubynCode::Agent::Loop

Inherits:
Object
  • Object
show all
Includes:
BackgroundJobHandler, FeedbackHandler, LlmCaller, ResponseParser, SystemPromptBuilder, ToolProcessor
Defined in:
lib/rubyn_code/agent/loop.rb

Overview

rubocop:disable Metrics/ClassLength -- core agent loop: LLM calls, tool dispatch, recovery, hooks

Constant Summary collapse

MAX_ITERATIONS =
Config::Defaults::MAX_ITERATIONS
GOAL_MAX_ITERATIONS =
Config::Defaults::GOAL_MAX_ITERATIONS

Constants included from FeedbackHandler

FeedbackHandler::NEGATIVE_PATTERNS, FeedbackHandler::POSITIVE_PATTERNS

Constants included from ToolProcessor

ToolProcessor::CORE_TOOLS, ToolProcessor::PLAN_MODE_RISK_LEVELS

Constants included from UsageTracker

UsageTracker::TASK_BUDGET_TOTAL

Constants included from SystemPromptBuilder

SystemPromptBuilder::INSTRUCTION_FILES

Constants included from Prompts

Prompts::PLAN_MODE_PROMPT, Prompts::SYSTEM_PROMPT

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**opts) ⇒ Loop

Returns a new instance of Loop.

Parameters:

  • opts (Hash)

    keyword arguments for loop configuration

Options Hash (**opts):



39
40
41
42
43
44
# File 'lib/rubyn_code/agent/loop.rb', line 39

def initialize(**opts)
  assign_dependencies(opts)
  assign_callbacks(opts)
  @plan_mode = false
  @static_prompt_sections = nil
end

Instance Attribute Details

#codebase_indexIndex::CodebaseIndex? (readonly)

Returns:



50
51
52
# File 'lib/rubyn_code/agent/loop.rb', line 50

def codebase_index
  @codebase_index
end

#plan_modeBoolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/rubyn_code/agent/loop.rb', line 47

def plan_mode
  @plan_mode
end

#todo_storeTools::TodoStore (readonly)

Returns shared checklist store, exposed for the REPL renderer.

Returns:



98
99
100
# File 'lib/rubyn_code/agent/loop.rb', line 98

def todo_store
  @todo_store
end

Instance Method Details

#allowed_tools_override=(allowed) ⇒ Object

Restrict the available tool set for the next LLM call only.

Parameters:

  • allowed (Array<String>, nil)

    tool names, or nil to clear the override.



109
110
111
# File 'lib/rubyn_code/agent/loop.rb', line 109

def allowed_tools_override=(allowed)
  @allowed_tools_override = allowed.is_a?(Array) && !allowed.empty? ? allowed : nil
end

#append_user_message(user_input, blocks) ⇒ Object

Append the user turn to the conversation. If image blocks were passed via blocks:, the message is stored as a mixed content array with a leading text block and the image blocks appended in order.



88
89
90
91
92
93
94
95
# File 'lib/rubyn_code/agent/loop.rb', line 88

def append_user_message(user_input, blocks)
  if blocks.is_a?(Array) && !blocks.empty?
    content = [{ type: 'text', text: user_input.to_s }, *blocks]
    @conversation.add_user_message(content)
  else
    @conversation.add_user_message(user_input)
  end
end

#model_override=(model_name) ⇒ Object

Apply a one-shot model override for the duration of the next LLM call. Used by Context#with_optional_model for custom-command frontmatter.



102
103
104
105
# File 'lib/rubyn_code/agent/loop.rb', line 102

def model_override=(model_name)
  stripped = model_name.to_s.strip
  @model_override = stripped.empty? ? nil : stripped
end

#send_message(user_input, blocks: nil) ⇒ String

Send a user message and run the agent loop until a final text response is produced or the iteration limit is reached.

Parameters:

  • user_input (String)
  • blocks (Array<Hash>, nil) (defaults to: nil)

    extra content blocks to attach to the user message (e.g. image blocks from @-mentions). When non-empty the user message is stored as a mixed content array instead of a string.

Returns:

  • (String)

    the final assistant text response



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/rubyn_code/agent/loop.rb', line 60

def send_message(user_input, blocks: nil)
  initialize_session!
  check_user_feedback(user_input)
  drain_background_notifications
  inject_skill_listing unless @skills_injected
  @decision_compactor&.detect_topic_switch(user_input)
  @skill_ttl&.tick!
  autoload_triggered_skills(user_input)
  append_user_message(user_input, blocks)
  reset_system_prompt_cache!
  reset_iteration_state

  iteration = 0
  loop do
    result = run_iteration(iteration)
    return result if result

    iteration += 1
    break unless keep_iterating?(iteration)
  end

  RubynCode::Debug.warn("Hit iteration limit (#{iteration})")
  max_iterations_warning(iteration)
end