跳到主要内容

SWE-ReX — 架构与原理

30 秒导读: SWE-ReX(SWE-agent Remote Execution Framework)是给 AI coding agent 用的"远程执行底座"。你的 agent 想跑一条 shell 命令,它负责:把命令送进一个真实 shell、识别命令何时跑完、抠出输出和退出码、再返回给你——不管这个 shell 是在你本机、还是在 Docker / Modal / AWS Fargate 上。agent 代码完全不用改。

1. 这是什么(零基础也能懂)

一句话定义: SWE-ReX 是一个沙箱 shell 执行运行时——把"让 agent 在某个环境里跑命令并拿回结果"这件事,做成一个跨环境统一的接口。

解决什么问题。 想象你在写一个会改代码的 AI agent(比如 SWE-agent)。agent 要跑 pytest、要开 ipython、要 gdb 调试。这些命令得在某个机器上真实执行。于是你撞上两类麻烦:

  • "在裸 shell 里跑命令"本身就难。subprocess 跑一条命令很容易;但要维持一个长活的交互式 shell(像人开着一个终端),在里面连续跑命令、还能开 ipython 这种不退出的程序——你得自己判断"这条命令到底跑完了没"、"退出码是多少"。裸 shell 不会主动告诉你。
  • "在哪跑"会绑死你的代码。 本地跑、Docker 里跑、Modal 云沙箱里跑、AWS Fargate 上跑——每种环境启动方式都不同。如果 agent 逻辑里到处是 docker execboto3,你就再也搬不动了。

给谁用。 写 / 评测 coding agent 的人。SWE-ReX 的原话:"帮你专注于开发和评测 agent,而不是基础设施"(README.md)。它从 SWE-agent 的实战里长出来,目标是大规模并行地在 SWE-bench 这种基准上跑 agent。

它能做什么(功能):

  • 维持多个长活 shell 会话,自动识别命令结束、抠出输出与退出码。
  • 支持 ipython / gdb 这类交互式命令行程序
  • 一个运行时里多个会话并行(shell、ipython、gdb 同时开着,像人一样)。
  • 同一份 agent 代码,后端可在 local / docker / modal / fargate / daytona / remote 之间切换。

用起来什么样。 核心是两层对象:Deployment(决定在哪跑)和它给你的 Runtime(在那上面执行)。一段最小用法长这样:

# 示意,非源码 —— 演示对外的 API 形状
from swerex.deployment.docker import DockerDeployment
from swerex.runtime.abstract import CreateBashSessionRequest, BashAction

# 1. 选一个后端(这里是本地 Docker 容器)
deployment = DockerDeployment(image="python:3.11")
await deployment.start() # 拉镜像、起容器、把 server 跑起来
runtime = deployment.runtime # 拿到一个能执行命令的 runtime

# 2. 开一个 bash 会话,在里面跑命令
await runtime.create_session(CreateBashSessionRequest(session="main"))
obs = await runtime.run_in_session(BashAction(command="echo hi && pytest", session="main"))
print(obs.output, obs.exit_code) # 输出 + 退出码,都帮你抠好了

await deployment.stop() # 关容器

把后端换成 ModalDeployment(image=...),上面第 2 步一个字都不用改——这就是它的卖点。

一句话直觉/类比。 把 SWE-ReX 想成"shell 界的数据库驱动":你写 SQL(BashAction),驱动(Runtime)负责把它送到真正的数据库(本地/云上的 shell)并把结果格式化回来;换数据库(换 Deployment)时你的 SQL 不变。

2. 顶层全景(它大概怎么转)

SWE-ReX 内部分成两个正交的轴,这是理解整个项目的钥匙:

回答的问题抽象基类关键文件
Runtime(运行时)命令怎么被执行、输出怎么拿回AbstractRuntimesrc/swerex/runtime/abstract.py
Deployment(部署)运行时在哪跑、怎么把它拉起来AbstractDeploymentsrc/swerex/deployment/abstract.py

为什么要分两层? 因为"在哪跑"几乎不影响"怎么跑"。无论容器在本机还是 AWS,真正执行命令的那段逻辑是同一份(LocalRuntime)。Deployment 只负责"把那段逻辑搬到目标机器、再开条线连回来"。

一张图:一次远程执行怎么流动

下面这张图从左到右读,是"一条命令从 agent 到 shell 再回来"的完整路径(以 Docker 后端为例):

你的 agent 进程(本地) 容器 / 云沙箱(远端)
┌─────────────────────────┐ ┌──────────────────────────────┐
│ DockerDeployment │ ① 起容器并在 │ │
│ .start() │ ───里面运行────▶ │ swerex-remote (server.py) │
│ │ swerex-remote │ FastAPI HTTP server │
│ .runtime ──────────┐ │ │ │ │
│ ▼ │ │ ▼ │
│ RemoteRuntime │ ② HTTP POST │ LocalRuntime │
│ .run_in_session(act) ─┼──/run_in_session▶│ .run_in_session(act) │
│ │ (+ 鉴权/重试) │ │ │
│ │ │ ▼ │
│ obs ◀─────────────────┼── ③ JSON 回包 ──┼─ BashSession (pexpect shell) │
│ (output, exit_code) │ │ 真·bash 进程,REPL 控制 │
└─────────────────────────┘ └──────────────────────────────┘

怎么读这张图:

  • Deployment 的活儿:把 swerex-remote(就是 server.pymain())跑在目标机器上,它是个 FastAPI HTTP 服务。
  • RemoteRuntimeLocalRuntime 实现同一个接口。前者把每个方法变成一次 HTTP POST,后者真的执行。server.py 里的路由几乎是接口方法的一一镜像(/run_in_sessionruntime.run_in_session,见 src/swerex/server.py:137-139)。
  • 真正干活的是远端的 BashSession——它用 pexpect 包着一个真实 bash 进程,负责把输出和退出码抠干净。

两条退化路径(理解了上面这条,这两条就是它的特例):

  • 本地直跑 LocalDeployment:不起容器、不走 HTTP,直接 new LocalRuntime()(src/swerex/deployment/local.py:54-56)。图里的远端框塌缩进本地。
  • 连已有服务 RemoteDeployment:容器你自己起,它只 new RemoteRuntime() 连过去(src/swerex/deployment/remote.py:59-68)。图里去掉 ① 这一步。

部件一句话职责

部件干什么在哪个文件
AbstractRuntime定义执行接口:create_session / run_in_session / execute / read_file / upload … 共 8 个抽象方法runtime/abstract.py:234
LocalRuntime真正执行:管理会话字典、跑 subprocess、读写文件runtime/local.py:365
BashSession一个长活 bash REPL 的封装(pexpect),输出/退出码解析都在这runtime/local.py:129
RemoteRuntime把接口的每个方法变成一次带鉴权 + 重试 + 幂等键的 HTTP 调用runtime/remote.py:44
server.pyFastAPI 服务,把 HTTP 请求转发给一个进程内的 LocalRuntimeserver.py:31-32
AbstractDeployment定义部署接口:start / stop / is_alive / runtime 属性deployment/abstract.py:11
DockerDeployment各后端:把 server 跑起来,再建 RemoteRuntime 连回去deployment/docker.py
*Config(pydantic)每个 runtime/deployment 都有一个配置类,带 type 判别字段,可序列化 / 走 CLIruntime/config.pydeployment/config.py

3. 阅读地图(建议顺序)

这是一个"两层抽象 + 一个硬核解析内核"的项目。按下面顺序读最顺:

  1. 01-runtime-and-sessions.md —— 先看"执行"这一层:8 个动作的接口、LocalRuntime 怎么管多个会话、BashSession 的生命周期。这是骨架。
  2. 02-bash-output-parsing.md —— 全项目工程含量最高的一章:在一个裸 bash 里,怎么知道"命令跑完了"、怎么抠退出码、怎么处理 ipython 这种不退出的程序。PS1 哨兵 + bashlex 切分 + EXITCODE 探针,坑很多。
  3. 03-deployment-backends.md —— 再看"在哪跑"这一层:RemoteRuntime 的 HTTP 协议、Docker/Modal 怎么把 server 拉起来、各后端的取舍。

如果你只想抓精华:读完 §2 这张图,直接跳 02——那一章是这个项目真正难的地方,也是最值得借鉴的地方。

4. 边界与局限(诚实)

  • 会话只有 bash 一种。 接口用 session_type 判别字段为将来留了口子(runtime/abstract.py:30:43),但目前 CreateSessionRequest 这个 union 里只有 CreateBashSessionRequest 一个成员(runtime/abstract.py:35),LocalRuntime.create_session 也只认它(runtime/local.py:395-399)。
  • 幂等不保证并发。 server 的请求去重只记"上一个"请求(ResponseManager 只存 last_processed_request_id 一个,server.py:50-61),代码注释自陈:多客户端并发时不保证幂等(server.py:47)。
  • 输出解析是"务实但脆"的。 见 02 章——靠在 shell 里 echo 哨兵字符串来定位边界,作者自己在注释里多次写 "somewhat brittle" / "bashlex is very buggy"(runtime/local.py:295:299-301)。
  • 退出码提取有前提。 命令必须能跑到"回到 PS1",才能 echo 出退出码;交互式命令(is_interactive_command)干脆放弃取退出码(runtime/local.py:245-247)。

5. 横向对比(同 shelf 兄弟)

SWE-ReX 和它的兄弟项目同属 SWE-agent 生态,分工清晰:

项目管什么与 SWE-ReX 的关系
SWE-agentagent 的"大脑":prompt、动作循环、工具定义用 SWE-ReX 作"手脚"去真实环境跑命令
SWE-bench评测基准:一堆真实 GitHub issue + 测试SWE-ReX 让 agent 能在每个 issue 的容器里并行跑
mini-SWE-agent极简版 agent同样需要某种执行底座

取舍上的特点: 别的"沙箱执行"方案(如直接用 Jupyter kernel、或 LangChain 的 shell tool)往往把执行和环境绑死。SWE-ReX 的独到之处是把"输出解析内核"(BashSession)做成与"部署位置"完全解耦,且专门为大规模并行 + 非 Linux/无 Docker 机器优化(README.md 明说支持"不带 Docker 的非 Linux 机器",靠 Modal/Fargate 后端)。

6. 代码地图(导航索引)

主题文件路径关键符号
执行接口(8 动作)src/swerex/runtime/abstract.pyAbstractRuntime
请求/响应数据模型src/swerex/runtime/abstract.pyBashActionBashObservationCommandCreateBashSessionRequest
本地执行实现src/swerex/runtime/local.pyLocalRuntime
bash REPL 封装src/swerex/runtime/local.pyBashSessionBashSession.run_run_normal
命令切分 / 语法检查src/swerex/runtime/local.py_split_bash_command_check_bash_command
HTTP 客户端运行时src/swerex/runtime/remote.pyRemoteRuntimeRemoteRuntime._request
服务端src/swerex/server.pyappResponseManagermain
部署接口src/swerex/deployment/abstract.pyAbstractDeployment
Docker 后端src/swerex/deployment/docker.pyDockerDeploymentDockerDeployment.start
Modal 后端src/swerex/deployment/modal.pyModalDeployment_ImageBuilder
配置与判别 unionsrc/swerex/deployment/config.pyDeploymentConfigget_deployment
异常体系src/swerex/exceptions.pySwerexExceptionNonZeroExitCodeErrorBashIncorrectSyntaxError