# Codex Lead Runtime Adapter

## Scope

This adapter maps the neutral Okstra lead operations to the Codex artifact-first CLI path. Read it only when the rendered launch prompt selects `leadRuntime=codex`.

## Capability declaration

| Field | Value |
|---|---|
| `runtime` | `codex` |
| `leadRoleLabel` | `Codex lead` |
| `userPromptMode` | `native-question` |
| `workerDispatchBackend` | `mixed` |
| `initialPromptDeliveryMode` | `eager-include` |
| `sessionAccounting` | `artifact-only` |
| `resumeMode` | `artifact-checkpoint` |
| `teardownMode` | `process-cleanup` |
| `leadEventSource` | `lead-events-jsonl` |

## Wizard interaction relay

Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. `semanticFunctions` is an allowlist: the skill may declare a function only when both this list and the live harness expose it. Codex `request_user_input` is single-select only (2–3 mutually exclusive choices, 1–3 questions); it has no native multi-select.

```json
{
  "schemaVersion": 1,
  "runtime": "codex",
  "semanticFunctions": [
    "plain_text_input",
    "native_single_select",
    "native_question_group"
  ],
  "nativeLimits": {
    "minOptions": 2,
    "maxOptions": 3,
    "maxQuestions": 3
  },
  "interactions": {
    "native-single": {
      "function": "request_user_input",
      "input": {
        "callCount": 1,
        "questions": "one",
        "id": "prompt.step",
        "header": "Q1",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-label-description"
      },
      "response": {
        "envelope": "answers",
        "key": "question-id",
        "selection": "answers-array",
        "submit": "matching-option-value"
      }
    },
    "native-group": {
      "function": "request_user_input",
      "input": {
        "callCount": 1,
        "questions": "all-in-original-order",
        "id": "questions[].step",
        "header": "Q<one-based-question-position>",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-label-description"
      },
      "response": {
        "envelope": "answers",
        "key": "question-id",
        "selection": "answers-array",
        "submit": "compact-step-json-values"
      }
    },
    "numbered-single": {
      "function": "host-text",
      "input": {
        "questions": "one",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-numbered-markdown-label-and-description"
      },
      "response": { "source": "next-message", "submit": "raw" }
    },
    "numbered-multi": {
      "function": "host-text",
      "input": {
        "questions": "one",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-numbered-markdown-label-and-description"
      },
      "response": { "source": "next-message", "submit": "raw" }
    },
    "sequential-group": {
      "function": "host-text",
      "input": {
        "questions": "all-one-at-a-time-in-original-order",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-numbered-markdown-label-and-description"
      },
      "response": {
        "source": "next-message-by-question-position",
        "submit": "compact-step-json-raw"
      }
    },
    "plain-text": {
      "function": "host-text",
      "input": { "questions": "one", "question": "label-with-progress" },
      "response": { "source": "next-message", "submit": "raw" }
    }
  }
}
```

For `request_user_input`, send one to three questions. Each question carries `id` (`prompt.step` or `questions[].step`), a header of at most 12 characters (`Q1`…`Q3`), the rendered question text including the progress suffix, and every wizard option as `{label, description}` in original order — the tool has no option `value` field. Do not include an `Other` option; the client adds a free-form row. Native plans are emitted only when the prompt fits `nativeLimits` (unique labels, two or three options, one to three questions) and no grouped question is multi-select. Other prompts use the text mapping so no option is dropped. Look up each answer by question `id`. The selected strings are in `answers[id].answers`. Match those strings to option labels, emit their `value` fields in original option order, and join with `,` when more than one is present. If the typed Other text matches no label, submit it unchanged.

For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer: do not translate a number such as `1`, a CSV reply such as `1, 3`, an option label, or an option value before `okstra wizard step`. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`; the wizard owns all normalization.


## Semantic operation mapping

| Operation | Mapping |
|---|---|
| `read_artifacts` | Read the manifest-provided paths through the current host's file interface. |
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
| `prompt_user` | Use `request_user_input` for approvals and clarifications that fit `nativeLimits` (two or three options). Do not print a numbered list in chat while that tool is available. Otherwise ask through host text and stop until an explicit answer arrives. |
| `dispatch_worker` | Verify each materialized invocation first. Dispatch `runner=native-session` with the current Codex host's primitive, the returned `promptPath`, and `hostModelValue`. Pass `runner=cli-wrapper` assignments to `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; use `--dry-run` first when required. **Not in a cmux run:** when `terminalBackend` is `cmux-pane`, the cmux adapter overrides this row. |
| `await_workers` | Await native host workers through the host primitive and CLI workers through synchronous dispatch, then verify team-state terminal records and Result Paths for both. |
| `redispatch_worker` | Materialize and verify a fresh invocation, then start a fresh native worker or `okstra worker-dispatch` attempt according to the persisted runner. |
| `shutdown_workers` | Perform process cleanup when a wrapper remains live; otherwise this operation is a no-op recorded in state. |
| `record_lead_event` | Append progress and activity records to the manifest-provided `leadEventsPath`. Emit the matching `PROGRESS:` line and, when an activity record is required, the immediately following `ACTIVITY:` line from the same structured fields. |
| `collect_usage` | Collect artifact/rollout-backed usage through the existing Okstra token-usage path; never read Claude session JSONL as a substitute. |

## Codex dispatch details

- For convergence reverify, consume the persisted round plan exactly. This adapter may map and transport each returned batch, but it cannot change batch membership and does not classify findings or branch on task type, provider, or model identity.
- Do not invoke Claude Code team or subagent tools.
- The prepared run manifest and team-state are the dispatch authority. A `runner=native-session` assignment stays in the current Codex host; a `runner=cli-wrapper` assignment uses the registered provider wrapper. Unsupported explicitly requested workers fail; an adapter must not silently change the roster.
- The report-writer follows its persisted provider, model, and runner assignment exactly. It has no Codex-only provider override or separate opt-in gate.
- Reverify and critic retries invoke a fresh worker attempt and persist the core-supplied `dispatchKind` (`reverify-r<N>` or `critic`) in the dispatch record; never reuse a prior rollout as a new vote.
- Native calls use only `hostModelValue`. Immediately before the host primitive, run `okstra agent-prompt record-dispatch` with the project root, run manifest, verified metadata path, and `--enforcement-mode host-native-spec-link-gate`; after the Result Path exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and that path before accepting it. CLI calls use only `modelExecutionValue` through `worker-dispatch`, which records its own dispatch. The native linkage proves association with a verified specification, not observed prompt delivery.
- Report-writer completion requires both the data.json Result Path and the worker-results audit path, even when the synchronous dispatch command exits successfully.

## Completion, cleanup, and resume

- A native host completion or successful synchronous CLI return alone is insufficient; verify terminal state, every required completion path, and the corresponding worker-dispatch audit record before counting the worker as complete.
- Resume from run artifacts and lead-events checkpoints. Do not invent a Claude session id.
