5.2 KiB
Orchestrator and Executor Split
Goal
Codex should be able to route tool calls through a remote executor without teaching every tool implementation about remote filesystems, RPC retries, or model-specific defaults.
For unified exec, the main rule should be:
- the executor advertises the
exec_commandandwrite_stdintool contracts - the orchestrator forwards tool calls plus any orchestrator-owned overrides
- the executor owns host-local execution details, session ids, and process lifecycle
- the executor does not invent model-dependent policy that only the orchestrator can know
Current Ownership Leak
Today the unified exec path is split across several layers:
core/src/tools/handlers/unified_exec.rsparses model args and normalizes approval-related fieldscore/src/unified_exec/process_manager.rsstill derives execution env, approval requirements, and other execution inputs fromTurnContextcore/src/tools/runtimes/unified_exec.rsstill rewrites commands for shell snapshot and PowerShell handlingcore/src/tools/spec.rsstill statically advertises unified exec from the orchestrator side instead of learning it from the executor
That makes the executor boundary fuzzy. A future remote executor would need to know more than "handle this tool call" because some request fields are still hidden behind local helpers and the tool itself is not yet executor-owned.
Ownership Rule
The orchestrator owns:
- model sampling
- tool routing and dynamic tool registration
- model-facing tool-call parsing
- shell selection and final argv construction
- working-directory resolution
- execution environment construction
- approval-policy evaluation
- output-policy selection, including max output truncation for the tool result
The executor owns:
- advertising host-local tool specs such as unified exec
- spawning and tracking the process
- streaming and buffering raw output
- minting unified-exec session ids
- applying the already selected sandbox attempt
- returning the requested output slice for the current poll
- filesystem and host-local resource access that should happen on the executor host
Unified Exec Wire Contract
The unified exec boundary should be expressed as an executor-advertised tool contract plus explicit orchestrator overrides that can be serialized and sent over a future executor transport.
UnifiedExecExecCommandRequest
command: final argv vectorcwd: absolute working directoryenv: fully constructed environment for the commandtty: whether to allocate a PTYyield_time_ms: initial poll windowmax_output_tokens: tool-output truncation budget selected by the orchestratorsandbox_permissions: already normalized sandbox requestadditional_permissions: already normalized additional permissionsadditional_permissions_preapproved: whether those permissions are already sticky-approvedjustification: optional user-facing approval reasonexec_approval_requirement: resolved approval plan for this request
The executor's exec_command tool result should include:
process_id: stable unified-exec session id chosen by the executor when the session stays open after the initial poll- no
process_idfor short-lived commands that exit before the initial poll completes
UnifiedExecWriteStdinRequest
process_id: existing unified-exec session idinput: bytes to write, expressed as text for the tool pathyield_time_ms: poll window for this write or empty pollmax_output_tokens: tool-output truncation budget selected by the orchestrator
These are intentionally higher level than codex-exec-server's low-level
process/start, process/read, and process/write methods. Unified exec is a
tool contract owned by the executor; the executor transport can map that
contract onto lower-level process RPCs internally.
Phase Plan
- Let the executor advertise unified exec tool specs to the orchestrator,
similar to MCP
listTools. - Define protocol-owned unified-exec request types for executor-owned tool calls plus orchestrator-owned overrides.
- Make the executor side mint unified-exec session ids and return them in tool results.
- Move the host-local unified-exec implementation behind the executor boundary.
- Reuse the same request types for a remote executor transport.
Crate Split
codex-executor-protocol: shared executor transport types with minimal dependenciescodex-executor: executor-side receive loop and future transport adaptercodex-cli:codex executorsubcommand that launches the executor process
Follow-ups
This work does not fully remove every host-local concern yet. In particular:
- remote network-proxy binding is still local to the executor path
- zsh-fork backend setup remains local because it depends on executor-host binaries and inherited file descriptors
- shell snapshot capture and executor bootstrapping still need a broader context-gathering protocol
- unified exec is still statically registered by the orchestrator today rather than advertised by the executor
Those should be follow-up steps, but they should build on the same principle: the orchestrator chooses only the policy and routing inputs that are truly orchestrator-owned, while the executor owns host-local tool behavior.