跳到主要内容

命令执行与代码执行

本章讲沙箱里「让东西跑起来」的三条路:两套命令执行(Shell / Bash)和一套统一代码执行(Code → Jupyter/Node)。重点是为什么命令执行要故意分两套

1. 核心设计:命令执行分两套

沙箱提供两个看起来都「跑命令」的子系统,但底层模型完全不同(website/docs/en/guide/basic/bash.md「Shell vs Bash」表):

/v1/shell(Shell)/v1/bash(Bash Pipe)
后端终端 / PTY(伪终端)subprocess 管道
适合交互式终端、WebSocket UI、人接管agent 工具调用、短命令、长命令轮询
输出一个合并的 output 字段stdoutstderr 分离
读取模型终端快照(wait + view)偏移增量读(offset / stderr_offset)
stdin终端输入流写进运行中进程的 stdin 管道
命令标识session 级每条命令一个 command_id

为什么要分两套? 因为「人用终端」和「机器用工具」是两种根本不同的读写需求:

  • 人 / REPL 需要 PTY。 vim、top、python REPL 这类交互程序依赖真实终端语义(光标控制、行编辑、TTY 检测)。PTY 的代价是 stdout/stderr 混在一个终端缓冲里,只能拿「快照」。
  • agent 需要结构化、可增量。 一个 agent 工具调用想要:stdout 和 stderr 分开(好分别判断结果与错误)、长命令能边跑边按偏移读新增输出(不必等结束)、每条命令有独立 id。这正是管道模型擅长的。

Shell 文档把这条原则讲得很直白(website/docs/en/guide/basic/shell.md:3-5):

AIO Sandbox Shell is PTY-based, so it is best for REPLs, interactive programs ... For programmatic command execution, separated stdout/stderr, or offset-based incremental log reads, prefer Bash Pipe.

一句话选型: 给人看的终端 / 跑交互程序 → Shell;给 agent 当工具跑命令 → Bash。

2. Shell:PTY 终端

Shell 子系统(sdk/python/agent_sandbox/shell/raw_client.py,符号 RawShellClient)的端点反映了「终端」的心智:

端点干什么
v1/shell/exec跑命令(省略 id 则自动开一个 session)
v1/shell/view取终端当前快照
v1/shell/wait等进程到某状态
v1/shell/write往终端写输入(模拟敲键)
v1/shell/kill杀进程
v1/shell/sessions/create·update·stats·sessions(列/删)管理终端会话
v1/shell/terminal-url拿 WebTerminal 的 WebSocket 地址

session 复用是关键。 每次 exec 返回一个 session_id;把它作为 id 传回去,就复用同一个终端会话——工作目录和环境变量在会话内保留(website/docs/en/guide/basic/shell.md:53-74):

# 第一次:进 /tmp 并设环境变量,拿到 session_id
SESSION_ID=$(curl -s -X POST localhost:8080/v1/shell/exec \
-d '{"command":"cd /tmp && export DEMO_FLAG=hello"}' | jq -r ".data.session_id")
# 第二次:复用同一会话,pwd 仍是 /tmp,$DEMO_FLAG 仍在
curl -X POST localhost:8080/v1/shell/exec \
-d '{"id":"'"$SESSION_ID"'","command":"pwd && echo $DEMO_FLAG"}'

Shell 还暴露 REST + WebSocket 两种接口:少量终端操作用 REST,内置终端 / 自定义 WebTerminal UI 用 WebSocket(website/docs/en/guide/basic/shell.md:3)。

3. Bash Pipe:给 agent 的管道执行

Bash 子系统(sdk/python/agent_sandbox/bash/raw_client.py)端点更精简,围绕「一条命令的生命周期」:

端点干什么
v1/bash/exec跑命令;多数工具调用一次就够
v1/bash/output按偏移增量读 stdout/stderr
v1/bash/write往进程 stdin 写
v1/bash/kill杀命令
v1/bash/sessions·sessions/create·sessions/{id}/close会话管理

两个超时是巧妙处。 exec 同时接受 timeouthard_timeout(website/docs/en/guide/basic/bash.md:27-34):

curl -X POST localhost:8080/v1/bash/exec -d '{
"command": "pwd && ls -la",
"exec_dir": "/home/gem",
"timeout": 30, # 软超时:到点就先返回(命令可能还在后台跑)
"hard_timeout": 120 # 硬超时:到点强杀
}'

这让 agent 既能快速拿到阶段性结果(软超时返回 + 后续 /output 轮询),又有兜底强杀(硬超时)防止僵死命令占资源。响应里 status 是命令生命周期状态、exit_code 才是成败指标、offset/stderr_offset 是下次增量读的游标(website/docs/en/guide/basic/bash.md:38-52)。

重要区分: 文档明确「请求成功 ≠ 代码/命令执行成功」——HTTP 200 只代表服务受理了,真正成败要看返回里的 statusexit_code(website/docs/en/guide/basic/code.md「Error Handling」、website/docs/en/guide/basic/bash.md:256;exit_code 作为成功指标的说明见 website/docs/en/guide/basic/bash.md:18)。这是写 agent 工具时最容易踩的坑。

4. 统一代码执行:一个入口,路由到对的内核

除了跑 shell 命令,沙箱还能直接跑代码/v1/code/execute 是一个统一入口,按 language 路由到对应运行时(website/docs/en/guide/basic/code.md):

LanguageRuntime
PythonJupyter kernel
JavaScriptNode.js
curl -X POST localhost:8080/v1/code/execute \
-d '{"language":"python","code":"print(sum([1,2,3]))"}'

需要更细的内核控制时,绕过统一入口,直接用专用子系统:

  • Jupyter(v1/jupyter/execute):支持 session_id 跨请求保持内核状态(变量留存)、kernel_namecwdtimeout(sdk/python/agent_sandbox/jupyter/raw_client.py:28-62,符号 execute_code)。
  • Node.js(v1/nodejs/*):有 sessions 系列做会话级管理(sdk/python/agent_sandbox/nodejs/raw_client.py)。

Jupyter 的 session 持久化是这套设计里最实用的一点——agent 可以像在一个 notebook 里那样,先一格定义变量,下一格继续用(sdk/python/agent_sandbox/jupyter/raw_client.py:42-44):

# 示意,基于 jupyter execute_code 的 session_id 语义
client.jupyter.execute_code(code="x = load_big_dataframe()", session_id="s1")
client.jupyter.execute_code(code="print(x.shape)", session_id="s1") # x 还在

5. 默认工作目录:都从 WORKSPACE 出发

Shell、Bash、Jupyter、Node、code-server 的默认工作目录都是 $WORKSPACE,且都能按请求覆盖(website/docs/en/guide/advanced/workspace.md「Service Behavior」表):

服务默认目录单请求覆盖
Shell / Bash$WORKSPACEexec_dir
Jupyter / Node.js$WORKSPACE请求选项(cwd 等)
code-server$WORKSPACE打开的文件夹

这就是第 0 章「共享文件系统」在执行层的落地:只要大家默认都在 $WORKSPACE 里,产物天然互通——把项目挂到 workspace、把生成物写到 workspace,browser/shell/file/code 都能读到。

6. 代码地图

主题文件路径符号名
Shell(PTY)端点sdk/python/agent_sandbox/shell/raw_client.pyRawShellClient(exec_command,view,wait_for_process,create_session,get_terminal_url)
Bash(管道)端点sdk/python/agent_sandbox/bash/raw_client.pyv1/bash/exec,v1/bash/output
Shell vs Bash 选型website/docs/en/guide/basic/bash.mdwebsite/docs/en/guide/basic/shell.md对比表
统一代码执行website/docs/en/guide/basic/code.md/v1/code/execute
Jupyter session 持久化sdk/python/agent_sandbox/jupyter/raw_client.py:28-62execute_code(session_id,kernel_name)
Node.js 会话sdk/python/agent_sandbox/nodejs/raw_client.pyv1/nodejs/sessions/*
默认工作目录website/docs/en/guide/advanced/workspace.mdWORKSPACE,exec_dir