# Okstra Lead Contract

## Overview

The lead orchestrates the selected AI workers against a prepared task bundle, collects their independent outputs, supervises convergence, and ensures the final report is produced. When `Report writer worker` is in the selected roster, that worker authors the report narrative Markdown; report assembly publishes the final record from role-owned inputs. The lead never substitutes its own reasoning for a worker result and never bypasses a rostered report writer.

The lead owns the approval decision ledger and writes it only through `okstra approval-decision`. The activity recorder owns the activity ledger. Runtime adapters own team state and usage. The convergence engine owns convergence state and the completed plan-body result. The design-surface detector owns the design-preparation snapshot. Report assembly validates these inputs and publishes `final-report-*.data.json` once; a failure is returned to the owner named in its diagnostic.

## When to Use

- User explicitly says "okstra", "cross verify", or "cross-check with other AIs"
- User wants multi-agent comparison or cross-check
- A prepared task bundle exists with `task-manifest.json`

**Do NOT use** for single-agent tasks or when no task bundle is prepared.

## Support contract index

This document is the operating contract and phase index. Detailed procedures live in the support contracts below; consult them whenever the corresponding phase is active.

| Contract | Scope |
|-------|-------|
| [context-loader](./context-loader.md) | Phase 1 task-bundle discovery, manifest fields, run-directory layout |
| [team-contract](./team-contract.md) | Phase 2–5 worker roster, model assignment rules, prompt composition (anchor headers, `[Required reading]`, `[Error reporting]`), worker output contract, terminal statuses, usage tracking |
| [convergence](./convergence.md) | Phase 5.5 finding convergence loop, finding categories, reverify dispatch (anchor headers, required-reading suppression), convergence state schema |
| [plan-body-verification](./plan-body-verification.md) | Phase 6 plan-body verification sub-step (implementation-planning only) — plan-item extraction, verdict semantics, gate resolution, state schema. Read only at that sub-step |
| [report-writer](./report-writer.md) | Phase 6 final-report authorship, dispatch template, resume-safe dispatch, shared-graph integrity check, Phase 7 token-usage collector |

Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`) are user-invoked skills, not lead support contracts — the lead does not consult them during a run.

## Quick Reference

| Phase | Action | Key contract |
|-------|--------|---------------|
| 1. Intake | Read task bundle in order | `context-loader` |
| 1.5 Graph hint | Build shared knowledge graph (optional) | this contract — see "Optional shared knowledge graph" below |
| 2. Prompts | Prepare shared + role-specific prompts | `team-contract` |
| 2.5 Team contract | Load operating rules for selected roster | `team-contract` |
| 3. Dispatch setup | Execute the selected adapter's pre-dispatch setup and record its state | selected runtime adapter + this contract |
| 4. Execution | Call `dispatch_worker` once per selected assignment | selected runtime adapter + `team-contract` |
| 5. Completion wait | Call `await_workers` and verify terminal state plus required artifacts | selected runtime adapter + `team-contract` |
| 5.5 Convergence | Semantically group findings, then drive deterministic state transitions through `ConvergenceEngine` via `okstra convergence` | `convergence` |
| 5.6 Critic pass | (opt-in) fresh one-shot critic pass through `redispatch_worker`: coverage gaps (discovery/error-analysis/impl-planning) or acceptance devil's-advocate (final-verification). The critic dispatch fires concurrently with the first 5.5 reverify round (its input is fixed at Round 0); gap/blocker verification (one round) completes here | `convergence` "Coverage critic pass" / "Acceptance critic pass" |
| 6. Synthesis | Dispatch Report writer worker, review draft. **For `implementation-planning`: then run the Phase 6 plan-body verification sub-step (see Phase 6 section below). Selected-direction plans verify `P-Dir-1`; legacy plans retain `P-Opt-*`.** | `report-writer` + `plan-body-verification` (sub-step) |
| 7. Persist | Call `collect_usage`, update manifests, run the cleanup approval gate, then call `shutdown_workers` only on approval | selected runtime adapter + `report-writer` + this contract |

## Core operating contract

- The `leader` owns orchestration, convergence supervision, and final-report review. It does not author the report narrative or assembled record when `Report writer worker` is in the roster. `lead` is a compatibility alias for `leader` and must not be written on new artifacts.
- Dispatch consumes stored role executions, not provider-named worker IDs. Canonical roles are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, `report-writer`, and `translator`. `executor` is a compatibility alias for `implementer`.
- Pane titles and operational rows use the stored `executionLabel`. Do not rebuild that label from a provider name or model string.
- `report-writer`, when in the roster, is the sole author of the report narrative. Lead reviews the draft and may request a revision through a follow-up dispatch, but MUST NOT edit that narrative or the assembled `data.json`. Contract v3 has no lead-authored fallback; a failed writer dispatch is retried or leaves the run blocked. Historical v2 reports may still carry `header.leadAuthoredFallback`, but no new run writes it.
- "Session resume", "team is no longer alive", and similar are NOT valid reasons to skip Report writer worker dispatch — see [report-writer](./report-writer.md) "Resume-safe dispatch".
- A shell command the lead runs must not be able to ask a question. The lead's shell is the user's own, where `cp`, `mv`, and `rm` are commonly aliased to their `-i` form; the confirmation that alias raises has nobody to answer it, so the call hangs until it is killed — observed as a `cp` over an existing state file stalling a whole self-fix round. Invoke these as `command cp` / `command mv` / `command rm`, which skips alias expansion and leaves the tool's own behaviour untouched. `-f` is not a substitute: it changes what the tool does on failure (`rm -f` reports success on a path that never existed).
- If the brief is incomplete, continue with explicit uncertainty markers rather than fabricating confidence.
- Required roles must not be replaced by unnamed generic parallel workers. Before the final verdict, every selected worker must have either a saved result file or an explicit terminal status with reason. Any attempted worker with status `completed`, `timeout`, or `error` must also have a saved worker prompt history file at its assigned run-level prompt path.

## Lifecycle Phase Boundaries (BLOCKING)

A single okstra run executes **exactly one** lifecycle phase. The phase is given by `task-manifest.json.workflow.currentPhase` (or, equivalently, the run's `Task Type`). The lead and every worker MUST stay inside that phase's boundary for the duration of the run. Crossing the boundary is a contract violation, not a productivity gain.

| Lifecycle phase | Allowed outputs | Forbidden actions |
|-----------------|-----------------|-------------------|
| `requirements-discovery` | classification, routing decision, missing-input list, next-phase recommendation | code edits, plan documents, build/test execution that mutates state |
| `error-analysis` | evidence, root-cause hypotheses, reproduction gaps, validation paths | code edits, implementation design, build/migration/deploy execution |
| `implementation-option-selection` | candidate comparison, counterevidence, criterion scores, requirement mappings, rejected-candidate audit | code edits, tests/builds, detailed file lists, stage maps, execution commands, plan approval |
| `implementation-planning` | option matrix, trade-offs, dependencies, recommended order, validation/rollback strategy, Tier3 conformance scripts + manifest under the task-root `qa/` tree, **explicit user-approval request** | source code edits, file writes outside the run's `reports/`, `prompts/`, `state/`, `manifests/`, `worker-results/`, `status/`, `sessions/` directories and the task-root `qa/` tree, build/migration/deploy execution |
| `implementation` | code edits authorised by an approved plan, accompanying tests | starting work without an approved `implementation-planning` final report carried in via `--clarification-response` or referenced in the brief |
| `final-verification` | acceptance verdict, residual risk, regression notes; read-only execution of existing test/validation commands, run-artifact writes (qa result sidecars, `okstra handoff record-verified` on acceptance), and qaEnv-replica-only conformance runs are permitted | source code edits, refactors, scope expansion, mutations of the project or shared environments |
| `release-handoff` | user-selected packaging only: PR creation / push of the implementation branch, handoff summary (lead-only phase) | local commits, history-rewriting pushes, direct pushes to a base branch, release-publishing commands, source edits, worker dispatch of any kind |
| `improvement-discovery` *(sidetrack — outside `PHASE_SEQUENCE`)* | lens-scoped improvement candidates (`## 5.9` report), per-candidate next-phase recommendation | code edits, planning or root-cause work, candidates outside the lens allowlist or beyond the cap, starting any lifecycle phase inside this run |

Phase-transition checklist (lead, end of run):

1. Confirm the current phase's required outputs are complete and recorded in the final report.
2. Set `workflow.phaseStates.<currentPhase>.state = "completed"` in `task-manifest.json` (validator does this when the run passes; verify the value).
3. Record **Phase Routing** in the final report — it is the source the Next-Phase Pointer is projected from, and Phase 7 validation recomputes the pointer from it. Update `workflow.lastCompletedPhase`. The Next-Phase Pointer (`workflow.nextRecommendedPhase`) itself is written per the Artifact Persistence Checklist in [report-writer](./report-writer.md), which owns its shape and its rules.
4. **Do NOT start the next phase inside the current run.** A new okstra invocation with the new `--task-type` is the only legal way to advance.

User-utterance interpretation rule:

- "proceed to the next step" / "move on to the next step" / equivalent phrases are scoped to **the current phase only**. Interpret them as "produce the remaining outputs of the current phase," never as "start the next lifecycle phase."
- If the current phase's outputs are already complete and the user clearly wants to advance, reply with the phase-transition checklist above and the exact next-run command. Wait for explicit user confirmation before any action that belongs to the next phase.
- If the Next-Phase Pointer target (`nextRecommendedPhase.phase`) is `implementation-planning`, the next run produces a **plan**, not code. The next run after that is `implementation`.

## Progress reporting (BLOCKING)

A single okstra run frequently spans 30–120 minutes with multi-minute silent windows while workers run; without progress signals the user cannot distinguish "still working" from "hung". Lead MUST emit a single short progress line at each checkpoint below — plain user-facing text in a separate brief message (not buried inside a tool call), one line per checkpoint, format: `PROGRESS: <phase-id> <verb-phrase>`. Emit the line raw — the literal `PROGRESS:` token must begin the line. Do NOT wrap it in inline-code backticks (`` `PROGRESS: ...` ``) or a ```` ``` ```` code fence; markdown wrapping is what the post-hoc conformance validator scrapes around, and raw emit keeps the signal unambiguous.

For an `implementation-planning` run whose run manifest declares `activityContractVersion: 1`, record every required activity boundary with `okstra agent-activity append` against the manifest-provided `leadEventsPath`. Model-facing calls pass prose through `--summary-file <md>` and a command through `--command`, `--command-cwd`, `--command-exit-code`, and `--command-output-file <md>`; do not construct `--command-record` JSON. The ordering is fixed: the structured append succeeds first, the matching `PROGRESS:` line is emitted second, and the immediately following `ACTIVITY:` line projects the same structured fields into the conversation language. Do not reconstruct structured activity from conversation text. If the append fails, do not present that activity boundary as completed.

The live projection follows this shape:

```text
PROGRESS: phase-4-dispatch worker=codex-worker model=gpt-5.6-sol
ACTIVITY: id=A-001 agent=codex-worker summary="Verify Stage Map paths and commands" items=P-Step-001,P-Step-002 result=runs/.../codex-worker-....md outcome=pending
```

Use the exact CLI projection: `id`, `agent`, quoted `summary`, comma-joined `items` (`<none>` when empty), `result` (`<none>` when empty), and `outcome`. Only prose inside `summary` is localized to the conversation language. Required kinds are `worker-dispatched`, `worker-completed`, `verification-round-completed`, `self-fix-applied`, `user-decision-required`, and `user-decision-evaluated`.

**Enforcement:** `tests/contract/test_host_orchestration_rules.py` keeps this instruction on every lead path. `validators/validate_session_conformance.py` `_check_activity_contract` checks the structured event log and does not treat an `ACTIVITY:` conversation line as evidence.

Required checkpoints:

- `PROGRESS: phase-1-intake reading task bundle` — at the start of Phase 1, before issuing parallel Read calls.
- `PROGRESS: phase-1-intake complete` — after all intake reads return.
- `PROGRESS: phase-2-prompts preparing <N> worker prompts` — at the start of Phase 2, before any `Write` to the assigned prompt paths.
- `PROGRESS: phase-3-team-create <adapter-specific-status>` — after selected-adapter setup is recorded in team-state. The stable phase id is retained for artifact compatibility.
- `PROGRESS: phase-4-dispatch worker=<role> model=<model>` — once per worker, immediately before `dispatch_worker`. `<role>` is the **roster** role, exactly as team-state's `workers[].role` records it (`Claude worker`, `Codex worker`) — the checkpoint is matched against that entry, so a phase-specific functional label (`Claude verifier`, `Codex executor`) names no roster worker and fails the check. Only `claude-worker`-style hyphenation of the same roster role is also accepted.
- `PROGRESS: phase-5-poll pending=<n> done=<m>` — emitted on each wakeup while the pending set is non-empty.
- `PROGRESS: phase-5-collect worker=<role> status=<terminal-status>` — once per worker, immediately after the result file is verified. `<role>` is the roster role, same rule as `phase-4-dispatch` above.
- `PROGRESS: phase-5.5-convergence round=<N> queue=<count>` — at the start of each convergence round (Phase 5.5).
- `PROGRESS: phase-5.6-critic provider=<provider> gaps=<n>` — after the critic result is collected (Phase 5.6, opt-in; the critic dispatch itself fires concurrently with the first 5.5 reverify round). Omitted when `convergence.critic.enabled == false`.
- `PROGRESS: phase-batch-cleanup panes=<n>` — immediately after cleaning up the previous batch's panes, at each batch boundary (① just before the first `phase-5.5-convergence` round ② just before the `phase-6-synthesis` report-writer dispatch). `<n>` is the number of panes closed at that boundary — the panes of dispatches this run recorded and that have since finished — read from the `okstra team reclaim --dry-run` pass taken immediately before the closing pass, never estimated. A pane the harness opened for its own teammate carries no recorded id, so it is not counted and not closed. Expose only the counts and NEVER expose a raw `paneId` or worker handle. Just before the first batch (analysis-worker dispatch) there is nothing to clean up, so it is a no-op and the marker is omitted.
- `PROGRESS: phase-6-synthesis dispatching report-writer-worker` — at the start of Phase 6.
- `PROGRESS: phase-5.5.9-plan-verify round=<N> items=<count>` — immediately before dispatching each plan-body verification round (`implementation-planning` only; see [plan-body-verification](./plan-body-verification.md) §"Round protocol"). Each round is a worker batch like any other, so round 2 and later MUST be preceded by a `phase-batch-cleanup` line reclaiming the previous round's verifiers. The numbering keeps this line sorted where the work happens — after Phase 6, because the round verifies the drafted plan body.
- `PROGRESS: user-confirm <C-NNN> <the question, one line>` — immediately before asking the user about anything that would otherwise become an open `Blocks=approval` row (see "User confirmation before an approval blocker" below). Not tied to a phase: it fires wherever the blocker surfaces. `<C-NNN>` is the id the row will carry, so the answer and the row can be matched afterwards.
- `PROGRESS: phase-7-persist updating manifests` — at the start of Phase 7.
- `PROGRESS: phase-7-teardown shutting-down-workers` — only after usage collection and user approval, immediately before `shutdown_workers`; omitted when no cleanup resource exists or the user keeps it.
- `PROGRESS: complete final-report=<relative-path>` — final summary line, after all persistence.

Do NOT replace them with prose ("Now I'm starting Phase 2..."), do NOT skip a checkpoint because "the previous message already said that", and do NOT batch multiple checkpoints into one. Each line stands alone so the user (or any operator scraping stdout) can timestamp it externally.

`okstra-run` surfaces these lines to the user directly; other launch paths persist them in the selected adapter's declared conformance evidence/event source for post-hoc retrieval.

**Enforcement:** the Phase 7 validator (`validators/validate-run.py` → `validate_session_conformance.py`) reads the selected adapter's declared conformance evidence/event source within the run window and fails the run as `contract-violated` when a required checkpoint is missing — including the per-worker `phase-4-dispatch` / `phase-5-collect` lines (which must name each worker's role) and the `phase-batch-cleanup` lines that MUST precede the first `phase-5.5-convergence` round and the `phase-6-synthesis` report-writer dispatch. When the plan-body state file records two or more rounds, `_check_plan_verify_cleanup_checkpoints` additionally requires a `phase-5.5.9-plan-verify` line per round and a `phase-batch-cleanup` between consecutive rounds. For activity-contract-v1 planning, `_check_activity_contract` validates the structured worker pairs, verification and self-fix counts, user-decision references, and `A-NNN` ordering. `phase-7-teardown` and `complete` fire after validation and are not checked.

## User confirmation before an approval blocker (BLOCKING)

An open `Blocks=approval` row stops the whole task: the plan cannot be approved, `implementation` cannot start, and the answer arrives only through a separate user-response cycle and a re-run. It is the most expensive artifact this contract lets the lead produce. **Before writing one, ask the user.**

This is not a phase. It fires wherever the blocker surfaces — during intake when the directive and the brief disagree, mid-convergence when workers split on something only the user can settle, in the §5.5.9 self-fix loop when an item no round can clear keeps the gate red.

The sequence is fixed:

1. Investigate before asking. Read every cited plan item, worker finding, and `path:line` the question depends on. You are not ready to ask while any option's outcome cannot be named as a concrete change: the extra work it creates, the already-decided thing it reverses, and which files or stages it touches. If a citation cannot be read, say so in the question; do not invent the missing fact.
2. Emit `PROGRESS: user-confirm <C-NNN> <the question, one line>` with the id the row would carry.
3. Ask in the user's language through the selected adapter's `prompt_user` mapping. Do not lead with `C-NNN`, `Kind`, `Blocks`, or `expectedForm`. The question body is why this is being asked, what is already decided, the fork, and each option as "if you pick this, then …". Keep the report-owned impact axes (reach or scope, added work, direction change). One question at a time. When that mapping names a native question function and the option count fits the relay `nativeLimits`, call that function with `{label, description}` options. Do not print a numbered list in chat while the native tool is available. Numbered text is only for a text-only relay or a prompt that does not fit `nativeLimits`.
4. On an answer — record the raw text in the row's `userInput`, set `status: answered` and `userConfirmation: asked-and-answered`, and apply the selected disposition in this run.
5. Only when asking fails does the row stay open: `asked-awaiting` when the user has not answered, `deferred-no-interactive-session` when this run has no user to ask.

For report contract v3 `implementation-planning`, record active approval decisions only through `okstra approval-decision`; report assembly derives each report row's status, resolution, and backtraces from that lead-owned ledger plus the activity ledger. Classify a user-owned selection as `user-decision`, a surviving non-correctness majority disagreement as `noncritical-dissent`, and a cited path/symbol mismatch, `P-Req-*` coverage mismatch, or independent Requirement Coverage blocker as `correctness-critical`. `select` is limited to `user-decision`. `accept-risk` is available to all three classifications: it ends the gate, keeps the DISAGREE votes and the clarification row as evidence, and later stages read that record. `request-revision` / `reject` are available to all three and still withhold the next phase. Contract v2 remains read-only compatible; do not create a new v2 report. **Enforced:** `scripts/okstra_ctl/approval_decisions.py` rejects invalid option/disposition combinations, and `validators/validate-run.py` `_validate_v3_approval_context` recomputes report backtraces.

The approval state transitions are fixed:

- `open → answered` when the raw user response is recorded
- `answered → resolved` after the selected disposition is applied, when that work actually completed
- `open → obsolete` only when a plan change removes the question

Do not move `answered` back to `open` because a check failed. The user's choice stands. Record the failed check on the row; later stages still see the DISAGREE votes.

`open` blocks until the user judges. `answered` with `select` / `accept-risk` / `answer`, `resolved`, and `obsolete` do not block approval or the next phase. `request-revision` and `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`). `accept-risk` does not require re-verification `AGREE`. The worker votes stay on the plan item so a later stage can still see the dissent. **Enforced:** `scripts/okstra_ctl/clarification_items.py` `row_blocks_progress`, `validators/validate-run.py` `_user_accepted_plan_item_ids`.

When a terminal row preserves a pre-correction dissent classification, keep superseded votes in `state/plan-body-verification-implementation-planning-<seq>.json`. Activities that implement or check the decision record the exact `C-NNN` in `clarificationRefs` and the affected `P-*` identifiers in `planItemIds`. Report assembly verifies that every resolution `checkRefs` value names an existing activity and derives each plan item's `clarificationRefs`; the lead never copies those references into `approvalContext`. A corrected coverage-only blocker keeps its `C-NNN` in the non-blocking Requirement Coverage row's `decisionRefs`. An `obsolete` row is invalid while its disagreement or coverage blocker remains active in the current plan. **Enforced:** `scripts/okstra_ctl/report_assembly.py` `_clarification_row` / `_attach_plan_backlinks` and `validators/validate-run.py` `_validate_v3_approval_context`.

**Predicting the blocker is not the same as raising it.** A lead that says "this will likely become an approval blocker; I will ask at that point" has already reached the moment — ask then, in that message. One run announced exactly that, never asked, wrote the row anyway, and then spent its entire self-fix budget on a gate no round could clear, because the user had already answered the question before the run started.

**`lead-directed` blockers cannot be deferred.** When the item is the lead's own judgment rather than a worker's finding, and this run has nobody to ask, the row is not the outlet — record a Working Assumption in `## 5. Missing Information and Risks` naming the assumption the plan proceeds under, exactly as a surviving planner-fixable item does, and let the plan proceed. Blocking a plan on the lead's own judgment in a run where that judgment cannot be put to the user only moves the work to a re-run.

**Never seed the answer into a worker prompt.** Instructing a worker to "raise this as a user decision rather than choosing" and then reporting the resulting agreement as an independent finding misrepresents where the blocker came from. If it is the lead's judgment, `origin` is `lead-directed` — see [_common-contract.md](../profiles/_common-contract.md) "Clarification request policy".

**Enforcement:** `validators/validate-run.py` `_validate_open_approval_blocker_provenance` fails any open approval blocker missing `origin` / `userConfirmation`, and any `lead-directed` one deferred for want of an interactive session; `validators/validate_session_conformance.py` `_check_user_confirm_checkpoints` fails a row claiming the user was asked when no matching `user-confirm` line exists in this run's evidence.

## Model assignments

**The lead never invents a model.** Every role's model comes from the `Worker Roster` section of `okstra model-io run-input`. A missing assignment is a run-input defect, not a license to fall back — see [team-contract](./team-contract.md) "Model Assignment Rules". Run preparation seeds the assignment values from `OKSTRA_DEFAULT_*_MODEL` (`scripts/okstra_ctl/run.py`).

**Reading an assignment is not enough — the selected adapter must apply it at dispatch.** `dispatch_worker` receives the complete manifest assignment. The selected runtime adapter passes `hostModelValue` to a `runner=native-session` host primitive or `modelExecutionValue` to a `runner=cli-wrapper` provider process without changing provider, role, or model. A missing or unsupported runner-specific mapping is a pre-dispatch contract failure, never a silent fallback.

The table below documents those prep-time seed values **for reference only** — it is NOT a lead-applied fallback:

| Role | Seed model | Worker assignment | Source definition |
|------|-----------|---------------|-------------------|
| Lead role | opus | -- | runtime-specific role label; orchestration + convergence supervision + final-report review/approval |
| Report writer worker | sonnet | report-writer-worker | `agents/workers/report-writer-worker.md` |
| Claude worker | opus | claude-worker | `agents/workers/claude-worker.md` |
| Codex worker | gpt-5.6-sol | codex-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |
| Antigravity worker | gemini-3.1-pro | antigravity-worker | duty + task instructions composed per invocation; deterministic `worker-dispatch` execution |

Each analysis assignment follows its recorded `runner`. `runner=native-session` uses the host's native subagent primitive after `host-native-spec-link-gate`. `runner=cli-wrapper` follows the planned execution surface after `core-pre-dispatch` verification: `okstra team dispatch` when `terminalBackend` is `cmux-pane`, otherwise the deterministic `okstra worker-dispatch` process boundary. No LLM transport wrapper sits in front of a provider CLI.

### Implementation phase: Executor binding

For `--task-type implementation` runs, the task bundle additionally pins one of `claude` / `codex` / `antigravity` as the Executor — the only worker permitted to mutate project files in that run. The binding is exposed in two canonical places:

- `instruction-set/analysis-profile.md` — top "Executor binding" block (provider, display name, model, runner, and dispatch mode)
- `runs/implementation/manifests/run-manifest-*.json` — `teamContract.executor` object (the same binding plus `appliesTo: "implementation"`)

Lead MUST dispatch Edit/Write-bearing work only through that executor binding: use the host primitive with `hostModelValue` for `runner=native-session`. For `runner=cli-wrapper`, use `okstra team dispatch` when `terminalBackend` is `cmux-pane`, or `okstra worker-dispatch` with `modelExecutionValue` otherwise. The other providers in the roster still run as read-only verifiers in the same run; the executor's own provider does not, because its worker ID materializes as the executor on every dispatch — so the diff is reviewed context-isolated by the remaining verifiers. Session isolation is the primary self-review safeguard — a verifier reusing the executor's model variant is acceptable in a distinct session. A different model variant (e.g. executor=opus / Claude verifier=sonnet) is recommended but not mandatory.

Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`); the model used by the executor is taken from the corresponding worker model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). For CLI-backed executors, the underlying file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`), not through the lead runtime's `write_artifact` operation.

#### Task worktree (BLOCKING for every task-type)

`okstra-ctl` provisions dedicated `git worktree`s at run-prep time. Lead, the Executor, and every verifier MUST treat the provisioned worktree as the canonical working directory regardless of task-type.

- **Task-key worktree (non-`implementation` phases):** `requirements-discovery`, `error-analysis`, `implementation-option-selection`, and `implementation-planning` share one worktree per task-key so phase N inherits the working-tree state phase N-1 left behind. Location: `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/` (override `OKSTRA_HOME` only for tests). All segments are sanitised — `/`, `:`, and other special chars collapse to `-`.
- **Stage worktree (`implementation`):** stage-isolated — one run = one stage, each in its own worktree at `.../<task-id-segment>/stage-<N>/` on its own branch. Single-stage `final-verification` (`--stage <N>`) reuses that stage worktree read-only; whole-task `final-verification` operates on the task-key worktree.
- Branch: `<work-category-namespace>/<task-id-segment>` (e.g. `feature/dev-9436`, `fix/dev-7311`); a stage worktree appends `-s<N>` (e.g. `feature/dev-9436-s2`). The task-key worktree is branched from the user-chosen `--base-ref` (default: `HEAD` of the repo's **main** worktree) at the first phase's prep time; a stage worktree's base is resolved from its `depends-on` anchors at prep time. The resolved base SHA is recorded in `EXECUTOR_WORKTREE_BASE_REF`.
- A global registry at `~/.okstra/worktrees/registry.json` (flock-guarded) reserves both task-keys and stage-keys (`<task-key>#stage-<N>`), mapping each to its path + branch, and prevents concurrent runs from colliding. Branch names are globally unique on this machine.
- Worktree sync mirrors the configured project-relative directories from the **main worktree** so task checkouts see the same filesystem state. This is filesystem continuity only: okstra-owned context and writes still stay inside `<PROJECT_ROOT>/.okstra/**` unless the brief explicitly authorizes a non-okstra path.
- The path, branch, base ref, and provisioning status (`created` | `reused` | `skipped-in-worktree` | `skipped-not-git`) are exposed through the launch prompt's `## Executor Worktree` section and the implementation profile's worktree block.
- **Skip conditions** (worktree provisioning is a no-op; task uses `project_root` directly):
  - `project_root` is already inside a non-main worktree (the run reuses the caller's worktree to avoid nesting).
  - `project_root` is not inside a git repository at all.
- **Failure mode**: any other error during `git worktree add` (on-disk path collision not tracked by the registry, branch collision with a different task-key, detached-HEAD with no SHA) raises `PrepareError`. Re-run after manually removing the stale path/branch — the worktree is intentionally not garbage-collected.
- **Lifecycle**: kept after the run for follow-up phases, manual PR authoring, rollback verification, and `final-verification`. Manual cleanup: `git -C <main-worktree> worktree remove <path>` then `git -C <main-worktree> branch -D <branch>`; remove the corresponding (task- or stage-) key from the registry by hand.

## Phase 1: Task-bundle intake and required reading order

**REQUIRED RESOURCE:** Read [context-loader](./context-loader.md) first to discover task bundle paths.

Treat cross verify input as a task bundle, not as a single file. If the user did not specify an explicit task key or task path, use context-loader's current-task pointer. For task browsing, task-id disambiguation, or project-level task inventory, use context-loader's rendered discovery result rather than reading discovery JSON directly.

After context-loader completes, read **only the compact intake files below** in a single parallel-Read message at the start of Phase 1. The other instruction-set files are loaded lazily at the phase that actually needs them — see "Lazy reading discipline" below. This split exists because re-absorbing the full instruction-set baseline at every phase entry is the dominant source of lead-token bloat — most of it is files only one downstream phase uses.

**Mandatory at Phase 1 start (parallel Read, one message):**

1. `okstra model-io run-input --run-manifest <run-manifest-path found by context-loader>` — fixed Markdown run identity and scope input
2. `instruction-set/analysis-profile.md` — needed to pick the right `Required workers:` block and phase rules
3. `instruction-set/analysis-packet.md` — primary compact input for analysis worker dispatch

**Lazy reading discipline (do NOT read at Phase 1):**

- `task-index.md` — only when the user explicitly asks for a human summary or when history disambiguation is required.
- `instruction-set/task-brief.md` — read only if the packet is insufficient, a cited source needs verification, or report-writer synthesis requires the full source.
- `instruction-set/analysis-material.md` — read only if the packet is insufficient or a source citation needs verification. Many task bundles have no meaningful material file beyond a duplicate brief wrapper.
- `instruction-set/reference-expectations.md` — read at Phase 6 synthesis (or whenever the report-writer worker is dispatched) — it informs the match/gap assessment. Analysis workers use the packet excerpt unless they need source verification.
- `instruction-set/final-report-template.md` — never read by Lead. The Report writer worker reads it as part of its own [Required reading]; Lead only references its path when dispatching.
- Run history timeline JSON — do not read or parse it. For carry-in or resume resolution, use the workflow snapshot, artifact paths, final status path, and resume command in `okstra model-io run-input`; report insufficient information instead of opening timeline JSON.
- Owned lifecycle artifacts are projected only through the purpose-specific `okstra model-io run-input` and `active-context-input` views.

**Implementation profile lazy reading discipline (BLOCKING — applies only when `task_type == "implementation"`):**

The `implementation` profile's thin core (`prompts/profiles/implementation.md`) is intentionally minimal so the Phase 1 baseline stays small. Three sidecar files carry the bulk of the rules and MUST be read at the listed phase — do NOT pre-load them at Phase 1. The sidecar list and each one's `Read at` phase live in that profile's "Lazy section pointers" table, which arrives in the Phase 1 intake via `analysis-profile.md`, so it is already in context whenever this discipline applies.

**Entry guard (BLOCKING).** Before transitioning into Phase 5 or Phase 6 for an `implementation` run, lead MUST load the sidecar(s) whose `Read at` (per that table) matches the entering phase — either a single `Read` tool call, or a shell command naming that file (`cat`, `sed -n`), since some hosts steer file reads to the shell. If lead enters the phase without that load recorded in the selected adapter's conformance evidence/event source, phase entry is refused — lead writes a `contract-violation` to the run-level errors log with `--message "implementation-sidecar-not-loaded"` and stops. Re-entry requires the sidecar Read first. **Enforcement:** the Phase 7 validator (`validate_session_conformance.py`) verifies post-hoc that all three sidecar loads exist in the selected adapter's declared source within this run's window, and that they precede the `phase-6-synthesis` / `phase-7-persist` checkpoints respectively.

The guard is not satisfied by memory from a prior run — each implementation run re-reads the sidecar fresh, since `okstra install` may have updated it between runs.

This pattern is implementation-only. Other profiles (`requirements-discovery`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `final-verification`, `release-handoff`) load their whole profile body at Phase 1 as before — they are short enough not to benefit from a split.

Extract from the compact intake files: task key, task type, work category, workflow lifecycle snapshot, selected worker roster, assigned models, worker result paths, worker prompt history paths, current run prompt directory, final report path, final status path, validator path, resume helper path, config-file references, deployment-manifest references, and their expected values or invariants.

If previous run reports exist, use as historical context only. If discovery metadata or current artifacts conflict with a newer user instruction, prefer the user instruction. If `reference-expectations.md` explicitly says expectations were not provided (you can confirm this without reading the file if the brief's "Expected state" section is empty), treat that as missing information and say `I don't know` rather than inventing expected states.

### Phase 1.5 — Analysis scope confirmation (BLOCKING)

For `project-analysis`, `feature-analysis`, and `change-impact-analysis`, Lead MUST use the run manifest's immutable pre-dispatch `analysisScopeConfirmation` snapshot as the structured reporter-confirmation evidence before Phase 4 worker dispatch. Its `status` MUST be `complete`; its `taskBriefPath` and `briefSha256` bind that status to the exact brief bytes captured when the run manifest was created. A later edit to the live brief, clarification prose, or inferred consent cannot substitute for this snapshot. `project-analysis` confirms which areas remain shallow; `feature-analysis` confirms the exact feature target and covered flows; `change-impact-analysis` confirms the proposed change, preserved behavior, and dependency boundary.

If that snapshot is incomplete, missing, or malformed, Lead MUST follow the shared Reporter Confirmation Required / Clarification Items contract and stop before dispatch. A scope reduction must be explicitly confirmed in the brief's `## Reporter Confirmations` before a fresh bundle records `analysisScopeConfirmation.status=complete`. Do not dispatch workers and then defer a foreseeable scope conflict to a final HTML question. **Enforcement:** `scripts/okstra_ctl/render.py` records the brief path, reporter-confirmation status, and brief byte digest in the run manifest before the lead can dispatch workers; `validators/validate_analysis_report.py` validates only that immutable snapshot, rejects required-worker execution before a complete snapshot, and recomputes the analysis verdict; `validators/validate-run.py` runs that check only after schema validation succeeds.

## Phase 2 — Phase 5: Prompt preparation, teammate setup, execution, completion poll

These phases are governed by [team-contract](./team-contract.md). It is the canonical source for:

- Worker prompt anchor headers and body composition rules.
- The `[Required reading]` clause (analysis-packet primary input for analysis workers, full source files for report-writer, scoped inputs for reverify dispatches).
- The `[Error reporting]` clause and the asymmetry between claude-worker and codex/antigravity-worker prompts.
- Worker output contract (sections 1–5 + optional Section 6; the Reading Confirmation block lives in the audit sidecar, never in the worker-results file — the preamble "Reading rules" section is canonical and the validator rejects violations), header standard, terminal statuses, errors-sidecar schema.
- Token-usage tracking conventions.

For `final-verification`, Lead persists initial analysis prompts with one shared semantic body. Any genuine run delta appears under exactly one `## Run-specific directive` heading and applies to every selected analysis worker; Lead never shards verification requirements by worker, provider, or model. If the shared directive would exceed 40 nonblank lines, Lead writes it into the instruction set and adds the same reference to `analysis-packet.md` before dispatch. Phase 7 validates the persisted initial analysis prompts through the shared prompt contract before the run can pass.

For `improvement-discovery`, Lead records `## Primary Pass Assignments` in the Phase 1.5 grilling log before worker prompt generation. Enumerate selected analyser worker instances in run-manifest `requiredWorkerRoles` order and rotate them over the resolved lenses in log order; provider and model names never determine assignment position. Every analyser still inspects every resolved lens. Phase 7 recomputes this rotation from the persisted roster and fails a missing, extra, duplicate, out-of-order, or out-of-scope assignment.

`Report writer worker` is NOT an analysis worker. Do not dispatch it in Phase 4/5 alongside analysis workers. It is invoked only in Phase 6 — see [report-writer](./report-writer.md).

### Phase 3 — Runtime adapter setup (BLOCKING)

1. Read the selected adapter contract path from the rendered launch prompt.
2. Verify the run manifest's `leadRuntime`, `leadAdapter`, dispatch backend, concurrency metadata, and artifact paths.
3. Execute the adapter's pre-dispatch setup without substituting another adapter's primitive.
4. Persist the setup outcome in team-state using the existing fields required by that backend.
5. Emit the canonical `PROGRESS: phase-3-team-create <adapter-specific-status>` checkpoint. The phase id remains stable for artifact compatibility; only the adapter-owned verb phrase varies.

### Phase 4 / Phase 5 — Dispatch, await, and error-log recording

For each selected worker assignment, persist the exact prompt history, emit the per-worker Phase 4 checkpoint, and call `dispatch_worker(assignment, prompt)` through the selected adapter. Then call `await_workers(handles)`. A dispatch acknowledgement or process/pane creation is never completion: verify the terminal status, Result Path, and worker-results audit path required by `team-contract` before emitting the Phase 5 collection checkpoint.

Retries and convergence re-verification always call `redispatch_worker` to create a fresh one-shot session. Never reuse a worker conversation or switch adapters/providers to hide a failed assignment.

### Errors log path wiring (BLOCKING)

The launch prompt's `## Run Logs (error-log wiring)` section gives Lead the resolved absolute path for the run-level errors log. When Lead constructs each worker's dispatch prompt body, Lead MUST inject this header line verbatim:

- `**Errors log path:** <absolute run-level errors log path from launch prompt>`

Workers are contractually required to extract this line and abort with `<WORKER>_ERRORS_PATH_MISSING` if it is absent (see each worker definition's "Path extraction (BLOCKING)" block). A worker records its tool failure through the typed `okstra error-log append-observed` form in that contract; it does not write an intermediate JSON file.

After each worker terminates, BEFORE classifying its terminal status, verify the canonical result file exists at the absolute path resolved from the `**Result Path:**` header. If it is absent — or the deterministic provider process returned `CODEX_RESULT_MISSING` / `ANTIGRAVITY_RESULT_MISSING` — re-dispatch the SAME worker once with the byte-identical prompt. Only after the second attempt also misses may the role be classified `error` with `--message "result-missing after 1 retry"`. Full rules: [team-contract](./team-contract.md) "Lead Redispatch Policy on Result-Missing".

`--agent`, `--agent-role`, and `--error-type` are **closed enums**, not free-form labels — the role names used elsewhere in these contracts (`Codex worker`, `Claude worker`) are rejected. Use exactly:

- `--agent` — `claude-worker` | `codex-worker` | `antigravity-worker` | `grok-worker` | `kimi-worker` | `report-writer`
- `--agent-role` — `lead` | `worker` | `report-writer`
- `--error-type` — `cli-failure` | `contract-violation` | `tool-failure`

For a lead-attributed event there is no value in the list above — the selected adapter names the lead identity to pass.

For deterministic Codex/Antigravity provider processes: if the CLI returns non-zero, times out, or hits a rate limit, immediately call `append-observed` with the captured exit code, duration, message, and stderr excerpt. `append-observed` additionally requires `--phase`, `--command`, and `--command-kind`, so copy this form rather than trimming the one above:

```bash
okstra error-log append-observed \
  --out <absolute-errors-log-path-from-launch-prompt> \
  --task-key <taskKey> --phase 5.5 \
  --agent codex-worker --agent-role worker --model <model> \
  --error-type cli-failure \
  --command "okstra-codex-exec.sh <project-root> <model> <prompt-path>" \
  --command-kind wrapper \
  --exit-code 124 --duration-ms 1800000 \
  --message "reverify-r1 wrapper never launched" \
  --stderr-excerpt "<last stderr lines, or use --stderr-excerpt-file>" \
  --cause sandbox-denied \
  --evidence targetProbe=wrapper-write-.okstra-state-denied \
  --evidence controlProbe=wrapper-write-tmp-succeeds
```

Keep `--message` to the error actually observed — asserting that a sandbox or permission boundary blocked the call requires `--cause sandbox-denied` plus both `--evidence targetProbe=<value>` and `--evidence controlProbe=<value>` probes, and an unevidenced block claim in `--message` is rejected.

The deterministic dispatcher records this through its selected adapter — Lead does NOT need to re-record. Token usage is not inferred from dispatch return values; call `collect_usage` at the start of Phase 7.

## Phase 5.5: Convergence loop

**REQUIRED RESOURCE:** Read [convergence](./convergence.md) for iterative cross-verification.

Convergence is enabled by default. Configure via task-manifest.json:

- `convergence.enabled`: true/false (default: true)
- `convergence.maxRounds`: 1–3 — **phase-aware default**: `1` for `requirements-discovery`, `2` for all other task types
- `convergence.verificationMode`: `"lightweight"` | `"full-reanalysis"` (default: `"lightweight"`; the adversarial phases below force `"full-reanalysis"`)
- `convergence.adversarial`: true/false — **phase-aware default**: `true` for `requirements-discovery` / `error-analysis` / `implementation-option-selection` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`, `false` otherwise. When `true`, Phase 5.5 runs in adversarial mode (verifiers refute findings; burden of proof on the claim). See [convergence](./convergence.md) "Adversarial Verification Mode".

When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolve the effective value via the phase-aware default above before entering Phase 5.5 and put it in the grouped input at `config.effectiveMaxRounds`.

The lead judges semantic similarity, ticket-set equality, and evidence. After writing the grouped input, it drives `okstra convergence seed → plan-round → apply-round → finalize → validate` and MUST NOT calculate queue membership, classification, history arithmetic, skip reasons, final state, or counts itself. `ConvergenceEngine` owns all of those deterministic transitions.

For every dispatch plan, create only the worker batches returned by the engine and send them through the selected adapter. Confirmed findings cannot reappear because queue pruning is engine-owned and monotonic. Reverify terminal failures are structured outcomes for `apply-round`, never lead-authored `DISAGREE` votes.

If any re-verification batch yields a `verification-error` terminal status, or a worker result fails the contract, Lead MUST record one event per violation via `okstra error-log append-observed --error-type contract-violation --agent <offending-agent> ...`. For an internally detected violation without a specific worker, use the selected adapter's lead-role event identity.

If convergence is disabled, `seed`/`finalize` produce the auto-disabled final state and Phase 6 uses the raw worker results for synthesis.

## Phase 6: Final report assembly

**REQUIRED RESOURCE:** Read [report-writer](./report-writer.md) for report ownership, dispatch, assembly, and Phase 7 rules.

### Authoring ownership (BLOCKING)

If `Report writer worker` is in the selected roster (`recommendedWorkers` / `resultContract.requiredWorkerRoles`), Lead dispatches it to author only `report-writer-narrative-<task-type>-<seq>.md`, its worker-result pointer, and its audit sidecar. The worker may read the complete run context but cannot write `final-report-*.data.json` or another role's ledger. After every required input exists, Phase 7 runs report assembly, which validates the role-owned inputs and atomically publishes the report record once. Phase 7 then renders the human HTML from that record. Contract v2 artifacts remain readable but no new run writes them. **Enforced:** report-writer dispatch completion paths in `scripts/okstra_ctl/dispatch_state.py`, granted artifacts in `scripts/okstra_ctl/dispatch_core.py`, and `scripts/okstra_ctl/report_assembly.py` `assemble_report`.

Before constructing the dispatch prompt, the lead MUST:

- Preserve the `**Report Language:**` value already materialized in the
  report-writer dispatch prompt. The dispatcher resolves project/global
  configuration and brief-language inference before the model receives the
  prompt; report assembly copies the immutable run-manifest value into the
  final record.

The convergence output provides four finding categories:

1. Full Consensus
2. Partial Consensus
3. Contested
4. Worker-Unique

All categories must appear in the final synthesis. Do not omit contested or worker-unique findings. If the brief does not define a stricter format, follow `instruction-set/final-report-template.md`. If `reference-expectations.md` defines explicit expected states for config files or deployment manifests, include a clear match/gap assessment.

If only one worker result is usable: reduced-confidence synthesis. If evidence is missing: say `I don't know`. If no meaningful worker differences: say so explicitly.

### Phase 6 sub-step: Plan-body verification (implementation-planning only, BLOCKING)

After the Report writer worker narrative is reviewed, **if** `task_type == "implementation-planning"` **and** `task-manifest.json` `convergence.planBodyVerification.enabled == true` (default), the lead MUST run the plan-body verification sequence on the consolidated plan body before declaring Phase 6 complete and entering Phase 7.

This is a Phase 6 sub-step — it does NOT introduce a new top-level lifecycle phase; the lead operating-phase model (Phase 1 Intake → Phase 7 Persist, labels in the "Quick Reference" table above as the single source of truth) is preserved. The round's outcome is read from the final report's `### 5.5.9 Plan Body Verification` section and `implementationPlanning.planBodyVerification` in its data.json — it is not a separate lifecycle phase identifier.

**REQUIRED RESOURCE:** Read [plan-body-verification](./plan-body-verification.md) for the round protocol, plan-item ID scheme (`P-Dir-1` for selected-direction; `P-Opt-*` for legacy candidate comparison; then `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*` / `P-Req-*` / `P-Prep-*`), verdict semantics (`AGREE` / `DISAGREE(a-f)` / `SUPPLEMENT`), classification rules, gate-result resolution, and the state-file schema at `runs/<task-type>/state/plan-body-verification.json`. For `P-Dir-1`, compare `directionRealization` with `selectedDirectionRef` and its snapshot: verify the core mechanism, architecture boundaries, planning invariants, and any hidden direction change.

Distinct from Phase 5.5 finding convergence:

- Phase 5.5 reconciles worker **findings** (F-*) from independent analysis.
- This sub-step reconciles the **consolidated plan body** (P-*) authored by the Report writer worker.
- The two rounds use disjoint queues and separate state files — see [plan-body-verification](./plan-body-verification.md) "MUTUAL EXCLUSION (BLOCKING)".

Lead's responsibilities in this sub-step (in order):

For a new `implementation-planning` run, the fixed order is initial verification → one planner self-fix → targeted re-verification → user gate. The initial verification is round 1 and the targeted re-verification is round 2. A second automatic self-fix is a contract violation. When `okstra plan-items prepare` reports `"gating": false` (one-stage `no-design-inputs` plan), skip the self-fix loop and the sweep batch: extraction and round 1 still run, then go to the user gate. Two-or-more stages, a PREP item, or non-empty `designPreparation.items` keep `gating: true` and the full order.

1. Build the queue with `okstra plan-items prepare --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`, place the output of `okstra plan-items prompt --run-manifest <run-manifest>` verbatim in every verifier prompt, then run `okstra plan-items validate-prepared --narrative <report-writer-narrative.md> --run-manifest <run-manifest>`. Python resolves the one convergence-owned state path from that run identity. The lead MUST NOT summarise, select, omit, reorder, or renumber the queue. Each prompt uses the compact subject plus the lossless payload, and asks every item:

   ```text
   What concrete false-positive input, failure ordering, or omitted dependency
   would make this plan item incorrect even if its happy path succeeds?
   ```

   An `AGREE` response records the considered counterexample and exclusion reason in its note; unverified external material is `verification-error`, not `DISAGREE`.
2. Dispatch a single plan-body reverify round to every analyser worker in the roster (`claude`, `codex`, and `antigravity` when opted in). `Report writer worker` is NOT a participant in this round.
3. Record each verifier Markdown result through `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md> --round <N>`. Python validates every submitted `P-*` identifier against the current convergence state and overwrites only that round's verdicts. Then resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
4. After `okstra plan-verify` succeeds, run `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. Python reads the current worker assignments, atomically appends the convergence-owned history, and updates its nested final projection. This state is the only plan-verification input report assembly reads.
5. Record every *in-scope execution* `majority-disagree` decision through `okstra approval-decision`; record its plan and clarification links only on activities. Do not promote `observed` / `deferred` / `record` items, and do not append `clarificationItems[]` directly.
6. Run report assembly after the final plan-body state, approval ledger, design snapshot, activity ledger, and team state are complete. Assembly writes `implementationPlanning.planBodyVerification` and derived clarification rows while publishing `data.json` once.
7. Publish the report record `frontmatter.approved` field as `false`. There is no in-body `- [ ] Approved` marker line — approval lives only in the record (see [plan-body-verification](./plan-body-verification.md) §"Round protocol" step 9). The user may set it to `true` (via `--approve` or the in-session wizard) only when the gate is `passed` or `passed-with-dissent`. **Enforced:** `validators/validate-run.py` `validate_phase_boundary` fails a report shipping `approved: true` under `blocked-by-disagreement` / `aborted-non-result`, and run-prep (`scripts/okstra_ctl/run.py` `_validate_approved_plan`) fail-closes the same case. Manually flipping a blocked gate to passing is a contract violation.

If `convergence.planBodyVerification.enabled == false` (set by `--no-plan-verification` or by `okstra config set plan-verification off`), the entire sub-step is skipped and the top-of-report Approval marker is rendered unconditionally (legacy behaviour). This opt-out is intended for fast iteration only and is not recommended for handoff-ready plans.

## Phase 7: Artifact persistence and validator handoff

The detailed persistence sequence lives in [report-writer](./report-writer.md). Drive it through `okstra report-finalize`; do not patch any final-report field manually.

Order of operations:

1. Run `okstra report-finalize ...`. Contract v3 collects usage, assembles the role-owned inputs into `data.json` once, checks the source, renders views, persists follow-ups, validates the run, and performs eligible teardown in order.
2. When `meta.reportLanguage` is not `en`, first run only `token-usage`, `project-activity`, and `check-source`. Dispatch the translator against that assembled record, then resume with only `render-views`, `spawn-followups`, `validate-run`, and `teardown-stages` so assembly is not repeated.

Keep the assigned worker prompt history paths stable in `team-state`, `run-manifest`, and `task-manifest`. Do not rewrite prompt artifacts to `/tmp` or omit prompt metadata for attempted workers.

After persistence, reply briefly in the resolved Report Language. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first. Then: completion status, the task-qualified human report path, the report record path, validator result, any remaining blocker. **Close with the user's next action** — one command they can run now. A prohibition (`do not start implementation`) is not a next action. A status dump is not a close. This closeout is also in the launch prompt (`prompts/launch.template.md` "User closeout (BLOCKING)") so it is not lazy-read.

Pick the next action from this table; the first matching row wins:

- Open `blocks: approval` rows → `/okstra-user-response` (name the `C-NNN` ids). Do not start implementation until those answers exist. An `accept-risk` / `select` / `answer` already recorded is not an open blocker.
- `workflow.awaitingApproval` is true → `/okstra-run` → `implementation` (asks `approve_plan_confirm`) or `--approve`. Do not propose another planning run.
- Phase 7 `validate-run` failed → one line naming the blocking cause, then `/okstra-run` to re-run this phase with the recorded sidecar, or `/okstra-inspect recap` if the resume flags are unknown.
- Pointer `status: ready` → `/okstra-run` for that `phase`.
- Otherwise → `/okstra-inspect status` for this task.

When the host native picker is available and two of those rows could apply, ask with that picker (recommended first). Do not end the turn after the status dump.

**Every run-artifact path in this reply MUST be task-qualified** — report the human report as `.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/reports/final-report-<task-type>-<seq>.html` rooted at the task bundle, NOT the bare `runs/<task-type>/reports/...` form (byte-for-byte identical across every task of the same task-type, so it cannot identify the task). Under that, cite the report record (`.data.json`) and one line to render the full reading copy: `okstra render-final-report <task-qualified data.json>`. The same task-qualified rule applies to the team-state path, resume command path, and any other run-artifact path this reply cites.

The run-level error log lives at `<runDir>/logs/errors-<task-type>-<seq>.jsonl`. It is informational. Its presence or absence does not affect the final verdict. Do not block report writing on it.

## Run-scoped worker-resource lifecycle

- At run start, call the selected adapter's setup required to distinguish lead-owned resources from worker-owned resources.
- Before every new worker batch, and between worker rounds within a phase, close **every** resource the prior round's completed workers still hold — display surfaces, roster entries, and live execution handles alike — before the next dispatch; never the lead and never an in-flight worker. Call `record_lead_event` for the batch-cleanup checkpoint. Which resources exist and how each one is released is the selected adapter's mapping.
- Before the lead asks the user for any approval, clarification, or decision through the runtime's prompt primitive after workers have been dispatched, first close every resource the completed workers still hold, exactly as at a round boundary — so no user gate is shown while a finished worker's display surface is still open. Never the lead and never an in-flight worker. Record the gate-cleanup checkpoint.
- Closing a completed worker's resources is one paired operation: releasing its display surface and stopping its live execution handle together. Stopping the execution handle alone idles the roster entry but leaves its display surface open, so a handle-stop by itself is never cleanup — every cleanup point (round boundary, user gate, run end) does both.
- After Phase 7 persistence and `collect_usage`, enumerate residual adapter-owned resources. If none remain, skip the question.
- If resources remain, call `prompt_user` once with a binary keep-or-clean choice. The answer controls the entire residual set; do not ask a second backend-specific cleanup question.
- On keep, preserve all resources and provide the selected adapter's manual-cleanup instruction.
- On clean, emit the teardown checkpoint and call `shutdown_workers` through the selected adapter. Never execute another adapter's cleanup primitive.
## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Calling a host primitive from an unselected adapter | Read the selected adapter path from the launch prompt and execute only its semantic-operation mappings |
| Substituting lead reasoning for a worker result | The lead synthesizes only — call `dispatch_worker` for the selected assignment |
| Skipping a worker silently | Always record terminal status with reason |
| Writing verdict before all workers report | Wait for all results or explicit terminal statuses |
| Ignoring task bundle model assignments | Task bundle overrides are canonical |
| Inserting per-worker emphasis sentences ("you focus on X") into dispatch prompts | Send byte-identical dispatch prompts per [team-contract](./team-contract.md) "Dispatch-prompt invariant" — specialization lives in Section 6 of the worker output, not the prompt body |
| Omitting contested or worker-unique findings | All categories must appear in the report |
| Running full re-analysis when lightweight suffices | Default lightweight; full only when manifest opts in |
| Using `/tmp/*prompt*.txt` for worker prompt persistence | Persist the exact worker prompt to the assigned run-level `prompts/` path |
| Lead writes `final-report-<suffix>.md` itself when `Report writer worker` is in the roster | Dispatch the Report writer worker per [report-writer](./report-writer.md) "Phase 6 dispatch template" |
| Skipping Report writer worker dispatch citing "session resume constraint" or "team is gone" | No such constraint exists — see [report-writer](./report-writer.md) "Resume-safe dispatch" |
| Selecting a generic worker assignment for Report writer worker | Use the rostered `report-writer-worker` assignment; the selected adapter owns its native field mapping |
| Including `final-report-template.md` in analysis worker `[Required reading]` | Template belongs only in the report-writer prompt — see [team-contract](./team-contract.md) audience-scoped enumeration |
| Injecting `[Required reading]` into lightweight reverify prompts | Lightweight reverify forbids re-reading source materials — see [convergence](./convergence.md) "Reverify prompt: required-reading suppression" |
| Letting `convergence.maxRounds` default to 2 for `requirements-discovery` | Resolve effective default to `1` for discovery and put it in the grouped input |
| Issuing serial Read calls in Phase 1 | The intake files are independent — issue all Read calls in a single message (parallel) |
| Flagging an adapter-shortened dispatch prompt as "incomplete" because it omits host-loaded material | The Worker Preamble pointer and core inputs remain mandatory; the selected adapter may omit duplicate host-loaded definitions |
| Waiting silently after `dispatch_worker` returns without a completed worker artifact | A dispatch acknowledgement is not completion — call `await_workers` and enforce the selected adapter's liveness policy |
| Re-sending a finding absent from the persisted round plan | Dispatch exactly the engine-returned `findingIds`; see [convergence](./convergence.md) "Re-verification Dispatch" |
| Aggregating a `timeout`/`error` reverify dispatch as `DISAGREE` | Put the terminal outcome in round results; `apply-round` records `verification-error`. See [convergence](./convergence.md) "Worker failure handling in reverify" |
| Bypassing `report-finalize` and running its Phase 7 steps manually | Run `okstra report-finalize ...`; it owns token substitution and the remaining persistence order. |
