# 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.

---

## 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)
  - [`--terminal-backend`](#--terminal-backend)
- [Interactive input flow](#interactive-input-flow)
- [Confirmation flow](#confirmation-flow)
- [`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] [--terminal-backend cmux-pane|cli-wrapper] --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 technical-verification`

`technical-verification` is an optional experiment phase outside the normal phase sequence. It tests explicitly classified unresolved technical facts from the same task's implementation-option-selection report. Supply that report's `.data.json` through `--clarification-response`; no selected direction is needed. Unresolved user decisions still block entry, and candidates with safety blockers are excluded.

The run freezes source identity and fact IDs under `state/technical-verification-input-<seq>.json`. Workers write plans, source copies, dependency changes and command logs only under `runs/technical-verification/experiments/<seq>/<worker-id>/`. Results distinguish supported, refuted, inconclusive and not-run facts. Report assembly checks the frozen identities and retained command evidence before publication.

The only next phase is `implementation-option-selection`. Carry the verification report into that comparison with `--clarification-response`; the wizard recommends it when it refers to the current comparison report. Fresh independent feasibility votes decide candidate validity. Experimental evidence does not approve adoption or expand a pilot into production rollout.

#### `--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`

When the task-group directory beside the brief holds `group-context.md` (`.okstra/briefs/<task-group>/group-context.md`, skeleton from `okstra group-context init`), preparation validates it, copies it to `instruction-set/task-group-context.md`, and the analysis packet carries its human sections as `## Task-Group Context` ahead of `## Task-Specific Brief Extract` and its okstra-written region — the group's start order and each sibling task's latest conclusion, recorded by `report-finalize` — as `## Task-Group Memory` after the brief extract, without this task's own entry. A file that still carries a template placeholder line fails preparation; delete it if the group needs no context. A file okstra created with only the memory region (no human section) passes as it is.

### `--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.

### `--tdd-bypass`

Records a **user-acknowledged** TDD bypass for one plan stage: `"<stage>:<reason>"`. Like `--analysis-target` and `--reverify-scope`, this is an internal `node bin/okstra render-bundle` input collected by the `/okstra-run` wizard — `scripts/okstra.sh` does not accept it. Only `--task-type implementation-planning` applies it.

Every plan stage must open with a `RED:` step whose outcome is FAIL and reach a later `GREEN:` step (validator check S10c), and `tddExemption` waives that only for `doc-only`, `config-only`, or `pure-rename` work. A stage that is truthfully none of the three had no passable value, so the plan got through by filing the nearest category — the record then says something that is not true. This flag is the fourth reason, and it is the one the run cannot grant itself.

- Value form — `<stage>:<reason>` where `<stage>` is the Stage Map number and `<reason>` is the user's own words. The reason is stored **verbatim**; a malformed value or a non-positive stage number aborts `render-bundle` with a `PrepareError`.
- Effect — prepare writes `{stage, reason, acknowledgedBy: "user", at}` into `<task-root>/qa/tdd-bypass.json` (the same task-level `qa/` home as the conformance manifest and `self-mock-waivers.json`). Re-granting the same stage replaces its row.
- What it unlocks — that stage may declare `tddExemption: user-bypass — <reason>`. Check S10e accepts the token only while the ledger records the grant for that stage number, so a plan asserting it on its own fails exactly as an arbitrary reason does. The refusal message names this flag.
- Scope — per stage number, per task, and it persists across runs of that task. Remove the row from the ledger to withdraw the grant.
- Not a substitute for closing a landed stage. When a stage's product change is already committed and its conformance result is PASS but no `done` row exists, the recovery is `okstra stage-close --from-commit` (see [`docs/architecture.md`](architecture.md)), not a bypass.

### `--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.

This flag speaks **provider ids**, not roster worker ids. A run's roster names each slot by its role (`codex-implementer`, `codex-verifier`), so a roster line is not a valid value here; `okstra model-io rerun-input` already folds the roster down to provider ids on its `Workers` line, and the recorded rerun argv does the same. Slot counts travel on `--role-count`, not here.

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 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, or Claude Code skill handoff. 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.
- With `external` requested, `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-1`, `claude-fable-5-1`, `fable-5`, `claude-fable-5`, `opus`, `opus-5`, `claude-opus-5`, `sonnet`, `sonnet-5`, `claude-sonnet-5`, `haiku`, `haiku-4-5`, `claude-haiku-4-5`
> - Codex (`--codex-model`): `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.4-mini`, `codex-auto-review`. Codex slugs are gated at dispatch against the provider catalog the CLI caches in `~/.codex/models_cache.json`; a slug that catalog does not list is rejected rather than renamed.
> - Antigravity (`--antigravity-model`): `gemini-3.1-pro` (default), `gemini-3.7-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-build-0.1`
> - Kimi (`--worker-model kimi=<model>`): `kimi-k3`, `k3`, `k3-256k` and their registered display aliases
>
> Not every registered alias is offered for selection. A model whose `ModelSpec` sets `selectable=False` stays in the catalog — so served-model attestation and historical pricing still resolve it — but it is hidden from the wizard's role-model picker, from `okstra model list`, and from `--role-model`. `okstra model list` still prints them, marked `selectable: no` with the reason. Currently hidden: `claude/fable-5-1`, `claude/fable-5`, `claude/opus-5`, `claude/sonnet-5`, `claude/haiku-4-5`, `claude/haiku-4-5-20251001` (each one the pinned twin of a channel entry that already appears; `claude/fable-5` is the previous served id, kept for attestation and pricing), `kimi/k3` (same model as `kimi/kimi-k3`), `codex/gpt-5.4-mini`, `codex/codex-auto-review`, `grok/grok-build-0.1`, `kimi/k3-256k`.

### `--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` 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 danger-full-access`, 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). No provider CLI enforces a sandbox boundary: the write scope tells a worker where its work belongs, and the run checks afterwards that it stayed there.
- **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, prepare 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>/` (a sibling of the task worktree — a nested layout made the task tree's prettier/tsc read the stage tree as source; whole-task `final-verification` prepare moves any remaining nested stage worktree out with `git worktree move` and updates the registry), 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.6 \
  --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 opt-in critic slot on `requirements-discovery`, `error-analysis`, `implementation-planning`, and `final-verification`. The value is `claude`, `codex`, `antigravity`, `grok`, or `kimi`. `--critic off` runs the phase with no critic; an analyser 1-1 tie then stays `needs-reverify` and `okstra plan-items next-dispatch` answers `user-decision` — the split becomes an approval decision for the user instead of another verification round. 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.

### `--terminal-backend`

Names the surface this run's workers get: `cmux-pane` (each worker opens its own
pane beside the lead) or `cli-wrapper` (each worker is a background subprocess
read through its status sidecar). Leave it unset and preparation probes for a
usable cmux session instead.

The flag exists for one case: preparing a run wrapper-backed on purpose after the
task's previous run of the same phase used cmux panes. Preparation refuses that
downgrade on its own, because the probe's last step is a cmux socket connect and
a sandboxed lead — codex `workspace-write` denies AF_UNIX connect — fails it while
the very cmux it is running inside is still open. Passing
`--terminal-backend cli-wrapper` declares the downgrade and skips the probe;
`--terminal-backend cmux-pane` asserts the surface without probing at all.

The value is recorded as `terminalBackend` in the run manifest, and every later
consumer reads it from there rather than probing again.

- **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.

---

## Z.ai GLM workers

Z.ai is a worker provider (`zai`) that uses the existing `claude` executable. Install Claude Code, provide `ZAI_API_KEY` once in `~/.env` or in the environment inherited by Okstra, and select `zai/glm-5.3` or `zai/glm-5.3-flash` in a role-model step. For explicit launch arguments, use `--role-model analyser=zai/glm-5.3`. These models support worker roles; this integration does not provide a Z.ai lead host.

A nonempty process `ZAI_API_KEY` takes precedence. Otherwise each GLM worker reads `ZAI_API_KEY` from the current user's `~/.env`, independently of its project or working directory. This also applies to workers opened in separate terminal panes. Only that key is selected; other file entries are not imported into the environment. Supported entries are single-line `ZAI_API_KEY=value` assignments with optional `export`, single or double quotes, and trailing comments. Shell commands and variable substitutions are never evaluated. If the key appears more than once, the last assignment wins. An unreadable file or malformed matching assignment fails with an error that omits its contents. No project `.env` is read.

Each GLM child process uses `https://api.z.ai/api/anthropic` and its own model/authentication environment. Okstra does not edit `~/.claude/settings.json`. GLM runs with `--setting-sources ""` so user, project, and local settings cannot override those connection values; settings-based hooks and integrations are consequently not inherited for GLM workers. The regular Claude provider keeps its existing settings behavior.

The runtime rejects a missing `ZAI_API_KEY` before starting the worker. Catalog visibility does not verify the account's model entitlement or remaining quota. The installed Claude Code version and Z.ai account must support the selected model. See [Z.ai's Claude Code setup](https://docs.z.ai/devpack/tool/claude) and [Coding Plan usage policy](https://docs.z.ai/devpack/usage-policy) for provider requirements.

GLM response model identities are recorded as `zai/<model>`; a startup model label alone is not treated as proof of the served model. The final Claude Code usage event supplies input, output, cache-creation, and cache-read counters. Repeated dispatches are summed by their status files. Costs use Z.ai's public API token prices regardless of whether the account uses a subscription or metered API billing. Missing usage or an unknown model price stays unavailable.

The [official API prices](https://docs.z.ai/guides/overview/pricing), checked on 2026-09-14, are USD per million tokens:

| Model | Input | Cached input | Output |
|---|---:|---:|---:|
| `glm-5.3` | $1.40 | $0.26 | $4.40 |
| `glm-5.3-flash` | $0.15 | $0.03 | $0.50 |

Cache-creation tokens use the input rate; cache-read tokens use the cached-input rate. Z.ai currently lists cached-input storage as free for a limited time. The reported cost is the API-equivalent token cost, not the subscription invoice. Regression coverage is in `tests/adapters/accounting/test_cli_provider_transcripts.py` and `tests/adapters/providers/test_provider_registry.py`.

## `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>… --run-manifest <run-manifest.json> --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 critic-prompt --run-manifest <path>` | Render the coverage-critic task instructions to stdout; the lead writes the output verbatim into the file the prompt materializer's `--instruction` takes. The body carries the run's Round 0 consolidated findings, one line per Phase 4 analyser (worker id, result path, its finding ids), the two mandates plus the `duplicateOf` declaration rule, and — on an implementation-planning re-run — an already-covered index of the prior report's requirement-coverage row ids, clarification row ids, and stage titles (ids and titles only, never body text). Emits the `**Prompt Delivery Mode:**` header and the `Primary analysis packet` line the critic dispatch contract requires, so no part of the critic body is hand-written. Exits 2 when the run has published no grouping yet, or when the manifest carries no `analysisPacketPath` |
| `okstra convergence reverify-prompt --run-manifest <path> --plan <round-plan.json> --worker <worker-id>` | Render one worker's reverify task instructions to stdout; the lead writes the output verbatim into the file the prompt materializer's `--instruction` takes. The body carries the round's mandate (adversarial or collaborative, from the grouping's `config`), the plan row's findings in plan order — each with its summary, origin worker, cited-evidence line, the origin worker's result file and `### <item-id>` section, and the origin audit sidecar the verifier is told it may open — and the response format the collector parses. Exits 2 when the plan dispatches nothing to that worker, names a finding the grouping lacks, or is not a `dispatch` plan |
| `okstra convergence critic-verify-prompt --run-manifest <path> --gaps <coverage-batch.json> --worker <worker-id>` | Render one Phase 4 analyser's coverage-critic gap verification instructions to stdout; the lead writes the output verbatim into the file the prompt materializer's `--instruction` takes and materializes it with `--dispatch-kind critic-verify` under the analyser's `reverify/<worker-id>` assignment ref. Input is the pre-vote coverage batch (`mode: coverage`, `gaps[]` with `gapId`, `summary`, `category`, `ticketIds`, `originEvidence`, optional `duplicateOf`) the lead later passes to `apply-critic-gaps`; gaps are assigned round-robin over the grouping's analysis roster exactly as `apply-critic-gaps` checks them, and the body carries only this analyser's share — each gap with the critic's result file (`<provider>-worker-critic-<task-type>-<seq>.md`) and `### [<gapId>]` section, the critic audit sidecar, and the adversarial response format. Exits 2 when the batch's taskKey or mode does not match, the run has no critic assignment, the critic result is not collected yet, the worker is not an analyser, or round-robin assigns it no gap |
| `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 apply-acceptance-critic --work-state <path> --results <path>` | Record the final-verification acceptance critic's confirm-or-downgrade accounting into `config.critic`. Takes `{schemaVersion, taskKey, mode: "acceptance-devils-advocate", provider, modelExecutionValue, candidates[]}` with one `{candidateId, verdict}` per candidate, `verdict` being `confirmed` or `downgraded`; `candidatesProposed` / `confirmedBlockers` / `downgradedToResidual` are derived here, not declared in the batch. `apply-critic-gaps` implements coverage merge/drop semantics and rejects this mode, so this is the only writer of the acceptance summary. Requires a terminal main queue and refuses a second application |
| `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\|acceptance-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`, `acceptance-batch` → `apply-acceptance-critic --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 group-context init --project-root <root> --task-group <group>` | Write the task-group context skeleton `.okstra/briefs/<task-group>/group-context.md` from `templates/reports/group-context.template.md` and print the sections to fill. Exits 2 when the file already has human sections; a file okstra created with only its memory region gets the human skeleton inserted above that region. The filled document is validated by `validators/validate-brief.py` (frontmatter `type: group-context`), copied by preparation into `instruction-set/task-group-context.md`, and carried in the analysis packet's `## Task-Group Context` section ahead of the brief extract; the trailing `## Task Memory` region between `<!-- okstra:task-memory:begin -->` / `end` markers is written by `report-finalize` (`record-group-memory`) and reaches sibling tasks as `## Task-Group Memory`. Preparation refuses a file that still carries a `<...>` placeholder line, and a task-group without the file prepares as before |
| `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, ending with the parser-facing `## Response format` block so the block reaches every verifier with the queue; `validate-prepared` proves the prepared queue still matches the narrative. With `--state <plan-body-verification.json>` the round is a re-verification: `prepare` also carries each queued item's recorded votes and `selfFixNote` into the envelope as `priorRounds`, `prompt` renders them as that item's `**Prior round dissent**` block behind a re-verification preamble, and `validate-prepared` re-derives the carry and rejects an envelope that dropped it. 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> [--prior-state <previous 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. `--prior-state` carries the previous **run**'s verdicts into this one: a newly seeded item whose `contentHash` equals that run's `verifiedContentHash` for the same id inherits its `verdicts[]` and is tagged `carriedForwardFromSeq` with the seq read off the prior filename, so round 1 does not re-judge text nobody changed. A matching id alone never carries — `P-*` ids are positional and shift. It requires `--state`, refuses a prior state whose task root differs from the one `--state` lives under (the state file carries no task identity, so its path is the only identity there is), and when it carries anything it rewrites the sibling `plan-items-*.json` `dispatchQueue` the way `incremental-carry` does. Adds `carried` / `carriedForwardFromSeq` to the reported 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. `--items <plan-items artifact>` narrows and persists the assignment shared by verdict validation and round completion to what a partial round actually dispatched — pass the `--tie-vote` artifact on a critic tie round, or every item the critic was never given is refused as unanswered. The unanswered check still applies inside the narrowed set. Earlier verdicts and completed-round history outside that set remain unchanged. The historical `--verdicts <file>` form remains automation compatibility only. Without `--append` every recorded verdict row of the queued items is replaced; when a row belongs to a round `complete-round` never closed, the command refuses before writing and names the `complete-round --round <M>` to run first. `--discard-open-rounds` replaces anyway — the recovery path when those rounds are being re-applied from their result files in order (the discarded rows are printed); it also restores `dispatchQueue` to the items those result files answer, since the persisted queue belongs to the latest round and an earlier round's verdicts would otherwise be refused as outside it. |
| `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N> [--items <plan-items artifact>] [--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. `--self-fix-group` requires `--self-fix-stop-reason` — there is no default. `--self-fix-stop-reason` alone records a stop for a round that rewrote nothing and leaves `selfFixGroups` / `selfFixRoundsApplied` untouched. When `--items` is omitted, completion automatically reads this run's canonical prepared queue and restores it when it includes all votes recorded for the requested round. If that file belongs to a narrower later batch, the persisted queue stays authoritative. This recovers saved critic results after an upgrade without requiring the lead to add a recovery flag. An explicit `--items` still selects a prepared artifact. Only the assigned items need verdicts for this round; earlier verdicts elsewhere remain unchanged. Missing-verdict errors name the round and exact dispatched item ids. Models do not write the state JSON. Stdout also carries `nextDispatch`. With `gating=false`, critic corrections are exempt from the analyser round limit and do not require an even split. A later `--append` updates the critic verdict while retaining analyser votes and completed round history. A second automatic rewrite is rejected; the one rewrite may contain several cause groups. `selfFixRoundsApplied` remains its verification round number, not a rewrite count. |
| `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 on a run that rostered a critic, `critic-worker` on those item ids only. `kind: user-decision` — the same 1-1 on a run with no critic rostered: no in-band vote can break it, so open one `okstra approval-decision open` per item (classification `correctness-critical` for unresolved kind `a` outside `P-Var-*` or kind `f` on `P-Req-*`, otherwise `noncritical-dissent`) plus its `## 1. Clarification Items` row and dispatch no further verification for them. `--run-manifest` is what tells the two apart (`invocationAssignments` `critic/*`); without it the answer stays `critic-tie`. A missing path is never environment-unverifiable.  After the single automatic self-fix, remaining items route to `lead-decision` first, then `user-decision`, with no further automatic worker batch. |
| `okstra plan-items resolve-dissent --state <plan-body-verification.json> --item <P-id> --decision-file <markdown-file>` | Record the lead decision, its authority within agreed scope, and cited evidence after the one self-fix. Accepts only current, noncritical design judgements; facts, user-input requirements and non-results require user confirmation. Keeps original votes and appends the decision to the report dissent log. The decision expires when its content, scope or verdicts change. |
| `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 <task-type> --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 body may span several lines. 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; the second and later lines of a body follow on rows indented by two spaces. `ack --through` rejects an id behind the current cursor. Room and display names `.` and `..` are reserved. A `.lock` left by a dead process (pid recorded in the file) or older than 5 s is reclaimed. 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. Prefer paired `--team-state <path> --dispatch-id <id>` options for identified attempts; do not mix them with `--worker`. Worker-name selection is retained only when the dispatch is unambiguous. Each retry starts a new wait for its own id. A selector error is a monitoring-input defect, not a failed 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 verification-target --project-root <dir> --run-manifest <path> --expected-head <commit> --command <declared-command> [--baseline <json>]` | Read-only target observation for implementation and final verification. Reads the active run's worktree, checks the commit and rejects checkout-changing command syntax without executing the command. Save the JSON before the check; compare it with `--baseline` afterwards. Changed source fingerprints, commands, worktrees or commits invalidate reuse. A successful observation does not prove the declared command was executed. Exit 1 on a target or input error. |
| `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. `--seq` accepts a bare number and zero-pads it to three digits, so `--seq 1` and `--seq 001` select the same run. Emits `{ok, inspected, inspectedFiles[], failures[], blocking[], advisory[], runImpact}` and exits 2 when `failures[]` (= `blocking` + `advisory`) is non-empty — and also when the selector matched no result file at all, in which case the payload carries `selectorError` and empty `failures[]`: nothing was judged, which is not a pass. Exit 2 means "fix it now", not "the run fails": only `blocking` rows (no audit sidecar) fail the run at Phase 7, while `advisory` rows (a citation with no matching Evidence read row) never fail the run and are only repairable while the worker session is alive — do not reject or re-dispatch a result over an advisory row alone. 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> \| --task-group <group>) …` | Backend for the okstra-inspect `recap` facet. `assemble` is read-only: for a task it prints a JSON summary of phase transitions across its runs; with `--task-group` it prints the group's start order (briefs in ordinal order, each `done` / `in progress` / `not started` from the catalog's task-manifests, memory entries only for tasks the catalog does not know) and every recorded task's latest conclusion from `group-context.md`'s Task Memory. `okstra model-io recap-input --task-group <group>` is the fixed-text projection of the same join. `record --kind <summary\|qa> --mode <artifact\|code> --answer <text> [--question <text>] [--citation <path:line> …]` appends one line to `<task-root>/recap/recap-log.jsonl`, or with `--task-group` to `.okstra/tasks/<group>/.recap/recap-log.jsonl`, and never mutates other artifacts. `note` is task-only. `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] }`. `sourcePlanPath` is the task's latest implementation-planning report — the authority on which stages exist, so a plan amendment's added stages are selectable as soon as the amended plan lands. `state` is `ready` for a resolved source and `missing` when no Stage Map exists; a corrupt source returns a structured non-zero error 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 stage-close <task-key> --stage <N> --from-commit <sha> [--cwd <dir>\|--project <dir>]` | Record the `done` stage-consumer row an implementation run would have written, for a stage whose work is already committed but which never registered as done — the run ended before writing its carry sidecar, and `backfill_done_from_carry` recovers only from that file. Without it `stage-map` reports `doneStages: []` while the branch carries the commit, the next plan re-describes the stage, and every RED expectation it produces is unreachable. Refuses unless the Stage Map has that stage, no `done`/`failed` row exists for it, `--from-commit` resolves to a commit in the project repo, and the stage's conformance gate permits progress — the same `decide_conformance_gate` the run validator uses, so a stage closed here is not one the validator would have blocked. Closing also moves the `stage-<N>-exit` tag and releases the stage reservation, exactly as a normal stage completion does. Emits `{ ok, taskKey, taskRoot, stage, headCommit, conformance, consumersPath }` |
| `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): 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`. `--run-manifest <path>` is required for a decision (not for `--preview`): the same decision is written to the record that manifest names in `incrementalDecisionPath`, so the report writer's authoring contract and `okstra incremental-carry` read it instead of CSVs the lead re-typed. `mode: "unresolved"` is a question back to the lead and is deliberately not recorded. `--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`. Pass `--decision <incrementalDecisionPath>` for the two stage sets — the same record the report writer's authoring contract was built from — instead of `--carry-stages` / `--reverify-stages`, which the lead re-typed off stdout; the two forms cannot be combined, and a record whose `mode` is not `incremental` is refused. 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> \| --note-file <path>] [--task-group <g>] [--project-root <dir>]` | Update user-managed `workStatus`. Existing briefs can be registered without a run. Direct completion requires a work and verification summary; use a UTF-8 note file for longer results. Records are shared through group context, inspect, and subsequent runs without claiming cross-verification. Ambiguous IDs return `matches[]`. A `share` failure with `statusRecorded:true` can be retried with the same command. |
| `okstra worktree-lookup <project-id> <task-group> <task-id>` | 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 jobs --project-root <dir> --run-manifest <path> --dispatch-kind <kind> --metadata <path> [--metadata <path>] --out <path> [--json]` | Generate an immutable v2 jobs file from verified invocation metadata. Reads canonical identity, role, five digests, and actual result anchors; validates the full batch through the dispatch consumer before publishing. Rejects mixed runs or dispatch kinds, duplicate attempts, and translator input (use canonical `worker-dispatch --workers translator`). Reuses identical output; preserves differing output and requests a new `--out` path. Does not launch workers. |
| `okstra agent-prompt materialize\|check-corrections\|apply-corrections\|verify\|record-dispatch\|link-result\|reject-result\|abandon-attempt\|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. Every run-branch report-writer prompt gets its `## Output` section (narrative, pointer record, reading audit) rendered by okstra, and an instruction body that writes a `## Output` or `## Corrections` heading is refused. A corrective report-writer round — the narrative at `reportNarrativePath` already exists and its structure parses, value defects included — must pass `--corrections <ledger>` (`schemas/report-writer-corrections-v1.0.schema.json`: `replace` / `remove` / `add` / `move` / `rewrite` entries keyed by the validator's field-path grammar, `baseNarrativePath` naming a preserved copy of the attempt, and optional `baseNarrativeSha256` binding that version): the ledger is applied to that base and checked against the writer-owned schema and the task's semantic validator before dispatch, every defect is reported at once, and okstra renders the prompt's `## Corrections` section from it; a report-writer materialization without a ledger over such a narrative is refused before any prompt is written, while a narrative whose structure does not parse (line grammar, unknown top-level field) is re-authored without one. `check-corrections --run-manifest <path> --corrections <ledger> [--json]` runs the same check without materializing (exit 1 lists the defects; `mechanical: true` means every entry is a validated `replace`, `remove`, `add`, or `move`, including derived planning step counts). `apply-corrections` with the same arguments applies such a mechanical ledger without a writer round: it writes the corrected narrative to `reportNarrativePath` and records a `lead-correction-applied` activity row (`evidenceRefs` = ledger path + correction ids) through the run's activity contract; `--rewrite-results <file>` also accepts hash-bound replacement values for exactly the requested rewrite ids. Correction-only materialization sends those target fields, evidence, and constraints instead of the initial instructions and complete synthesis packet; the runtime merges the submitted values and checks the complete narrative before writing. It refuses unresolved `rewrite` entries or any defect, a stale live narrative, a base that is the live narrative, a run without `activityContractVersion` 1, and a ledger already applied. An empty ledger can preflight the initial writer result and derive `stageMap[].stepCount` from matching execution rows without another writer call. 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. The corrective dispatch is a new invocation: an invocation whose last attempt finished with a mutation takes no further attempt (`execution_manifest._validate_next_attempt` lets only `failed-no-mutation` be followed), so a retry attempt of the rejected invocation itself is refused by the manifest, and `reject-result` does not make it possible. `abandon-attempt --invocation-ref <ref> --reason <text>` closes a started attempt whose worker died without producing a result — the one case neither `link-result` (which needs the result file) nor the dispatch-failure path covers — so a retry can follow it instead of the run having to be re-rendered. It refuses any attempt whose `writePolicy.sourcePolicy.mode` is not `source-readonly`: closing an attempt records `failed-no-mutation`, which is true by policy for a read-only worker and a guess for a mutating one. 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 pane-backed workers. Default dispatch excludes report writer; Phase 6 selects it explicitly, and mixed analysis/report jobs are rejected. If a pane cannot be opened, 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 lead-progress append --project-root <dir> --run-manifest <path> --phase <phase-id> [--worker <role>] [--field NAME=VALUE]… [--detail <text>]` | Append one `PROGRESS:` checkpoint to the run's `leadEventsPath` and print the line to emit to the user as `progressLine`. The checkpoint is what `validate_session_conformance.py` reads, and on a host whose adapter declares `sessionAccounting: artifact-only` the ledger is the only place it can read one — a conversation line alone is not retained there. `--phase` accepts the phase ids the lead contract's "Progress reporting (BLOCKING)" list defines; the fixed-prose checkpoints render their contract wording without `--detail`. `--worker` is resolved against team-state and rewritten to the roster `workers[].role` the per-worker checks match, so a phase-specific functional label still lands on the right worker; a name that matches no roster row is written through with a note on stderr. |
| `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. `carry --from-responses <instruction-set/clarification-response.md>` is the source of truth for an answer given in an earlier run: the bundle is task-level and cumulative, each response section names the report that posed the question, and `--clarification-id` repeats to carry several ids in one call. `carry --source-ledger` remains for a prior run's ledger that is still on disk and needs `--source-run-ref`. Prepare seeds `carriedDecisions[]` itself when it creates a run's ledger from a `--clarification-response` that names a report record — every row that record answered or resolved, plus rows its user-responses sidecars answered (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`) — so `carry` is for ids that record does not answer. |
| `okstra option-votes gaps --task-manifest <task-manifest.json> (--report <final-report .data.json> \| --narrative <report-writer narrative.md>) [--json]` | List the `implementation-option-selection` candidates that fail the ranking rule on the every-analyser clause alone, and name the analyser owing each vote. Round 1 runs the designers in parallel, so each votes only on the candidates it proposed and the merged set keeps a different hole per analyser; a run whose comparison had converged can end `blocked` with an empty `rankedOptions` for that reason alone. The lead reads this before concluding `blocked` and dispatches one vote-completion assignment per named analyser — a feasibility verdict on the named candidate and nothing else, so the run stays in `candidate-comparison` mode. A candidate carrying `safetyBlockers` or `unresolvedFeasibilityFacts`, or one that could not reach two `feasible` votes even with every missing vote, is excluded: another round would not change it. `--narrative` reads the writer's markdown before assembly and tolerates its value defects; `--report` reads a published record. |
| `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, translates (`translate`: for a non-English `reportLanguage` it materializes and dispatches the translator worker unless the `*.i18n.<lang>.json` sidecar already exists, and fails when the worker leaves none), then renders with that sidecar overlaid, spawns follow-ups, validates, records the run's conclusion and the group's start order into the task-group's `group-context.md` (`record-group-memory`, creating the file when absent), 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`; `record-group-memory` and `teardown-stages` are skipped so a failed run neither hands an unvalidated conclusion to sibling tasks nor reclaims worktrees. The result carries `nextInGroup` (the first task in start order not yet started) and, for a terminal pointer, `nextCommand` closes on starting it from its brief. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. Repairable failures return `recovery.mode=same-run`, complete assembly owner issues, and `resumeCommand` using this manifest; `nextCommand` does not ask for a new run. A validator-only failure targeting an earlier phase retains that recovery target. Finalization step failures and run-bound contract exceptions from `plan-items` and `agent-prompt` write bounded runtime error records, with any logging failure reported separately. 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, Response ID parity, and that every in-page `href="#…"` lands on an element of the page. For a non-English report the command prints two counts: `translated N string(s) into <lang> (M left in English, K unresolved)` from the sidecar overlay, and `rendered R line(s) still in English on the <lang> page` from the written page itself — the second sees fields the extractor does not offer, so `M = 0` with `R > 0` means a reader-facing key is missing from `PROSE_KEYS`. |
| `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. `check-corrections` and `apply-corrections` belong to the same family and are described in the table row above.

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 pane-tagging dispatch backend; and `okstra-trace-cleanup.sh` followed, because its title scan never matched a cmux surface 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`.
