Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/swe-agent/mini-swe-agent/llms.txt

Use this file to discover all available pages before exploring further.

mini-swe-agent is built around a deliberately simple, stateless design. Rather than maintaining a persistent shell session, every action is an independent subprocess.run call. The message list is the entire trajectory — there is no separate state object. Each iteration appends to that list and nothing else, which makes the agent easy to reason about, extend, and debug.

The main loop

DefaultAgent.run initializes the message history with a system message and a user message rendered from Jinja2 templates, then calls step() in a loop until a message with role="exit" appears at the end of the list.
1

Initialize messages

run() renders the system_template and instance_template configs using Jinja2 and adds both messages to the (initially empty) self.messages list. The task description and any extra keyword arguments are available as template variables.
2

Agent.step()

Each iteration calls step(), which is a single line:
def step(self) -> list[dict]:
    return self.execute_actions(self.query())
It sequences two operations: first query the language model, then execute whatever actions the model requested.
3

Agent.query()

query() checks cost and step limits before calling Model.query(self.messages). The returned message is appended to the history via add_messages() and also returned to the caller. Cost is accumulated from message["extra"]["cost"].
def query(self) -> dict:
    # checks step_limit, cost_limit, wall_time_limit_seconds ...
    message = self.model.query(self.messages)
    self.cost += message.get("extra", {}).get("cost", 0.0)
    self.add_messages(message)
    return message
4

Agent.execute_actions()

execute_actions() iterates over every action embedded in the model message, calls Environment.execute() for each one, then formats the results into observation messages via Model.format_observation_messages(). The observations are appended to the history.
def execute_actions(self, message: dict) -> list[dict]:
    outputs = [self.env.execute(action) for action in message.get("extra", {}).get("actions", [])]
    return self.add_messages(*self.model.format_observation_messages(message, outputs, self.get_template_vars()))
5

Repeat or exit

Control returns to the while True loop in run(). If the last message has role="exit", the loop breaks and run() returns the extra dict from that message (containing exit_status and submission). Otherwise, step() is called again.

Actions and the code block format

The model signals a bash command by wrapping it in a fenced code block with the language tag mswea_bash_command:
```mswea_bash_command
pytest tests/
```
This custom tag avoids ambiguity with ordinary bash examples that may appear in prompts or documentation. For text-based (non-tool-calling) models, the action_regex config option controls exactly which pattern is extracted. When using the default tool-calling model class, the model invokes a native “bash” tool and the actions are extracted from the tool-call payload instead.

Flow control via exceptions

Error conditions and the finish condition are both signalled by raising exceptions that inherit from InterruptAgentFlow (defined in minisweagent.exceptions). Every exception in the hierarchy carries one or more messages that get appended to the trajectory before the loop checks whether to stop.
ExceptionTrigger
SubmittedEnvironment output starts with COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT
LimitsExceededstep_limit or cost_limit exceeded
TimeExceededwall_time_limit_seconds exceeded
FormatErrorModel output is not in the expected format
UserInterruptionUser cancels the run
while True:
    try:
        self.step()
    except InterruptAgentFlow as e:
        self.add_messages(*e.messages)
    if self.messages[-1].get("role") == "exit":
        break
Unrecognised exceptions (those that do not inherit from InterruptAgentFlow) are caught by a separate except Exception branch, which adds an exit message and then re-raises, so the caller still sees the error.
Because every action is an independent subprocess.run call there is no shell session to corrupt. Problems like “when has the command finished?”, “what if the command kills the shell?”, and “what if interrupt signals pollute subsequent output?” simply do not arise. The trade-off is that directory changes and exported environment variables do not persist across actions — but prefixing cd /path/to/project && to each command (which many models do automatically) is all that is needed.

Key method reference

MethodLocationDescription
DefaultAgent.run()agents/default.py:85Initializes messages and drives the step loop
DefaultAgent.step()agents/default.py:107One iteration: query() then execute_actions()
DefaultAgent.query()agents/default.py:111Checks limits, calls Model.query, accumulates cost
DefaultAgent.execute_actions()agents/default.py:135Runs each action, formats and appends observations
DefaultAgent.add_messages()agents/default.py:66Appends messages to self.messages and returns them

Build docs developers (and LLMs) love