Skip to content

B6|执行环境:cwd 在哪台机器上,审批仍在本机 Orchestrator ​

先别把 environment 当成第二份 worktree ​

读完 B3,下一件错事是把 environment_id 理解成 git checkout,或把 codex exec-server 理解成第二条 run_turn。搜不到云端 kernel,就开始给 exec-server/ 补 Seatbelt 审批循环。

判断先说清楚:Environment 是执行后端,不是工作区副本,也不是 Cloud。 默认 id local 是本机进程和文件系统;远程是另一台机器上已经拉起的 exec-server。Turn 绑的是 TurnEnvironmentSelection:哪一个 id、cwd 的 PathUri、workspace roots、这份附件的配置状态。工具副作用走 ExecutorFileSystem;审批、permission profile、ExecPolicy 仍在 Session 的 Orchestrator。

E2 的 worktree 换的是 同一台机器 上的第二份 checkout。E3 的 Cloud 是闭源 VM,缝是 git diff。Remote control 是手机看本机 Session。这三样都不是 environment。

一句话画成图:能搬家的和不能搬家的,是两条分开的线。

fs 那一格可以搬到另一台机器上;orch 那一格永远钉在本机 Session 里。B3 的三套旋钮判断的是"要不要做",这一章的 environment 只回答"在哪做"——两件事分层,才不会出现"远程机器自己发明一套 Allow"。


1. EnvironmentManager:发现环境,不立刻干活 ​

LOCAL_ENVIRONMENT_ID 是保留字 "local"(exec-server/src/environment.rs:107)。用户 toml 里不许再声明一个叫 local 的环境(:308-311,environment_toml.rs:255-258)。遗留的 Noise 会合用 "remote"(:108)。

发现顺序(EnvironmentManager::prepare_from_codex_home,:178-182):

  1. 环境变量里的 Noise rendezvous 配置 → 默认环境变成 remote,不带 local(:227-250)
  2. 否则读 $CODEX_HOME/environments.toml
  3. 再否则走遗留的 CODEX_EXEC_SERVER_URL

environments.toml(environment_toml.rs:23-33)很瘦:default、include_local(缺省 true,:73)、一组 { id, url | program+args }。stdio 拉起的是远端 exec-server 进程,websocket URL 连的是已经在跑的执行面。构建 snapshot 时 不 等连接成功;远程环境 start_connecting() 在后台(environment.rs:338-341)。

新 thread 的默认列表是 default_environment_ids()(:363-380):默认 id 在前,其余随后。没有默认环境,列表为空——不是悄悄回落到 cwd 字符串。

没有「Manager 只发现、连接懒启动」会怎样?启动 TUI 会去等一台还没开机的 devbox;或者每个 Turn 自己 Command::new 猜执行面,本机和远程的 workspace-write 立刻分叉。


2. Turn 绑的是 selection,配置有所有权 ​

TurnEnvironmentSelection(protocol/src/protocol.rs:154-158):

字段是什么
environment_idManager 里的那一台执行后端
cwd这台后端上的工作目录(PathUri,可带环境身份)
workspace_roots这台后端上的可写/可见根
config这份附件的配置状态

EnvironmentConfigState(protocol/src/environment.rs:17-25)四种:

  • FromThread — 跟 thread 的 fallback profile 走
  • Pending — 所有者稍后才给配置
  • Ready(EnvironmentConfig) — 这份附件自己的政策已经齐
  • Failed(String) — 所有者给不出配置

EnvironmentConfig 一旦 Ready,带着的是 这台后端上的整份执行合同(:58-80):login shell、workspace roots、PermissionProfileSnapshot、shell 环境变量政策、Windows/Linux 沙箱变体、可选 ExecPolicy / MCP policy / 网络政策、capability roots。不是「再传一个 cwd 字符串」。

Session 上的运行时是 ThreadEnvironments(core/src/environment_selection.rs:235-242)。附件边界上要记下配置归谁:EnvironmentConfigOrigin::{Thread, Owner}(:46-49)。Thread 拥有的会跟着 thread settings 刷新;Owner 拥有的不会被 thread 默认值冲掉(:172-190)。

has_full_access 写死了 Full Access 的定义(environment.rs:28-53):AskForApproval::Never,并且 每一个 已选环境都是 PermissionProfile::Disabled。Pending / Failed 永远不是 Full Access。未解析的远程附件,不能靠「thread 已经 yolo」把远端当成裸跑。

连接:StartingTurnEnvironment::wait_until_ready(environment_selection.rs:224-231)。模型侧还有 wait_for_environment(tools/handlers/wait_for_environment.rs:19-22):只等 <environment_context> 里标成 starting 的 id;失败就继续用已经能用的工具,不把整轮打死。

没有「selection + 所有权」会怎样?远程环境还在 Pending,本机 yolo 被理解成远端 DangerFullAccess;或者 cwd 只是本机路径,patch 打到错误的机器。


3. 副作用在远端,决策在本机 ​

Environment::get_filesystem() 返回 ExecutorFileSystem(exec-server/src/environment.rs:1122-1124)。本地走本机 FS;远程是 RemoteFileSystem,RPC 到那台 exec-server。apply_patch、读文件、一部分 MCP 的 cwd,都拿这份 FS,不在 Handler 里 std::fs。

附录 E 的补丁文法可以带 *** Environment ID:,就是为了让 hunk 的路径落在 选中的那台 后端上,而不是默认 local。附录 B 的 unified_exec:远程 environment_id 上的 PTY 仍走同一套 process_state,Orchestrator 仍在本机做审批和沙箱选择。

D3 已经写过:MCP 的 environment_id 默认 "local",可以指到远程 exec-server 上起 stdio 服务器——进程仍不在本机 Seatbelt 里。environment 换的是 spawn 发生在哪,不是给 MCP 补一套 OS 沙箱。

Session 把 permission profile 按当前 selection 物化(session/session.rs:184-245):主环境的 workspace roots 填进 profile,再投影成兼容用的 SandboxPolicy。远端附件可以带自己的 EnvironmentConfig.permission_profile;没有 Ready 配置时回落到 thread 档案。

没有「FS 在远端、政策在 Session」会怎样?远程 exec-server 自己发明 Allow;或本机审批通过后,远端按另一套 workspace-write 落盘。多 OS 执行环境共用一条 turn 的前提,就是决策不随 FS 搬家。


4. exec-server 是 JSON-RPC 执行面,不是第二条循环 ​

codex exec-server 的 crate 头写得很白(exec-server/README.md:3-15):给子进程和文件系统提供 JSON-RPC。生命周期是 initialize → initialized → process/start / 文件 RPC。它 没有 Op、没有 run_turn、没有模型。

传输(同 README):

模式是什么
本机 ws://默认;一条 websocket 一条 JSON-RPC
--remote + Noise向环境注册表登记,再经 rendezvous 加密中继
AWS Direct + SigV4明文 JSON-RPC 走认证 websocket;和 Agent Identity 互斥
forward只转发,不解析 RPC、不执行;目的地拥有 session 和进程

登记可以用 ChatGPT 登录、Agent Identity JWT(CODEX_ACCESS_TOKEN)、或 CODEX_API_KEY bearer。这是 执行面怎么被找到,不是 A1 那套「完整 Codex 产品圈」。API Key 能登记一台 exec-server,仍没有 Apps / 云端任务。

CLI 还有隐藏的 stdio-to-uds 一类继电器。读者只要记住:harness 是 client,exec-server 是执行插座。F3 第一周不要实现 Noise。

没有这层会怎样?每个远程机器复制一份 Session;中断、compact、rollout 和远端进程对不齐。执行面必须比 Turn 循环更笨。


5. 和 worktree / Cloud / Remote 切开 ​

换什么决策在哪本仓库
Environment哪台机器跑命令、改文件Session Orchestratorexec-server/ + ThreadEnvironments
Worktree(E2)同一台上哪一份 checkout仍是这台的沙箱codex-worktree;远程 session 上禁止创建
Cloud(E3)闭源 VM 里的整次任务云端;本地只 git apply客户端,无 kernel
Remote control(E3)谁在看这条 Session仍是 host 的 run_turnapp-server 挂出去

TUI 拒绝在远程 workspace 上建 managed worktree(E2),就是因为 worktree 假设本机 git。environment 可以是远程 FS,不能再 git worktree add 假装隔离。

并行 Goal、D5 worker 若 cwd 相同,抢的是 同一 environment 上的同一棵树。换 environment 不等于换 worktree;两台机器各写各的盘,也解决不了「两条 thread 指向同一 remote cwd」。


6. 结语:带走一句话 ​

执行环境是 Turn 绑住的那台后端:local 是本机 exec-server,远程是另一台机器上的 JSON-RPC 插座——文件和进程可以搬家,审批、permission profile 和 ExecPolicy 不许跟着搬家;它不是 worktree,也不是 Cloud。