docs: add orchestrator/executor design note

This commit is contained in:
Michael Bolin
2026-03-22 10:19:49 -07:00
parent 7754dd1b89
commit 805e5078d5
+133
View File
@@ -0,0 +1,133 @@
# 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_command` and `write_stdin` tool 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.rs` parses model args and normalizes
approval-related fields
- `core/src/unified_exec/process_manager.rs` still derives execution env,
approval requirements, and other execution inputs from `TurnContext`
- `core/src/tools/runtimes/unified_exec.rs` still rewrites commands for shell
snapshot and PowerShell handling
- `core/src/tools/spec.rs` still 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 vector
- `cwd`: absolute working directory
- `env`: fully constructed environment for the command
- `tty`: whether to allocate a PTY
- `yield_time_ms`: initial poll window
- `max_output_tokens`: tool-output truncation budget selected by the
orchestrator
- `sandbox_permissions`: already normalized sandbox request
- `additional_permissions`: already normalized additional permissions
- `additional_permissions_preapproved`: whether those permissions are already
sticky-approved
- `justification`: optional user-facing approval reason
- `exec_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_id` for short-lived commands that exit before the initial poll
completes
`UnifiedExecWriteStdinRequest`
- `process_id`: existing unified-exec session id
- `input`: bytes to write, expressed as text for the tool path
- `yield_time_ms`: poll window for this write or empty poll
- `max_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
1. Let the executor advertise unified exec tool specs to the orchestrator,
similar to MCP `listTools`.
2. Define protocol-owned unified-exec request types for executor-owned tool
calls plus orchestrator-owned overrides.
3. Make the executor side mint unified-exec session ids and return them in tool
results.
4. Move the host-local unified-exec implementation behind the executor boundary.
5. Reuse the same request types for a remote executor transport.
## Crate Split
- `codex-executor-protocol`: shared executor transport types with minimal
dependencies
- `codex-executor`: executor-side receive loop and future transport adapter
- `codex-cli`: `codex executor` subcommand 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.