# okstra — CLI Argument and Option Manual

> This document supplements [README.md](../README.md). For internal architecture and storage/workflow contracts, see [architecture.md](architecture.md).
>
> This manual covers the phase-execution flags of `okstra.sh`. For the subcommand reference of the separate `okstra container` entry point, see [container.md](container.md).

The Node CLI requires Node.js 22 or newer. Its TypeScript sources are compiled from `src/**/*.mts` to `dist/**/*.mjs`; `bin/okstra` executes the compiled files. The central state helper remains `scripts/okstra-central.sh`.

---

## Index

- [Command forms](#command-forms)
- [Required arguments](#required-arguments)
  - [`--project-id`](#--project-id)
  - [`--task-group`](#--task-group)
  - [`--task-id`](#--task-id)
  - [`--task-type`](#--task-type)
  - [`--task-brief`](#--task-brief)
  - [`--yes`](#--yes)
- [Optional arguments and options](#optional-arguments-and-options)
  - [`--task-key`](#--task-key)
  - [`--clarification-response`](#--clarification-response)
  - [`--selected-direction`](#--selected-direction)
  - [`--reverify-scope`](#--reverify-scope)
  - [`--resume-clarification`](#--resume-clarification)
  - [`--project-root`](#--project-root)
  - [`--analysis-target`](#--analysis-target)
  - [`--evidence-inputs`](#--evidence-inputs)
  - [`--directive`](#--directive)
  - [`--fix-cycle`](#--fix-cycle)
  - [`--workers`](#--workers)
  - [`--role-count`](#--role-count)
  - [`--role-model`](#--role-model)
  - [`--lead-runtime`](#--lead-runtime)
  - [`--claude-model`](#--claude-model)
  - [`--lead-model`](#--lead-model)
  - [`--codex-model`](#--codex-model)
  - [`--antigravity-model`](#--antigravity-model)
  - [`--report-writer-model`](#--report-writer-model)
  - [`--executor`](#--executor)
  - [`--critic`](#--critic)
  - [`--approved-plan`](#--approved-plan)
  - [`--approve`](#--approve)
  - [`--work-category`](#--work-category)
  - [`--base-ref`](#--base-ref)
  - [`--related-tasks`](#--related-tasks)
  - [`--render-only`](#--render-only)
  - [`--no-plan-verification`](#--no-plan-verification)
- [Interactive input flow](#interactive-input-flow)
- [Confirmation flow](#confirmation-flow)
- [okstra Control Center — installation and common commands](#control-center-install-and-common-commands)
- [okstra Control Center](#okstra-control-center)
  - [Installation (global wrapper)](#install-global-wrapper)
  - [Common commands](#common-commands)
  - [`okstra` Node CLI — introspection subcommands](#okstra-node-cli--introspection-subcommands)
    - [`okstra design-prep`](#okstra-design-prep)
  - [Live-log sidecar](#live-log-sidecar)

---

## Command forms

Base command for initial entry with full arguments:

```bash
scripts/okstra.sh [--render-only] [--yes] [--no-plan-verification] --task-type <task-type> [--workers worker1,worker2] [--role-count <role>=<N>] [--role-model <role>=<modelRef>] [--lead-runtime <host-id-or-alias>] [--lead-provider <provider>] [--lead-model <model>] [--worker-model provider=model,...] [--report-writer-provider <provider>] [--report-writer-model <model>] [--executor claude|codex|antigravity|grok|kimi] [--critic claude|codex|antigravity|grok|kimi] [--related-tasks taskA,taskB] [--work-category bugfix|feature|refactor|ops|improvement|unknown] [--base-ref <branch|tag|sha>] [--clarification-response <previous-final-report>] [--selected-direction <selection-final-report.md>] [--approved-plan <plan-path>] [--approve] --project-id <project-id> --task-group <task-group> --task-id <task-id> --task-brief <brief-path> [--directive <directive>] [--fix-cycle <yes|no>]
```

Analysis input ownership is narrower than the base shell command. The `/okstra-run` wizard collects `--analysis-target` and `--evidence-inputs` values and passes them internally to `node bin/okstra render-bundle`. `scripts/okstra.sh` does not accept either flag. Because `feature-analysis` requires a target, start that task type with the in-host skill; the two option sections below document the internal Node render inputs, not standalone shell options.

Short form for a later phase when an existing task-manifest.json is available:

```bash
scripts/okstra.sh --task-key <project-id>:<task-group>:<task-id> [--task-type <task-type>]
```

Immediately resume after answering clarification items:

```bash
scripts/okstra.sh --resume-clarification --task-key <project-id>:<task-group>:<task-id>
```

The following additional rules apply in an interactive terminal:

- Missing required arguments are read directly from stdin.
- Once all inputs are available, a summary is shown before execution.
- Creation or execution proceeds only when you enter `y` or `yes`.
- Non-interactive runs do not prompt for missing arguments and exit with an error instead.
- `--yes` skips both prompts and confirmation, but all required arguments must already be present.
- Precedence is `explicit CLI argument > interactive prompt or non-interactive error`.

## Required arguments

### `--project-id`

A globally unique project ID. On the first run, it self-registers in `<PROJECT_ROOT>/.okstra/project.json`. On later runs from the same PROJECT_ROOT, okstra exits immediately if the argument differs from the stored `projectId`.

Examples:

- `sample-project-v2-api`
- `jobs`

### `--task-group`

A logical group for related tasks.

Examples:

- `feature-8858`
- `bugfix-auth`
- `tasks`

### `--task-id`

A stable task identifier that can be reused within the same group.

Examples:

- `email-recipient-loss`
- `8852`

### `--task-type`

The single input that determines the purpose of this run, profile selection, run-directory segment, and lifecycle phase routing.
For standard values and phase-specific responsibilities, see [Task type](#--task-type) above.

The lifecycle task types run in this order:

| task type | Responsibility | Normal handoff |
|---|---|---|
| `requirements-discovery` | Fix the requirement ledger and decide whether cause analysis is needed. | `error-analysis` or `implementation-option-selection` |
| `error-analysis` | Establish a credible cause and counter-evidence. | `implementation-option-selection` or continued `error-analysis` |
| `implementation-option-selection` | Compare or validate read-only directions and expose only exact-coverage candidates. | `implementation-planning` after direction confirmation |
| `implementation-planning` | Realize one selected direction as files, stages, validation, and rollback. | `implementation` after separate plan approval |
| `implementation` | Execute one approved stage. | `final-verification` |
| `final-verification` | Verify acceptance and classify any cause, direction, or plan defect. | `release-handoff`, `error-analysis`, `implementation-option-selection`, or `implementation-planning` |
| `release-handoff` | Perform only the user-selected delivery action. | done or follow-up |

#### `--task-type improvement-discovery`

`improvement-discovery` is a sidetrack entry point outside `PHASE_SEQUENCE`. It uses multi-worker consensus to find improvement candidates within the codebase scope and lens allowlist.

- Input: a brief with the frontmatter marker `scope: codebase`.
  - `priority-lenses`: one to four values. The lens allowlist is the `LENSES` constant in `scripts/okstra_ctl/improvement_lenses.py`.
  - `scan-scope`: one or more paths.
  - `out-of-scope`: optional.
  - `candidate-cap`: 1–12; default 8.
- Output: the `## 5.9 Improvement Candidates` table with 11 columns: Cand ID / Lens / Title / Scope / Severity / Effort / Consensus / Source workers / Recommended next-phase / Expected behavior after / Evidence.
- Verdict Token: `analysis-complete` / `analysis-partial` / `blocked` — the shared analysis enum, which is what `schemas/final-report-v2.0.schema.json` admits. Finding no candidates is not a verdict: the run stays `analysis-complete` and records an empty candidate set with a `no-candidate` row per lens. (`candidates-ready` / `no-candidates` appear only in a schema-v1 legacy report's `## 7. Final Verdict`.)
- Routing: there is no automatic spin-off. The user selects candidates and starts each under a new task ID with `requirements-discovery`, `implementation-option-selection`, or `error-analysis`.
- Workers: claude + codex + antigravity + report-writer are all required.
- Primary-pass assignment: selected analyser instances are enumerated in `requiredWorkerRoles` order, then the lead rotates the primary pass across the resolved priority lenses. Provider/model names do not affect the order, and every analyser still covers every resolved lens after its primary pass.
- Two bidirectional grilling points: an enhanced Step 4 in `okstra-brief-gen` with a budget of 8, and the lead's Phase 1.5 reflect-back with a budget of 12.
- Validator: `validators/validate_improvement_report.py` enforces the 11-part contract for an `improvement-discovery` final report.
- Because an `improvement-discovery` run is not in `PHASE_SEQUENCE`, its report has no routing field to project from, so the run leaves `workflow.nextRecommendedPhase` `pending` with no phase. The `--task-key` short form therefore cannot fill `--task-type` after one.

#### Analysis sidetrack task types

The three read-only analysis types are independent sidetracks outside `PHASE_SEQUENCE`:

| task type | Purpose |
|---|---|
| `project-analysis` | Map the current project's components, dependencies, entry points, data stores, external systems, and feature index. |
| `feature-analysis` | Trace one existing feature through flows, domain rules, state changes, external interactions, and test coverage. |
| `change-impact-analysis` | Map the blast radius of a proposed change across preserved behavior, dependencies, tests, and operations. |

Each one starts from a brief and produces a report only. The target project is strictly read-only: no edits, tests, builds, migrations, or deployments. A run ends with `workflow.nextRecommendedPhase` `pending` and carrying no phase; it does not advance the normal phase sequence.

### `--analysis-target`

At the `node bin/okstra render-bundle` boundary, `--analysis-target` is required only for `feature-analysis`. It accepts either a free-text feature boundary or a `PF-NNN` ID from a selected `project-analysis` feature index. A `PF-NNN` value must match exactly one feature in the selected project-context evidence. `project-analysis`, `change-impact-analysis`, and non-analysis task types reject this render input.

### `--evidence-inputs`

At the `node bin/okstra render-bundle` boundary, `--evidence-inputs` accepts a comma-separated list of prior analysis final-report paths. Relative paths resolve from the target project root, selection order is preserved, and duplicate paths are rejected. The allowed relationships are:

| Consumer and source | Stored relation |
|---|---|
| `feature-analysis` ← `project-analysis` | `project-context` |
| `change-impact-analysis` ← `project-analysis` | `project-context` |
| `change-impact-analysis` ← `feature-analysis` | `feature-baseline` |

`project-analysis` accepts no evidence inputs. `feature-analysis` and `change-impact-analysis` may omit evidence, but every supplied report must match one of the relationships above.

Only `accepted` reports appear in automatic evidence choices. An explicitly selected, otherwise valid unreviewed report is recorded as `user-unverified`; `revision-requested` and `rejected` reports are rejected as evidence. A revision request instead makes the wizard prioritize a same-task, same-type full rerun. The selection is snapshotted in `run-manifest.evidenceInputs` with source task/run identity, relation, review status, source commit, and freshness.

Analysis reports expose four independent meanings. The report verdict is `analysis-complete`, `analysis-partial`, or `blocked`. The `Analysis Review` sidecar status is `accepted`, `revision-requested`, or `rejected`. Evidence freshness is current (`exact` in the stored snapshot) or `stale`. Direct user verification of an explicitly selected unreviewed report is `user-unverified`. These are separate axes: for example, a complete report can still be rejected by its reviewer, and accepted evidence can become stale after the source commit changes.

### `--task-brief`

The path to the task brief that serves as the basis for analysis.
Relative paths are resolved from the target project root.

This argument does not apply to `release-handoff`: a brief is input to an entry phase
(requirements-discovery / error-analysis / improvement-discovery / project-analysis /
feature-analysis / change-impact-analysis), while
release-handoff preparation automatically creates an input document that cites the verification report
at `<task_root>/release-handoff-input.md` and uses it in place of a brief.
A non-empty `--task-brief` is rejected immediately for release-handoff.

Examples:

- `.project-docs/linear/feature/8858/okstra-task-brief.md`
- `.project-docs/tasks/8852/BUG_REPORT.md`

### `--yes`

Skips interactive prompts and confirmation.

The following values are required instead:

- `project-id`
- `task-group`
- `task-id`
- `task-type`
- `brief-path`

If any required argument is missing, the command exits immediately without prompting.

Example:

```bash
scripts/okstra.sh --yes --render-only --task-type error-analysis --project-id jobs --task-group tasks --task-id 8852 --task-brief .project-docs/tasks/8852/BUG_REPORT.md
```

## Optional arguments and options

### `--task-key`

A short form that supplies `--project-id`, `--task-group`, and `--task-id` together when starting a later phase for an existing task.

- Input format: `<project-id>:<task-group>:<task-id>`
- Behavior:
  - Splits the value into the three IDs and populates `PROJECT_ID`, `TASK_GROUP`, and `TASK_ID`.
  - If the same values are also provided explicitly, they are accepted only when they match; a conflict exits immediately with an error.
  - If the task has a `task-manifest.json`, a missing `--task-brief` is filled from the manifest's `taskBriefPath`, and a missing `--task-type` is filled from `workflow.nextRecommendedPhase.phase` — but only while that pointer's `status` is `ready`.
  - Without a manifest, as on initial entry, the normal missing-argument validation remains in effect and full arguments are required.
- Explicit arguments always take precedence over manifest values.
- Under any other `status` — `pending`, `blocked`, or `terminal` — the pointer names no runnable phase and `--task-type` is not filled automatically. Supply `--task-type` explicitly or supplement the brief, then rerun the command.

Examples:

```bash
scripts/okstra.sh --task-key jobs:tasks:8852
```

```bash
scripts/okstra.sh --task-key jobs:tasks:8852 --task-type final-verification
```

### `--clarification-response`

Carries a previous run's final report—with user answers filled inline in `## 1. Clarification Items`—into the next run.

- Input: a path matching `<previous-run>/reports/final-report-<task-type>-<seq>.md`.
- Behavior: copies the file to the current run's `instruction-set/clarification-response.md`. In Section 0, the lead updates the `Status` of previous `Q*` rows to `resolved` or `obsolete`, then continues.
- Use it after a phase such as `requirements-discovery`, `error-analysis`, or `implementation-planning` leaves unresolved questions or approval-blocking clarification. Fill in the answers, then carry the report into the next phase or a resume of the same phase.

Example:

```bash
scripts/okstra.sh \
  --task-type implementation-planning \
  --project-id jobs --task-group tasks --task-id 8852 \
  --task-brief .project-docs/tasks/8852/BUG_REPORT.md \
  --clarification-response .okstra/tasks/tasks/8852/runs/2026-04-29/error-analysis/reports/final-report-2026-04-29_10-15-30.md
```

### `--selected-direction`

Starts a new `implementation-planning` run from a validated `implementation-option-selection` final report. The option-selection report may contain at most three displayed directions. Every displayed direction has exact requirement coverage and exact scope precision: both percentages are 100, with no unmapped commitment or contradicted requirement.

In `candidate-comparison` mode, the user first confirms one displayed direction in the report's `DIRECTION SELECTION` response. Prepare validates the report, its sibling data JSON, the response sidecar, the selected option ID, and the source-data digest before writing `instruction-set/selected-direction.json`. In `preselected-validation` mode, the validated upstream direction is used without generating or selecting an alternative.

The direction decision does not approve the detailed plan. Planning produces a separate `approved: false` report, and implementation still requires explicit plan approval. A new planning run without `--selected-direction` is rejected. A same-task planning rerun instead uses `--clarification-response` with its prior planning report.

Example:

```bash
scripts/okstra.sh --task-type implementation-planning \
  --selected-direction .okstra/tasks/tasks/8852/runs/implementation-option-selection/reports/final-report-implementation-option-selection-001.md \
  --project-id jobs --task-group tasks --task-id 8852
```

### `--reverify-scope`

Pins how much of an `implementation-planning` clarification re-run is verified again. Like `--analysis-target` and `--evidence-inputs`, this is an internal `node bin/okstra render-bundle` input collected by the `/okstra-run` wizard — `scripts/okstra.sh` does not accept it.

- Accepted values — exactly three forms; anything else is rejected rather than read as a default:
  - `auto` (or empty, the default) — leave the call to the lead's deterministic `okstra incremental-scope` decision.
  - `full` — re-verify every stage. The lead still records the decision, passing `--full-reason "user requested full re-verification"`.
  - a stage-number list such as `2,3` — the named stages enter the lead's `--impacted` set, unioned with whatever it resolves itself. The CLI's dependency closure and cutoff still apply on top, so a wide enough pin correctly resolves to full.
- Requires `--clarification-response`, and only `--task-type implementation-planning` accepts a non-empty value. Without a carried report there is no prior Stage Map to narrow against.
- The wizard asks for this when the re-run is narrowable **or** an answered `C-NNN` traces to no stage. An unlinked id does not freeze the run at full: the picker then offers stage numbers (recommended) or full, and omits `auto`. Direct stage input is validated against the prior report's Stage Map, so a number that plan never had is refused at the prompt. Empty input is auto only when every answered id already traces to a stage.
- The value reaches the lead as the `REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` tokens the "Clarification Response Carried In" section of the lead prompt reads. It narrows or widens the inputs to `okstra incremental-scope`; it does not bypass that decision.

### `--resume-clarification`

Immediately edits the final report from the previous `requirements-discovery` or `error-analysis` run and automatically reruns the same phase. You do not need to manage the `--clarification-response` path manually.

- Input: task identity supplied as `--project-id` + `--task-group` + `--task-id`, or as `--task-key <p>:<g>:<i>`.
- Sequence:
  1. Automatically selects the latest timestamped file under the task's `runs/{requirements-discovery,error-analysis}/reports/final-report-*.md` paths.
  2. Opens the file with `$EDITOR`, or `vi` when unset. Fill answers inline in the Section 5 `Q*` rows, save, and exit.
  3. okstra invokes itself again with `exec` for the same task type and carries the edited file in through `--clarification-response`.
  4. Confirmation, rendering, and Claude handoff then proceed as in a normal run.
- When an explicit `--task-type` is supplied, only that task type's reports are searched. Otherwise, okstra searches `error-analysis` and then `requirements-discovery` and uses the report with the later timestamp.
- It cannot be combined with `--render-only`, `--clarification-response`, or `--approved-plan`.
- Setting `EDITOR` to a non-interactive tool such as `true` skips answer editing and immediately reruns the task. This is recommended only for debugging.

Examples:

```bash
scripts/okstra.sh --resume-clarification --task-key jobs:tasks:8852
```

```bash
EDITOR=code\ -w scripts/okstra.sh --resume-clarification --project-id jobs --task-group tasks --task-id 8852
```

Internally, this is equivalent to:

```bash
$EDITOR <project-root>/.okstra/tasks/<group>/<id>/runs/<task-type>/reports/final-report-<latest>.md
scripts/okstra.sh --task-type <same-type> \
  --project-id <p> --task-group <g> --task-id <i> \
  --task-brief <from manifest> \
  --clarification-response <edited final-report-<latest>.md>
```

### `--project-root`

The absolute path to the target project. When omitted, `okstra.sh` resolves it in this order:

1. Find the current directory or nearest ancestor containing `.okstra/project.json` and use it as PROJECT_ROOT.
2. If that fails, use the result of `git rev-parse --show-toplevel`.
3. If both fail, exit immediately with an error.

On the first run from the resolved PROJECT_ROOT, `<PROJECT_ROOT>/.okstra/project.json` is created automatically with this schema:

```json
{
  "projectId": "<--project-id argument value>",
  "projectRoot": "<resolved absolute path>",
  "createdAt": "<ISO8601 UTC>",
  "updatedAt": "<ISO8601 UTC>"
}
```

If the file already exists, okstra checks that `projectId` matches and updates only `projectRoot` and `updatedAt`. A mismatched `projectId` exits immediately, preventing two IDs from being used in the same directory.

User-added fields are preserved across that upsert, so optional settings can be hand-edited into the file. One of them is `architecture.style` — the project's declared architecture, one of `hexagonal`, `layered`, or `none` (default `none` when the field is absent, unrecognized, or unreadable):

```json
{
  "projectId": "<--project-id argument value>",
  "projectRoot": "<resolved absolute path>",
  "createdAt": "<ISO8601 UTC>",
  "updatedAt": "<ISO8601 UTC>",
  "architecture": { "style": "hexagonal" }
}
```

Declaring a style promotes that architecture's placement rules from advisory to binding. Under `hexagonal`: a variation point that `implementation-planning` decides to extract has to be extracted behind a port (`interfaceKind: "port"` — validator-enforced; deciding *not* to extract a point stays legal, the style does not force extraction), the implementation executor loads `architectures/hexagonal.md` even when directory-shape detection did not match it, and the verifier grades a placement violation as a blocking `FAIL` rather than a recommendation. `layered` has no preflight pack resource; its binding invariant is dependency direction — an upper layer may import a lower one, never the reverse — and a reverse import is a blocking placement violation found by worker judgement, since no machine check reads layer names. Leaving the field out changes nothing — the style-agnostic planning rules (variation-point analysis and test seams) apply either way. See [`architecture.md`](architecture.md) § Project self-registration for the full two-layer model.

A second optional field is `reviewRulePacks` — an array of **absolute** paths to the project's own review rule packs (a team PR-review skill's `SKILL.md`, for instance). A pack otherwise reaches a run only when the task brief cites its exact path, so the team standard applied or not depending on who wrote the brief; declaring it here applies it to every run, and the brief citation keeps working alongside it. `implementation-planning`, the implementation executor's preflight, the implementation verifier, and `final-verification` all read it. A relative entry is dropped (a worker's cwd is a worktree, where it would name a different file), and `okstra doctor --phase <phase>` fails its `review rule packs` check when a declared path is not a readable file:

```json
{
  "reviewRulePacks": ["/Users/me/.claude/skills/team-pr-reviewer/SKILL.md"]
}
```

Example:

```bash
scripts/okstra.sh --task-type error-analysis --project-id jobs --project-root /Volumes/Workspaces/workspace/projects/jobs ...
```

### Schedule generation skill

The public schedule entry point is the host skill `/okstra-schedule-gen [task-group]`; `stage-map` and `validate-schedule.py` are its backend contracts, not an additional schedule-generation shell command. For each candidate task, the skill branches on the `stage-map` `state` and `sourcePlanPath`, records the selected, completed, and full stage sets in a temporary `.selection.json`, and runs deterministic `--selection-json` validation before an independent narrative verifier. Only the same draft that passes both gates is promoted, and the temporary selection contract is removed after final validation.

### `--directive`

A free-text channel for passing user intent to the lead, workers, and downstream skills. The value is embedded in a `## Directive` section at the end of `instruction-set/analysis-material.md` and in `instruction-set/analysis-packet.md`, the analysis workers' primary input. It is also backed up to `instruction-set/directive.txt`.

Use it to:

- Override a default heuristic—for example, request a Gantt chart even for one XL task with two phases totaling about 10 days, or request an explicit reason for omitting one. `## Cumulative Timeline` has been removed from the schedule contract and is no longer a toggle.
- Direct the emphasis of analysis or planning—for example, prioritize rollout risks or report missing test coverage as a separate finding.
- Suggest Phase/Step divisions or day estimates in advance—for example, Part 1: 4d, measurement: 0.5d, Part 2: 4d, operations: 1d.

Interpretation rules:

- The lead and analysis workers read the compact intake (`active-run-context`, `analysis-packet.md`) first, so the directive propagates without separate work. `analysis-material.md` is a fallback when the packet is insufficient or source citations must be verified.
- A skill must give the user prompt **precedence** over its own contract—for example, the "Directive override (highest priority)" section of the `okstra-schedule-gen` skill. When a heuristic is overridden, the result document states that fact in one line.

Example:

```bash
scripts/okstra.sh --task-type implementation-planning ... \
  --directive "Use two sequential phases (Part 1 ~4d, Part 2 ~5d) for ~10d total. Include a Gantt chart with the critical path."
```

### `--fix-cycle`

- `--fix-cycle <yes|no>` records whether re-entry into an entry phase (`requirements-discovery` / `error-analysis` / `implementation-option-selection` / `implementation-planning`) after completion through release-handoff is a bug-fix cycle. When omitted, no cycle is recorded. The `fix_cycle_confirm` step in the okstra-run wizard accepts the same input.
- `yes` opens a new cycle only when both conditions hold: the task type is an entry phase, and the manifest's `workflow.lastCompletedPhase` is `release-handoff`. Preparation rejects the request if either condition is false. Once a cycle is open, later runs for the same task attach to it automatically without this flag.

### `--workers`

Compatibility input only. Prefer `--role-count` and `--role-model` for launch selection.

When supplied, the CSV of provider names converts into role slots and model refs for the profile's initial cross-verification role (for example analysers or planners). It is not a provider roster picker and is not the selection unit on the wizard start screen.

If the same role is also named by `--role-model` (or other canonical role flags), the converted providers must match the model-ref provider prefixes in order and slot count. Matching values keep the canonical role models and drop the legacy constraint. Different values fail before any worktree or state file is created.

Example:

```bash
scripts/okstra.sh --task-type implementation-planning --workers claude,codex --project-id jobs --task-group tasks --task-id 8852 --task-brief .project-docs/tasks/8852/BUG_REPORT.md
```

### `--lead-runtime`

Selects a lead adapter by registered host ID or alias. The default is `claude-code`. Terminal `okstra run <host-id-or-alias>` requests the same registry entry and verifies its `spawn-process` readiness before launch; an unavailable selected host is never replaced silently.

Lead runtime independence boundary:

- `claude-code`: the current default execution path. Claude Code v2.1.178 removed `TeamCreate`, and the session owns an implicit team. Workers are dispatched with `Agent(name: ..., run_in_background: true)` without `team_name`. `teamName` is audit/display metadata; Claude session JSONL is used for usage accounting.
- `codex`: the runtime marker for the Codex lead adapter. `okstra codex-run` owns `--render-only --lead-runtime codex` to prepare a task bundle, and the prepared run manifest can be passed to `okstra codex-dispatch` for CLI-backed worker execution.
- `antigravity`: the runtime marker for the Antigravity CLI lead adapter. The current Antigravity session owns the native lead, keeps Antigravity assignments native, and routes every other provider through the deterministic provider-process dispatcher.
- `grok` and `kimi`: registered native lead adapters for their corresponding CLIs. They can lead, analyse, and criticise, but they do not add executor, verifier, or report-writer worker roles.
- `external`: without Claude Code Teams, `--render-only --lead-runtime external` prepares the task bundle and external lead prompt. The lead manages the tmux-pane worker lifecycle with `okstra team dispatch`, `okstra team await`, and `okstra team teardown`. This path does not use `TeamCreate` / `Agent(...)` and uses artifact-only accounting.

Host IDs and providers are separate axes. Bundled and user-installed host adapters resolve from the registry; user adapters must be installed under `~/.okstra/adapters/hosts/<id>/` or `~/.okstra/adapters/providers/<id>/`. Project-local adapter code is not discovered.

The current Claude Code independence boundary covers the external lead prompt and `okstra team *` worker dispatch. non-render `okstra_ctl.run --lead-runtime external` remains blocked; a complete external lead driver remains separate future work. `--runtime external` only selects the runtime adapter. `okstra install` creates `~/.agents/skills/` by default and also installs Claude skills and agents when `~/.claude` exists. Selecting the `claude` worker still requires the local Claude CLI wrapper.

Host-runtime readiness is independent of worker selection. When `/okstra-setup`
creates `<PROJECT_ROOT>/.claude/settings.local.json` in an already-open Claude
Code session, that session may not have accepted workspace trust yet. The next
`/okstra-run` preflight stops before the wizard when trust is required or cannot
be verified. The default `okstra preflight` output is a fixed text projection
whose first line is `Okstra preflight: ready` or `Okstra preflight: failed`.
Success names the project ID, project root, runtime, readiness state, and relay
contract. Each adapter check retains its ID, status, and action as three
repeated lines. Failure names the stage, observed reason, and recovery action. The user runs
that recovery and repeats the command. This check applies only to a `claude-code` host; `codex`,
`antigravity`, and `external` hosts do not inspect Claude Code state, regardless of whether the
worker roster contains Claude, Codex, or Antigravity.

External automation uses `okstra preflight --machine`, which returns the
automation JSON contract with its existing exit codes. `--json` is a deprecated
one-release alias for `--machine`. Model-facing skills use the default text
projection and do not parse the machine response.

### Runtime auto-detection (`auto`)

`okstra run` defaults to `auto`. `src/lib/host-registry-client.mts` asks the Python host registry to resolve explicit IDs and aliases, the `OKSTRA_RUNTIME_HOST` environment declaration, Claude Code skill handoff, or the external tmux claim. It fails fast when no adapter claims the session. Installed CLI presence alone never selects a host.

- Inside a registered host, the installed `okstra-run` skill uses `current-session`, declares the live semantic function list, and reuses the session you are already in.
- From a terminal, `okstra run <host-id-or-alias>` uses `spawn-process` and starts the selected host CLI. The leading word is an alias for `--lead-runtime`.
- `okstra run external` does not start a lead — it orchestrates an all-CLI run through `okstra team`.
- Starting a lead whose CLI sandboxes itself prints what is given up and waits for a `y`; `--yes` answers it. Today only Codex asks, because a sandboxed lead can reach neither cmux nor the worker CLIs' own configuration.
- In a generic terminal with tmux, `okstra run` orchestrates `render-bundle --lead-runtime external` -> `okstra team dispatch` -> `okstra team await`.

`doctor` and `render-bundle` also default to `auto`, but a plain terminal without a host signal must specify a host. `okstra install` and `ensure-installed` are exceptions. Installation places the shared runtime and skill targets based on agent homes rather than the runtime host, so it records the `auto` request and unresolved state in the manifest and continues even without a host signal. Skill copy targets depend on the presence of agent homes and the default Agent target policy, not runtime resolution. `~/.agents/skills/` is always created, and Claude skills/agents are installed when `~/.claude` exists. The host-aware `okstra-run` skill passes the current harness runtime explicitly; Claude-only maintenance skills continue to pass `--runtime claude-code` where that host is part of their contract.

`installed-runtimes.json` uses schemaVersion 2 and separates `installRequest`, `runtimeResolution`, `installedRuntimes`, and `installedAssets`. `installed-skills.json` uses a schemaVersion 2 target list with `provider`, `root`, and `skills` to record Claude and Agent skill targets; uninstall removes files according to this list. `codexAdapter` / `externalAdapter` retain read compatibility with v1 manifests only.

For a Codex lead dry run, use `okstra codex-run <args...>`. It adds `--render-only --lead-runtime codex` itself and prints the prepared task bundle and lead prompt without dispatching workers.
The generated team-state, run manifest, and task manifest point `leadEventsPath` to `runs/<task-type>/state/lead-events-<task-type>-<seq>.jsonl`; rendering records a `bundle-prepared` event.
Then `okstra worker-dispatch --project-root <dir> --run-manifest <run-manifest> [--workers <csv>]` reads each persisted assignment. `runner=native-session` rows stay with the current host; `runner=cli-wrapper` rows run through the registered provider process only after adjacent invocation metadata verifies. Without `--workers`, only CLI-backed analysis assignments are selected. Report writer is deferred to Phase 6 and must be dispatched explicitly with `--workers report-writer`; mixing it with analysis workers in one invocation is rejected before process creation. `okstra codex-dispatch` is a compatibility alias for this provider-neutral command. The report-writer provider and model still come from the manifest without a Codex-only opt-in flag; on success, postprocessing runs check-source → token-usage substitution → render-views → spawn-followups → validate-run in order.

The Codex worker (`--workers codex`, `--codex-model`) and Codex lead runtime are separate. The former creates a worker assignment whose runner depends on the host; the latter selects Codex as the native lead boundary. On Claude Code the Codex worker uses a CLI wrapper, while on Codex it uses the host-native worker/session primitive.

> Every `--*-model` flag accepts only aliases registered in the provider mappings in `scripts/okstra_ctl/models.py`. An unregistered value is immediately rejected with `UnknownModelError`, preventing a contract violation where the manifest's `modelExecutionValue` differs from the actual execution value. Allowed values:
> - Claude (`--lead-model` / `--claude-model` / `--report-writer-model`): `fable`, `fable-5`, `claude-fable-5`, `opus`, `opus-5`, `claude-opus-5`, `opus-4-8`, `claude-opus-4-8`, `opus-4-7`, `claude-opus-4-7`, `opus-4-6`, `claude-opus-4-6`, `sonnet`, `sonnet-5`, `claude-sonnet-5`, `sonnet-4-6`, `claude-sonnet-4-6`, `haiku`, `haiku-4-5`, `claude-haiku-4-5`, `claude-haiku-4-5-20251001`
> - Codex (`--codex-model`): `gpt-5.6-sol`, `gpt-5.6`, `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex`, `gpt-5.2`, `codex-auto-review`
> - Antigravity (`--antigravity-model`): `gemini-3.1-pro` (default), `gemini-3.6-flash`, `gemini-3.5-flash`, and their space-separated aliases. The antigravity worker uses the `agy` CLI to run Gemini-family models, so model IDs retain the `gemini-*` form.
> - Grok (`--worker-model grok=<model>`): `grok-4.6`, `grok-4.5`, `grok-build-0.1`
> - Kimi (`--worker-model kimi=<model>`): `kimi-k2.7-code`, `kimi-for-coding`, `kimi-k3`, `k3`, `k3-256k` and their registered display aliases

### `--role-count`

Sets how many slots open for one static role: `--role-count <role>=<N>`. Repeat the flag for each role. `N` must fall in that role's profile range `min..max`. Omit the flag to use the profile **recommended** count (not a separate roster default). Roles with `min == max` are fixed quantity and reject this flag. Dynamic roles and roles absent from the profile also reject it. `lead` is a compatibility alias for `leader`. New records write `leader`.

Each confirmed count becomes `RoleInstance` ordinals. `ModelPool` then assigns one model ref per ordinal. A pinned model is kept only when the host can bind it exactly. Roles with `min = 0` do not open slots by default; optional add steps or an explicit count above zero open them.

### `--role-model`

Pins one model reference onto a role slot, in ordinal order: `--role-model <role>=<modelRef>`. Repeat the flag to fill later ordinals. `modelRef` is `<provider>/<model>`, for example `claude/opus-5` or `codex/gpt-5.6-sol`.

A known selectable model may be assigned to any canonical role. The same role must not receive the same model ref twice; duplicate refs in one role panel fail before any worktree or state file is created. Same provider with different models is allowed. Fewer models than the confirmed count are filled from the model-default chain. Extra models do not raise the count; set `--role-count <role>=<N>` first. Unknown roles and unknown model refs fail before side effects.

Legacy `--lead-model`, `--claude-model`, `--codex-model`, `--antigravity-model`, `--worker-model`, `--report-writer-model`, `--executor`, and `--workers` remain compatibility inputs. They convert to role-model selections. A new flag and a legacy flag that name different models (or, for `--workers`, different provider prefixes) for the same role fail.

`okstra model list [--role <role>] [--host <host>] [--json]` prints the catalog without an inference call. `okstra model default set|unset <role> ... --scope project|global` writes `modelDefaults` to `.okstra/project.json` or `~/.okstra/config.json`.

### `--lead-provider`

Compatibility assertion for previously recorded invocations. The value must match the host-native provider: `claude` on Claude Code, `codex` on Codex, and `antigravity` on Antigravity. It is not an independent lead selector. Other providers belong in the worker roster and run through CLI wrappers; preparation rejects a cross-provider lead rather than recording one model and silently running another.

### `--worker-model`

Adds generic `provider=model` overrides, separated by commas. It is the model input for registry providers without a legacy dedicated flag, including Grok and Kimi.

### `--report-writer-provider`

Selects a registry provider that declares the `report-writer` capability. Claude remains the default; Codex is also supported. The provider, model, and resolved runner are persisted in `workerAssignments[]`.

### `--claude-model`

Selects the model used by the `Claude worker`.
When omitted, it uses the central default `OKSTRA_DEFAULT_CLAUDE_MODEL`, falling back to `opus`.

### `--lead-model`

Selects the model used by the host-native Okstra lead. Claude Code resolves it through the Claude catalog; Codex resolves it through the Codex catalog; Antigravity resolves it through the Antigravity catalog. When omitted, the host-native provider's lead default applies.

### `--codex-model`

Selects the model used by the `Codex worker`.
When omitted, it uses the central default `OKSTRA_DEFAULT_CODEX_MODEL`, falling back to `gpt-5.6-sol`.

### `--antigravity-model`

Selects the model used by the `Antigravity worker`.
When omitted, it uses the central default `OKSTRA_DEFAULT_ANTIGRAVITY_MODEL`, falling back to `gemini-3.1-pro`.

### `--report-writer-model`

Selects the model used by the `Report writer worker`.
When omitted, it uses the central default `OKSTRA_DEFAULT_REPORT_WRITER_MODEL`, falling back to `sonnet`. The report writer does not vote in convergence, so its default sits below the lead tier to cut report-generation wall-clock; pass `--report-writer-model opus` to restore the previous behavior.

The central-default environment variables are:

- `OKSTRA_DEFAULT_LEAD_MODEL`
- `OKSTRA_DEFAULT_CLAUDE_MODEL`
- `OKSTRA_DEFAULT_CODEX_MODEL`
- `OKSTRA_DEFAULT_ANTIGRAVITY_MODEL`
- `OKSTRA_DEFAULT_REPORT_WRITER_MODEL`
- `OKSTRA_DEFAULT_EXECUTOR` (`claude` | `codex` | `antigravity`, fallback `claude`)

Fallback defaults are:

- Claude Code lead: `opus`
- Codex lead: `gpt-5.6-sol`
- Antigravity lead: `gemini-3.1-pro`
- `Report writer worker`: `sonnet`
- `Claude worker`: `opus`
- `Codex worker`: `gpt-5.6-sol`
- `Antigravity worker`: `gemini-3.1-pro`
- Implementation executor: `claude`, so the default is `Claude executor`.

### `--executor`

Selects the provider that performs the Executor role for `--task-type implementation`. The value is `claude`, `codex`, `antigravity`, `grok`, or `kimi`; it is ignored for other task types.

- Default: `OKSTRA_DEFAULT_EXECUTOR` → fallback `claude`.
- The Executor is the **only worker allowed to mutate project files** in this run. The other providers are dispatched as strict read-only verifiers in the same run.
- The Executor reuses the provider's worker model flag. With `--executor codex`, its model comes from `--codex-model`, default `gpt-5.6-sol`; with `--executor antigravity`, it comes from `--antigravity-model`, default `gemini-3.1-pro`. With `--executor grok`, its model comes from `--worker-model grok=`, default `grok-4.6`.
- All three Claude, Codex, and Antigravity verifiers are always dispatched regardless of the Executor provider. Even the verifier using the same provider runs in a separate CLI session with isolated context, preserving the self-review safeguard.
- Codex and Antigravity mutate files through each CLI's auto-edit mode, for example `codex exec --sandbox workspace-write`, without passing through Claude-side Edit/Write tools. Mutations occur in the task worktree described below. Every `okstra-<provider>-exec.sh` entrypoint receives the worktree path as its fourth positional argument and adds it to the worker's write scope, which each provider is told as repeated `--add-dir` (Codex names the project root with `-C` and skips the repeat). Without it, the Codex `workspace-write` sandbox rejects worktree writes with EPERM.
- **Claude Executor cwd handling**: Claude's Bash tool has no per-call cwd argument and inherits the lead session cwd. To run cwd-sensitive toolchains such as `cargo`, `npm`, `pnpm`, `bun`, `pytest`, `make`, or `go` inside the worktree, prefix the invocation with `cd {{EXECUTOR_WORKTREE_PATH}} && <cmd>`. Keep `cd` as the leading token in a single Bash call so Claude Code permission auto-allow works; do not wrap it in `bash -lc "..."` or `bash -c "..."`, which hides `cd` and causes a permission prompt on every call. Prefer a tool's working-directory option—such as `git -C <path>`, `cargo --manifest-path`, or `pytest --rootdir`—over a `cd && ` chain. Edit/Write/Read tools already use absolute paths and need no cwd handling. This rule applies only to the Claude Executor; the Codex and Antigravity wrappers inject cwd.
- **Task worktree (automatic isolation for every task type)**: During the first phase's preparation for any task type, `okstra-ctl` creates a `git worktree` at `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/` and branches `<work-category-namespace>/<task-id-segment>` from the resolved commit of the user-selected `--base-ref`, for example `feature/dev-9436` or `fix/dev-7311`. Later phases for the same task key reuse the path and branch and record status `reused`; no new `git worktree add` occurs during run preparation. Special characters such as `/` and `:` in every segment are normalized to `-`, and `~/.okstra/worktrees/registry.json` globally manages task-key-to-path/branch mappings under flock. Executor edits, writes, builds, tests, and commits—and verifier reads—run in this worktree. If the caller is already in another worktree or project_root is not a Git repository, provisioning is skipped and records `skipped-in-worktree` or `skipped-not-git`. Path or branch collisions fail immediately with `PrepareError`. Worktrees are not deleted after a run; remove one manually with `git worktree remove`, then `git branch -D`, then delete the registry entry. **The implementation stage isolation below is the exception.**
- **`implementation` stage isolation (concurrent parallelism)**: The task-key worktree above applies only from `requirements-discovery` through `implementation-planning`. Each `implementation` run executes in a **stage-specific isolated worktree** at `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/stage-<N>/`, on branch `<work-category-namespace>/<task-id-segment>-s<N>`. The registry atomically reserves a stage key, `<task-key>#stage-<N>`, under flock. `_resolve_effective_stages` excludes `started` rows in `consumers.jsonl` and reserved stages. Stage selection, worktree creation, and registry reservation all happen in one critical section protected by the task-key provisioning mutex at `~/.okstra/.locks/worktree-provision/`, so concurrent `implementation` runs safely select different ready stages: **one run = one stage**. A stage worktree's base depends on its dependency shape: independent (`depends-on (none)`) uses the common anchor fixed once at first stage entry; a single dependency (`depends-on X`) uses the predecessor stage's completed `head_commit`; multiple dependencies (`depends-on X,Y…`) use task-worktree HEAD after all predecessors have been merged, verified with `git merge-base --is-ancestor`, and otherwise fail with `PrepareError` and merge guidance. Select the stage with `--stage <auto|N>` for `okstra.sh`/`render-bundle`, or with the okstra-run wizard's `stage_pick` step. If `project_root` is not a Git repository or is a nested worktree, stage isolation also degrades to flat operation.
- **Single-stage `final-verification` artifact isolation**: `--task-type final-verification --stage <N>` reuses the implementation stage worktree read-only from the registry. Run artifacts are isolated per stage under `runs/final-verification/stage-<N>/`, and the team name receives a `-fv-s<N>` suffix. Concurrent final-verification runs for different stages do not collide in state, worker results, or team names. For concurrent verification of the same stage, `teamName` is only an audit label and each session has its own implicit team, so the sessions can coexist without a TeamCreate/name collision. This statement is limited to per-session team identity and does not claim safety for shared mutable state. Whole-task verification with an empty stage keeps the flat `runs/final-verification/` layout.

Example:

```bash
scripts/okstra.sh --task-type implementation \
  --executor codex \
  --codex-model gpt-5.5 \
  --approved-plan .project-docs/.../runs/implementation-planning/.../reports/final-report-implementation-planning-001.md \
  --project-id jobs --task-group tasks --task-id 8852 \
  --task-brief .project-docs/tasks/8852/BUG_REPORT.md
```

### `--critic`

Selects the provider for the required critic slot on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification`. The value is `claude`, `codex`, `antigravity`, `grok`, or `kimi`. `--critic off` is rejected. Prefer `--role-model critic=<provider>/<model>` so the user picks the model. The critic also settles plan-body 1-1 splits in `implementation-planning` (`critic-worker` on `--tie-vote` items).

- Critic dispatch runs concurrently with the first convergence reverify round in Phase 5.5, with critic input fixed to the integrated Round 0 result. One gap/blocker verification round runs in Phase 5.6 after convergence finishes. It detects coverage gaps in discovery, error-analysis, and implementation-planning, and acts as an acceptance devil's advocate in final-verification. Follow the "Coverage critic pass" and "Acceptance critic pass" sections of `prompts/lead/convergence.md` for the detailed contract.
- It shares the same value space as the critic-selection step in the in-session `okstra-run` wizard. `_resolve_model_bindings` in `prepare_task_bundle` validates the value; anything else is rejected immediately with `PrepareError`.

### `--approved-plan`

Accepts the path to a previous `implementation-planning` **report record** (`.data.json`) as input to `--task-type implementation`. The record's `frontmatter.approved` field must be `true`. A full reading copy (`.md`) is rejected. Schema-v1 plans have no record and cannot be passed here.

A selected-direction plan also must have `planningContract: selected-direction`, `outcome: plan-ready`, exact plan coverage, and a valid `selectedDirectionRef`. It does not accept `--implementation-option`. An already approved candidate-comparison plan without the discriminator keeps its candidate-selection behavior, including an explicit `--implementation-option` or fallback to the recommended option.

Approval format:

- The report record (`final-report-*.data.json`) owns `frontmatter.approved` as a boolean.
- The report writer always publishes `approved: false`. Implementation becomes available after the user approves it.

When the record is not `approved: true`, the run is rejected as `contract-violated`. Use `--approve` or the in-session wizard. Editing the full reading copy does not approve the plan.

### `--approve`

Used with `--approved-plan` and `--task-type implementation`, this flag **treats the CLI invocation itself as plan approval**:

- Sets `frontmatter.approved` to `true` on the report record and refreshes the full reading copy from that record.
- If the record is already `approved: true`, leaves the record unchanged.
- If the report record has no `frontmatter.approved` field (schema-v1: no markdown `approved:` line), exits immediately with an error because the path may refer to the wrong plan file.

Using `--approve` outside `--task-type implementation` is meaningless and exits with an error. Selected-direction semantics are validated before this flag can mutate the plan. Use it in CI or scripts, or when approval and the next phase must happen in a single command. The former `--ack-approved` alias was removed in 0.8.0.

Example:

```bash
scripts/okstra.sh --task-type implementation \
  --approve \
  --approved-plan .okstra/tasks/.../runs/implementation-planning/reports/final-report-implementation-planning-001.data.json \
  --project-id jobs --task-group tasks --task-id 8852 \
  --task-brief .project-docs/tasks/8852/BUG_REPORT.md
```

### `--work-category`

Classifies the work in this task. Allowed values:

- `bugfix`
- `feature`
- `refactor`
- `ops`
- `improvement`

Normally, the `requirements-discovery` phase infers work-category automatically. In a lifecycle that skips that phase—for example, one starting directly at `implementation-planning`—use this flag to classify the work explicitly. The value is preserved in `task-manifest.json` and used for grouping by the schedule and status skills.

The effective value for a run is resolved by `scripts/okstra_ctl/work_categories.py` (`resolve_work_category`) in this order: **the explicit flag → the classification already recorded in the task's `task-manifest.json` → `feature`** (`DEFAULT_WORK_CATEGORY`). This is why a later phase run without `--work-category` still lands on the same branch namespace that `requirements-discovery` classified—for example `feature/dev-9436` rather than the `task/` fallback. The `okstra-run` in-session wizard never asks for this flag, so it always takes the resolved path; its worktree preview runs the same resolver, so the preview matches the branch that is actually created. Already-created `task/…` branches are not renamed automatically—the registry key and `consumers.jsonl` history would drift, so they need manual cleanup.

Example:

```bash
scripts/okstra.sh --task-type implementation-planning \
  --work-category refactor \
  --project-id jobs --task-group tasks --task-id 8852 \
  --task-brief .project-docs/tasks/8852/PLAN.md
```

### `--base-ref`

The Git ref used as the base when the isolated worktree for a task key is first created. It may be a branch name, tag, or commit SHA.

- Required during first-phase preparation. The `okstra-run` skill collects it through the same menu as the PR base picker: `main`, `dev`, `staging`, `preprod`, `prod`, or custom input.
- Later phases for the same task key reuse the worktree path, branch, and base ref stored in the registry, so a new value is ignored.
- If `project_root` is not a Git repository or is already inside a non-main worktree, provisioning is skipped and this value is unused.

Example:

```bash
scripts/okstra.sh --task-type requirements-discovery \
  --base-ref main \
  --project-id jobs --task-group tasks --task-id 8852 \
  --task-brief .project-docs/tasks/8852/BRIEF.md
```

### `--related-tasks`

Accepts a comma-separated list of related task identifiers.

Example:

```bash
scripts/okstra.sh --task-type error-analysis --related-tasks scanner-regression,reply-ui --project-id jobs --task-group tasks --task-id 8852 --task-brief .project-docs/tasks/8852/BUG_REPORT.md
```

Related tasks are reference metadata and do not replace the current task key.

### `--render-only`

Creates the prompt and task bundle without executing Claude.

This mode does not:

- Execute Claude.
- Create `final-report-<task-type>-<seq>.md`.
- Create `final-<task-type>-<seq>.status`.

It does:

- Create or update the stable task root.
- Create or update `task-manifest.json`.
- Create or update `task-index.md`.
- Create or update `instruction-set/`.
- Create or update `instruction-set/reference-expectations.md`.
- Create or update `instruction-set/final-report-template.md`.
- Record the current `manifests/run-manifest-<task-type>-<seq>.json`.
- Update `history/timeline.json`.
- Create or update the project-level discovery pointer.

### `--no-plan-verification`

Disables the Phase 6 plan-body verification round for the `implementation-planning` task type. It is enabled by default and ignored for other task types.

- **Enabled (default)**: Immediately after the report-writer worker drafts its narrative in Phase 6, the lead extracts the synthesized plan into `P-*` items and dispatches them for reverification to every analyzer worker: `claude`, `codex`, and opted-in `antigravity`. A selected-direction plan uses `P-Dir-1` plus its step, dependency, validation, rollback, requirement, preparation, and variation items. A legacy candidate plan retains `P-Opt-*`. Worker verdicts (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`) are aggregated into one of four gate results: `passed`, `passed-with-dissent`, `blocked-by-disagreement`, or `aborted-non-result`. The approval control is available only for `passed` or `passed-with-dissent`. Items with majority DISAGREE become rows with `Blocks=approval` in `## 1. Clarification Items`. There is no automatic revision; the user answers and resumes the same phase.
- **Disabled (with `--no-plan-verification`)**: The entire Phase 6 substep is skipped and the Approval marker is always rendered at the top of the final report, matching legacy behavior. This is a fast-iteration opt-out and is not recommended for a handoff-ready plan.
- **Advisory auto-path (not this flag)**: when `designPreparation.mode` is `no-design-inputs` and the Stage Map has exactly one row, `okstra plan-items prepare` sets `convergence.planBodyVerification.gating=false`. Extraction and one verification round still run; the self-fix loop and a sweep batch do not. Two-or-more stages, a PREP item, or non-empty design-preparation items keep `gating=true`.
- The flag records `false` in the manifest at `convergence.planBodyVerification.enabled`. The resume command must include the same flag to preserve behavior; `_canonical_argv` guarantees faithful emission on resume.
- For the detailed round protocol, verdict semantics, and state-file schema, see the "Plan-body verification mode (implementation-planning only)" section of `prompts/lead/convergence.md`.

## Interactive input flow

When required arguments are omitted in an interactive terminal, `okstra` may ask for these values in order:

- `Project ID`
- `Task Group`
- `Task ID`
- `Task Type`
- `Task Brief Path`

For example:

```bash
scripts/okstra.sh --render-only --task-type error-analysis
```

The command reads missing required values from stdin, then proceeds through the final summary and confirmation.

## Confirmation flow

Before creating files in an interactive run, okstra shows a summary containing:

- Whether the run is render-only.
- Task type.
- Project ID.
- Task group.
- Task ID.
- Task brief path.
- Analysis target.
- Related tasks.
- Selected workers.
- Lead model.
- Configured worker models.
- Task key.
- Task root.
- Current run directory.

It then displays `Continue? [y/yes]:`. Any response other than `y` or `yes` exits without executing.

`--yes` skips this confirmation flow entirely.

---

<a id="control-center-install-and-common-commands"></a>
## okstra Control Center — installation and common commands

## okstra Control Center

`okstra-ctl` is a CLI for querying, monitoring, and rerunning okstra runs across all target projects through the indexes under the user's `~/.okstra/` home.

<a id="install-global-wrapper"></a>
### Installation (global wrapper)

As with `okstra`, placing a wrapper under `~/.local/bin/` makes it available from any directory.

```bash
cat > ~/.local/bin/okstra-ctl <<'WRAPPER'
#!/usr/bin/env bash
exec /Volumes/Workspaces/workspace/projects/Okstra/scripts/okstra-ctl.sh "$@"
WRAPPER
chmod +x ~/.local/bin/okstra-ctl
```

`~/.local/bin` must be included in `PATH`.

<a id="common-commands"></a>
### Common commands

| Purpose | Command |
|---|---|
| List projects with recorded work | `okstra-ctl projects` |
| Find recent runs | `okstra-ctl list --since 7d` |
| Restrict results to one project | `okstra-ctl list --project sample-project` |
| View in-progress runs | `okstra-ctl tail active` |
| Show metadata for one run | `okstra-ctl show <runId-or-prefix>` |
| Print the result-report path | `okstra-ctl open <runId-or-prefix>` |
| Regenerate a final-report HTML view | `okstra render-views <final-report.data.json\|final-report.md>` |
| Rerun one run | `okstra-ctl rerun <runId-or-prefix> --yes` |
| Rerun multiple filtered runs | `okstra-ctl rerun --filter --project X --status failed --yes` |
| Rerun the latest matching run | `okstra-ctl rerun last --project X --task-group Y --yes` |
| Backfill or rescan indexes | `okstra-ctl reindex` |
| Reconcile active runs | `okstra-ctl reconcile [--project <id|all>]` |
| Show batch progress | `okstra-ctl batch status <batch-id>` |

### `okstra` Node CLI — introspection subcommands

The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and introspection commands used by skills and agents. It goes through the Node wrapper instead of invoking the Python runtime directly, so `src/lib/python-helper.mts` wires `PYTHONPATH`.

| Command | Purpose |
|---|---|
| `okstra paths [--field <name>\|--shell]` | Print package, runtime, home, bin, Python path, and version locations |
| `okstra install [--runtime claude-code\|codex\|antigravity\|external\|all] [--refresh\|--dry-run\|--link <repo>]` | Install or update the runtime, templates, skills, and agents. The default runtime is `auto`; skill targets are not runtimes. `~/.agents/skills/` is always created, and Claude skills and agents are also installed when `~/.claude` exists |
| `okstra ensure-installed [--runtime claude-code\|codex\|antigravity\|external\|all] [-q]` | Check installation state and reinstall stale assets for the same runtime. Default `auto` continues without a host signal and checks drift in the Agent skill target and any existing Claude target |
| `okstra uninstall [--purge -y]` | Remove installed assets. By default, removes files listed by the `installed-skills.json` targets and `installed-agents.json` while preserving user data |
| `okstra doctor [--runtime claude-code\|codex\|antigravity\|external\|all] [--phase <phase>] [--json]` | Diagnose the runtime, Python imports, skill/agent installation, and model-pool state. The JSON `modelPool` object reports pool errors, default errors, binding precision, observed model, max write boundary, and invocation downgrade reason. It never sends an inference call. The `codex`, `antigravity`, and `external` runtimes omit Claude skill checks. `--phase` adds readiness checks for `implementation`, `final-verification`, `release-handoff`, or `improvement-discovery` |
| `okstra model list [--role <role>] [--host <host>] [--json]` | List catalog models for a host and optional role. Unselectable exact bindings report `exact binding unavailable`. No inference call |
| `okstra model default set\|unset <role> ... --scope project\|global [--cwd <dir>]` | Atomically write or remove `modelDefaults` for one canonical role |
| `okstra setup --project-id <id>` | Create or update `.okstra/project.json` in the current project |
| `okstra check-project [--json]` | Verify that the current project is registered |
| `okstra preflight [--runtime <name>] [--cwd <dir>] [--machine]` | Single skill-preflight call combining `ensure-installed`, with silent reinstall when stale, `check-project`, and host-specific `runtimeReadiness`. It defaults to a fixed text projection. `--machine` returns the automation JSON contract, and `--json` is a deprecated one-release alias. A `claude-code` host checks project workspace trust. A `codex` current-session host verifies write access to `~/.okstra/worktrees/registry.lock`; a sandbox denial blocks before the wizard with the `switch-codex-to-full-access-and-rerun` action. `antigravity` and `external` hosts return ready without reading Claude Code state. Step 0 of every project-scoped skill converges on this command |
| `okstra convergence seed --groups <path> --work-state <path> --final-state <path> --migration-dir <dir> [--restart-from-round0]` | Create, resume, reuse, or explicitly recover deterministic convergence state |
| `okstra convergence plan-round --work-state <path> --plan <path>` | Persist the next roster-aware dispatch plan without mutating working state |
| `okstra convergence collect-results --plan <round-plan.json> --mode <adversarial\|collaborative> --result <worker>=<path>… --dispatch <worker>=<status>:<durationMs>… --output <round-results.json>` | Read one round's worker responses into the `apply-round --results` shape. `--mode` picks the verdict vocabulary — the adversarial prompt answers `REFUTED` / `SURVIVES` / `SURVIVES-WITH-CAVEAT` / `UNVERIFIABLE`, which this maps to `disagree` / `agree` / `supplement` / `unverifiable`, and copies `**Basis**` into `disagreeBasis`. `--dispatch` supplies the terminal status and duration, which live in the dispatch rather than the response; a worker that never returned gets a `--dispatch` and no `--result`. Exits 2 on a dispatched finding with no verdict, a verdict for a finding the plan did not dispatch to that worker, a planned worker with no recorded outcome, or a vote with no explanation |
| `okstra convergence apply-round --work-state <path> --plan <path> --results <path>` | Validate one complete structured result set and atomically reduce it into working state |
| `okstra convergence apply-critic-gaps --work-state <path> --results <path>` | Apply one verified coverage-critic batch after the main queue reaches a terminal state |
| `okstra convergence finalize --work-state <path> --output <path>` | Materialize the terminal schema v1.3 convergence state |
| `okstra convergence validate --state <path> --kind <working\|final>` | Validate replayable working state or a terminal final state |
| `okstra convergence example --kind <groups\|round-results\|critic-results\|coverage-batch>` | Print one deterministic valid input example as JSON. Each kind feeds one command: `groups` → `seed --groups`, `round-results` → `apply-round --results`, `coverage-batch` → `apply-critic-gaps --results`. `critic-results` feeds nothing — it is the critic worker's own result document, and feeding it to `apply-critic-gaps` is rejected by design; that reducer takes the coverage batch the lead assembles from those candidates plus each analyser's vote, which is what `--kind coverage-batch` prints |
| `okstra plan-items extract (--narrative <report-narrative.md>\|--data <historical-data.json>) --output <items.json>` | Deterministically extract the complete implementation-planning `P-*` queue. Contract v3 reads the report-writer narrative before publication; `--data` is the historical v2 reader. |
| `okstra plan-items validate (--narrative <report-narrative.md>\|--data <historical-data.json>) --items <items.json>` | Require the persisted `P-*` queue to match a fresh deterministic extraction exactly. |
| `okstra plan-items derivations --data <data.json> --response <user-response sidecar> [--clarification C-NNN]` | List the plan statements an answered clarification may have falsified. Extracts the symbols, paths, and ids the answer names (backticked spans plus `R-001` / `DEV-10174`-style ids) and reports every string in the plan body that mentions one, as a JSON pointer plus excerpt. Advisory: it says where a decision's subject is mentioned, never which mentions are now wrong — the supersession rule (`prompts/profiles/_common-contract.md`) requires the author to enumerate before editing, and this supplies the enumeration |
| `okstra plan-items <prepare\|prompt\|validate-prepared> --run-manifest <path> …` | Bind the implementation-planning verification queue to the run manifest. `prepare` extracts the exact queue from `--narrative` and, when `designPreparation.mode` is `no-design-inputs` and the Stage Map has one row, flips `convergence.planBodyVerification.gating` to `false` (stdout `Gating`). `prompt` emits its fixed lossless view; `validate-prepared` proves the prepared queue still matches the narrative. Python resolves the convergence-owned state path, so model callers never choose it. |
| `okstra plan-items seed --narrative <report-narrative.md> --state <plan-body-verification.json>` | Create the convergence-owned `planBodyVerification.planItems[]` rows every verdict lands in, from the same deterministic extraction `extract` uses. The historical v2 form is `--data <data.json>`. Idempotent by id: an existing row keeps its verdicts and carried fields. Reports `seeded` / `existing` counts. |
| `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md>… --round <N>` | Read each worker's Markdown verdict directly, validate every current `P-*` id, reject duplicate worker submissions, and overwrite that round's stored verdicts. The historical `--verdicts <file>` form remains automation compatibility only. |
| `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N> [--self-fix-note <item-id>=<markdown-file>]… [--self-fix-group <cause-file>=<item-id>[,<item-id>...]]… [--self-fix-stop-reason <all-resolved\|no-progress\|max-rounds-reached>]` | After `plan-verify` succeeds, atomically derive and record the round's per-item votes, gate result, participant counts from the actual assigned roster, immutable completion time, convergence history, and optional self-fix notes/groups read from Markdown files. Models do not write the state JSON. Stdout also carries `nextDispatch`. |
| `okstra plan-items next-dispatch --state <plan-body-verification.json> [--run-manifest <path>]` | After `apply-verdicts`, decide whether this round opens a worker batch. `kind: none` — missing-dependency `UNVERIFIABLE` only, no new batch. `kind: worker-correction` — re-prompt only those workers; peers stay idle. `kind: critic-tie` — unsettled analyser 1-1, `critic-worker` on those item ids only. A missing path is never environment-unverifiable. |
| `okstra plan-items correction-prompt --state <plan-body-verification.json> --run-manifest <path> --worker <id>` | Emit the planning-time environment-gap paragraph, then the assigned queue. The environment exception is first. Exits 2 unless `next-dispatch` named that worker as a blanket-UNVERIFIABLE correction target. |
| `okstra error-log append-observed --out <errors.jsonl> --task-key <key> --phase <phase> --agent <assigned-worker-id> --agent-role worker --model <model> --error-type tool-failure --command-file <markdown-file> --command-kind <kind> --message-file <markdown-file> [--cause <cause> --evidence-file <kind=file>]…` | Worker-facing typed error recording surface. Python validates and serializes the JSONL record; a worker supplies scalar identity fields plus Markdown files for free-form command, message, and probe content, never a JSON sidecar or JSON argument. `sandbox-denied` requires both `targetProbe` and `controlProbe` evidence files. |
| `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
| `okstra memory <add\|list\|search\|show\|archive>` | Manage global conversation memory in `~/.okstra/memory-book`, a user-home store separate from project `.okstra/` and the CLI basis of the `save this in okstra` natural-language skill |
| `okstra chat <rooms\|create\|join\|members\|send\|unread\|inbox\|log\|ack>` | Global rooms under `~/.okstra/chat`. Join takes an explicit `--name`. Send takes exactly one of `--to` (`all` or a member; may equal `--as`) or `--reply-to` (an id in that room), and exactly one of `--body` or `--body-file`. A reply inherits `to` from the parent (`all` stays `all`, otherwise the parent's `from`). Unread is the inbox after the read cursor minus messages the participant sent; it does not move the cursor. `inbox` and `log` keep those arrivals, including after ack. Rows are `id @from YYYY-MM-DD HH:MM body`; a reply inserts `↑parentId` after the time. CLI basis of the `okstra-chat` skill. |
| `okstra model-io active-context-input --project-root <dir> --run-manifest <path>` | Resolve the prior implementation-planning active context only through the current project and run authority, then emit fixed fields such as the executor base ref. Caller-supplied active-context paths are not accepted. |
| `okstra report-translate <source\|write\|check-data> --run-manifest <path> …` | Resolve the report data and translation sidecar from the run manifest. `source` emits a source digest with the fixed translation queue; `write` requires that digest and rejects a stale report; `check-data` verifies the derived sidecar. Models never choose the data or sidecar path. |
| `okstra convergence prepare-groups --run-manifest <path> --input <grouping.md>` | Parse fixed grouping Markdown, validate distinct per-worker evidence and group semantics against the run authority, then publish the schema-valid groups artifact at the run-owned path. |
| `okstra manager <init\|discover-projects\|new\|task> [--json]` | Public CLI for grouping cross-project okstra tasks into manager-owned context. The default is purpose-specific fixed text for model use; `--json` preserves the machine contract and exit codes. `new project --project-root` accepts only existing directories and performs setup-equivalent registration only if `.okstra/project.json` is absent. |
| `okstra rollup [--task-group <group>] [--project-root <dir>] [--cwd <dir>] [--text\|--json]` | Read-only backend for the okstra-rollup skill. `--text` emits ordered fixed labels for model use. The default and `--json` preserve the full machine JSON contract and exit codes. Omitting `--task-group` targets the whole project catalog. |
| `okstra usage-report [--days <positive-int>] [--project-root <dir>] [--cwd <dir>] [--text\|--json]` | Read-only backend for the okstra-usage skill. `--text` emits ordered fixed labels for model use. The default and `--json` preserve the full machine JSON contract and exit codes. Defaults to the current project's last 30 days. |
| `okstra worker-state transition --team-state <path> --worker <id> --status <in-progress\|completed\|timeout\|error\|not-run> [--reason <text>] [--model <execution-value>]` | Atomically update one persisted worker row. `in-progress` records the authoritative `startedAt` and clears `endedAt`; terminal states record `endedAt`; `timeout`, `error`, and `not-run` require a reason. Dispatch adapters use this same transition path, so CLI-backed and in-process orchestration share the status timestamp contract |
| `okstra worker-liveness [--team-state <path> --worker <id>]… [--max-idle <seconds>] [--launch-grace <seconds>] [--stall-confirm <seconds>] [--json]` | Judge whether pending workers are still alive so the lead's poll ends a stalled wait early instead of paying the full deadline. The selector repeats; each `--team-state` must have a paired `--worker`. The worker row's `livenessMode` picks the probe: `audit-heartbeat` reads its `auditSidecarPath` and reports `stalled` when the `- PROGRESS:` heartbeat is past the idle budget; `wrapper-status` reads its `promptPath` and reports `did-not-launch` when neither the wrapper `.log` nor `.status.json` appears. Both graces start at the persisted `startedAt`, never at an artifact mtime — the audit sidecar is reused on re-dispatch, so a heartbeat older than this dispatch counts as no signal yet rather than a stall. A heartbeat budget breach is confirmed before it is reported: the probe re-reads the sidecar after `--stall-confirm` seconds (default: half that stage's budget; `0` disables) and reports `stalled` only when the newest heartbeat has not advanced, so a worker inside one long uninterruptible tool call is not judged dead for being slow. Healthy probes report `live`. It only judges—it never kills or re-dispatches. Exit 1 on an unhealthy verdict, so a poll loop can branch without parsing JSON. The heartbeat line shape and budget come from the `okstra_ctl.worker_heartbeat` SSOT shared with the Phase 7 audit (`validators/validate_session_conformance.py`) |
| `okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <type> --seq <nnn> [--worker <id>]` | Apply the Phase 7 worker audit-sidecar rules mid-run, while the worker session is still alive. For each of this run's `worker-results/<worker>-<task-type>-<seq>.md` it checks that the file carries no `## 0. Reading Confirmation` heading, that the matching audit sidecar exists, and — for prompts carrying the required-v1 evidence-ledger marker — that every backticked `path:line` citation has an Evidence read row in that sidecar. `--worker` scopes it to the role that just returned. Emits `{ok, failures[]}` and exits 2 when `failures[]` is non-empty. The rules come from the `okstra_ctl.worker_audit_ledger` SSOT shared with `validate-run.py`, so an early pass and the Phase 7 pass cannot disagree. Run it right after collecting a result: the same failure at Phase 7 leaves only a retroactive edit, which breaks the audit chain, or a failed run |
| `okstra log-report [--project-root <dir>] [--cwd <dir>] [--top <N>] [--json]` | Read-only inventory of wrapper transcript `.log` files and their sibling prompt `.md` files. Each ranked entry preserves `path` / `sizeBytes` for compatibility and also reports `transcriptPath`, `transcriptBytes`, `promptPath`, `promptBytes`, and `transcriptToPromptRatio`; totals distinguish prompt bytes from transcript bytes and count paired files. Ranking remains transcript-size descending |
| `okstra recap <assemble\|record\|note> <task-root\|task-key> …` | Backend for the okstra-inspect `recap` facet. `assemble` is read-only and prints a JSON summary of phase transitions across a task's runs. `record --kind <summary\|qa> --mode <artifact\|code> --answer <text> [--question <text>] [--citation <path:line> …]` appends one line to `<task-root>/recap/recap-log.jsonl` and never mutates other artifacts. `note --kind <verification-evidence\|decision-draft\|analysis-note> --slug <topic> --purpose <text> --scope-note <text> (--body <markdown>\|--body-file <path>)` writes an agent-authored note to `<task-root>/notes/` and prints its path plus the `--clarification-response` argument for feeding it into a later run |
| `okstra user-response <list-view\|show-view\|begin\|answer\|plan-decision\|legacy-report-authoring\|finalize> …` | Backend for the `/okstra-user-response` skill. `list-view` and `show-view --report <md\|data.json> --project-root <dir>` are fixed-text model views; `show-view` validates that the report belongs to the explicit project root and prints each open row's why-asked line, linked plan items, and cited `path:line` artifacts so the skill can read them before asking. The legacy `list` and `show` JSON reads retain their automation-compatible fields. `begin --report <md\|data.json> --task-key <key>` returns an opaque transaction id. A predefined clarification choice uses `answer --transaction <id> --clarification-id <C-NNN> --kind <kind> --option-number <N>`; Python resolves the answer, disposition, reach, and scope effects from the validated report. Direct input instead uses `--disposition <answer\|reframe> --value-file <md> [--rationale-file <md>]`. Every value, rationale, and reason file must be a regular file under `<PROJECT_ROOT>/.okstra/tmp/user-response/`; external paths and symbolic links are rejected. `plan-decision` accepts `approved`, `revision-requested`, or `rejected`, validates any `--implementation-option` against the report candidates, and requires `--reason-file` for the latter two statuses. `legacy-report-authoring` is restricted to report contract 2.0. `finalize` validates the complete existing sidecar before a lossless merge, uses compare-and-swap under a run-local lock, and atomically publishes only the user-owned sidecar; exit 0 ok / 1 error. |
| `okstra pr <template\|branches\|gen> … [--json]` | Backend for the okstra-pr-gen skill. Git-only—no project registration required. `template list\|show <name\|default>\|add --name <name> (--content <text>\|--file <path>)\|path` manages PR body templates under `~/.okstra/template/pr/` (bundled fallback `src/commands/pr/default.md`); `branches` recommends a base branch; `gen --base <ref> [--template <name\|default>]` emits fixed `Base`, `Current branch`, `Template name`, `Commits`, `Diff stat`, and `Template` sections by default. `--json` preserves the machine bundle for automation. |
| `okstra migrate [--apply] [--cwd <dir>] [--quiet]` | One-time migration of the project artifact root from `.project-docs/okstra/` to `.okstra/`. It is a dry run by default; `--apply` performs the move with `git mv` in a Git worktree, removes an empty `.project-docs/`, and synchronizes the `<PROJECT>/CLAUDE.md` import line, `.gitignore`, the project's rows in `~/.okstra/{recent,active}.jsonl`, and `~/.okstra/worktrees/registry.json`. It exits 1 if `.okstra/` already exists or the legacy directory is absent. Scheduled for removal by the end of v0.x |
| `okstra task-list [--project-root <path>]` | Combine `list_project_tasks` and `read_latest_task` into JSON containing the task catalog and latest task |
| `okstra task-show <task-key> [--project-root <path>]` | Summarize workflow, phase, status, and artifacts from the Task Read-Side Snapshot |
| `okstra stage-map <task-key> [--cwd <dir>\|--project <dir>]` | Dump the task's implementation-planning Stage Map as JSON: `{ ok, taskKey, taskRoot, state, sourcePlanPath, stages:[{stage_number,title,depends_on,step_count}], doneStages:[int] }`. `state` is `ready` for one resolved source and `missing` when no Stage Map exists; corrupt or conflicting sources return structured non-zero errors instead of silently selecting another report. `doneStages` is read from the implementation-planning stage consumer state (with carry recovery). This is the read-side source `/okstra-schedule-gen [task-group]` uses to derive selectable unfinished stages and their completed dependency closure |
| `okstra incremental-scope <args…>` | Decide re-verify vs carry-forward scope for an `implementation-planning` clarification re-run. Thin shim into `scripts/okstra_ctl/incremental_scope.py` (deterministic pure function): it reads the dependency graph from the prior run `data.json`'s `implementationPlanning.stageMap` and returns `mode:"incremental"` only when the base-ref SHA is unchanged and the affected stages' `downstream_stage_closure` covers at most half of all stages; `--full-reason` (selected option / Stage Map / approach) still forces `mode:"full"`. An answered `C-NNN` that traces to no stage returns `mode:"unresolved"` rather than full — pass `--impacted` with the stage numbers or `--full-reason`. `--preview --prev-data <path> --answered-clarifications <csv>` runs the link half alone — no base SHA, no side effects — and prints `{wouldForceFull, unlinkedIds, reason}`; unlinked ids set `wouldForceFull: false` and fill `unlinkedIds` |
| `okstra incremental-carry <args…>` | Merge carried-forward plan-item verdicts into an incremental re-run. Contract v3 takes `--prev-data`, `--cur-narrative`, and the convergence-owned `--state`; it verifies carried stage rows and writes only `--out-state`, tagging copied verdicts with `carriedForwardFromSeq`. Unchanged `P-Val-*` / `P-Req-*` / `P-Rb-*` rows whose extract hash still matches are carried the same way, and the sibling `plan-items-*.json` `dispatchQueue` is rewritten to match. The historical v2 `--cur-data --out` form remains readable. Ownership, scope, item, or schema drift raises `CarryError` and forces a full fallback. |
| `okstra code-review target --task-key <k> --stage <N> [--project-root <dir>] [--cwd <dir>] [--json]` / `okstra code-review target --branch <name> [--base <ref>] [--date <YYYY-MM-DD>] [--project-root <dir>] [--cwd <dir>] [--json]` | Resolve what a code review reads and where its result file goes. Output is always JSON, so `--json` only makes that explicit. `--project-root` and `--cwd` are shared pre-dispatch arguments and apply to both modes; `--cwd` is only consulted when `--project-root` is absent. Both modes return `{ ok, projectRoot, mode, worktreePath, branch, baseCommit, headCommit, reviewPath, round }`; stage mode additionally returns `taskKey`, `taskRoot`, and `stage`. Stage mode takes the diff base from the `base_ref` recorded on that stage's worktree-registry row when it was provisioned — not from a rule re-applied at review time — and names the result `.okstra/tasks/<task-group>/<task-id>/code-reviews/stage-<NN>.md`, where a re-review of the same stage becomes `-r2`, `-r3`, … (the `round` field). Only a legacy row provisioned before `base_ref` was recorded falls back to re-deriving the base through `stage_targets`, and a failure there is reported as `stage_base_unresolved`. `worktreePath` comes back empty whenever the stage worktree is not usable as a live checkout — the registry row is no longer `active` (whole-task final-verification released it), the row never carried a path, or the recorded directory is gone — and the review then reads the `branch` ref instead. Branch mode uses `--base` when given, otherwise the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`), and names the result `.project-docs/code-reviews/<branch>/<YYYY-MM-DD>-<NN>.md`, where `<NN>` (the `round` field) is the next sequence number for that date — the highest already on disk plus one. Read-only: it resolves paths and creates no directory and no file, so the review directory does not exist until the caller writes the report. Backend for the okstra-code-review skill |
| `okstra set-work-status <token> <todo\|in-progress\|blocked\|done> [--note <text>] [--task-group <g>] [--project-root <dir>]` | Update user-managed `workStatus` in task-manifest.json, along with `workStatusUpdatedAt` and, when `--note` is supplied, `workStatusNote`. `<token>` is a full task key or bare task ID. It uses the manifest renderer's serialization rules and returns `stage:"ambiguous"` plus `matches[]` when ambiguous |
| `okstra worktree-lookup <task-key>` | Return the `worktree_registry.lookup` result: reserved path, branch, base ref, and current status |
| `okstra worktree-status [--path <dir>] [--check-clean]` | Answer "is this worktree clean?" over source paths only, excluding what okstra provisioned there — `.okstra`, the configured sync entries (`.project-docs`, `.claude`, …), and any nested stage worktree. A bare `git status --porcelain` in a task worktree is never empty for that reason, so a plan step asserting a clean tree with one fails on okstra's scaffolding instead of on the stage's own work; this is the same gate `handoff` and stage integration use. Output is JSON `{ ok, path, clean, entries, excluded }` where `entries` holds the `git status --short` rows that made it dirty. Exit code is 0 regardless unless `--check-clean` is given, which exits 1 on a dirty tree so it can stand as a shell assertion (`okstra worktree-status --check-clean`). okstra writes `stage-<N>-exit` itself when it settles the stage, so a plan step must not tag. A path outside a git work tree exits 2 rather than reporting a clean tree |
| `okstra plan-validate <plan-path>` | Run `_validate_approved_plan` and report frontmatter `approved` recognition plus unresolved Blocks=approval rows |
| `okstra render-bundle <args…> [--stage <auto\|N>] [--stages <csv>]` | Thin shim over `prepare_task_bundle(render_only=True)` with the same signature as `python3 -m okstra_ctl.run --render-only`. `--stage` is for `implementation` and `final-verification`: for implementation, `auto` (default) selects the earliest incomplete stage with satisfied dependencies, while `<N>` forces a stage; for final-verification, `<N>` verifies one stage with artifacts under `runs/final-verification/stage-<N>/` and a `-fv-s<N>` team suffix, while an empty value performs whole-task verification with the flat layout. The separate `--stages <csv>` channel is for `release-handoff`: stage-group mode bundles the listed stage numbers into one PR, while an empty value selects whole-task mode. Preparation enforces eligibility—`done` + accepted `verified` + not yet `pr`—and automatically creates an input document that cites verification reports |
| `okstra profile show <task-type> [--resolved]` | Print a phase profile. `--resolved` expands its `{{INCLUDE:}}` targets and appends the lazy-read sidecars named in the profile body — transitively, because sidecars name sidecars of their own (`_implementation-executor.md` points at the coding-conventions preflight, the diff-review sweep, and the completion self-check). That matters because a profile is assembled from three places, so grepping only the top-level file returns false negatives: `grep clarification prompts/profiles/implementation.md` finds nothing while the assembled profile has many hits. One grep over this output answers whether a task-type covers a rule. The sidecar list is read from the profile body, never hard-coded, so a newly added sidecar is picked up without a code change. Read-only: it writes no manifest and registers no run, which is what separates it from `render-bundle` — `render-bundle` answers the same question but records a run in `recent.jsonl`, so it cannot be used to look something up. Exits 2 for an unknown task-type |
| `okstra codex-run <args…>` | Codex lead-adapter dry-run entry point. Accepts the same arguments as `render-bundle` but owns `--render-only --lead-runtime codex`. It prepares the task bundle and prints the prompt for the Codex lead without dispatching workers |
| `okstra worker-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Provider-neutral deterministic dispatcher for `runner=cli-wrapper` assignments. It verifies each adjacent invocation specification against the immutable run manifest immediately before process creation and records `core-pre-dispatch`; native-session rows stay with the host. The default selects CLI analysis assignments only. Phase 6 uses explicit `--workers report-writer`, and a mixed analysis/report batch is rejected. `--dry-run` performs the same verification and resolution without starting a provider process. |
| `okstra codex-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Compatibility alias for `okstra worker-dispatch`; it no longer selects a Codex-only transport-agent path. |
| `okstra agent-prompt materialize\|verify\|record-dispatch\|link-result\|materialize-result\|complete\|verify-completion` | Internal invocation-contract CLI. `materialize` composes model assignment, functional duty, and task instructions; `verify` rejects identity, path, snapshot, assignment, source, or digest drift. Run-backed calls resolve `assignmentRef` from the manifest, enforce `authorizedPaths`, and reject real-path or symbolic-link escape. `record-dispatch` records a verified host-native specification before dispatch and `link-result` binds the accepted result; one result path belongs to one dispatch, so a corrective round retires the first attempt with `reject-result --dispatch-id <first> --superseded-by <corrective> --reason <text>` before the new link is accepted — the rejected row stays in `agentResultLinks` carrying `supersededBy` and `rejectionReason` rather than being deleted. Standalone calls are identified by `(purpose, invocationId)` under `.okstra/agent-invocations/<purpose>/`; they publish a canonical result envelope and publish the completion marker last. Consumers use only the `returnedBody` from `verify-completion`. Metadata contains exactly `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`; JSON inputs use UTF-8, sorted keys, compact separators, and no non-finite values, while duty files use versioned sorted-name/byte framing. Instruction sources use `{kind: project\|runtime, path: <relative POSIX path>}` and never persist an installed absolute runtime path. A published prompt is immutable, so re-running `materialize` with an edited instruction file fails as `existing_invocation_conflict`; `--replace-undispatched` is the one exit, for a call that failed a pre-dispatch gate and therefore ran nowhere — it covers a differing prompt and a differing metadata alike, since the two are published together and describe one call. It republishes prompt and metadata together, and it is verified rather than trusted — a row in `agentDispatches` or `workerDispatches` naming this `invocationId` refuses the replacement and names the dispatch that used it. |
| `okstra team dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--jobs-file <path>] [--dry-run]` / `okstra team await --project-root <dir> --run-manifest <path> [--json]` / `okstra team teardown --project-root <dir> --run-manifest <path> [--dry-run] [--json]` | Read a `leadRuntime=external` run manifest and dispatch, await, or tear down tmux-pane workers. Default dispatch excludes report writer; Phase 6 selects it explicitly, and mixed analysis/report jobs are rejected. If a tmux pane cannot be created, gracefully degrade to the CLI wrapper and record the fallback in `workerDispatches[].degradedFrom` |
| `okstra agent-activity append --project-root <dir> --run-manifest <path> --kind <kind> --agent <assigned-id> (--summary <text>\|--summary-file <markdown>) --outcome <outcome> [--plan-item-id <current-id>]… [--command <text> --command-cwd <dir> --command-exit-code <n> --command-output-file <markdown>] [--request-ref <returned-ref>]` | Append one structured activity after checking the agent against this run's role assignments and every plan item against its current convergence state. Python returns an `activityRequestRef`; supply only that returned value with `--request-ref` to retry idempotently. A new call without it remains a distinct activity even with identical contents. Legacy JSON command records remain automation compatibility only. |
| `okstra agent-activity project --project-root <dir> --run-manifest <path> --data <data.json>` | Project this run's canonical activity events into `agentActivity[]`. The command preserves event order, rejects duplicate or decreasing activity IDs, and replaces no other report field. A historical manifest without `activityContractVersion: 1` returns an empty projection and leaves data.json unchanged. Normal Phase 7 execution reaches this behavior through `report-finalize`; use the standalone command only for diagnostics. |
| `okstra approval-decision <open\|resolve\|carry> --ledger <approval-decisions.json> …` | Write the lead-owned clarification and approval ledger. `open` validates classification-specific dispositions and complete option fields, `resolve` requires real `A-NNN` check references, and `carry` keeps prior resolved decisions outside the active clarification list. |
| `okstra design-snapshot --narrative <report-narrative.md> --output <design-preparation.json>` | Detect implementation-planning design surfaces and write the detector-owned snapshot consumed by final report assembly. |
| `okstra plan-verify --narrative <report-narrative.md> --state <plan-body-verification.json>` | Recompute the plan-body gate from the convergence-owned state before `data.json` publication. `--report <historical-data.json>` remains the v2 reader. |
| `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run Phase 7 in the manifest's contract order. Contract v3 collects usage into team state, assembles all single-owner inputs into `data.json` once, then checks, renders, spawns follow-ups, validates, and tears down eligible stage worktrees. Contract v2 retains its historical in-place projection sequence as a read-only compatibility path. A failed step still runs every later check through `validate-run`; only `teardown-stages` is skipped so a failed run does not reclaim worktrees. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. This is the shared path for every lead adapter. |
| `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly, and schema v3 data uses the same always-generated, task-specific human HTML path. The full reading copy uses `templates/reports/final-report-v2.template.md` and is rendered on demand with `okstra render-final-report`. Passing the Markdown sibling locates the same data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, and Response ID parity. |
| `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
| `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
| `okstra token-usage ...` | Wrap the installed `okstra-token-usage.py` to collect and substitute run token usage. Session JSONL is incrementally scanned by default through a byte-cursor cache at `$OKSTRA_HOME/cache/token-usage/`; `--no-cache` bypasses the cache and forces a full rescan as an accuracy fallback |

In prose, `okstra agent-prompt materialize|verify|record-dispatch|link-result|materialize-result|complete|verify-completion` denotes that internal command family; the vertical bars separate subcommands and are not literal shell arguments.

The convergence state lifecycle is `groups v1.0 → work v1.0 → final v1.3`; round-plan, round-results, and optional critic-results v1.0 artifacts provide the auditable transitions between those endpoints.

`okstra convergence apply-critic-gaps` is the only transition that may add verified coverage gaps to terminal main-queue state. `okstra plan-items extract` creates the complete plan queue, and `okstra plan-items validate` rejects any omission or drift before verifier dispatch.

`okstra convergence` and `okstra plan-items` are internal admin CLI families used by the lead protocol. Each is an internal admin CLI, not a user-facing skill, and their presence does not add a public skill. The former `okstra-convergence` skill remains obsolete; the installed `prompts/lead/convergence.md` and `prompts/lead/plan-body-verification.md` contracts tell the lead when to invoke these operations.

> Every subcommand is wired to `PYTHONPATH` and `~/.okstra/lib/python` by the Python helper (`src/lib/python-helper.mts`) spawned by `bin/okstra`. When invoking `python3 -m okstra_ctl.*` directly, you must configure `PYTHONPATH` yourself.

#### `okstra design-prep`

Use these commands after an `implementation-planning` report has prepared concrete domain, schema, interface, lifecycle, rollout, or manual-test input. `show` and `write` accept the Markdown report path or its `.data.json` sibling.

```bash
okstra design-prep list [--project-root <path>] [--report <path>]
okstra design-prep show --report <path> [--item PREP-001]
okstra design-prep write --report <path> --item PREP-001 \
  --decision <accept-draft|modify-draft|reject-draft|defer> \
  [--overrides '<json-object>'] [--notes <text>] --confirmed
```

- `list` returns `{"reports": [<snapshot-summary>, ...]}`. With `--report`, it reads only that report; otherwise it discovers planning reports below the resolved project root.
- `show` without `--item` returns `report`, `outcome`, `reason`, `warnings`, and all item views. With `--item`, it returns `report`, the full AI-prepared `item`, `effectiveInput`, `assessmentFingerprint`, and `requestPath`.
- `write` returns `path`, `revision`, `inputId`, and `assessmentFingerprint`. The path is a new `design-prep-inputs/...-r<revision>-<uuid>.md` sidecar; it never mutates the report or an earlier response.
- `--overrides` must be a JSON object and is accepted only with `modify-draft`. `reject-draft` requires a non-empty note.
- `--confirmed` is required for every write. Without it, no revision, UUID, timestamp, or file is allocated.

Successful commands and `--help` exit `0`. Contract, path, JSON, or confirmation errors exit `1` and write a `design-prep:` message to stderr. Argument-parser errors such as a missing required flag or unknown decision exit `2`. Successful command stdout is JSON.

The selected implementation stage resolves only items that cite it in `stageRefs`:

| Effective state or response | Preflight result |
|---|---|
| `ready`, `not-applicable`, or `no-design-inputs` | `proceed` |
| `provisional` with no response, `defer`, `accept-draft`, or a non-triggering `modify-draft` | `proceed`; inject the effective proposal and working assumption into the executor prompt |
| `provisional` with `reject-draft` | `wait_for_input` |
| `provisional` with a modified `replanTriggerFields` path | `replan` |
| `blocked` with no response, `defer`, or `reject-draft` | `wait_for_input` |
| `blocked` with `accept-draft` or `modify-draft` | `replan`, because the approved plan must incorporate the newly authorized decision |
| markerless legacy plan | `proceed` with `legacy-unassessed` warning; no report rewrite |

Both `wait_for_input` and `replan` stop before a stage worktree is provisioned and before a `status:"started"` consumer row is appended. Other stages whose `stageRefs` do not include the unresolved item remain eligible.

### Live-log sidecar

For every dispatch, whichever provider runs it, okstra creates a `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` sidecar and writes the CLI's output into it. All five `okstra-<provider>-exec.sh` entrypoints share one implementation for this, so the log contract does not vary by provider.

**Progress appears in the worker's own pane.** Earlier versions split a sibling `tail -F` trace pane next to each worker; they no longer do, and no trace pane is created at all. Instead the presentation is passed to the entrypoint as `--presentation live|quiet`, and only a backend that opened a pane asks for `live` — the default, and what a `cli-wrapper` subagent dispatch passes, is `quiet`. Under `live` each event becomes one readable row on the worker's own streams — `→ Bash: npm run check`, then `  ← ok (2481 bytes)`, and `!! PERMISSION DENIED — <tool>: <reason>` for a refusal. Under `quiet` progress is withheld and only the worker's closing text is printed, which is what a dispatch on a machine with no pane surface needs. The `.log` sidecar records the progress either way, so withholding it from the screen loses nothing.

Every pane tag this script once scanned has lost its writer, and the script itself is gone. `@okstra_trace_run` / `@okstra_status` went inert when the wrappers stopped splitting a trace pane; `@okstra_worker_run` went with the `tmux-pane` dispatch backend; and `okstra-trace-cleanup.sh` followed, because its `tmux kill-pane` title scan could only ever work in a tmux-hosted session and current runs are cmux (`terminalBackend: cmux-pane`). What okstra closes now are the panes it opened itself and recorded as `paneId` in `team-state.workerDispatches[]`. `okstra team reclaim` is the round boundary: it closes the panes of dispatches that have finished, leaves an in-progress one alone, and with `--dry-run` prints the same set without closing so the lead can count and report `PROGRESS: phase-batch-cleanup panes=<n>`. `okstra team teardown` is the end of the run: every recorded pane, plus a write-off for any dispatch that never finished. A pane the harness opened for its own teammate is out of scope for both — okstra never opened it and holds no id for it. An `okstra-compact-reminder.sh` `SessionStart` hook (matcher `compact`) re-injects the boundary obligation after a `/compact`.
