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):
- 环境变量里的 Noise rendezvous 配置 → 默认环境变成
remote,不带 local(:227-250) - 否则读
$CODEX_HOME/environments.toml - 再否则走遗留的
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_id | Manager 里的那一台执行后端 |
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 Orchestrator | exec-server/ + ThreadEnvironments |
| Worktree(E2) | 同一台上哪一份 checkout | 仍是这台的沙箱 | codex-worktree;远程 session 上禁止创建 |
| Cloud(E3) | 闭源 VM 里的整次任务 | 云端;本地只 git apply | 客户端,无 kernel |
| Remote control(E3) | 谁在看这条 Session | 仍是 host 的 run_turn | app-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。