# Grok Lead Runtime Adapter

## Scope

This adapter maps neutral Okstra lead operations to the Grok CLI. Read it only when the rendered launch prompt selects `leadRuntime=grok`.

## Capability declaration

| Field | Value |
|---|---|
| `runtime` | `grok` |
| `leadRoleLabel` | `Grok lead` |
| `userPromptMode` | `native-question` |
| `workerDispatchBackend` | `mixed` |
| `initialPromptDeliveryMode` | `eager-include` |
| `sessionAccounting` | `artifact-only` |
| `resumeMode` | `native-session-id` |
| `teardownMode` | `process-cleanup` |
| `leadEventSource` | `lead-events-jsonl` |

## Wizard interaction relay

Set `multi_select` explicitly on each object in `questions[]`: `true` for a multi-select question and `false` for a single-select question. The field belongs inside the question object, not at the call's top level. Grok uses `multi_select`; `multiSelect` is the Claude Code field. Before calling `ask_user_question`, check that every question carries the value specified by this relay. The relay field names and values are covered by `tests/contract/test_host_orchestration_rules.py`; the live host call still depends on the lead passing them.

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.

```json
{
  "schemaVersion": 1,
  "runtime": "grok",
  "semanticFunctions": [
    "plain_text_input",
    "native_single_select",
    "native_multi_select",
    "native_question_group"
  ],
  "nativeLimits": {
    "minOptions": 2,
    "maxOptions": 15,
    "maxQuestions": 8
  },
  "interactions": {
    "native-single": {
      "function": "ask_user_question",
      "input": {
        "callCount": 1,
        "questions": "one",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-label-description",
        "multi_select": false
      },
      "response": {
        "envelope": "answers",
        "key": "rendered-question-text",
        "selection": "selected-label",
        "submit": "matching-option-value"
      }
    },
    "native-multi": {
      "function": "ask_user_question",
      "input": {
        "callCount": 1,
        "questions": "one",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-label-description",
        "multi_select": true
      },
      "response": {
        "envelope": "answers",
        "key": "rendered-question-text",
        "selection": "selected-labels-joined-comma-space",
        "submit": "csv-matching-option-values-in-option-order"
      }
    },
    "native-group": {
      "function": "ask_user_question",
      "input": {
        "callCount": 1,
        "questions": "all-in-original-order",
        "question": "label-with-progress",
        "options": "all-in-original-order-as-label-description",
        "multi_select": "questions[].multi"
      },
      "response": {
        "envelope": "answers",
        "key": "rendered-question-text",
        "selection": "selected-label-or-labels-joined-comma-space",
        "multiValue": "csv-matching-option-values-in-option-order",
        "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" }
    }
  },
  "recovery": {
    "native-question-refused": {
      "trigger": "a-declared-native-question-function-is-refused-or-errors-at-call-time",
      "retry": "none",
      "dropSemanticFunctions": [
        "native_single_select",
        "native_multi_select",
        "native_question_group"
      ],
      "continueWith": "text-mapping-for-the-rest-of-this-session",
      "wizardState": "unchanged-refetch-the-pending-prompt-with-no-submit",
      "notifyUser": "one-line-host-picker-unavailable-continuing-as-numbered-lists"
    }
  }
}
```
`recovery` names what to do when a function this relay declares does not work in the live client. `native-question-refused` fires the first time a declared native question function is refused or errors at call time, whatever the reason (the client cannot present the card, the session has no view, the call returns no answer). Do not call it again and do not hunt for another native function: drop every token in `dropSemanticFunctions` from the intersection you computed in Step 1, keep `plain_text_input`, and render every remaining screen through the text mapping (`numbered-single` / `numbered-multi` / `sequential-group`), which preserves every option. Numbered text is the recovery path, so the ban on printing a numbered list does not apply once this fires. The wizard state file is untouched by a refused call — the pending prompt is still the current step, and `okstra wizard step --state-file <path> --no-submit` returns it again. Tell the user in one line that the host picker is unavailable and the run continues as numbered lists, then continue the wizard. Never abandon the run or ask the user to retype an identifier because the picker is gone.

For `ask_user_question`, map each wizard option to the tool's `{label, description}` input without removing, shortening, or reordering entries. Do not send `preview`. Look up each answer by the exact rendered `question` string sent to the tool, including the progress suffix. A single-select answer is the selected label. A multi-select answer is one string whose selected labels are joined with `, `. Match those labels back to the complete original option list, emit their `value` fields in original option order, and join the values with `,` for the wizard. Native plans are emitted only when the prompt fits `nativeLimits` (unique option labels, two to fifteen options, one to eight questions); other prompts use the text mapping so no option is dropped. The host adds a free-text row on every question. Do not drop a wizard option to make room for it. If the user types in that row and the text matches no option label, submit the typed text unchanged. For a group, key the compact JSON object by the corresponding `questions[].step`; a multi-select question stores its ordered value CSV as one string, not a JSON array.

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 numbers, CSV members, labels, or values before `okstra wizard step`. A sequential group wraps each raw reply in one compact JSON object keyed by `questions[].step`; the wizard owns normalization.

## Semantic operation mapping

| Operation | Mapping |
|---|---|
| `read_artifacts` | Read the manifest-provided paths through the current Grok host file interface. |
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
| `prompt_user` | Use `ask_user_question` for approvals and clarifications that fit `nativeLimits`. Do not print a numbered list in chat while that tool is available. Emit the question as the last thing in that turn: assistant text emitted after the call renders below the question and separates it from the user's answer. Do not infer an answer from silence. |
| `dispatch_worker` | Verify the materialized invocation. Use the host primitive with `promptPath` and `hostModelValue` for `native-session`; use deterministic `okstra worker-dispatch` with `modelExecutionValue` for `cli-wrapper`. **Not in a cmux run:** the cmux adapter overrides this row. |
| `await_workers` | Await through the selected common dispatch backend, then verify terminal state and Result Paths. |
| `redispatch_worker` | Start a fresh attempt from the persisted assignment and record the supplied dispatch kind. |
| `shutdown_workers` | Clean up only host or process resources owned by this run. |
| `record_lead_event` | Append progress and activity records to the manifest-provided `leadEventsPath`. Use `okstra lead-progress append --phase <phase-id>` for a checkpoint and `okstra agent-activity append --kind <kind>` for an activity record; both resolve the ledger path from the run manifest. Emit the matching `PROGRESS:` line — the command prints it as `progressLine` — and, when an activity record is required, the immediately following `ACTIVITY:` line from the same structured fields. |
| `collect_usage` | Return explicit unavailable lead usage until Grok registers a session transcript or CLI usage artifact contract. |

## Completion, cleanup, and resume

- Do not infer the current host from an installed `grok` executable. The runtime must come from an explicit request or current-session declaration.
- Keep persisted provider, model, runner, and dispatch-kind assignments unchanged.
- Before a native call, 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. This is a verified specification link, not proof of delivered prompt bytes.
- Resume with the persisted Grok session ID when one exists; otherwise resume from Okstra run artifacts.
