Legacy prototype API: this document describes the currently implemented
seed-runnermount/session CLI. It remains useful as compatibility reference, but the target product contract is Remote Runner's machine/session API indocs/reference/REMOTE_RUNNER_API.md.
seed-runner 是 SEEDRunner 项目的核心工具,为 Agent 提供与远程 VM 交互的统一接口。
设计原则:
- 隐藏 SSH、tmux、sshfs 的复杂性
- 提供最小化的命令集
- 挂载管理和 session 管理分离
- 所有输出自动写入共享文件夹,Agent 通过本地文件读取
- Agent 使用相对路径操作,对远程文件系统无感知
- 共享目录只承担同步职责,Agent 面向的是远端本地磁盘工作目录
Agent (本地)
↓ (调用 seed-runner CLI)
seed-runner (本地工具)
├─ 挂载管理层
│ ├─ 管理 sshfs 挂载
│ └─ 维护挂载元数据
├─ Session 管理层
│ ├─ 管理 tmux session
│ ├─ 处理 SSH 命令转义
│ └─ 写入日志文件
└─ 日志管理层
└─ 按 session name 组织日志
├─ 同步层
│ └─ 将共享目录同步到远端本地工作目录
↓ (SSH + tmux)
远程 VM
├─ hidden sync dir
│ └─ 通过 sshfs 挂载本地共享目录(不直接暴露给 Agent)
├─ remote exec dir
│ ├─ 位于远端本地磁盘
│ ├─ docker-compose / ELF / sudo 等在这里运行
│ └─ Agent 看到的 remote_work_dir 指向这里
└─ 执行命令并生成输出
↓ (日志与产物同步回本地共享目录)
本地共享目录(--local-dir,即 sshfs 挂载根)
├─ artifacts/ # 保留目录:命令日志、与远端同步的产物等(由 seed-runner 与同步逻辑写入)
│ ├─ logs/
│ │ ├─ exp-web-01/
│ │ │ ├─ cmd_001.log
│ │ │ ├─ cmd_002.log
│ │ │ └─ ...
│ │ └─ exp-crypto-02/
│ │ ├─ cmd_001.log
│ │ └─ ...
│ ├─ code/ # 示例:同步产物(具体布局依实验而定)
│ ├─ results/
│ └─ ...
├─ metadata.json
└─ (其余文件与目录由 Agent 自由创建,例如手册、Labsetup、脚本等)
命令:
seed-runner status用途:
- 当用户忘记
mount_id或session_id时,先通过该命令恢复全局上下文 - 返回本地持久化状态中的当前 mount / session 概览,不要求指定单个 ID
- 已销毁的 session 和已卸载的 mount 不会继续保留在全局状态中
返回值(JSON):
{
"mounts": [
{
"mount_id": "mnt_20260407_001",
"machine": "vm-seed-01",
"local_path": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace",
"remote_path": "/home/user/seed-experiment",
"status": "mounted",
"mounted_at": "2026-04-07T10:30:00Z",
"session_count": 1
}
],
"sessions": [
{
"session_id": "sess_20260407_001",
"session_name": "exp-web-01",
"machine": "vm-seed-01",
"mount_id": "mnt_20260407_001",
"status": "active",
"busy": false,
"command_count": 2,
"created_at": "2026-04-07T10:30:00Z"
}
],
"summary": {
"mount_count": 1,
"session_count": 1
}
}命令:
seed-runner mount create \
--machine <machine-id> \
--local-dir <local-mount-point> \
[--remote-dir <remote-experiment-dir>] \
[--timeout <seconds>]参数:
--machine(必需) — 目标机器的标识符,对应 SSH 配置中的 host--local-dir(必需) — 本地 sshfs 挂载根目录(绝对或相对路径均可)。该目录整棵子树会出现在远端隐藏 sync 目录的根下。唯一保留名称为artifacts/:命令日志写入<local-dir>/artifacts/logs/<session-name>/,经同步进入该目录的产物也由工具与实验输出共同填充。挂载根下除artifacts/与metadata.json外,其余路径由 Agent 自由支配。--remote-dir(可选) — 远程 VM 中的实际工作目录路径,默认~/seed-experiment- 如果默认路径与另一项仍在运行的实验冲突,可以显式指定唯一目录,例如
/home/seed/seed-experiments/ARP_lab --timeout(可选) — 挂载操作的超时时间,单位秒,默认 30
返回值(JSON):
{
"mount_id": "mnt_20260407_001",
"machine": "vm-seed-01",
"local_path": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace",
"remote_path": "/home/user/seed-experiment",
"status": "mounted",
"mounted_at": "2026-04-07T10:30:00Z"
}行为:
- 验证 SSH 连接到目标机器
- 在远程 VM 中准备一个隐藏的 sync 目录,并通过 sshfs 将
--local-dir解析后的目录作为挂载源挂到该 sync 目录(挂载根即用户指定的本地目录,而不是其父目录) - 在远程 VM 中准备实际执行用的本地工作目录(即
--remote-dir) - 后续 session 将把共享目录内容同步到该工作目录,再在该目录中执行命令
- 返回 mount_id 供后续使用
错误处理:
- SSH 连接失败 → 返回错误信息
- 远程目录创建失败 → 返回错误信息
- sshfs 挂载失败 → 返回错误信息,清理已创建的目录
命令:
seed-runner mount status --mount-id <mount-id>参数:
--mount-id(必需) — mount_id
返回值(JSON):
{
"mount_id": "mnt_20260407_001",
"machine": "vm-seed-01",
"local_path": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace",
"remote_path": "/home/user/seed-experiment",
"status": "mounted",
"mounted_at": "2026-04-07T10:30:00Z",
"session_count": 3
}状态值:
mounted— 挂载正常error— 挂载出现错误
命令:
seed-runner mount destroy --mount-id <mount-id> [--cleanup]参数:
--mount-id(必需) — mount_id--cleanup(可选) — 是否清理远程 VM 中的实验目录,默认 false
返回值(JSON):
{
"mount_id": "mnt_20260407_001",
"status": "unmounted",
"unmounted_at": "2026-04-07T10:31:00Z",
"artifacts_preserved": true,
"artifacts_location": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace"
}行为:
- 卸载 sshfs 挂载
- 保留本地日志和产物(便于事后审计)
- 如果指定
--cleanup,删除远程 VM 中的实验目录 - 从全局持久化状态中移除该 mount;后续
seed-runner status不再列出它
错误处理:
- Mount 不存在 → 返回错误
- 卸载失败 → 返回警告,但继续清理
命令:
seed-runner session create \
--machine <machine-id> \
--mount-id <mount-id> \
--name <session-name> \
[--timeout <seconds>]参数:
--machine(必需) — 目标机器的标识符--mount-id(必需) — 由mount create返回的 mount_id--name(必需) — Session 的可读名称,用于日志分组(如exp-web-01)--timeout(可选) — 整个 session 的超时时间,单位秒,默认 3600
返回值(JSON):
{
"session_id": "sess_20260407_001",
"session_name": "exp-web-01",
"machine": "vm-seed-01",
"mount_id": "mnt_20260407_001",
"local_mount_point": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace",
"remote_work_dir": "/home/user/seed-experiment",
"status": "ready",
"tmux_session": "seed_sess_20260407_001",
"created_at": "2026-04-07T10:30:00Z"
}行为:
- 验证 mount 存在且状态为 mounted
- 在远程 VM 中创建 tmux session
- 在 tmux session 中自动执行
cd <remote_work_dir>(设置初始工作目录) - 在本地创建日志目录
<local_mount_point>/artifacts/logs/<session-name>/ - 返回 session_id 供后续命令使用
关键设计:
- Session 创建时自动进入远端本地磁盘工作目录,而不是 sshfs 挂载目录
- Agent 之后使用相对路径操作,对同步与 staging 机制无感知
- 日志按 session name 分组,便于查找和审计
错误处理:
- Mount 不存在 → 返回错误
- Mount 状态异常 → 返回错误
- tmux 创建失败 → 返回错误
命令:
seed-runner session exec \
--session <session-id> \
--cmd "<shell-command>" \
[--timeout <seconds>]参数:
--session(必需) — 由session create返回的 session_id--cmd(必需) — 要执行的 shell 命令(原始字符串,不需要转义)--timeout(可选) — 单条命令的超时时间,单位秒,默认 300
返回值(JSON):
{
"session_id": "sess_20260407_001",
"session_name": "exp-web-01",
"command": "cd code && make",
"exit_code": 0,
"log_file_local": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace/artifacts/logs/exp-web-01/cmd_002.log",
"log_file_remote": "/home/user/seed-experiment/logs/exp-web-01/cmd_002.log",
"log_filename": "cmd_002.log",
"executed_at": "2026-04-07T10:30:15Z",
"duration_ms": 2345
}行为:
- 验证 session 存在且状态为 active
- 在命令执行前,将共享目录中的输入同步到
remote_work_dir - 在 tmux session 中于
remote_work_dir执行命令(整体执行,不拆分) - 捕获 stdout、stderr、exit code
- 将输出写入
<remote_work_dir>/logs/<session-name>/<log-filename> - 将日志与产物同步回本地共享目录
- 返回执行结果(包含本地和远程路径)
工作目录语义:
remote_work_dir是远端本地磁盘目录,是 Agent 应该操作的唯一路径- 底层 sshfs 挂载目录属于内部实现细节,不直接暴露给 Agent
- Docker bind mount、本地 ELF、sudo 读脚本等都应以
remote_work_dir为准
日志文件命名:
- 自动递增:
cmd_001.log,cmd_002.log, ... - 不需要 Agent 指定
- 便于按执行顺序排序
日志文件格式:
[2026-04-07T10:30:15Z] $ cd code && make
[2026-04-07T10:30:15Z] Entering directory '/home/user/seed-experiment/code'
[2026-04-07T10:30:17Z] gcc -o test test.c
[2026-04-07T10:30:18Z] $ exit_code: 0
字符转义处理:
seed-runner session exec内部处理所有 shell 转义- Agent 传入原始命令字符串,无需手动转义
- 支持的特殊字符:
$,",',\,|,&,;,>,<, 等
错误处理:
- Session 不存在 → 返回错误
- Session 已销毁 → 返回错误
- 同一个 session 上存在未结束的命令 → 返回 busy 错误,不接受新的并发 exec
- 命令执行超时 → 返回超时错误;如果远端命令仍在运行,session 保持 busy,直到该命令真正结束
- 命令执行失败(exit code != 0) → 返回失败状态,session 保持 active
命令:
seed-runner session status --session <session-id>参数:
--session(必需) — session_id
返回值(JSON):
{
"session_id": "sess_20260407_001",
"session_name": "exp-web-01",
"status": "active",
"machine": "vm-seed-01",
"mount_id": "mnt_20260407_001",
"local_mount_point": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace",
"remote_work_dir": "/home/user/seed-experiment",
"created_at": "2026-04-07T10:30:00Z",
"last_command": "cd code && make",
"last_exit_code": 0,
"last_executed_at": "2026-04-07T10:30:18Z",
"command_count": 5,
"elapsed_seconds": 45,
"timeout_seconds": 3600
}状态值:
active— session 正常运行busy— session 中已有命令正在运行,暂不接受新的 exectimeout— session 已超时error— session 出现错误
错误处理:
- Session 不存在 → 返回错误
命令:
seed-runner session destroy --session <session-id>参数:
--session(必需) — session_id
返回值(JSON):
{
"session_id": "sess_20260407_001",
"session_name": "exp-web-01",
"status": "destroyed",
"destroyed_at": "2026-04-07T10:31:00Z",
"logs_preserved": true,
"logs_location": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace/artifacts/logs/exp-web-01"
}行为:
- 销毁远程 tmux session
- 保留本地日志和产物(便于事后审计)
- 不卸载 mount(mount 由 Agent 显式销毁)
- 从全局持久化状态中移除该 session;后续
seed-runner status不再列出它
错误处理:
- Session 不存在 → 返回错误
<local-mount-point>/
├─ artifacts/ # 保留目录
│ ├─ logs/ # 所有 session 的命令日志
│ │ ├─ exp-web-01/
│ │ │ ├─ cmd_001.log
│ │ │ ├─ cmd_002.log
│ │ │ └─ ...
│ │ ├─ exp-crypto-02/
│ │ │ ├─ cmd_001.log
│ │ │ └─ ...
│ │ └─ ...
│ ├─ code/ # 示例:同步产物(布局依实验而定)
│ ├─ results/
│ └─ ...
├─ metadata.json # 挂载元数据(位于挂载根)
└─ (其余由 Agent 自行组织,例如 Labsetup、脚本、手册等)
若挂载根目录本身命名为 artifacts(例如 runs/<实验名>/artifacts),则命令日志的完整路径为 <挂载根>/artifacts/logs/...,路径中会出现连续两段 artifacts,表示「挂载根下的保留子目录 artifacts」,并非笔误。若希望路径更直观,可将挂载根命名为 workspace 等任意非 artifacts 的目录名(见上文 JSON 示例中的 workspace)。
<remote-work-dir>/
├─ logs/ # 执行期日志(与本地 artifacts/logs 对应并同步)
│ ├─ exp-web-01/
│ │ ├─ cmd_001.log
│ │ ├─ cmd_002.log
│ │ └─ ...
│ └─ ...
└─ artifacts/ # 实验产物
├─ code/
├─ results/
└─ ...
{
"mount_id": "mnt_20260407_001",
"machine": "vm-seed-01",
"local_path": "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace",
"remote_path": "/home/user/seed-experiment",
"mounted_at": "2026-04-07T10:30:00Z",
"sessions": [
{
"session_id": "sess_20260407_001",
"session_name": "exp-web-01",
"created_at": "2026-04-07T10:30:00Z",
"commands": [
{
"index": 1,
"cmd": "cd code && make",
"log_file": "cmd_001.log",
"exit_code": 0,
"executed_at": "2026-04-07T10:30:15Z"
},
...
]
},
...
]
}# 1. 创建挂载
$ seed-runner mount create \
--machine vm-seed-01 \
--local-dir /Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace
# 返回:mount_id = "mnt_20260407_001"
# 2. 创建 session(自动进入远程工作目录)
$ seed-runner session create \
--machine vm-seed-01 \
--mount-id mnt_20260407_001 \
--name exp-web-01
# 返回:session_id = "sess_20260407_001"
# 3. 执行命令 1(使用相对路径)
$ seed-runner session exec \
--session sess_20260407_001 \
--cmd "ls -la"
# 返回:
# log_file_local = "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace/artifacts/logs/exp-web-01/cmd_001.log"
# exit_code = 0
# 4. 执行命令 2
$ seed-runner session exec \
--session sess_20260407_001 \
--cmd "cd code && make"
# 返回:
# log_file_local = "/Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace/artifacts/logs/exp-web-01/cmd_002.log"
# exit_code = 0
# 5. 查询状态
$ seed-runner session status --session sess_20260407_001
# 返回:status = "active", command_count = 2
# 6. 销毁 session
$ seed-runner session destroy --session sess_20260407_001
# 返回:status = "destroyed"
# 6.1 再次查询该 session 会得到 not found
# $ seed-runner session status --session sess_20260407_001
# 7. 销毁挂载
$ seed-runner mount destroy --mount-id mnt_20260407_001
# 返回:status = "unmounted"
# 7.1 全局状态中也不会再列出这两个资源
# $ seed-runner status
# 返回:mount_count = 0, session_count = 0# 1. 创建挂载(一次)
$ mount_id=$(seed-runner mount create \
--machine vm-seed-01 \
--local-dir ./workspace | jq -r '.mount_id')
# 2. 创建 session 1
$ sess1=$(seed-runner session create \
--machine vm-seed-01 \
--mount-id $mount_id \
--name exp-web-01 | jq -r '.session_id')
# 3. 创建 session 2(共享同一个挂载)
$ sess2=$(seed-runner session create \
--machine vm-seed-01 \
--mount-id $mount_id \
--name exp-crypto-02 | jq -r '.session_id')
# 4. 在 session 1 中执行命令
$ seed-runner session exec --session $sess1 --cmd "make"
# 日志写到:./workspace/artifacts/logs/exp-web-01/cmd_001.log
# 5. 在 session 2 中执行命令
$ seed-runner session exec --session $sess2 --cmd "make"
# 日志写到:./workspace/artifacts/logs/exp-crypto-02/cmd_001.log
# 6. 销毁 session(挂载保留)
$ seed-runner session destroy --session $sess1
$ seed-runner session destroy --session $sess2
# 7. 销毁挂载
$ seed-runner mount destroy --mount-id $mount_id# 执行一条会失败的命令
$ seed-runner session exec \
--session sess_20260407_001 \
--cmd "cd /nonexistent && ls"
# 返回:exit_code = 1(失败,但 session 保持 active)
# Agent 可以读取日志,理解失败原因
$ cat /Users/ely/workspace/research/agent/SEEDRunner/runs/exp-web-01/workspace/artifacts/logs/exp-web-01/cmd_003.log
# 输出:bash: cd: /nonexistent: No such file or directory
# Agent 可以继续执行其他命令
$ seed-runner session exec \
--session sess_20260407_001 \
--cmd "pwd"
# 返回:exit_code = 0(继续执行)| 错误代码 | 含义 | 处理建议 |
|---|---|---|
| 2001 | SSH 连接失败 | 检查网络、SSH 配置、目标机器是否在线 |
| 2002 | sshfs 挂载失败 | 检查 sshfs 是否安装、权限是否正确 |
| 2003 | tmux 创建失败 | 检查远程 VM 中 tmux 是否安装 |
| 2004 | Mount 不存在 | 检查 mount_id 是否正确 |
| 2005 | Session 不存在 | 检查 session_id 是否正确 |
| 2006 | Session 已销毁 | 创建新的 session |
| 2007 | 命令执行超时 | 增加 --timeout 参数或优化命令 |
| 2008 | 日志写入失败 | 检查本地磁盘空间、权限 |
| 2009 | 挂载卸载失败 | 检查是否有进程占用挂载点 |
| 2010 | 全局状态查询失败 | 检查本地状态目录与权限 |
- 灵活性 — 一个挂载可以被多个 session 共享,减少重复挂载的开销
- 职责分离 — 挂载管理和 session 管理是两个独立的关注点
- 简化错误处理 — 挂载失败不会导致 session 创建失败
- 透明性 — Agent 使用相对路径操作,对远程文件系统无感知
- 简化命令 — 不需要每条命令都
cd到工作目录 - 一致性 — 所有 session 的初始状态相同
- 一致性 — 所有日志都按递增序列命名,便于排序
- 可追踪性 — 日志文件名直接反映执行顺序
- 简化 Agent — Agent 不需要关心日志文件名
- 可读性 — 日志目录名直接反映 session 的用途
- 可维护性 — 便于查找和审计特定 session 的日志
- 隔离性 — 不同 session 的日志互不干扰
- 符合 shell 语义 —
cd /path && make && ./test作为一个整体执行 - 减少黑盒 — Agent 能清楚地看到每条命令的完整执行过程
- 便于调试 — 失败时能准���定位问题
- 模拟真实终端 — 真实的终端中,命令失败不会关闭终端
- 支持调试 — Agent 可以继续执行其他命令来诊断问题
- 提高容错性 — 允许 Agent 自动重试或调整策略
seed-runner session exec 必须正确处理以下特殊字符:
- Shell 元字符:
$,",',\,|,&,;,>,<,(,),{,},[,] - 空格和制表符
- 换行符(多行命令)
实现方式:使用 shlex.quote() (Python) 或等价的转义函数。
- 使用递增的数字前缀:
cmd_001.log,cmd_002.log, ... - 便于按执行顺序排序
- 时间戳在日志内容中记录
- 单条命令超时:返回超时错误;如果命令仍在运行,session 保持
busy,直到日志中出现最终 exit code - 整个 session 超时:标记 session 状态为
timeout,后续命令返回错误
- 同一个 session 同时只允许一个前台命令执行
seed-runner在命令开始和结束时短暂锁定state.json,避免并发 CLI 进程互相覆盖状态- 如果同一 session 上有命令尚未结束,新的
session exec会直接返回 busy 错误,而不是静默排队
- 如果 SSH 连接中断,
seed-runner session exec应尝试重连(最多 3 次) - 如果 sshfs 挂载断开,自动重新挂载
- 如果无法恢复,返回错误,session 标记为
error
- Session 创建时,在 tmux session 中执行
cd <remote_work_dir> - 后续所有命令都在这个目录下执行
- Agent ��以使用相对路径操作
- 批量执行 — 支持一次性执行多条命令
- 条件执行 — 支持
if exit_code == 0 then ... - 并行执行 — 支持在多个 pane 中并行执行
- 交互式命令 — 支持需要用户输入的命令(如
sudo) - 文件传输 — 支持本地 ↔ 远程的文件传输
- 后台任务 — 支持
--background参数,后台执行并返回 job_id
这些功能可以在后续版本中逐步添加,不影响当前的最小化设计。