1. Panorama

Mini-SWE-Agent: A Minimalist SWE Scaffold

  • Mini-SWE-Agent is a minimalist, bash-centric software engineering agent scaffold that maintains core capabilities like code navigation, testing, and iterative refinement.
  • It operates as a single-agent loop executing one thought and one bash command per cycle, supporting both repository repair and embodied controller generation.
  • Empirical studies show MSWEA achieves competitive resolution rates with strong models while facing challenges such as context degradation and token bloat.

The main structure of MSWEA is like this:

The file structure is :

1
2
3
4
5
6
7
8
src/minisweagent/
├── __init__.py # protocols / interfaces ...
├── agents/ # harness parts
├── environments/ # action execution environments
├── models/ # LLM interface
├── config/ # yaml files
├── run/ # entrance scripts
└── utils/ # log, serialize

2. __init__.py

__init__.py mainly defines 4 files:

version, Model Protocol, Environment Protocol and Agent Protocol.

2.1 Version

Current version is 2.4.6 (2026-09-09).

1
__version__ = "2.4.6"

2.2 Model Protocol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Model(Protocol):
"""Protocol for language models."""

config: Any

def query(self, messages: list[dict[str, str]], **kwargs) -> dict: ...

def format_message(self, **kwargs) -> dict: ...

def format_observation_messages(
self, message: dict, outputs: list[dict], template_vars: dict | None = None
) -> list[dict]: ...

def get_template_vars(self, **kwargs) -> dict[str, Any]: ...

def serialize(self) -> dict: ...

This part defines a LLM protocol.

2.2.1 query

  • Function Name:

    query

  • Params:

    self

    messages: list[dict[str, str]]

    **kwargs

  • Return:

    dict

  • Function Description:

    Send the conversation history to the LLM, receive the response, parse the bash action to be executed, and return it wrapped in a dict.

  • e.g

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    message = self.model.query(self.messages)

    # self.message example
    # [
    # {"role": "system", "content": "You are a helpful assistant..."},
    # {"role": "user", "content": "Please solve this issue:repair add function"},
    # ]

    # the returned message like this:
    {
    "role": "assistant",
    "content": "I'll first look at the repository structure.",
    "extra": {
    "actions": [{"command": "ls -la", "tool_call_id": "call_abc123"}], # parsed bash action
    "cost": 0.0123, # cost of this call(dollar)
    "timestamp": 1730000000.0,
    },
    }

2.2.2 format_message

  • Function Name:

    format_message

  • Params:

    self

    **kwargs

  • Return:

    dict

  • Function Description:

    Assembles the input fields into a standard message; the default implementation mainly expands multimodal content.

  • e.g

    1
    2
    3
    4
    5
    6
    7
    8
    # assemble system prompt:
    self.model.format_message(role="system", content="You are a helpful assistant...")
    # return:
    {"role": "system", "content": "You are a helpful assistant..."}

    # assemble user message:
    self.model.format_message(role="user", content="Please solve this issue: ...")
    {"role": "user", "content": "Please solve this issue: ..."}

2.2.3 format_observation_messages

  • Function Name:

    format_observation_messages

  • Params:

    self

    message: dict

    outputs: list[dict]

    template_vars: dict | None = None

  • Return:

    list[dict]

  • Function Description:

    Render the command execution result into an observation message, append it back to the conversation history, and let the model see what the previous step produced.

  • e.g

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # assume the last command is `ls -la`
    outputs = [{"output": "file1.py\nfile2.py\n", "returncode": 0, "exception_info": ""}]

    # agent calls:
    self.model.format_observation_messages(message, outputs, self.get_template_vars())

    # return:
    [{
    "role": "tool",
    "tool_call_id": "call_abc123",
    "content": "<returncode>0</returncode>\n<output>\nfile1.py\nfile2.py\n</output>",
    "extra": {"raw_output": "file1.py\nfile2.py\n", "returncode": 0, "timestamp": 1730000001.0},
    }]

2.2.4 get_template_vars

  • Function Name:

    get_template_vars

  • Params:

    self

    **kwargs

  • Return:

    dict[str, Any]

  • Function Description:

    Provide the variables needed to render the Jinja2 template (e.g., model name, model_kwargs, etc.).

    • e.g

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def get_template_vars(self, **kwargs):
      return self.config.model_dump()

      # return likes:
      {
      "model_name": "anthropic/claude-sonnet-4-5-20250929",
      "model_kwargs": {"drop_params": True},
      ...
      }

      These variables will be used to render system/instance template by Agent

2.2.5 serialize

  • Function Name:

    serialize

  • Params:

    self

  • Return:

    dict

  • Function Description:

    Serialize the model config/state for writing to the trajectory file at the end, recording which model was used.

  • e.g

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # when agent archive, it merges model information into final data
    recursive_merge(agent_data, self.model.serialize(), self.env.serialize(), *extra_dicts)

    # LitellmModel.serialize() return:
    {
    "info": {
    "config": {
    "model": {"model_name": "anthropic/claude-sonnet-4-5-20250929", ...},
    "model_type": "minisweagent.models.litellm_model.LitellmModel",
    }
    }
    }

    # the information will be written into last_mini_run.traj.json:
    # {
    # "info": { "model_stats": {...}, "config": {"agent": {...}, "model": {...}, "environment": {...}} },
    # "messages": [ ...dialog history... ]
    # }

2.3 Environment Protocol

2.3.1 execute

  • Function Name:

    execute

  • Params:

    self

    action: dict

    cwd: str = “”

  • Return:

    dict[str, Any]

  • Description:

    Actually execute a bash command and return a dict bundling stdout, the exit code, and whether it errored. There’s also a hidden key behavior: if the first line of the output is “COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT”, it raises a Submitted exception to signal task completion.

  • e.g

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    # e.g agent parses action from response of LLM:
    action = {"command": "ls -la", "tool_call_id": "call_abc123"}

    # agent calls:
    outputs = [self.env.execute(action) for action in actions]

    # the Agent calls subprocess to run the command actually,
    # the return is:
    {
    "output": "total 8\ndrwxr-xr-x ...\nfile1.py\nfile2.py\n",
    "returncode": 0, # 0 == success
    "exception_info": "", # empty == no error raised
    }

    # if command fails:
    # 如果命令失败了(比如文件不存在):
    {
    "output": "ls: cannot access 'nope': No such file or directory\n",
    "returncode": 2, # not-0 == failure
    "exception_info": "",
    }
  • If task finished, it can be detected:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # when task finished
    echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT

    # raise submitted
    raise Submitted({
    "role": "exit",
    "content": "",
    "extra": {"exit_status": "Submitted", "submission": ""},
    })

2.3.2 get_template_vars

  • Function Name:

    get_template_vars

  • Params:

    self

    **kwargs

  • Return:

    dict[str, Any]

  • Description:

    Collect environment-related template variables (working directory config, OS info, environment variables, etc.) for the agent to fill in when rendering the system/instance prompts.

  • e.g.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # Impelemtation in LocalEnvironment
    def get_template_vars(self, **kwargs):
    return recursive_merge(self.config.model_dump(), platform.uname()._asdict(), os.environ, kwargs)

    # it returns three types information
    {
    # ① Env Config
    "cwd": "", "env": {...}, "timeout": 30,
    # ② OS information(platform.uname())
    "system": "Linux", "release": "6.8.0-...", "version": "...", "machine": "x86_64",
    # ③ all Environment Variables(os.environ)
    "PATH": "/usr/bin:...", "HOME": "/root", ...
    }

2.3.1 serialize

  • Function Name:

    serialize

  • Params:

    self

  • Return:

    dict

  • Description:

    Serialize the environment config/type into a dict for writing to the trajectory file at the end, recording which environment it ran in.

  • e.g.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # when agent archives, the information will be merged.
    recursive_merge(agent_data, self.model.serialize(), self.env.serialize(), *extra_dicts)

    # return of LocalEnvironment.serialize()
    {
    "info": {
    "config": {
    "environment": {"cwd": "", "env": {}, "timeout": 30},
    "environment_type": "minisweagent.environments.local.LocalEnvironment",
    }
    }
    }

2.4 Agent Protocol

2.4.1 run

  • Function Name:

    run

  • Params:

    self

    task: str

    **kwargs

  • Return:

    dict

  • Description:

    Run the agent loop (step()) until it finishes. Returns a dict containing exit_status and submission from the final exit message.

  • e.g.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # From hello_world.py — the simplest entry point
    agent = DefaultAgent(
    ...
    LitellmModel(model_name="anthropic/claude-sonnet-4-5"),
    LocalEnvironment(),
    )
    info = agent.run("Write a sudoku game")

    # info returned by run():
    # {
    # "exit_status": "Submitted", # how the run ended
    # "submission": "...", # the final text/patch the agent produced
    # }

2.4.2 save

  • Function Name:

    save

  • Params:

    self

    path: Path | None

    *extra_dicts

  • Return:

    dict

  • Description:

    Serialize the full trajectory and write it to path (if given). Returns the complete serialized dict.

  • e.g.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # Save the trajectory to a file
    data = agent.save(Path("last_run.traj.json"))

    # Internally it does:
    # data = self.serialize(*extra_dicts) # build the full dict
    # path.write_text(json.dumps(data, indent=2))# write JSON to disk
    # return data # return the same dict

    # The saved file looks like:
    {
    "info": {
    "model_stats": {"instance_cost": 0.05, "api_calls": 3},
    "config": {"agent": {...}, "model": {...}, "environment": {...}},
    "exit_status": "Submitted",
    "submission": "..."
    },
    "message": [ ...entire conversation history... ]
    }

3. Agent

The agent part structure is :

1
2
3
4
5
6
7
8
agents/
|-- README.md
|-- __init__.py
|-- default.py
|-- interactive.py
-- utils
|-- __init__.py
|-- prompt_user.py

3.1 __init__.py

Import:

1
2
3
4
import copy
import importlib

from minisweagent import Agent, Environment, Model

Class Rejistry:

1
2
3
4
_AGENT_MAPPING = {
"default": "minisweagent.agents.default.DefaultAgent",
"interactive": "minisweagent.agents.interactive.InteractiveAgent",
}

get_agent_class(), Look up the class by name.:

1
2
3
4
5
6
7
8
9
def get_agent_class(spec: str) -> type[Agent]:
full_path = _AGENT_MAPPING.get(spec, spec)
try:
module_name, class_name = full_path.rsplit(".", 1)
module = importlib.import_module(module_name)
return getattr(module, class_name)
except (ValueError, ImportError, AttributeError):
msg = f"Unknown agent type: {spec} (resolved to {full_path}, available: {_AGENT_MAPPING})"
raise ValueError(msg)

get_agent(), create an agent class:

1
2
3
4
def get_agent(model: Model, env: Environment, config: dict, *, default_type: str = "") -> Agent:
config = copy.deepcopy(config)
agent_class = get_agent_class(config.pop("agent_class", default_type))
return agent_class(model, env, **config)

3.2 default.py

3.2.1 Import

1
2
3
4
5
6
7
8
9
10
11
12
import json            # JSON
import logging # log
import time # to time
import traceback # print traceback
from pathlib import Path # replace os.path

from jinja2 import StrictUndefined, Template # template engine
from pydantic import BaseModel # config class

from minisweagent import Environment, Model, __version__
from minisweagent.exceptions import FormatError, InterruptAgentFlow, LimitsExceeded, TimeExceeded
from minisweagent.utils.serialize import recursive_merge

3.2.2 AgentConfig

1
2
3
4
5
6
7
8
class AgentConfig(BaseModel):
system_template: str
instance_template: str
step_limit: int = 0
cost_limit: float = 3.0
wall_time_limit_seconds: int = 0
max_consecutive_format_errors: int = 3
output_path: Path | None = None
  • system_template: Template for system prompt
  • instance_template: Template for user prompt
  • step_limit: Max turn limit
  • cost_limit: Max cost limit
  • wall_time_limit_seconds: Stop agent after this many seconds of wall-clock time. 0 means no limit.
  • max_consecutive_format_errors: Give up after N consecutive format errors.
  • output_path: Path to save trajectory

3.2.3 DefaultAgent

3.2.3.1 __init__

1
2
3
4
5
6
7
8
9
10
11
def __init__(self, model: Model, env: Environment, *, config_class: type = AgentConfig, **kwargs):
self.config = config_class(**kwargs)
self.messages: list[dict] = []
self.model = model
self.env = env
self.extra_template_vars = {}
self.logger = logging.getLogger("agent")
self.cost = 0.0
self.n_calls = 0
self.n_consecutive_format_errors = 0
self._start_time = time.time()
  • Params:

    • model: Model
    • env: Environment
    • config_class: default value is AgentConfig
    • **kwargs
  • Initialization

    • self.config: from param **kwargs.
    • self.messages: Initialize message history as a empty list.
    • self.model: from param model.
    • self.env: from param env.
    • self.extra_template_vars: extra template vars, initialize as empty.
    • self.logger: create a logger.
    • self.cost: costed fee, initialize as 0.
    • self.n_calls: number of calls of LLM.
    • self.n_consecutive_format_errors: number of consecutive format errors.
    • self._start_time: timestamp of start time.
  • The Initialized Agent:

    1
    2
    3
    4
    5
    6
    7
    8
    agent
    ├── .config → AgentConfig
    ├── .messages → [] (will be filled when call `run` func)
    ├── .model → model passed in
    ├── .env → environment passed in
    ├── .cost → 0.0
    ├── .n_calls → 0
    └── ._start_time → current timestamp

3.2.3.2 tool functions

The function get_template_vars is to get template variables, for example, a config file (YAML):

1
2
3
4
5
instance_template: |
Please solve this issue: {{task}}
<system_information>
{{system}} {{release}} {{version}} {{machine}}
</system_information>

Function get_template_vars is to extract the variables: {{task}}, {{system}}, {{release}}, {{version}} and {{machine}}.

get_template_vars:

1
2
3
4
5
6
7
8
9
10
11
12
13
def get_template_vars(self, **kwargs) -> dict:
return recursive_merge(
self.config.model_dump(), # ① agent config → dict
self.env.get_template_vars(), # ② environment vars
self.model.get_template_vars(),# ③ model vars
{ # ④ dynamic states
"n_model_calls": self.n_calls,
"model_cost": self.cost,
"elapsed_seconds": int(time.time() - self._start_time),
},
self.extra_template_vars, # ⑤ vars paseed in by USER like task
kwargs, # ⑥ extra vars
)

_render_template:

1
2
def _render_template(self, template: str) -> str:
return Template(template, undefined=StrictUndefined).render(**self.get_template_vars())

add_messages:

1
2
3
4
def add_messages(self, *messages: dict) -> list[dict]:
self.logger.debug(messages)
self.messages.extend(messages) # append new messages
return list(messages) # ccopy and return

3.2.3.3 ‘query’

1
2
3
4
5
6
7
8
9
10
11
12
def query(self) -> dict:
# ① check limit conditions
if 0 < self.config.step_limit <= self.n_calls or 0 < self.config.cost_limit <= self.cost:
raise LimitsExceeded({... "exit_status": "LimitsExceeded" ...})
if 0 < self.config.wall_time_limit_seconds <= int(time.time() - self._start_time):
raise TimeExceeded({... "exit_status": "TimeExceeded" ...})
# ② query
self.n_calls += 1
message = self.model.query(self.messages)
self.cost += message.get("extra", {}).get("cost", 0.0)
self.add_messages(message)
return message

3.2.3.4 ‘execute_actions’

1
2
3
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()))

3.2.3.5 ‘step’

1
2
def step(self) -> list[dict]:
return self.execute_actions(self.query())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
run() in while loop:

step()
│
├─ query()
│ ├─ check step/cost/time limit ──over limit──▶ raise LimitsExceeded/TimeExceeded
│ ├─ self.model.query(self.messages) ← send message history to LLM
│ ├─ cumulate cost + append assistant message
│ └─ return message(including actions)
│
└─ execute_actions(message)
├─ exevute action: self.env.execute(action) → collect outputs
└─ format_observation_messages → append tool message(

go back to run():if messages[-1].role == "exit" → break

3.2.3.6 ‘run’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def run(self, task: str = "", **kwargs) -> dict:
self.extra_template_vars |= {"task": task, **kwargs} # record task
self.messages = [] # clear history
self.add_messages( # add system prompt & user prompt
self.model.format_message(role="system", content=self._render_template(self.config.system_template)),
self.model.format_message(role="user", content=self._render_template(self.config.instance_template)),
)
while True: # ④ main loop
try:
self.step() # ⑤ one step
self.n_consecutive_format_errors = 0
except FormatError as e: # ⑥ format error → retry or give up
...
except InterruptAgentFlow as e: # ⑦ cancel signal → stop & record
self.add_messages(*e.messages)
except Exception as e: # ⑧ Unexpected error → log & aise.
self.handle_uncaught_exception(e)
raise
finally:
self.save(self.config.output_path) # ⑨ save at each step
if self.messages[-1].get("role") == "exit": # ⑩ stop if see `exit`
break
return self.messages[-1].get("extra", {}) # ⑪ return

3.2.3.7 ‘serialize’ & ‘save’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def serialize(self, *extra_dicts) -> dict:
last_message = self.messages[-1] if self.messages else {}
last_extra = last_message.get("extra", {})
agent_data = {
"info": {
"model_stats": {
"instance_cost": self.cost,
"api_calls": self.n_calls,
},
"config": {
"agent": self.config.model_dump(mode="json"),
"agent_type": f"{self.__class__.__module__}.{self.__class__.__name__}",
},
"mini_version": __version__,
"exit_status": last_extra.get("exit_status", ""),
"submission": last_extra.get("submission", ""),
},
"messages": self.messages,
"trajectory_format": "mini-swe-agent-1.1",
}
return recursive_merge(agent_data, self.model.serialize(), self.env.serialize(), *extra_dicts)
1
2
3
4
5
6
def save(self, path: Path | None, *extra_dicts) -> dict:
data = self.serialize(*extra_dicts)
if path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data, indent=2))
return data
  • serialize: Build a JSON-serializable dict of the whole agent state (stats, config, messages, model/env info).
  • save: Serialize the state and write it to path if given; returns the same dict.

3.3 interactive.py

It’s a subclass of DefaultAgent that inherits the core logic (the main loop) wholesale and does exactly one thing: it puts a human in the loop — letting the user approve, reject, issue commands directly, and interject mid-run.

It provides 3 modes:

  • human: No LLM calling, the User inputs commands directly.
  • confirm(default): The actions returned by LLM need human’s confirm to execute.
  • yolo: Auto-execution actions.

4. environment

…

5. Comparison between SWE-agent and MSWEA

Dimension SWE-agent mini-SWE-agent Why it changed
Tool Layer Multi-tool bundles; per-tool commands, parameters, auto-generated command docs; blocklist blocking interactive tools (vim, nano, gdb) One single bash tool; every model response must contain one bash call Models are already fluent in shell; designing a tool per capability is the scaffold doing the model’s thinking, with diminishing returns. Bash-only also means zero extra dependencies for any model or environment
Execution / session model Stateful bash session via SWE-ReX: cwd, env vars and shell state persist across commands; plus file read/write APIs Stateless per-command execution (subprocess.run / docker exec): each command is a fresh process Isolation — a previous command cannot contaminate the next; timeouts can kill the whole process group; swapping sandboxes means swapping one command; trivially parallelizable
History / context Pluggable HistoryProcessor pipeline that may compress, truncate, or inject cache-control markers before sending to the model A plain list with add_messages; the trajectory *is* what the model sees No transformation layer: what you save is what the model saw — far easier to debug and fine-tune, and no processor-composition complexity
Action parsing 4 selectable parsers (function-calling, JSON, thought/action, action-only) 2 (tool-call or regex), tool-call explicitly recommended Collapses the parser matrix; the tool-call format is guaranteed by the model API itself
Control flow action_sampler (sample several candidate actions) + reviewer / RetryLoop (score and retry submissions) One query, one execute — no second opinion Sampling/review/retry is the harness correcting the model; mini hands correction back to the model, which self-iterates on observations
Extensibility Three-layer hooks (agent / environment / run) for out-of-tree behavior injection No hooks; subclass and override methods (e.g. InteractiveAgent overrides four) Python inheritance is native and sufficient; a dedicated hook framework is an extra abstraction layer

5.1 Tool layer

5.1.1 Tool in SWE-agent

There are two main kinds tools in SWE-agent: the python class and the shell class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
tools/
│
├── ① Window & File Viewing ────────────────────── [model-facing] 5 cmds
│ ├── windowed/ Python
│ │ ├── open "<path>" [<line>] open a file, optionally jump to a line
│ │ ├── goto <line_number> move the display window to a line
│ │ ├── create <filename> create and open a new file
│ │ ├── scroll_up / scroll_down shift the window up / down
│ │ └── _state [internal] report open_file + cwd
│ │
│ └── filemap/ Python + tree-sitter
│ └── filemap <file> print a Python skeleton
│ (elides function bodies > 5 lines)
│
├── ② File Editing ─────────────────────────────── [model-facing] 4 cmds
│ ├── edit_anthropic/ Python (31 KB, the heaviest)
│ │ └── str_replace_editor <command> ... view / create / str_replace
│ │ / insert / undo_edit
│ ├── windowed_edit_replace/ Python
│ │ ├── edit <search> <replace> [--all] search-and-replace
│ │ └── insert <text> [<line>] insert text
│ ├── windowed_edit_linting/ Python
│ │ └── edit <start>:<end> ... end_of_edit replace a line range
│ └── windowed_edit_rewrite/ Python
│ └── edit <text> rewrite the whole window
│
├── ③ Search ───────────────────────────────────── [model-facing] 3 cmds
│ └── search/ bash (the only pure-shell bundle)
│ ├── find_file <name> [<dir>] find files by name / glob
│ ├── search_dir <term> [<dir>] full-text search across a directory
│ └── search_file <term> [<file>] search within one file
│
├── ④ Submission & Termination ─────────────────── [model-facing] 3 cmds
│ ├── submit/ bash
│ │ └── submit git diff → /root/model.patch
│ │ prints <<SWE_AGENT_SUBMISSION>>
│ ├── review_on_submit_m/ Python
│ │ └── submit variant of the above (-f hidden)
│ └── forfeit/ bash
│ └── exit_forfeit prints ###SWE-AGENT-EXIT-FORFEIT###
│
├── ⑤ Web Browsing ─────────────────────────────── [model-facing] 17 cmds
│ └── web_browser/ Python + Playwright
│ ├── navigation open_site · close_site · navigate_back / forward · reload_page
│ ├── input click_mouse · double_click_mouse · move_mouse · drag_mouse
│ │ type_text · press_keys_on_page · scroll_on_page
│ ├── observation screenshot_site · get_console_output
│ └── control execute_script_on_page · wait_time · set_browser_window_size
│ run_web_browser_server
│
├── ⑥ Other Modalities ─────────────────────────── [model-facing] 1 cmd
│ └── image_tools/ Python
│ └── view_image <file> image → inline base64 markdown
│
└── ⑦ Infrastructure (not exposed to the model) ── [internal] 4 cmds
├── registry/ Python
│ ├── _read_env <var> [<default>] read /root/.swe-agent-env
│ └── _write_env <var> <value> write /root/.swe-agent-env
├── diff_state/ Python
│ └── _state_diff_state git diff → the "diff" field of state.json
├── multilingual_setup/ bash
│ ├── install.sh import env vars from /proc/1/environ
│ └── do_nothing empty placeholder file
└── review_on_submit_m/
└── install.sh (0 bytes)

5.1.2 Tool in MSWEA

Those tools in SWE-agent can be divided into 3 other classes:

  1. can be implemented by bash + software engineering needed (most) -> delete
  2. cannot be implemented by bash + software engineering needed (none) -> keep
  3. cannot be implemented by bash + software engineering not needed (delete)

In the end, there is only 1 tool in MSWEA: bash.

Because most tools provided above, can be generated by LLM and executed by the bash tool.

There are 4 extra operations:

  1. src/minisweagent/config/default.yaml:

    The harness gives some using examples in User Prompt template agent.instance_template:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ## Useful command examples

    ### Create a new file:

    ```mswea_bash_command
    cat <<'EOF' > newfile.py
    import numpy as np
    hello = "world"
    print(hello)
    EOF

    ### Edit files with sed:
    
    {%- if system == "Darwin" -%}
    <important>
    You are on MacOS. For all the below examples, you need to use `sed -i ''` instead of `sed -i`.
    </important>
    {%- endif -%}
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 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:
    1
    2
    # View specific lines with numbers
    nl -ba filename.py | sed -n '10,20p'
    ### Any other command you want to run
    1
    anything
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    2. `src/minisweagent/config/default.yaml`:

    Env Settings for bash:

    ```yaml
    environment:
    env:
    PAGER: cat
    MANPAGER: cat
    LESS: -R
    PIP_PROGRESS_BAR: 'off'
    TQDM_DISABLE: '1'
  2. src/minisweagent/config/default.yaml:

    observation_template: The template structure the outputs:

    • distinct return code and output:

      1
      2
      <returncode>{{output.returncode}}</returncode>
      <output>{{ output.output }}</output>
    • Optimize the extremely long text:

      1
      2
      3
      4
      5
      6
      7
      <warning>
      The output of your last command was too long.
      Please try a different command that produces less output.
      If you're looking at a file you can try use head, tail or sed...
      If you're using grep or find and it produced too much output, you can use a more selective search pattern.
      If you really need to see something from the full command's output, you can redirect output to a file and then search in that file.
      </warning>
  3. src/minisweagent/config/default.yaml:

    format_error_template: Guidance template for format errors.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    {% if finish_reason is defined and finish_reason in ["length", "tool_calls"] -%}
    Your previous response reached the output token limit (finish_reason={{ finish_reason }}) before you produced a complete action, so it was cut off. Respond more concisely and provide exactly one action in the required format. If you need to think more, do so briefly.
    {%- else -%}
    Format error:

    <error>
    {{error}}
    </error>

    Here is general guidance on how to format your response:

    Please always provide EXACTLY ONE action in triple backticks, found {{actions|length}} actions.
    If you want to end the task, please issue the following command: `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT`
    without any other command.
    Else, please format your response exactly as follows:

    <response_example>
    Here are some thoughts about why you want to perform the action.

    ```mswea_bash_command
    <action>
    ```
    </response_example>

    Note: In rare cases, if you need to reference a similar format in your command, you might have
    to proceed in two steps, first writing TRIPLEBACKTICKSBASH, then replacing them with ```mswea_bash_command.
    {%- endif %}

5.2 Execution / Session model

5.3 History / Context

The SWE-agent has a HistoryProcessor to deal with the long history. It will compress, truncate the long history, then send it to LLM.

5.4 Action Parsing

5.5 Control Flow

5.6 Extensibility