# Codex Connect: Alpha design

## Ownership and composition

The package registers `openai-codex` through Harness's public `LlmRuntime` and `PiAiAdapter` surfaces. The main model path is not a one-shot subagent: it remains the normal Harness agent loop, preserving native tool approvals, permission policy, streaming, attachment resolution, reasoning replay, session persistence, compaction, and recovery.

The bundle patch inserts only `llm-openai-codex`. It never writes `agent-default-model` or `web.searchProvider`. `enableSearch` and `enableImageTool` are capability gates defaulting to `false`; the optional service injections are not registered while disabled.

The startup-only `enableQuotaTool` gate also defaults to `false`. When explicitly enabled, `codex_connect_quota` uses the Host tool runtime and the existing `OpenAICodexQuotaState`; it has no separate transport, account selector, refresh bypass or budget controller. The projection includes only common Codex window numbers and freshness, without upstream names/ids, balances or routing authority. Successful snapshots now retain their local fetch time across cache hits. See [quota observation](quota-tool.md).

The Host registers `llm-openai-codex` as the plugin-owned capability settings namespace. DSH's `llm-pi-ai` catalog owns the `openai-codex` configurable-provider directory entry, while Codex Connect registers the active adapter without redeclaring that entry. The browser binds the plugin namespace through Harness's settings-scope transport and renders account, quota, Save/Discard capability controls in the existing Plugin configuration card. Revision-fenced field writes preserve unrelated settings. Committed changes reconcile search and image registrations live. Default-model settings are never written.

Fast Mode remains per-session process-local state. Separate default-off profile settings seed newly started top-level and subagent sessions through `agent/created`; resume, setting edits, and repeated startup notifications do not change an existing session's Composer choice. The adapter adds `service_tier: 'priority'` only on Codex requests for an enabled session.

## Task-level model selection: frozen

Phase 1 #236 and Phase 2 #239 were merged; #248 subsequently paused public activation, which remains paused in Alpha 4.52. The [maintenance scope](maintenance-scope.md) freezes further orchestration development. Retain old-state readback and authorized safety exits. The two candidate descriptions below are historical architecture context, not current release status or instructions to finish acceptance.

The optional conversation control grants a bounded main-model/effort scope to one task, initially Sol/Medium. The task runtime, adapter dispatch wrapper and existing backend governor share durable request accounting. There is no separate Think, Split or Remember product selector. Host-owned permissions, session history and compaction remain their existing owners; model choice grants no new tool authority. See [the consolidated contract and migration](experiments/adaptive-task-phase1.md) for source selection from #232–#235, cancellation, recovery and acceptance limits.

The local unreleased Phase 2 candidate implements [task-scoped read-only delegation](experiments/adaptive-task-phase2-design.md), tracked in #198. A single composition preserves v1 grants until an explicit idle upgrade; child model/effort and evidence-file consent are separate from the main-task grant. Every root has an owner/session-bound private artifact namespace. Ordinary standalone adapters still need only `llm`; task controls require live agent and session services. New sessions remain off. See [Phase 2 exact-host and installed acceptance](experiments/adaptive-task-phase2-acceptance.md) for current verification, failures and remaining gates. [Phase 1 acceptance](experiments/adaptive-task-phase1-acceptance.md), local implementation, remote CI and release authority remain separate.

## OAuth persistence

The plugin uses `$DSH_HOME/.openai-codex-auth.json`, separate from Codex CLI/Desktop state. Format version 2 stores at most 16 OAuth accounts and names exactly one active account. Existing version-1 documents remain readable and migrate on the first credential mutation; that migration first creates the owner-only `.openai-codex-auth.json.v1-backup` rollback copy. A pre-existing rollback copy must be owner-only and match the current version-1 document or migration stops before replacing the main document. Removing any account or signing out deletes the rollback copy so removed credentials do not persist there. Documents are limited to 512 KiB. POSIX reads reject group/world-accessible files, non-regular files are rejected, writes are atomic, and mutations use the Harness cross-process file lock. Callers receive cloned credentials.

Browser account routes expose deterministic plugin-derived keys, local display labels, masked email addresses when present in the OAuth token, and active state. They never expose the provider's account id or token fields. Activation and deletion are serialized with browser OAuth. Each model request captures its current account before auth resolution; a refresh updates only that captured account without changing the current selection. After OAuth resolution, quota, image-generation, and Auto-review requests resolve the account id stored with that exact access token, so a concurrent account switch cannot combine credentials from different accounts. Deleting the active account while another remains requires the caller to name the replacement; provider-global logout deletes every account. Browser-origin trust is a separate `$DSH_HOME/.openai-codex-trusted-origins.json` sidecar with `version: 1`, `mode: "allowlist"`, and exact normalized HTTP(S) origins; it never contains OAuth material and is changed only by the standalone CLI.

The settings routes and CLI reuse the existing OAuth path and route names for migration compatibility. Only an explicit login operation emits an authorization URL or code. Browser requests are accepted by default only from loopback; a remote request must use an exact effective HTTP(S) origin in the current sidecar, must not carry cross-site Fetch Metadata, and must match any supplied Origin exactly. The sidecar is re-read for every request, and unknown fields or modes fail closed. A login challenge accepts only credential-free HTTPS URLs and fails closed after 30 seconds or when the provider finishes without a URL; logout and disposal cancel pending waiters. Status responses are redacted. Doctor uses `lstat` metadata and never opens the document.

Browser account and update requests have a 45-second deadline covering response headers and JSON bodies. A timed-out account mutation is not retried automatically: while the account view is observed, the browser reads server state again because the mutation may already have committed. Failed state reads retry after five seconds, including when no account is stored. Plugin disposal aborts browser waits without logging out the server account.

## Backend request governance

Ordinary adapter profiles and the backend governor do not import Task policy. The composition root explicitly supplies provider wrapping and pre-fetch accounting for retained legacy tasks; see [core transport separation](agent-notes/core-transport-decoupling.md). Removing experimental execution must preserve these historical-task safety obligations.

Authenticated runtime traffic to `https://chatgpt.com/backend-api/` passes through one plugin-owned request governor before network dispatch. The governor fail-closes on any other origin/path, composes caller and route deadlines, keeps the existing Codex-only proxy scope alive for the complete logical direct request or provider stream, assigns a fresh `x-client-request-id` to every HTTP attempt, and bounds one plugin instance to eight concurrent backend requests. Concurrency is counted per HTTP attempt, not during authentication; even parallel fetches within one logical operation share that limit. Requests queued behind that limit remain cancellable and within route deadlines. Cancelling unread/paused bodies, response-hook failures and scope disposal releases resources; scope disposal runs before other plugin teardown waits. URL/header inputs are snapshotted before queue waits and authenticated requests never automatically follow redirects. HTTP `429` or `503` responses with a valid `Retry-After` create a lane-local cooldown that is rechecked on admission, never occupies a network slot while waiting, and honors longer service delays using timer slices of at most 15 minutes; the governor does not invent an account-block verdict or replay a failed model turn. Route-specific protocol retries remain with their existing owners, such as pi-ai model retry semantics and native-compaction's bounded retry loop.

Direct plugin routes use the honest shared identity `originator: deepseek-harness` and `User-Agent: dsh-codex-connect`; Search, quota, image generation, Auto-review, and native compaction all use that policy. The pi-ai model route deliberately preserves pi-ai's provider identity rather than impersonating Codex CLI/Desktop, while still receiving the same attempt id, concurrency, proxy, cancellation, server-directed cooldown, and bounded HTTP diagnostic metadata. Native compaction travels through the provider fetch seam at runtime and therefore shares the model governor lane while retaining the direct-plugin identity on its own request. This split is explicit policy, not a claim about undocumented upstream risk controls.

Standalone capability/Auto-review probes reuse the same pure URL/header policy but keep their isolated one-shot dispatcher and deadline instead of depending on a running plugin governor. The proxy reachability probe is also a bootstrap exception because it tests the proxy primitive used by the governor itself. OAuth authorization/refresh and credential-free public image fetching are outside `backend-api` and keep their separate security boundaries. Browser calls to this plugin's own local HTTP routes are likewise not upstream backend traffic.

## Search and images

When `enableSearch: true`, the plugin registers its standalone search provider. It does not append a plugin-owned required-on-read Session event because an external package cannot guarantee that its `@deepseek-ai/dsh-session` module instance owns the Host persistence vocabulary; the standard web Tool call and result remain in the Session log. DSH `0.1.2-rc.1` does not expose the WebRuntime provider selection through its settings service, so the compatibility adapter verifies that release's runtime field, records the previous provider, and selects Codex only while the capability is active. Disable and disposal restore the recorded provider unless another owner has selected a newer route. An unsupported runtime fails activation instead of reporting a route change that did not occur. Search responses are mapped to Harness text and citation records.

When `enableImageTool: true`, `view_image` is registered only after tools, filesystem, and attachment services are available. Local files remain bounded by the Harness filesystem surface. Remote images allow only credential-free public HTTP(S): all DNS answers must be public unicast, each redirect is revalidated, and each socket is pinned to the validated address to close DNS-rebinding gaps. The tool also checks bounded bytes, accepted media signatures, and current-model image support before saving a Harness attachment.

Each remote-image redirect hop has one 30-second deadline covering DNS, connection, and body consumption. Cancellation settles the caller's wait immediately; an operating-system DNS lookup may still finish later, but its result cannot start a new HTTP request after cancellation or timeout.

Successful Codex image-generation Tool results also render in the independent completed-Turn tail. DSH groups Tool calls into a collapsed processing disclosure, so the keyed Tool card alone cannot make generated images visible with the answer. The tail reads only this Turn's settled Tool rows, including nested PTC calls and restored history, and uses the same validated result metadata, session-bound preview loader, Lightbox, and download controls as the Tool card. Failed, malformed, unrelated, or post-answer results do not enter the visible image row. The Tool card remains available in processing details.

Image preview recovery uses additive metadata/envelope version 3 only when original persistence completed but preview creation failed. It records exact originals with `previewStatus: unavailable` and no fabricated attachment. Host validation precedes original persistence, so preview failures cannot bypass the original image validation gate. Native and PTC history can admit these originals for the creating session or an authorized inherited prefix; arbitrary text cannot. The existing result card exposes original download and explicit editing without a new image request. Normal version 1/2 results are unchanged; cancellation still cleans unpublished originals. See [implementation and validation](agent-notes/image-preview-recovery.md).

## Conflicts and diagnostics

Before registration the plugin checks current provider ids. An existing `openai-codex` adapter produces a focused message naming the likely legacy-bundle or manual-provider cause. The boot-free CLI `doctor` reports package/runtime version, OAuth path metadata, capability defaults, and safe conflict guidance without returning auth content.

## Compatibility boundary

Alpha 4.27 pins Harness `0.1.2-rc.1` development dependencies and follows its pi-ai range `^0.84.2`. Supported Node.js remains `^22.19.0 || >=24.0.0`. The keyed `settings.plugin.item` integration remains, while client types come from their Session Controller, Settings, Store, and Renderer owners. The published compatibility record lists the exact Alpha 4.27 and DSH `0.1.2-rc.1` pair. Backend eligibility, quotas, models, service-side context capacity, and protocol details remain controlled upstream. Tests use temporary OAuth documents and mocked network responses; CI does not perform real authentication.
