--- title: Codex supervision summary: "Architecture and product boundary for supervising native Codex sessions from OpenClaw." read_when: - Designing Codex session discovery, continuation, or archive behavior - Changing the native session catalog UI or Gateway RPCs - Extending Codex supervision across paired nodes --- # Codex supervision ## Goal Codex supervision lets an OpenClaw operator discover native Codex sessions and, when safe, create a local branch through the normal OpenClaw Chat surface. Codex App Server remains the thread and model-loop owner. OpenClaw supplies the fleet catalog, authenticated operator UI, session binding, and channel delivery. The feature belongs to the official `codex` plugin. There is no separate Supervisor plugin or second Codex protocol implementation. ## Product boundary The catalog registers whenever the Codex plugin is active. Enable agent-facing supervision tools with: ```text plugins.entries.codex.config.supervision.enabled = true ``` The active initial product is intentionally smaller than the long-term fleet plan: - List only non-archived Codex threads. - Group local and opted-in paired-node rows by stable host identity. - Create a normal, model-locked Chat branch from a stored or idle Gateway-local thread, start its full Codex harness thread on the first turn, or open the Chat created for an earlier branch. - Archive a stored or idle Gateway-local thread only after explicit no-other-runner confirmation. - Show active local sources without new-branch or archive controls while still allowing an existing supervised Chat to open. - Show the newest rows per host in the main sidebar, keep the full catalog on the sessions page, and provide bounded, cursor-paginated transcript reads for local and paired-node rows. - Isolate catalog failures by host. The catalog is the non-archived collection. A row within it can still have an idle, active, `notLoaded`, or error turn status. Agent-facing supervision remains opt-in. Guided onboarding attempts to install and enable it after native Codex installation detection succeeds and the selected inference backend passes its live check, independently of which primary backend the user selects. Supervision activates only when that opportunistic plugin setup succeeds. An explicit disabled plugin, policy block, or `supervision.enabled: false` remains authoritative for supervision tools, but does not disable the operator session catalog. ## Ownership The `codex` plugin owns all Codex App Server behavior: - endpoint discovery and connection lifecycle - protocol initialization and version checks - thread list, read, resume, archive, and event handling - approval and user-input bridges - native thread bindings to OpenClaw sessions - Codex-only model and harness enforcement after continuation The Control UI and Gateway consume that plugin-owned service. They do not read Codex rollout files directly and do not implement another App Server client. The default local topology is: ```text Codex Desktop -> private stdio App Server -> user Codex home ^ OpenClaw Codex plugin -> supervision App Server connection (defaults to managed user-home stdio; explicit appServer settings are honored) -> passive source catalog and read -> snapshot pin -> canonical appServer-source branch -> visible-history injection and every later supervised Chat turn Ordinary OpenClaw Codex sessions -> managed agent-home stdio by default -> ordinary full harness threads -> OpenClaw Chat and channel delivery ``` Enabling supervision does not change the ordinary Codex harness: it remains agent-scoped by default. The separate supervision connection defaults to managed user-home stdio, so its catalog and snapshot operations see native stored threads. Explicit `appServer` connection settings are honored. When `homeScope` is unset, the supervision connection resolves it to `"user"` for stdio or Unix and `"agent"` for WebSocket. Set `appServer.homeScope: "user"` explicitly only when the ordinary harness should also share the native Codex home. A Chat adopted from the Codex sidebar group is the exception: its private supervision binding keeps source reads, canonical branch creation, and later turns on the supervision connection. Live status and ownership remain process-local; a thread unknown to OpenClaw's supervision process is `notLoaded` even when Codex Desktop is actively running it. Codex has an experimental canonical local daemon with a separate installer-managed bootstrap contract. This feature must not bootstrap, claim, or assume that daemon implicitly. ## Catalog flow The generic Gateway method `sessions.catalog.list` dispatches to the `codex` catalog provider, which always requests `archived: false` and the interactive `cli` and `vscode` source kinds. It combines: 1. Gateway-local `thread/list` results from the supervision App Server, which defaults to managed user-home stdio. 2. `codex.appServer.threads.list.v1` results from each connected, opted-in node. Transcript selection uses `thread/turns/list` with `itemsView: "full"` locally or the versioned `codex.appServer.thread.turns.list.v1` command on the selected node. Every response contains at most 20 persisted turns plus opaque forward/backward cursors. The Control UI requests newest-first pages, renders each page in chronological order, and prepends older pages. It never falls back to an unbounded `thread/read`. OpenClaw also rejects any serialized item page above 20 MiB before it can cross the node or Gateway transport. The native macOS paired-node implementation supports only an unset/default or explicit `appServer.transport: "stdio"` with unset/default supervision scope or explicit `appServer.homeScope: "user"`. It carries configured `command`, `args`, and normalized `clearEnv` into the child process. With `"unix"`, `"websocket"`, or explicit `homeScope: "agent"`, it advertises neither the catalog capability nor command; direct invocation also fails closed. It must never expose the user Codex home for an agent-scoped configuration or substitute local stdio for an explicit endpoint. The catalog projection normalizes identifiers, title, cwd, status, active wait flags, timestamps, source, model provider, Codex version, and Git branch. It does not return transcript previews, turns, rollout paths, Codex home paths, Git remotes, commit SHAs, raw endpoints, or raw App Server errors. Transcript responses contain only the explicitly requested App Server item page and its opaque cursors. Host failures remain local to each host result. An offline node or unavailable local App Server does not erase healthy hosts from the page. Connectivity is a host property, not a thread status: a failed host result contains no fresh session rows and does not project `offline` onto native threads. Catalog discovery is passive. Listing or reading metadata must not call `thread/resume`, subscribe the OpenClaw client to live thread requests, or answer an approval. Search is title-only and case-insensitive. For each returned catalog page, the Gateway and paired Mac scan a bounded number of native pages without passing the query to App Server, because native search can also match transcript previews. The returned native cursor lets callers continue the scan. ## Operator CLI boundary The plugin registers three Gateway-backed shell commands: ```text openclaw codex sessions [--search ] [--host ] [--limit ] [--cursor ] [--json] [gateway-options] openclaw codex continue [--json] [gateway-options] openclaw codex archive --confirm-no-other-runner [--json] [gateway-options] ``` `[gateway-options]` is `--url `, `--token `, `--timeout `, and the inherited `--expect-final` switch. Session listing defaults to 75,000 ms; continue and archive default to 30,000 ms; `--expect-final` has no additional effect for these unary RPCs. Session search is title-only and case-insensitive; each response scans a bounded native page chain, and `--cursor` continues older results. The limit defaults to 50 per host and accepts 1 through 100, and a cursor requires one stable `--host` destination. No command accepts an archived/include-archived option. Only `sessions` can target paired hosts; `continue` and `archive` always send `hostId: "gateway:local"`, and archive requires the explicit confirmation flag. The shell namespace is not the in-chat `/codex` runtime namespace. In particular, `/codex sessions --host ` lists Codex CLI session files on one node, `/codex threads` lists App Server threads for the current conversation connection, and `/codex resume` or `/codex bind` mutates that conversation's binding. Those commands do not replace `sessions.catalog.continue`, and there is no `/codex continue` or `/codex archive` runtime command. ## Local continuation For a stored or idle Gateway-local row, the UI calls `sessions.catalog.continue` with `catalogId: "codex"` plus the host and thread ids. The plugin: 1. Reuses the existing supervised Chat when the source already has one. 2. Otherwise projects bounded user and assistant history through the source's last terminal persisted turn (completed, interrupted, or failed) into a new OpenClaw Chat and records a pending harness branch. 3. Stores the pending Codex-only model-lock policy, not a concrete model or provider selection, plus the private supervision connection scope, and returns the OpenClaw `sessionKey`. The history projection selects the newest tail of visible user and assistant messages, with hard limits of 200 messages, 512 KiB of UTF-8 text in total, and 64 KiB per message. It replaces image and local-image inputs with `[Image attachment]`, never copies image payloads or paths, and omits reasoning, tool calls, and tool results. The UI navigates to normal Chat with that session key. No canonical harness thread exists yet. On the first normal Chat turn, the harness installs the real Codex approval, elicitation, event, and delivery handlers, then: 1. Uses the supervision connection to call native `thread/fork` without a model or provider override and pin the persisted source snapshot. Codex's current `ConfigManager` state selects the model and provider, and the fork response reports the actual pair. If the model differs from the last model recorded in the source, Codex emits its normal model-difference warning. 2. On that same connection, starts the canonical full Codex harness thread with `threadSource: "appServer"`, OpenClaw's cwd, policy, config, environment, the full OpenClaw harness tool surface, and exactly the model and provider returned by the fork for this initial start. 3. Injects the bounded visible user and assistant history through that connection, commits the canonical binding without dropping its supervision scope, runs the turn, and archives the temporary fork. Before the first turn, the Chat is a locked pending branch with a visible history mirror; afterward, every model turn runs through the canonical Codex harness thread on the supervision connection. The branch is not a full native rollout clone: source reasoning, tool calls, and tool results are deliberately omitted. If snapshot pinning or canonical thread creation fails, the pending branch remains retryable. A binding race, disabled supervision, or an unavailable or mismatched supervision connection fails closed before the turn runs instead of falling back to the ordinary agent-home harness. This guarantees Codex-owned selection, not preservation of the source's historical model. The fork's returned pair is used for the canonical thread start, and Codex persists that thread's native model and provider. Later resumes omit OpenClaw model and provider overrides, so Codex restores the persisted pair. If a separate native Codex control changes the canonical thread, OpenClaw accepts that native persisted selection. The outer OpenClaw model and fallback chain never substitute for it. Model changes, session deletion, and session reset/new operations fail closed for the supervised model-locked Chat. Mutating `/codex model `, `/codex bind`, `/codex resume` (including node `--bind here`), and `/codex detach` or `/codex unbind` also fail closed because they replace or clear the binding. The `/codex model` query and `/codex fast`, `/codex permissions`, and `/codex threads` remain available. The `codex_threads` agent tool cannot attach a new fork or archive the bound native thread. List and metadata-only read remain available; transcript fields require `supervision.allowRawTranscripts`, while rename, unarchive, detached fork, and archive of an unrelated thread require `supervision.allowWriteControls`. Neither option can replace the locked binding. Deleting or resetting the OpenClaw entry would otherwise discard the native binding and create or permit a generic thread behind a Codex-looking session. Retention maintenance therefore preserves model-locked entries even when they exceed ordinary age, count, or disk-budget limits. Disabling or uninstalling the owning plugin also retains the lock and plugin ownership marker. The Chat stays unavailable and fails closed until the same plugin is re-enabled; cleanup never converts it into an ordinary model session. The source is never resumed or mutated by this action. The temporary fork pins a snapshot; it is not the durable continuation thread. Starting a distinct canonical harness thread on the first turn prevents OpenClaw from becoming a competing source writer merely because process-local status failed to see a Desktop-owned turn. The visible-history mirror and pinned snapshot may omit work that has not yet completed in an active source. The original CLI or VS Code source remains eligible for both native and OpenClaw catalogs. The canonical branch remains a native Codex thread in the supervision store, but native clients may filter its `appServer` source kind, so Codex Desktop visibility is not a contract. ## Archive behavior For a stored or idle Gateway-local row, `sessions.catalog.archive` with `catalogId: "codex"` requires explicit `confirmNoOtherRunner: true`, freshly reads current process-local status, proceeds only for `idle` or `notLoaded`, calls native `thread/archive`, and returns success only after Codex accepts the operation. The row then leaves the non-archived catalog. An active or error status from the fresh read rejects archive. So does an initializing or pending supervised branch from the source: the first Chat turn must materialize its canonical branch before the source can be archived. A known active OpenClaw binding owner for the exact target or any non-archived spawned descendant also rejects archive. OpenClaw paginates Codex's experimental `thread/list ancestorThreadId` relation and fails closed on request or response errors, cursor or thread cycles, and safety-limit exhaustion. Native archive can shut down loaded parent and descendant work, so archive is not an interrupt shortcut. The read, descendant enumeration, and archive calls are not atomic. An independent client can still own or start work on a row that appears idle or `notLoaded` locally. The no-other-runner confirmation covers unknown clients and that race until Codex has a conditional archive or cross-process lease. Paired-node archive is prohibited. There is no archived view in the Codex catalog. A thread restored with `thread/unarchive` in another owner-authorized Codex surface becomes eligible for the non-archived catalog again. ## Active thread safety Codex serializes mutations for a thread among clients of one App Server, but it does not expose an exclusive cross-process runner or approval-owner lease. Independent stdio App Servers can append to the same rollout, while each sees only its own in-memory status. Approval requests can also reach every subscriber of one server, with the first valid response completing the request. Therefore: - passive catalog clients do not subscribe or auto-deny approvals - rows currently reported active expose neither a new branch nor Archive - an unmapped source becomes a visible-history branch whose canonical harness thread never resumes the source - `notLoaded` is shown as activity unknown and can be archived only after informed no-other-runner confirmation - local archive requires that confirmation plus a fresh `idle` or `notLoaded` read, while acknowledging the protocol race between read and archive Interrupt and multi-client handoff are future product decisions. They are not implied by showing an active row. ## Paired-node boundary Node invoke is currently request/response only. It can safely return bounded catalog metadata and transcript turn pages, but it cannot carry the long-lived event stream, approval requests, tool calls, cancellation, and assistant deltas required by a Codex harness run. The node contract therefore supports list and transcript-turn pages. Remote rows stay readable, but **Continue** and **Archive** are unavailable, regardless of idle status. A real remote continuation requires a node-side runner and streaming bridge that preserves the same approval and binding invariants as the local harness. ## Permissions Each computer opts in locally. Enabling the Gateway does not authorize another node to read its Codex metadata. The node capability must pass normal pairing and command-policy approval. Fleet listing and transcript viewing use the `operator.write` Gateway scope because they invoke paired nodes. Local continuation and archive are authenticated operator actions and remain subject to host and status checks. Autonomous agent and standalone MCP access is separate. The shipped `codex_endpoint_probe`, `codex_sessions_list`, `codex_session_read`, `codex_session_send`, and `codex_session_interrupt` tool contracts remain owned by the `codex` plugin. With supervision enabled, raw `codex_threads` transcript reads and transcript-derived list fields also require `supervision.allowRawTranscripts`; every `codex_threads` fork, rename, archive, or unarchive requires `supervision.allowWriteControls`. Both policies default to disabled. ## Compatibility `openclaw doctor --fix` migrates shipped `plugins.entries.codex-supervisor` configuration, including endpoints and transcript/write policies, plus plugin allow/deny references into `plugins.entries.codex.config.supervision`. Explicit canonical destination values win conflicts. Runtime code uses only the canonical `codex` plugin shape after migration. The official plugin retains exactly five Supervisor compatibility tools: `codex_endpoint_probe`, `codex_sessions_list`, `codex_session_read`, `codex_session_send`, and `codex_session_interrupt`. Session list is loaded-only by default; there is no `loaded_only` parameter. `include_stored: true` adds non-archived state-database rows, bounded per endpoint by `max_stored_sessions` (default 200, accepted range 1 through 1,000); loaded rows are uncapped by that setting. Transcript-derived fields and reads remain gated by `allowRawTranscripts`; send and interrupt remain gated by `allowWriteControls`. Compatibility send never starts or resumes an idle thread. `mode: "start"` is always refused; `"auto"` and `"steer"` steer only a readable active turn. Interrupt likewise requires an active readable turn. Idle continuation routes to the native Codex catalog so the full harness owns approvals, tools, and the binding. The standalone legacy MCP adapter resolves these same tools from the official plugin and is the only path that honors the retained legacy policy environment variables. The July catalog UI, Gateway method, node capability, and CLI registration had not shipped under the old plugin id. They move directly to `codex` ownership without a second runtime facade. ## Future work - node-side streaming runner and event bridge for remote continuation - explicit runner and approval-owner leases for simultaneous client handoff - remote archive after a runner-ownership lease or equivalent fencing exists - interrupt and richer active-session observation - audited handoff between Codex Desktop, CLI, and OpenClaw Archived browsing is not part of the planned supervision sidebar. Native Codex surfaces remain the recovery path for archived threads. ## Acceptance tests - Enabling supervision lists non-archived local sessions. - Archived sessions never appear in the catalog response or UI. - Healthy hosts remain visible when another host fails; an unavailable host returns no fresh rows instead of inventing an offline session status. - A stored or idle local row creates a Chat mirror with a Codex-only model/runtime lock; the first turn pins a temporary snapshot and starts the canonical full harness thread, and repeating Continue opens the existing Chat. - The first turn omits model/provider overrides on the snapshot fork and pins the canonical start to the exact pair returned by Codex, even when Codex warns that its current model differs from the source's last recorded model. - Pending and committed supervised bindings use the supervision connection for source access, canonical branch creation, and every later turn; ordinary Codex sessions remain agent-scoped. - Later resumes omit OpenClaw model/provider overrides, preserve Codex's canonical persisted selection, accept separate native changes to that thread, and never substitute the outer OpenClaw model or fallback chain. - Disabling supervision or losing the binding/connection lifecycle fails closed instead of moving the Chat to the ordinary agent-home harness. - A supervised model-locked Chat cannot be deleted while it protects the native binding. - The Chat mirrors at most 200 user and assistant messages, 512 KiB total, and 64 KiB per message. Images become placeholders; source reasoning, tool calls, tool results, image payloads, and local paths are not cloned. - The branch flow never resumes the source thread. - The original source remains eligible for both catalogs. The canonical native branch uses the `appServer` source kind and is not guaranteed to appear in Codex Desktop. - Active local sources cannot create a branch or be archived; an existing supervised Chat can still open. - Activity-unknown rows can branch without confirmation; archiving requires explicit no-other-runner confirmation. - A source with an initializing or pending supervised branch cannot be archived until the first Chat turn materializes the canonical branch. - A known active binding owner for the exact target or any non-archived spawned descendant blocks archive; descendant enumeration failures fail closed, and explicit confirmation remains responsible for unknown clients and the status-to-archive race. - Confirmed stored or idle local archive removes the row after native success. - Paired-node rows remain visible without Continue or Archive. - Passive listing never subscribes to or answers thread approvals. - Legacy Supervisor config migrates to the canonical Codex config shape. - Legacy list is loaded-only by default, stored enumeration obeys its per-endpoint cap, and compatibility send never starts or resumes an idle thread.