Repository navigation
mini-swe-agent: a Base Agent, an acting agent and a deployment host - #49
Merged
haizhongzheng merged 2 commits intoNov 9, 2026
Merged
Conversation
Michael-Jiahao-Zhang
force-pushed
the
feat/mini-swe-agent
branch
from
November 8, 2026 22:07
63367dd to
b5047fc
Compare
Michael-Jiahao-Zhang
force-pushed
the
feat/mini-swe-agent
branch
from
November 9, 2026 01:30
769203a to
b42cff9
Compare
- `mini-swe-agent` is a CLI Base Agent beside Codex, Claude Code and Pi (`MiniSweAgent` in CLIS): one call runs one task through mini-swe-agent's Python API in a process of its own (hermit/agent/base/mini_swe_agent_run.py), which writes the session as JSON events, the stream CliAgent keeps and reads into the reply, the trajectory steps and the usage. Isolation, the home, the tool server, the clock and the retries of a provider that could not serve are CliAgent's. The call's tools are one bash command, `hermit-tool`, answered through the tool server's JSON entrance; the model's key goes with each request, the commands get only `env`, and on Linux the process is non-dumpable. A setting that fails every task (a config that names the model, a wrong key or model id found by the first call) starts no session; at the timeout the running command is killed too. - `agent.name: mini-swe-agent` (or `agent.base: mini-swe-agent`) is the workspace Acting Agent with that backend, its model bound as the other CLIs' are; a reference takes the litellm model classes only. - `hermit deploy --host mini-swe-agent` deploys as every host does (the copy under hermit/<id>/, what mini-swe-agent cannot load named in the context) and writes `mini-swe-agent.yaml`, whose system template gives `mini` the system message the Base Agent sends. - The `mini-swe-agent` extra (installed by CI), a tutorial and an example, and the agent and host in the reference tables.
Michael-Jiahao-Zhang
force-pushed
the
feat/mini-swe-agent
branch
from
November 9, 2026 01:43
b42cff9 to
7cebb3c
Compare
…n sandbox, core-only test collection) - after a format error mini's own message told the model to end with the bare COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT, an empty submission: Hermit's format_error_template ends with `... && cat answer.txt` - model_class is litellm or litellm_textbased in every case: the *_response classes' messages and the openrouter / portkey classes' errors are not litellm's (usage uncounted, a rate limit not retried) - under isolation: sandbox the home names no host path, as Pi's: the config is read into the settings, the system instructions go on the command (past ARG_MAX in a file, Claude Code's _prompt_file, now CliAgent's) - a thinking model's reasoning_content is a reasoning step of the trajectory - tests/core collects without mini-swe-agent (jinja2 imported only when present) - docs: the tutorial's time-limit sentence; agent.base / agent.<base> rows in config-fields Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
haizhongzheng
left a comment
Member
There was a problem hiding this comment.
Thanks for the PR. We reviewed it in seven rounds (our own read plus an independent Codex review each round) and pushed the fixes as one commit, 844897b, on this branch. Every fix has a test that fails without it.
Three-layer abstraction
It follows docs/framework/agent-layers.md:
- Layer 2. mini-swe-agent is a Base Agent. It is a
CliAgentsubclass inCLIS, with its stream reader intrajectory.py. - Layer 1. Its model binding is in
backends.py. - Layer 3. The workspace Acting Agent is reused.
- Imports and the shared runner. Imports go one way only.
MiniSweAgentuses only hooks that already exist and does not change the shared runner.
One small leak stays as it is, by our decision: _deploy in acting/workspace.py branches on backend == "mini-swe-agent" to choose who serves the tools.
What we patched (844897b)
| Before | After | |
|---|---|---|
| Test collection | tests/core/test_mini_swe_agent.py imported jinja2 at the top. With only pip install -e '.[test]' (AGENTS.md's setup), collection of tests/core stopped with an error. |
jinja2 is imported only when present, and the module is skipped without mini-swe-agent. |
| Empty submission | After a format error, mini's inherited model.format_error_template told the model to end with a bare echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT. That gives an empty submission, which is likely the failure seen in the e2e run. |
mini_swe_agent.yaml overrides it for mini, ending with `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT && cat answer.txt`. render_config puts it in the model section. |
| Model classes | Without a binding, any class was accepted. The *_response classes return Responses-API messages, so their usage was not counted and their steps were empty. The openrouter, portkey and requesty classes raise their own errors, so a rate limit was not retried and a wrong key was not a setup error. |
model_class must be litellm or litellm_textbased in every case. Another provider's model goes through litellm (openrouter/<model>). |
isolation: sandbox |
The home's mini-swe-agent.json held host paths: the config file's path and the system text. The sandbox translates only the command, the environment and the prompt, so the session never started. |
The home names no host path, as for Pi. The config is read into the settings. The system instructions go on the command as --system, or --system-file past ARG_MAX, using Claude Code's _prompt_file, moved unchanged into CliAgent. An end-to-end bubblewrap test covers short and long instructions. |
| Reasoning | reasoning_content was dropped, so the trajectory had no reasoning steps, unlike Codex, Claude Code and Pi. |
It is kept in the events and read as Step("reasoning"). |
| Docs | The tutorial said a time-limit stop returns the last assistant message; the reply is empty. config-fields.md omitted mini-swe-agent. |
Both corrected. |
Tests: test_mini_swe_agent, test_layers, test_context_tool, test_base_agents, test_pi_agent and test_cli_agent give 148 passed and 2 skipped, with mini-swe-agent 2.4.6. In a core-only environment the mini-swe-agent tests are skipped.
Left for follow-ups (not blocking)
- Retry accounting. A run the provider could not serve is retried, but only the last attempt's usage is kept. This is the shared Layer 3 retry loop for every CLI. mini meets it more often, since it retries after billed replies.
- Background processes. A command's background child (
server &) is not stopped at the end, timeout or cancel underisolation: none. Upstream's_runbehaves the same, and a sandbox ends it. - Long system file. Past
ARG_MAX, the system-instructions file keeps host paths: its contents are not translated. Claude Code's_prompt_filehas the same behaviour. deploy --python. It is not the interpreter the adapter's tools run in, because mini runs them in its own process. The deployment's launch command already uses that interpreter, butcommands.mdcould say so.
Minor, recorded only:
${VAR}inagent.params.configis not expanded.- The example judge accepts
195.0as195. - An empty submission falls back to the last assistant message.
- Outputs of commands that run before a submission in the same response are not in the trajectory.
- mini's
MSWEA_GLOBAL_*limits can raise before a response's usage is recorded. - The JSON tool entrance is not passed through
Tool.render. Adapter tools render identically. - Host paths inside a user config are not translated under the Layer 2 sandbox.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Change
Adds mini-swe-agent (2.4.6) to Hermit on the agent layers. It is one of the further agent integrations of #37 §3, discussed with the maintainers beforehand.
The PR was rebuilt twice:
Layer 2: the
mini-swe-agentBase Agent.MiniSweAgentis aCliAgentsubclass inCLIS(hermit/agent/base/cli.py), beside Codex, Claude Code and Pi.Process. One
runis one task. It runs through mini-swe-agent's Python API, the way its batch runner runs one, in a process of its own:python -P -m hermit.agent.base.mini_swe_agent_run(-Pkeeps the workspace off its import path). That process writes the session as JSON events on stdout. CliAgent keeps them as the stream (agent-mini-swe-agent.jsonl) and reads them into the reply, the steps (mini_swe_agent_stepsintrajectory.py) and the usage (parse_mini_swe_agentinrecords.py).What it reuses from CliAgent. Isolation (
noneor the run'ssandbox; not a container, as for Pi), the home (MSWEA_GLOBAL_CONFIG_DIR), the tool server, the clock and the retries of a provider that could not serve. Its hooks are the command, the home's settings file,served,errors,outcome,last_message,parseandsteps.Messages.
mini_swe_agent.yaml) go over mini-swe-agent's ownmini/mini_textbasedconfig, then aconfigfile's settings.adapter_block): the deployment's context (hermit.contextin the workspace'smini-swe-agent.yaml), the call's tools and the call's system instructions.echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT && cat answer.txt); the reply is the submission, else the last assistant message.Tools. The call's tools are one bash command,
hermit-tool <name> '<JSON arguments>'. The process's environment (mini_swe_agent_env.py) answers it through the tool server's JSON entrance (HERMIT_TOOLS_URL).The model's key.
env).envand a few basic variables only./proc..envis the sign-in file (home_files), asauth.jsonis Codex's and Pi's.Failures (
outcome, the contract Pi follows):ValueError)configthat names the model or the environment class, or a first model call refused for the settings (401, 404, or the 400 DeepSeek returns for a wrong key)exitmessage (killed)failed: no answer is judged and the task is an error record (session_failed), as Pi's failed request is; its tokens countunavailable, not served: the task runs again (retries)Refused. These are refused with
Unsupported:roles;session(mini-swe-agent cannot resume);login;web_search: true;litellm/litellm_textbased, since another class would send the binding's key and URL to its own service.Layer 3.
agent.name: mini-swe-agentis the workspace Acting Agent (hermit/agent/acting/workspace.py) with backendmini-swe-agent, soprompt,files,mounts,prepare,answer,collect,context_queryandretrieswork as for the other CLIs._checkrefuses a login and a container for it in the branch it now shares with Pi. The layered form isagent.base: mini-swe-agentwith anagent.mini-swe-agentsection.bind_agent_paramskindmini-swe-agent, listed inCLI_KINDS, withNEEDSset to chat completions.Deploy.
hermit deploy --host mini-swe-agent(hermit/hosts/mini_swe_agent.py) follows the shared layout.hermit/<id>/, and the context names what mini-swe-agent cannot load (skills, roles, hooks, other files) throughhermit.hosts.inputs.resources.mini-swe-agent.yaml. It holds Hermit's prompts over mini-swe-agent'sminiconfig, with the adapter's block in the system template as literal text. The block is a Jinja raw block, with every spelling ofendrawescaped.AdapterEnvironment(a Session in mini-swe-agent's process) as the environment class, plus an audit log.mini -c mini-swe-agent.yamlgets the system message the Base Agent sends (a test asserts it). The acting agent's ownmodel_class/configare not deployed (documented).Dependency. A
mini-swe-agentoptional extra (pip install "hermit-harness[mini-swe-agent]"). It is not a dependency group, because the groups are the methods' (METHOD_REQUIREMENTS). CI installs it so the tests run rather than skip.render_configlocates the package rather than importing it, so a deploy or the Trainer never gets mini-swe-agent's import side effects.Docs.
docs/_examples/mini_swe_agent/: nine CSV questions, an exact-format judge, GEPA as the learner, DeepSeek.Core changes. These are shared layers that every method group runs on. They extend existing tables and branches; no new mechanism.
hermit/agent/base/cli.py: the subclass inCLIS.trajectory.py: the reader inREADERS/STREAM_HOSTS/MARKS; it keys onsession.started, which no other CLI emits.records.py: the usage parser.parse_lognow also dispatches Pi, which before fell through to Claude Code's reader.acting/workspace.py,acting/agent.py: the backend, the deploy branch, the settings the Base Agent takes, and Pi's_checkbranch shared.config.py,defaults.yaml: theagent.basevalue and its section.model/backends.py:CLI_AGENTS,CLI_KINDS,NEEDS, the binding kind.hermit/isolate.py: the config check that refuses a login for Pi now refuses it for mini-swe-agent too, with the agent's name. This is configuration validation only; what the sandbox shows or allows is unchanged..github/workflows/tests.yml: the extra.Validation
Tests.
tests/core/test_mini_swe_agent.py, 24 tests. The stand-in is the model: an OpenAI-compatible server with the API's payload shape. mini-swe-agent's loop, its environment and litellm run unchanged. Run time is about 70 s; the longest test is the 9 s timeout test, whose delay is what it holds.What the 24 tests cover
env, no key in any file, steps, tokens and cost.yaml.pythat does not shadow the runner's imports./proc/$PPID/environ(Linux, non-root).workspace._checkandisolate's config check, including a badconfigraising rather than scoring.endrawspellings;hermit-toolthrough mini-swe-agent's ownget_environment; roles named; local environment without tools; redeploy; the other-host refusal; the CLI.mini.yamlin the working directory.The regression tests for the review findings fail without their fixes (checked by reverting each fix).
test_context_tool.pyadds the host to the not-portable refusal.test_layers,test_selectionandtest_packagingpass.Test runs at this head, with litellm 1.108.3 and openai 3.46.0, the versions CI resolves with the other groups:
pytest tests/core(before the last audit's small fixes)mainon this machine:test_backends::test_check_reports_models_and_train_refuses_a_missing_dialect,test_cli_agent::test_a_stream_replaced_by_a_link_is_not_read_as_the_sessions, twotest_harbor_runner,test_isolate::test_a_worker_whose_log_cannot_be_opened_leaves_no_descriptor_open,test_lean_swarm_launch,test_session_sandbox::test_a_server_that_cannot_start_is_reportedtest_mini_swe_agent.py,test_pi_agent.py,test_layers.pyandtest_selection.pypass (2 Linux-only skips)test_mini_swe_agent.py: 23 passed, 1 skipped (bubblewrap); at b42cff9 alsotest_pi_agent.pyandtest_cli_agent.py(92 passed, skips for bubblewrap and the pi CLI)tests/corebwrap: loopback: Failed RTM_NEWADDR), plus the onetest_backendsfailure abovetest_mini_swe_agent.py: 23 passed, the sandboxed rollout among them. The/proctest is skipped there, since root reads any process's environmentbash docs/build.sh(warnings as errors) passes.End to end with DeepSeek (
deepseek/deepseek-flash), the example as shipped.eval --adapter nonetraineval --adapter finalsandbox.enforce=required(every task in the task sandbox, the learner in its own), this headsandbox.enabled=false(bubblewrap cannot run there), before the #89 rebasehermit checkwith a wrongDEEPSEEK_API_KEYreports[PROBLEM] agent probe ... exited 2 before starting a session: ... Authentication Failsin about 5 s.mini -c mini-swe-agent.yaml -m deepseek/deepseek-flash -y --exit-immediately -t "..."answered{"answer": 448}(correct) in 3 calls.deepseek-flashis in the built-in backend, so costs are unknown.One rollout's sandbox record (
runs/mini-swe-agent/sandbox/rollout-1.json, privileged container, abridged){"role": "rollout", "program": "/opt/venv/bin/python3", "enforced": true, "network": "isolated", "read_only": ["/opt/venv/...", "/work/hermit", "/work/.demo/m/environment", "/work/.demo/m/runs/mini-swe-agent/rollout_adapter"], "read_write": [".../.logs/rollout/<task>", ".../.logs/rollout/<task>/home"], "allow": ["api.deepseek.com"], "probe": {"passed": true, "outside": {"1.1.1.1:443": "blocked (OSError)", "8.8.8.8:53": "blocked (OSError)"}, "denied": {"/root/.bashrc": "absent", "/work/.demo/m/tasks.jsonl": "absent", "/work/.demo/m/train.txt": "absent", "/work/.demo/m/val.txt": "absent", "/work/.demo/m/test.txt": "absent", "/work/.demo/m/judge.py": "absent"}}, "environment": ["DEEPSEEK_API_KEY", "HOME", "HTTPS_PROXY", "MSWEA_GLOBAL_CONFIG_DIR", "PATH", "PYTHONPATH", "..."]}Not verified:
isolation: sandboxfor mini-swe-agent: the acting agent reaches the sandbox through the task sandbox, where the inner isolation isnone.ps ewwshows a process's environment to the same user (documented).Review
--host mini-swe-agent,agent.name/agent.base: mini-swe-agentandagent.mini-swe-agent.*are listed where the other CLIs are.agent.*→bind_agent_params→base_agent()settings → the home's JSON → the process. Errors, timeouts, retries and failed sessions are covered above, with tests.load_agent,Config.loadand the Base Agent'srun, the real mini-swe-agent and litellm, the deploy host and mini-swe-agent's ownget_environment. The e2e runs go throughhermit check/eval/train/deployand theminiCLI.tests/core/, which--changedruns for these shared paths.tmp_pathand use no network beyond loopback.hermit/isolate.pyis the config check's message (see Core changes). The sandbox itself is unchanged.configfile must be declared inenvironment.read(documented).post-pr-audit(at b5047fc, and at b42cff9 after the Add Pi as a CLI Base Agent (acting agent and pi Evolver) #89 rebase). Findings and fixes, with regression tests where testable:-P.render_configwithout importing mini-swe-agent;MSWEA_GLOBAL_CONFIG_DIRhonoured; refusal messages naming the agent; docs listing Pi and mini-swe-agent alike; the task-sandbox note forconfig; tests for the merged refusals.answerreads.