Class: Miniswen::Agent
- Inherits:
-
Object
- Object
- Miniswen::Agent
- Defined in:
- lib/miniswen/agent.rb
Overview
A Ruby port of mini-swe-agent's loop (mini.yaml at commit a83fcae): ask the model for bash tool calls, run them, repeat until it submits or a limit trips.
Defined Under Namespace
Classes: BashTool, CostSource, Result
Constant Summary collapse
- SUBMIT_MARKER =
"COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT"- MAX_OBSERVATION_CHARS =
10_000- MAX_CONSECUTIVE_FORMAT_ERRORS =
3- TRUNCATION_FINISH_REASONS =
Both finish_reason dialects accepted raw: OpenAI-shaped providers say "length"/"tool_calls", Anthropic says "max_tokens"/"tool_use".
%w[length max_tokens].freeze
- CLAIMED_TOOL_FINISH_REASONS =
%w[tool_calls tool_use].freeze
- EXEC_ENV =
{ "PAGER" => "cat", "MANPAGER" => "cat", "LESS" => "-R", "PIP_PROGRESS_BAR" => "off", "TQDM_DISABLE" => "1" }.freeze
- LOCAL_PROVIDERS =
Providers that serve local inference (and cost zero)
%i[ollama gpustack].freeze
- SYSTEM_TEMPLATE =
<<~PROMPT You are a helpful assistant that can interact with a computer. PROMPT
- INSTANCE_TEMPLATE =
<<~PROMPT.freeze Please solve this issue: %<instruction>s You can execute bash commands and edit files to implement the necessary changes. ## Recommended Workflow This workflow should be done step-by-step so that you can iterate on your changes and any possible problems. 1. Analyze the codebase by finding and reading relevant files 2. Create a script to reproduce the issue 3. Edit the source code to resolve the issue 4. Verify your fix works by running your script again 5. Test edge cases to ensure your fix is robust 6. Submit your changes and finish your work by issuing the following command: `echo #{SUBMIT_MARKER}`. Do not combine it with any other command. <important>After this command, you cannot continue working on this task.</important> ## Command Execution Rules You are operating in an environment where 1. You issue at least one command 2. The system executes the command(s) in a subshell 3. You see the result(s) 4. You write your next command(s) Each response should include: 1. **Reasoning text** where you explain your analysis and plan 2. At least one tool call with your command **CRITICAL REQUIREMENTS:** - Your response SHOULD include reasoning text explaining what you're doing - Your response MUST include AT LEAST ONE bash tool call - Directory or environment variable changes are not persistent. Every action is executed in a new subshell. - However, you can prefix any action with `MY_ENV_VAR=MY_VALUE cd /path/to/working/dir && ...` or write/load environment variables from files - Submit your changes and finish your work by issuing the following command: `echo #{SUBMIT_MARKER}`. Do not combine it with any other command. <important>After this command, you cannot continue working on this task.</important> Example of a CORRECT response: <example_response> I need to understand the structure of the repository first. Let me check what files are in the current directory to get a better understanding of the codebase. [Makes bash tool call with {"command": "ls -la"} as arguments] </example_response> <system_information> %<system_information>s </system_information> ## Useful command examples ### Create a new file: ```bash cat <<'EOF' > newfile.py import numpy as np hello = "ciao" print(hello) EOF ``` ### Edit files with sed: %<macos_sed_note>s ```bash # Replace all occurrences sed -i 's/old_string/new_string/g' filename.py # Replace only first occurrence sed -i 's/old_string/new_string/' filename.py # Replace first occurrence on line 1 sed -i '1s/old_string/new_string/' filename.py # Replace all occurrences in lines 1-10 sed -i '1,10s/old_string/new_string/g' filename.py ``` ### View file content: ```bash # View specific lines with numbers nl -ba filename.py | sed -n '10,20p' ``` ### Any other command you want to run ```bash anything ``` PROMPT
- MACOS_SED_NOTE =
<<~NOTE <important> You are on MacOS. For all the below examples, you need to use `sed -i ''` instead of `sed -i`. </important> NOTE
- NO_TOOL_CALLS_ERROR =
"No tool calls found in the response. Every response MUST include at least one tool call."- TRUNCATION_ERROR_MESSAGE =
<<~MESSAGE Your previous response reached the output token limit (finish_reason=%<finish_reason>s) before you produced a tool call, so it was cut off. Respond more concisely and finish with exactly one bash tool call. If you need to think more, do so briefly. MESSAGE
- TOOL_CALL_ERROR_MESSAGE =
<<~MESSAGE.freeze Tool call error: <error> %<error>s </error> Here is general guidance on how to submit correct toolcalls: Every response needs to use the 'bash' tool at least once to execute commands. Call the bash tool with your command as the argument: - Tool: bash - Arguments: {"command": "your_command_here"} If you want to end the task, please issue the following command: `echo #{SUBMIT_MARKER}` without any other command. MESSAGE
Instance Attribute Summary collapse
-
#environment ⇒ Object
readonly
Returns the value of attribute environment.
-
#messages ⇒ Object
readonly
Returns the value of attribute messages.
Instance Method Summary collapse
-
#initialize(model:, environment:, max_steps: 0, max_time: 0, max_cost: nil, exec_timeout: 30, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }, reporter: nil) ⇒ Agent
constructor
modelis a litellm-style name ("openrouter/z-ai/glm-5.2"). -
#provider_env ⇒ Object
The env a remote miniswen needs to drive this model: the resolved provider's required config options, named the way ruby_llm.rb reads them back from ENV on boot (the option upcased).
- #run(instruction) ⇒ Object
Constructor Details
#initialize(model:, environment:, max_steps: 0, max_time: 0, max_cost: nil, exec_timeout: 30, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }, reporter: nil) ⇒ Agent
model is a litellm-style name ("openrouter/z-ai/glm-5.2"). Limits of 0 or nil are disabled.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/miniswen/agent.rb', line 214 def initialize(model:, environment:, max_steps: 0, max_time: 0, max_cost: nil, exec_timeout: 30, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }, reporter: nil) @provider, @id = model.split("/", 2) unless @id @id = @provider @provider = nil end @model = model @environment = environment @bash_tool = BashTool.new @max_steps = max_steps.to_i @max_time = max_time.to_f @max_cost = max_cost @exec_timeout = exec_timeout @clock = clock @reporter = reporter end |
Instance Attribute Details
#environment ⇒ Object (readonly)
Returns the value of attribute environment.
208 209 210 |
# File 'lib/miniswen/agent.rb', line 208 def environment @environment end |
#messages ⇒ Object (readonly)
Returns the value of attribute messages.
208 209 210 |
# File 'lib/miniswen/agent.rb', line 208 def @messages end |
Instance Method Details
#provider_env ⇒ Object
The env a remote miniswen needs to drive this model: the resolved provider's required config options, named the way ruby_llm.rb reads them back from ENV on boot (the option upcased).
280 281 282 283 |
# File 'lib/miniswen/agent.rb', line 280 def provider_env _, provider = resolved provider.configuration_requirements.to_h { [_1.to_s.upcase, RubyLLM.config.public_send(_1)] }.compact end |
#run(instruction) ⇒ Object
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/miniswen/agent.rb', line 237 def run(instruction) uname = execute("uname -srvm").output.to_s.strip @messages = [ { role: "system", content: SYSTEM_TEMPLATE }, { role: "user", content: format(INSTANCE_TEMPLATE, instruction: instruction, system_information: uname, macos_sed_note: uname.start_with?("Darwin") ? "\n#{MACOS_SED_NOTE}" : "") } ] @steps = 0 @cost = 0.0 @totals = { input_tokens: 0, output_tokens: 0, cached_tokens: 0, thinking_tokens: 0 } @cost_known = true @consecutive_format_errors = 0 @started_at = @clock.call loop do (status = limit_reached) and return finish(status) actions = next_actions if actions.nil? return finish(:format_error) if @consecutive_format_errors >= MAX_CONSECUTIVE_FORMAT_ERRORS next end actions.each do |action| reporter&.on_tool_call(action) result = execute(action.fetch(:arguments).fetch("command")) # The submit command's output is observed too, so the final tool # call has a linked result in the trajectory. observe(action, result) return finish(:submitted, submission: submission_from(result)) if submitted?(result) end end end |