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 exec、boto3,你就再也搬不动了。
给谁用。 写 / 评测 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(运行时) | 命令怎么被执行、输出怎么拿回 | AbstractRuntime | src/swerex/runtime/abstract.py |
| Deployment(部署) | 运行时在哪跑、怎么把它拉起来 | AbstractDeployment | src/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.py的main())跑在目标机器上,它是个 FastAPI HTTP 服务。 - ②
RemoteRuntime和LocalRuntime实现同一个接口。前者把每个方法变成一次 HTTP POST,后者真的执行。server.py里的路由几乎是接口方法的一一镜像(/run_in_session→runtime.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.py | FastAPI 服务,把 HTTP 请求转发给一个进程内的 LocalRuntime | server.py:31-32 |
AbstractDeployment | 定义部署接口:start / stop / is_alive / runtime 属性 | deployment/abstract.py:11 |
DockerDeployment 等 | 各后端:把 server 跑起来,再建 RemoteRuntime 连回去 | deployment/docker.py 等 |
*Config(pydantic) | 每个 runtime/deployment 都有一个配置类,带 type 判别字段,可序列化 / 走 CLI | runtime/config.py、deployment/config.py |
3. 阅读地图(建议顺序)
这是一个"两层抽象 + 一个硬核解析内核"的项目。按下面顺序读最顺:
- 01-runtime-and-sessions.md —— 先看"执行"这一层:8 个动作的接口、
LocalRuntime怎么管多个会话、BashSession的生命周期。这是骨架。 - 02-bash-output-parsing.md —— 全项目工程含量最高的一章:在一个裸 bash 里,怎么知道"命令跑完了"、怎么抠退出码、怎么处理
ipython这种不退出的程序。PS1 哨兵 + bashlex 切分 + EXITCODE 探针,坑很多。 - 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-agent | agent 的"大脑":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.py | AbstractRuntime |
| 请求/响应数据模型 | src/swerex/runtime/abstract.py | BashAction、BashObservation、Command、CreateBashSessionRequest |
| 本地执行实现 | src/swerex/runtime/local.py | LocalRuntime |
| bash REPL 封装 | src/swerex/runtime/local.py | BashSession、BashSession.run、_run_normal |
| 命令切分 / 语法检查 | src/swerex/runtime/local.py | _split_bash_command、_check_bash_command |
| HTTP 客户端运行时 | src/swerex/runtime/remote.py | RemoteRuntime、RemoteRuntime._request |
| 服务端 | src/swerex/server.py | app、ResponseManager、main |
| 部署接口 | src/swerex/deployment/abstract.py | AbstractDeployment |
| Docker 后端 | src/swerex/deployment/docker.py | DockerDeployment、DockerDeployment.start |
| Modal 后端 | src/swerex/deployment/modal.py | ModalDeployment、_ImageBuilder |
| 配置与判别 union | src/swerex/deployment/config.py | DeploymentConfig、get_deployment |
| 异常体系 | src/swerex/exceptions.py | SwerexException、NonZeroExitCodeError、BashIncorrectSyntaxError |