okstra 0.111.0 → 0.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.kr.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/okstra +7 -1
  4. package/docs/for-ai/README.md +2 -2
  5. package/docs/for-ai/skills/okstra-brief.md +2 -3
  6. package/docs/for-ai/skills/okstra-container-build.md +2 -3
  7. package/docs/for-ai/skills/okstra-inspect.md +5 -12
  8. package/docs/for-ai/skills/okstra-rollup.md +3 -4
  9. package/docs/for-ai/skills/okstra-run.md +3 -5
  10. package/docs/for-ai/skills/okstra-schedule.md +2 -7
  11. package/docs/for-ai/skills/okstra-setup.md +1 -1
  12. package/docs/kr/architecture/storage-model.md +1 -2
  13. package/docs/kr/architecture.md +2 -2
  14. package/docs/kr/cli.md +4 -2
  15. package/docs/project-structure-overview.md +5 -3
  16. package/package.json +1 -1
  17. package/runtime/BUILD.json +2 -2
  18. package/runtime/prompts/coding-preflight/overview.md +3 -1
  19. package/runtime/prompts/launch.template.md +1 -5
  20. package/runtime/prompts/lead/context-loader.md +2 -4
  21. package/runtime/prompts/lead/convergence.md +11 -240
  22. package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
  23. package/runtime/prompts/lead/plan-body-verification.md +240 -0
  24. package/runtime/prompts/lead/report-writer.md +7 -7
  25. package/runtime/prompts/lead/team-contract.md +15 -17
  26. package/runtime/prompts/profiles/_common-contract.md +1 -37
  27. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  28. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  29. package/runtime/prompts/profiles/final-verification.md +1 -1
  30. package/runtime/prompts/profiles/implementation-planning.md +2 -2
  31. package/runtime/prompts/profiles/implementation.md +1 -1
  32. package/runtime/python/okstra_ctl/path_hints.py +1 -0
  33. package/runtime/python/okstra_ctl/paths.py +3 -0
  34. package/runtime/python/okstra_ctl/render.py +8 -0
  35. package/runtime/python/okstra_ctl/set_work_status.py +147 -0
  36. package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
  37. package/runtime/skills/okstra-brief/SKILL.md +32 -176
  38. package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
  39. package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
  40. package/runtime/skills/okstra-container-build/SKILL.md +7 -20
  41. package/runtime/skills/okstra-graphify/SKILL.md +8 -8
  42. package/runtime/skills/okstra-inspect/SKILL.md +27 -43
  43. package/runtime/skills/okstra-rollup/SKILL.md +6 -6
  44. package/runtime/skills/okstra-run/SKILL.md +7 -16
  45. package/runtime/skills/okstra-schedule/SKILL.md +64 -419
  46. package/runtime/skills/okstra-setup/SKILL.md +25 -223
  47. package/runtime/skills/okstra-setup/references/project-config.md +188 -0
  48. package/runtime/templates/reports/schedule.template.md +2 -2
  49. package/runtime/templates/worker-prompt-preamble.md +1 -1
  50. package/runtime/validators/validate-run.py +7 -7
  51. package/src/cli-registry.mjs +14 -0
  52. package/src/commands/inspect/set-work-status.mjs +31 -0
  53. package/src/commands/lifecycle/check-project.mjs +68 -56
  54. package/src/commands/lifecycle/install.mjs +1 -0
  55. package/src/commands/lifecycle/preflight.mjs +82 -0
@@ -30,10 +30,11 @@ This document is the operating contract and phase index. Detailed procedures liv
30
30
  |-------|-------|
31
31
  | [context-loader](./context-loader.md) | Phase 1 task-bundle discovery, manifest fields, run-directory layout |
32
32
  | [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 |
33
- | [convergence](./convergence.md) | Phase 5.5 finding convergence loop, finding categories, reverify dispatch (anchor headers, required-reading suppression), convergence state schema, **plus Phase 6 plan-body verification mode (implementation-planning only)** |
33
+ | [convergence](./convergence.md) | Phase 5.5 finding convergence loop, finding categories, reverify dispatch (anchor headers, required-reading suppression), convergence state schema |
34
+ | [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 |
34
35
  | [report-writer](./report-writer.md) | Phase 6 final-report authorship, dispatch template, resume-safe dispatch, shared-graph integrity check, Phase 7 token-usage collector |
35
- | `okstra-inspect` | Unified read-side skill — sub-commands `status` (workStatus updates), `history` (past runs, re-run, resume), `report` (find/read final-report), `time` (per-task/per-worker elapsed-time breakdown), `logs` (wrapper log sidecar inventory + cleanup suggestions) |
36
- | `okstra-schedule` | Generate a consolidated work schedule for a task-group |
36
+
37
+ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule`) are user-invoked skills, not lead support contracts — the lead does not consult them during a run.
37
38
 
38
39
  ## Quick Reference
39
40
 
@@ -48,8 +49,8 @@ This document is the operating contract and phase index. Detailed procedures liv
48
49
  | 5. Completion poll | Self-scheduled poll for worker result files | `team-contract` |
49
50
  | 5.5 Convergence | Cross-verify findings across workers | `convergence` |
50
51
  | 5.6 Critic pass | (opt-in) reused-worker critic pass: 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" |
51
- | 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).** | `report-writer` + `convergence` (sub-step) |
52
- | 7. Persist | Run token-usage collector, update manifests, ask once about residual tmux panes plus worker teammates, then close panes and dismiss teammates together on approval (reconcile stale members + `SendMessage` shutdown_request) | `report-writer` + `_common-contract.md` "Phase wrap-up" / "Run-end teammate teardown" |
52
+ | 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).** | `report-writer` + `plan-body-verification` (sub-step) |
53
+ | 7. Persist | Run token-usage collector, update manifests, ask once about residual tmux panes plus worker teammates, then close panes and dismiss teammates together on approval (reconcile stale members + `SendMessage` shutdown_request) | `report-writer` + this contract see "Run-scoped pane & teammate lifecycle" below |
53
54
 
54
55
  ## Core operating contract
55
56
 
@@ -103,7 +104,7 @@ Required checkpoints:
103
104
  - `PROGRESS: phase-batch-cleanup panes=<n> teammates=<m>` — 이전 배치 pane·완료 teammate 를 정리한 직후, 각 배치 경계에서(① 첫 `phase-5.5-convergence` round 직전 ② `phase-6-synthesis` report-writer dispatch 직전). 카운트만 노출하고 `%NNN`/lead-pane.id/raw Agent name 은 절대 노출하지 않는다. 첫 배치(분석 워커 dispatch) 직전엔 치울 게 없어 no-op 이며 마커도 생략.
104
105
  - `PROGRESS: phase-6-synthesis dispatching report-writer-worker` — at the start of Phase 6.
105
106
  - `PROGRESS: phase-7-persist updating manifests` — at the start of Phase 7.
106
- - `PROGRESS: phase-7-teardown dismissing teammates` — after token-usage collection and after the pane-disposition prompt, only when the user approved combined pane/team cleanup, immediately before reconciling stale members + dismissing them via `SendMessage` shutdown_request (Teams mode only; see `_common-contract.md` "Run-end teammate teardown"). Skipped when split-pane is off (no roster) or when the user keeps the panes/teammates.
107
+ - `PROGRESS: phase-7-teardown dismissing teammates` — after token-usage collection and after the pane-disposition prompt, only when the user approved combined pane/team cleanup, immediately before reconciling stale members + dismissing them via `SendMessage` shutdown_request (Teams mode only; see "Run-scoped pane & teammate lifecycle" → "Run-end teammate teardown" below). Skipped when split-pane is off (no roster) or when the user keeps the panes/teammates.
107
108
  - `PROGRESS: complete final-report=<relative-path>` — final summary line, after all persistence.
108
109
 
109
110
  These lines are the only structured signal the user has during a long run. 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.
@@ -143,19 +144,19 @@ Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>`
143
144
 
144
145
  #### Task worktree (BLOCKING for every task-type)
145
146
 
146
- `okstra-ctl` provisions a dedicated `git worktree` per task-key at run-prep time of the **first** phase, and every subsequent phase of the same task-key reuses the same worktree on the same branch. Lead, the Executor, and every verifier MUST treat the provisioned worktree as the canonical working directory regardless of task-type.
147
+ `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.
147
148
 
148
- - One worktree per task-key `requirements-discovery`, `error-analysis`, `implementation-planning`, and `implementation` all share the same path and branch so phase N inherits the working-tree state phase N-1 left behind.
149
- - 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 `-`.
150
- - Branch: `<work-category-prefix>-<task-id-segment>` (e.g. `feat-dev-9436`, `fix-dev-7311`). Branched from `HEAD` of the repo's **main** worktree at the first phase's prep time; base SHA is recorded in `EXECUTOR_WORKTREE_BASE_REF`.
151
- - A global registry at `~/.okstra/worktrees/registry.json` (flock-guarded) maps each task-key to its path + branch and prevents concurrent runs from colliding. Branch names are globally unique across task-keys on this machine.
149
+ - **Task-key worktree (non-`implementation` phases):** `requirements-discovery`, `error-analysis`, 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 `-`.
150
+ - **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.
151
+ - 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`.
152
+ - 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.
152
153
  - 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.
153
154
  - 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.
154
155
  - **Skip conditions** (worktree provisioning is a no-op; task uses `project_root` directly):
155
156
  - `project_root` is already inside a non-main worktree (the run reuses the caller's worktree to avoid nesting).
156
157
  - `project_root` is not inside a git repository at all.
157
158
  - **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.
158
- - **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 registry entry by hand or via `okstra worktree release <task-key>` (when available).
159
+ - **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.
159
160
 
160
161
  ## Phase 1: Task-bundle intake and required reading order
161
162
 
@@ -163,7 +164,7 @@ Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>`
163
164
 
164
165
  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 `.okstra/discovery/latest-task.json` as the current-task convenience pointer. If task browsing, task-id disambiguation, or project-level task inventory is needed, inspect `.okstra/discovery/task-catalog.json` first.
165
166
 
166
- 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 came from observed lead-token bloat: in `fontsninja-classifier-v2:dev-9461:dev-9495` RD-001 the lead burned 71 M tokens (97 % cache_read) largely because every phase entry re-absorbed a 93 KB instruction-set baseline that included files only one downstream phase ever actually used.
167
+ 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 was the dominant observed source of lead-token bloat most of that baseline is files only one downstream phase ever uses.
167
168
 
168
169
  **Mandatory at Phase 1 start (parallel Read, one message):**
169
170
 
@@ -183,15 +184,9 @@ After context-loader completes, read **only the compact intake files below** in
183
184
 
184
185
  **Implementation profile lazy reading discipline (BLOCKING — applies only when `task_type == "implementation"`):**
185
186
 
186
- 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.
187
-
188
- | Sidecar | Read at | Owned by |
189
- |---------|---------|----------|
190
- | `prompts/profiles/_implementation-executor.md` | **Phase 5**, after Stage Map parse, BEFORE issuing the Executor's first `Edit` / `Write` | Executor role binding, Pre-implementation context exploration, TDD loop, Stage execution contract, allowed actions, commit-message format |
191
- | `prompts/profiles/_implementation-verifier.md` | **Phase 5**, between Executor stage completion and the first verifier dispatch | Verifier roles, Two-tier command lookup, deny-list, discrepancy rule, Read-only command log, verifier-specific forbidden actions |
192
- | `prompts/profiles/_implementation-deliverable.md` | **Phase 6**, after Phase 5.5 convergence completes, BEFORE constructing the report-writer dispatch prompt | Required deliverable shape, Validation / TDD evidence rules, Verifier results structure, Self-review pass, Lead post-stage persistence |
187
+ 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 the profile's "Lazy section pointers" table (`prompts/profiles/implementation.md`) — the profile arrives in the Phase 1 intake via `analysis-profile.md`, so that table is already in context whenever this discipline applies.
193
188
 
194
- **Entry guard (BLOCKING).** Before transitioning into Phase 5 or Phase 6 for an `implementation` run, lead MUST emit a single Read tool call for the sidecar(s) above whose `Read at` matches the entering phase. If lead enters the phase without that Read on record (visible in the lead session jsonl), phase 진입 거부 — 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 Reads exist in the lead session jsonl within this run's window, and that they precede the `phase-6-synthesis` / `phase-7-persist` checkpoints respectively.
189
+ **Entry guard (BLOCKING).** Before transitioning into Phase 5 or Phase 6 for an `implementation` run, lead MUST emit a single Read tool call for the sidecar(s) whose `Read at` (per that table) matches the entering phase. If lead enters the phase without that Read on record (visible in the lead session jsonl), phase 진입 거부 — 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 Reads exist in the lead session jsonl within this run's window, and that they precede the `phase-6-synthesis` / `phase-7-persist` checkpoints respectively.
195
190
 
196
191
  The guard is not satisfied by remembering content from a prior run — each implementation run reads the sidecar fresh, because the sidecars are part of the runtime shipped via `okstra install` and may have been updated between runs.
197
192
 
@@ -215,13 +210,13 @@ These phases are governed by [team-contract](./team-contract.md). It is the cano
215
210
 
216
211
  ### Phase 3 — Teammate setup (BLOCKING)
217
212
 
218
- Claude Code **v2.1.178 removed the `TeamCreate` / `TeamDelete` tools and the `Agent(...)` `team_name` parameter**. With `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` (seeded by `okstra install` into the user's `settings.json`), every session already owns **one implicit team** from the moment it starts — there is no creation step to perform and no `team_name` to pass on dispatch. Workers spawned with `Agent(name: ...)` auto-join that implicit team. Do NOT search for, probe, or call `TeamCreate`: its absence is the expected state on every current Claude Code, NOT an "Agent Teams unavailable" signal, and is never a reason to degrade the run. There is no `select:TeamCreate` probe and no doubly-empty-select branch anymore — those keyed off a tool that no longer exists.
213
+ Every session owns **one implicit team** — there is no creation step to perform and no `team_name` to pass on dispatch; workers spawned with `Agent(name: ...)` auto-join it. The canonical dispatch rule (including the do-NOT-probe-`TeamCreate` rule) is [team-contract](./team-contract.md) Operating Rule 0.
219
214
 
220
215
  1. Record team-state before any dispatch:
221
- - `teamName` — the verbatim name from the launch prompt's gate block (`okstra-<task-key>`; implementation stage runs append `-s<N>`, single-stage final-verification appends `-fv-s<N>`). Audit/display label only — the implicit team's on-disk directory is `session-<leadSessionId-prefix>`, but run-end teardown resolves it from the CURRENT live session (see `_common-contract.md` "Run-end teammate teardown"), NOT from the phase-3 `lead.sessionId` snapshot recorded here, which drifts across resume / compaction; it is never resolved from `teamName`.
216
+ - `teamName` — the verbatim name from the launch prompt's gate block (`okstra-<task-key>`; implementation stage runs append `-s<N>`, single-stage final-verification appends `-fv-s<N>`). Audit/display label only — the implicit team's on-disk directory is `session-<leadSessionId-prefix>`, but run-end teardown resolves it from the CURRENT live session (see "Run-scoped pane & teammate lifecycle" → "Run-end teammate teardown" below), NOT from the phase-3 `lead.sessionId` snapshot recorded here, which drifts across resume / compaction; it is never resolved from `teamName`.
222
217
  - `teamCreate: { attempted: false, status: "implicit" }` — the audit marker that this run rides the session's implicit team (no explicit creation tool exists). A concurrent-run launch prompt instead pre-records `status: "skipped", reason: "concurrent-run"`; honor whichever the gate specifies.
223
218
  2. Verify `team-state.lead.sessionId` is populated. The `okstra.sh` exec path fills it automatically (`generate_claude_session_id` → `claude --session-id ...`). The render-only / in-session takeover path (`okstra-run` skill) auto-detects via `resolve_inproc_lead_session_id`, which is best-effort and may return empty if `~/.claude/projects/<encoded-cwd>/` is unreadable or has no jsonl yet. If `lead.sessionId` is empty at this point, write the running session's id into team-state before proceeding — Phase 7 token-usage collection depends on it and will fail with `lead jsonl not found (sessionId=)` otherwise.
224
- 3. Decide split-pane capability, record it, then emit the phase-3 checkpoint and proceed to Phase 4. Split-pane teammates require a tmux session — this is a host capability, NOT an okstra success/failure gate, and is the only thing the removed "TeamCreate" probe should ever have been deciding:
219
+ 3. Decide split-pane capability, record it, then emit the phase-3 checkpoint and proceed to Phase 4. Split-pane teammates require a tmux session — this is a host capability, NOT an okstra success/failure gate:
225
220
  - `$TMUX` set → workers render as split-pane teammates under `teammateMode: auto`. Record `teamCreate.splitPane: true`.
226
221
  - `$TMUX` unset → teammates run in-process (no split-pane possible — a host limitation, not a degraded run). Record `teamCreate.splitPane: false`.
227
222
  Dispatch is identical either way: there is no "Agent Teams unavailable → background" branch. Only when the launch prompt carries the `Concurrent-run marker` or `Tmux Worker Dispatch Gate` block do the dedicated branches below apply.
@@ -233,15 +228,15 @@ If the launch prompt contains `Tmux Worker Dispatch Gate`, Phase 3 is recorded a
233
228
 
234
229
  ### Phase 4 / Phase 5 — Execution and error-log dump
235
230
 
236
- Spawn **analysis workers only** in the same turn with `Agent(name: ..., run_in_background: true)` and **no `team_name`** — the parameter was removed in CC v2.1.178 and teammates auto-join the session's implicit team. `run_in_background: true` keeps the dispatch async so the lead can run the self-scheduled completion poll. Preserve exact roster, role labels, assigned models from the task bundle.
231
+ Spawn **analysis workers only** in the same turn with `Agent(name: ..., run_in_background: true)` and **no `team_name`** ([team-contract](./team-contract.md) rule 0). `run_in_background: true` keeps the dispatch async so the lead can run the self-scheduled completion poll. Preserve exact roster, role labels, assigned models from the task bundle.
237
232
 
238
233
  **Agent `name` on dispatch (BLOCKING — token-usage attribution depends on it).** Every analysis-worker `Agent(...)` call MUST set `name: "<workerId>-worker"` — `name: "claude-worker"` / `name: "codex-worker"` / `name: "antigravity-worker"` — exactly as the report-writer dispatch sets `name: "report-writer"` ([report-writer](./report-writer.md)). The Agent harness records this `name` as `agentName` in the subagent session jsonl, and the Phase 7 token collector matches each worker's session by that `agentName` (`okstra_token_usage/collect.py`). A worker dispatched **without** `name` produces a session with no `agentName`; the collector cannot attribute it and records the worker as `source: "unavailable"` even though the session exists and is team-tagged. Convergence reverify dispatches keep the prefix (`<workerId>-worker-reverify-r<N>`). For `implementation` and `final-verification` dispatches the `name` carries the **functional role** so the FleetView teammate pill and trace panes name the actual job instead of a generic `worker`: the Executor dispatch sets `name: "<workerId>-executor"` (e.g. `codex-executor`) and every verifier dispatch sets `name: "<workerId>-verifier"` (e.g. `claude-verifier`). These role suffixes still attribute correctly — the collector matches any `agentName` beginning `<workerId>-` (`okstra_token_usage/collect.py` `match_prefixes`). **For every CLI (Codex / Antigravity) dispatch, Lead MUST inject a `**Pane role:**` line into the dispatched prompt body whose value is the Agent `name` with the leading `<cli>-` prefix removed** — the wrapper subagent reads it and passes it as the wrapper's 5th `<role>` argument so the tmux trace pane title reads `<cli>-<role>` (which equals the FleetView teammate name) instead of a generic `<cli>-worker` (see `agents/workers/_cli-wrapper-template.md`). So `name: "codex-worker"` → `**Pane role:** worker`, `name: "codex-worker-reverify-r1"` → `**Pane role:** worker-reverify-r1`, `name: "codex-executor"` → `**Pane role:** executor`, `name: "codex-verifier"` → `**Pane role:** verifier`. The wrapper defaults to `worker` when the line is absent, but always inject it so the pane names the actual job.
239
234
 
240
235
  **Agent `model:` on dispatch (BLOCKING — assignment is otherwise ignored).** The `Claude worker` `Agent(...)` call MUST set `model: "<family token of that role's modelExecutionValue>"` (`fable` / `opus` / `sonnet` / `haiku`), per [team-contract](./team-contract.md) "Model Assignment Rules" #3–#4. The claude-worker definition is `model: inherit`, so omitting this parameter makes the worker silently run on the lead's model instead of its manifest assignment — the assigned-vs-actual deviation. `Codex worker` / `Antigravity worker` are exempt: their CLI model is applied via the wrapper's own `--model` argument, so leave their Agent `model:` at `inherit` (rule #5).
241
236
 
242
- There is no separate "Teams mode" vs "no-`team_name` fallback" split anymore: every analysis-worker dispatch is `Agent(name: ..., run_in_background: true)` with no `team_name`, and teammates join the session's implicit team automatically. `teamCreate` is still recorded in team-state before any dispatch (`status: "implicit"`, or `"skipped"` / `reason: "concurrent-run"` when the launch prompt's concurrent-run gate applies) — it is now an audit marker, not a branch selector.
237
+ `teamCreate` is recorded in team-state before any dispatch (`status: "implicit"`, or `"skipped"` / `reason: "concurrent-run"` when the launch prompt's concurrent-run gate applies) — an audit marker, not a branch selector.
243
238
 
244
- **Completion detection after dispatch (BLOCKING).** The `Agent(name: ..., run_in_background: true)` call returns `Spawned successfully` immediately; that ack is NOT completion. After dispatching the analysis workers (async), Lead MUST detect their completion via the self-scheduled polling protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)" do NOT restate the algorithm here. Lead MUST NOT end its turn with a prose "waiting for workers" statement; that path stalls the run until the user manually nudges it.
239
+ **Completion detection after dispatch (BLOCKING).** The spawn ack is NOT completion detect worker completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", and do NOT end the turn with a prose "waiting for workers" statement.
245
240
 
246
241
  ### Errors log path wiring (BLOCKING)
247
242
 
@@ -321,20 +316,20 @@ After the Report writer worker draft is reviewed (or after the lead-authored fal
321
316
 
322
317
  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, with the labels in the "Quick Reference" table above as the single source of truth) is preserved.
323
318
 
324
- **REQUIRED RESOURCE:** Read [convergence](./convergence.md) "Plan-body verification mode (implementation-planning only)" for the round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`), classification rules, gate-result resolution, and the state-file schema at `runs/<task-type>/state/plan-body-verification.json`.
319
+ **REQUIRED RESOURCE:** Read [plan-body-verification](./plan-body-verification.md) for the round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics (`AGREE` / `DISAGREE(a-e)` / `SUPPLEMENT`), classification rules, gate-result resolution, and the state-file schema at `runs/<task-type>/state/plan-body-verification.json`.
325
320
 
326
321
  Distinct from Phase 5.5 finding convergence:
327
322
 
328
323
  - Phase 5.5 reconciles worker **findings** (F-*) from independent analysis.
329
324
  - This sub-step reconciles the **consolidated plan body** (P-*) authored by the Report writer worker.
330
- - The two rounds use disjoint queues and separate state files — see [convergence](./convergence.md) "MUTUAL EXCLUSION (BLOCKING)".
325
+ - The two rounds use disjoint queues and separate state files — see [plan-body-verification](./plan-body-verification.md) "MUTUAL EXCLUSION (BLOCKING)".
331
326
 
332
327
  Lead's responsibilities in this sub-step (in order):
333
328
 
334
- 1. Extract `P-*` plan items from the draft report's `## 5.4 Implementation Plan Deliverables` per the prefix → source-section mapping in the convergence contract.
329
+ 1. Extract `P-*` plan items from the draft report's `## 5.4 Implementation Plan Deliverables` per the prefix → source-section mapping in the plan-body-verification contract.
335
330
  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.
336
331
  3. Aggregate verdicts and resolve the gate result to one of `passed` / `passed-with-dissent` / `blocked-by-disagreement` / `aborted-non-result`.
337
- 4. Write `runs/<task-type>/state/plan-body-verification.json` (schema in the convergence contract).
332
+ 4. Write `runs/<task-type>/state/plan-body-verification.json` (schema in the plan-body-verification contract).
338
333
  5. Populate `### 5.5.9 Plan Body Verification` in the final-report file (template at `templates/reports/final-report.template.md` §5.5.9 — Round count, Gate result, per-item verdict tables grouped under each plan item's `subject`, Dissent log).
339
334
  6. For every `majority-disagree` plan item, append a row to `## 1. Clarification Items` with `Blocks=approval` and the 1:1 ID match in the verdict table's `Classification` column (`majority-disagree → C-<N>`). Do NOT create a parallel `Open Questions` block — see `prompts/profiles/implementation-planning.md` self-review step 6 for the orphan-on-either-side contract.
340
335
  7. Conditionally render the top-of-report `- [ ] Approved` marker line: present iff gate ∈ {passed, passed-with-dissent}, absent iff gate ∈ {blocked-by-disagreement, aborted-non-result}. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence. Manually flipping a blocked gate to passing in order to render the marker is a contract violation.
@@ -373,12 +368,53 @@ The errors log is informational. Its presence/absence does not affect the final
373
368
 
374
369
  After persistence, reply briefly in the resolved Report Language with: completion status, final report path, team-state path, validator result, resume command path, any remaining blocker.
375
370
 
371
+ ## Run-scoped pane & teammate lifecycle
372
+
373
+ Lead-only operating duties spanning the whole run — recorded once here; the profile common contract points at this section instead of restating it.
374
+
375
+ - Run-start pane recording (shared — runs ONCE at run start, before the FIRST worker dispatch):
376
+ - The codex/antigravity wrappers now self-anchor their trace pane by walking their own ancestor PIDs against tmux `pane_pid`s (see `lib/okstra/tmux-pane.sh`), so they no longer depend on this file. The lead still records its own pane id here so the cleanup script below can know which pane to never kill and can detect tmux absence itself — the lead does NOT read it back to gate those steps. A bare `tmux display-message -p '#{pane_id}'` is NOT reliable for this — Claude Code's Bash tool strips `$TMUX`/`$TMUX_PANE`, so that command returns the most-recently-active *client's* pane (often a different session, or a foreign pane when the lead is launched outside tmux entirely). The lead therefore records via the same ancestry resolver.
377
+ - The lead MUST run once, at run start: `mkdir -p "<RUN_DIR>/state" && { . "$HOME/.okstra/bin/lib/okstra/tmux-pane.sh" 2>/dev/null && okstra_resolve_caller_pane; } > "<RUN_DIR>/state/lead-pane.id" 2>/dev/null || true` (substitute the run's absolute `RUN_DIR`). When the lead is not inside a tmux pane (e.g. Claude launched from the GUI app) no ancestor matches a pane and the file is empty. The cleanup steps below do NOT read this file to decide whether to run — they always run, and the script itself reads `lead-pane.id` (with its own ancestry fallback) only to know which pane to never kill and to detect tmux absence. The lead never interprets this file to gate a pane step.
378
+ - This recording is **silent internal setup**: the lead MUST emit NOTHING to the user about it — no pane id, no `%NNN`, no `lead-pane.id` path, no announcement of the phase-start-cleanup / wrap-up-disposition steps. Just record the file and proceed.
379
+ - Phase-start cleanup — prior-batch panes + completed teammates (shared — runs BEFORE dispatching each new worker batch, at the SAME boundaries: just before each `PROGRESS: phase-5.5-convergence round=<N>` marker and just before `PROGRESS: phase-6-synthesis dispatching report-writer-worker`):
380
+ - **(a) tmux panes.** okstra creates two kinds of tmux pane per run: **worker-agent panes** the harness gives to dispatched subagents (titled `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`), and **trace panes** the codex/antigravity wrappers spawn (`<cli>-<role>-<pid>-tail`). Both accumulate because each new batch spawns fresh panes and the prior ones are never reclaimed. The lead MUST ALWAYS run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"` immediately before dispatching the next batch — NEVER gating this call on its own reading of `lead-pane.id`. The script self-resolves the lead pane and is a safe no-op outside tmux (it returns an empty pane list), so an always-run call cannot do harm; it closes every prior-batch okstra pane (worker-agent + trace) for this run while NEVER killing the lead's own pane. The lead MUST NOT fabricate a synthetic pane list.
381
+ - **(b) completed teammates (split-pane runs only).** Each convergence round / critic / gap-verification / report-writer dispatch spawns teammates under fresh Agent `name`s (`<role>-reverify-r<N>`, `<provider>-worker-critic`, `report-writer-worker`, …); completed teammates do NOT leave the FleetView roster on their own — they linger as idle pills until run-end teardown, so a long run's roster grows unreadable. Before dispatching the next batch, the lead MUST send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to every teammate it spawned earlier in THIS run **whose completion is already confirmed** (its Result Path exists / it is in the self-scheduled-polling done set). The `message` MUST be the object literal shown, NEVER a JSON string stuffed into a text field. This is the same graceful-shutdown primitive used at `Run-end teammate teardown`, applied per batch instead of once at the end.
382
+ - **Only confirmed-complete teammates, never the lead.** NEVER send a shutdown_request to a teammate whose result file has not yet appeared. In particular the critic runs CONCURRENTLY with the first reverify round (see `convergence` "Coverage critic pass"), so it MUST NOT be shut down until its own result is collected. NEVER target the lead's own session.
383
+ - Only individual completed teammates are dismissed here; the rest of the roster stays for the remaining batches. There is no team-delete tool — the implicit team itself ends with the session; teardown only dismisses teammates.
384
+ - Silent-skip in `tmux-pane` backend runs (external-lead workers are not session teammates); otherwise the on-disk existence check at run-end teardown is the authority on whether a roster exists.
385
+ - **(c) record the current session into team-state (축1 — re-issued session generation capture).** batch cleanup 직후(그리고 아래 `PROGRESS: phase-batch-cleanup` 방출 전), lead 는 재발급된 세션 세대가 Phase 7 토큰·PROGRESS 수집에서 누락되지 않도록 현재 live 세션을 team-state 에 기록한다: `okstra token-usage "<TEAM_STATE_PATH>" --record-observed-session --project-root "<PROJECT_ROOT>"` 를 1회 호출한다(`<TEAM_STATE_PATH>` 와 `<PROJECT_ROOT>` 는 shell 변수·`$(...)` 없이 리터럴 절대경로로 치환 — `Bash(okstra:*)` 매칭 유지). `--project-root` 는 **필수**다: 생략하면 collector 가 team-state 의 상위 디렉터리(run/state 경로)를 project root 로 잘못 추론해 엉뚱한 `~/.claude/projects/<encoded-cwd>/` 를 뒤지고 조용히 no-op 하므로 축1 이 꺼진다. 실패는 무시 가능한 best-effort — worker 세션이거나 이미 기록된 세대이면 no-op 다. `python3 "$HOME/..."` 직접 호출은 금지(`$HOME` 확장이 리터럴-토큰 permission 매칭을 깨고 매 호출 확인 프롬프트를 유발) — 반드시 `okstra token-usage` 서브커맨드를 쓴다.
386
+ - Both (a), (b), and (c) run **automatically without any interactive prompt**. After running them, emit the checkpoint line `PROGRESS: phase-batch-cleanup panes=<n> teammates=<m>` (counts only — NEVER expose `%NNN`, `lead-pane.id`, or raw Agent names) and proceed; this is the BLOCKING checkpoint the Phase 7 validator requires before each convergence round and before the report-writer dispatch (see "Progress reporting (BLOCKING)" above). **Effect caveat:** Claude Code exposes no tool to surgically delete a roster entry mid-run, so a dismissed teammate may still show as an idle pill; the shutdown still reclaims its session and prevents the roster from growing with live members. On the FIRST batch of a run nothing has been dispatched yet — the pane/teammate steps (a)/(b) no-op; step (c) still records the current session.
387
+ - Phase wrap-up — okstra pane disposition (shared, runs AFTER Phase 7 persistence/token collection and BEFORE teammate teardown):
388
+ - At run end the only residual okstra panes are the LAST phase's (e.g. the `report-writer-worker` agent pane and any codex/antigravity trace pane). `okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>"` returns one tab-separated `<pane_id>\t<pane_title>` line per residual okstra pane (worker-agent + trace) for this run.
389
+ - After the final-report file has been written and the routing recommendation has been issued, the lead MUST ALWAYS run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>"` exactly once — NEVER gating this call on its own reading of `lead-pane.id`. The output lists every residual okstra pane (worker-agent + trace) for this run, never the lead's own pane; outside tmux it is a safe no-op (empty output).
390
+ - If the list is empty and no matching on-disk team config exists (see Run-end teammate teardown below), skip the question — there is nothing to ask about (the phase-start resets above usually already cleared prior phases).
391
+ - Otherwise the lead MUST present the user with a strict binary choice **before** declaring the phase complete. This single choice controls BOTH residual pane cleanup and worker teammate dismissal; do not ask a second teammate-cleanup question. Use one prompt of this shape (Korean preferred, English acceptable if the rest of the run is in English):
392
+ > 현재 phase 종료 시점입니다. 다음 okstra pane 및 worker teammate 가 남아 있습니다 — 닫고 정리할까요?
393
+ > <인용된 `--list` 출력>
394
+ > (예) 모두 닫고 팀원 정리 / (아니오) 그대로 두기
395
+ - On `예` / `y` / `close` → run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"`, then run the Run-end teammate teardown sequence below without asking a second question. Report the pane kill count and teammate cleanup outcome back in one sentence.
396
+ - On `아니오` / `n` / `keep` → leave panes and teammates intact; remind the user that tagged trace panes will be cleaned up automatically when Claude `/exit` fires the `SessionEnd` hook (`--reap`), while FleetView/Teams teammates remain visible until session end or manual Teams UI cleanup.
397
+ - The question MUST be a clean yes/no — do NOT offer "close some / keep some" partial answers, do NOT propose alternatives like "close only codex panes". The whole-set decision keeps the wrap-up predictable: lead pane excluded, every okstra worker/tail pane and worker teammate cleaned together on approval.
398
+ - This step is mandatory for every phase (`requirements-discovery`, `error-analysis`, `implementation-planning`, `implementation`, `final-verification`, `release-handoff`). Whether there is anything to ask about is decided by the `--list` output, NOT by the lead's reading of `lead-pane.id`: an empty list → skip the question (above); the lead MUST NOT fabricate a synthetic pane list.
399
+ - Run-end teammate teardown (shared — MUST run AFTER the pane disposition question and BEFORE `PROGRESS: complete`):
400
+ - This run rode the session's **implicit team** (Phase 3 recorded `teamCreate.status: "implicit"` plus the `teamName` audit label; canonical rule: `team-contract` Operating Rule 0). There is no tool to delete a team — the implicit team ends when the session ends. What teardown CAN do is gracefully dismiss the worker teammates the lead spawned, so they stop showing as live members in the FleetView roster.
401
+ - **Whether to run this step is decided by on-disk fact, resolved at teardown time by the CURRENT live session — NOT from `team-state.lead.sessionId`.** The harness names the team directory `session-<leadSessionId-prefix>` (the `teamName` label never becomes a directory name), but `team-state.lead.sessionId` is only a phase-3 snapshot: Claude Code re-issues the session id across resume / compaction mid-run, so the live roster usually sits under a DIFFERENT `session-*` dir than the snapshot names. Resolving from the snapshot is what made split-pane teardown silently miss its own live teammates. The resolver in step 1 below keys off the live session (falling back to the snapshot dir only when the live one is absent) and prints the members to dismiss.
402
+ - **Resolver printed one or more `dismissible-member: <name>` lines** → teammates exist; run the dismissal sequence below.
403
+ - **Resolver printed `no live roster for this session`, or split-pane was off** → nothing to dismiss → silent-skip.
404
+ - The pane disposition prompt above is the only user prompt for this cleanup decision. Do NOT ask a second teammate-only question.
405
+ - On `아니오` / `n` / `keep` → dismiss nothing. Tell the user the teammates remain visible in FleetView/Teams until the session ends, and can be removed earlier from the Teams UI.
406
+ - On `예` / `y` / `close` → emit `PROGRESS: phase-7-teardown dismissing teammates`, then run the sequence below. Token-usage collection MUST already be complete — it reads `~/.claude/projects/` jsonls, which dismissal never touches, but the read MUST precede teardown.
407
+ 1. Run `$HOME/.okstra/bin/okstra-team-reconcile.sh --project-root "<PROJECT_ROOT>" --fallback-team "session-<lead.sessionId-prefix>"` exactly once. It resolves the live session's `~/.claude/teams/session-<live>/config.json` (falling back to the snapshot `session-<lead.sessionId-prefix>` dir only if the live one is absent), flips dead-pane stale-active members to inactive (no-op when tmux is unavailable or nothing is stale), and prints one `dismissible-member: <name>` line per non-lead member. Do NOT loop it, and do NOT hand-resolve a config path from `team-state.lead.sessionId`.
408
+ 2. Send each printed `dismissible-member: <name>` **whose completion is confirmed** (its Result Path exists) a structured `SendMessage(to: <name>, message: { type: "shutdown_request" })` — the `message` MUST be the object literal shown, NEVER a JSON string stuffed into a text field. NEVER target the lead's own session, and never use `TaskStop` (teammates are not background tasks — `TaskStop` 404s on a member address).
409
+ - Claude Code exposes no tool to delete the team or surgically remove a roster entry, so a dismissed teammate may still show as an idle pill until the session ends. Do not pretend the team was deleted. If the user wants the roster gone entirely, surface the manual action: `Teams/FleetView 에서 team <teamName> 삭제`, and if tmux panes were kept earlier also show `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"`.
410
+ - **Enforcement / safety net.** This teardown fires after the Phase 7 validator's window, so `validate_session_conformance.py` cannot check it (it is not a required checkpoint). The net is the `SessionEnd` hook `$HOME/.okstra/bin/okstra-team-reconcile.sh --session-end` (seeded into `settings.template.json`): when the Claude session ends it reconciles that session's roster regardless of whether this step ran, so a dead-pane member never lingers as a live pill past session end.
411
+ - Report the outcome in one short line (e.g. `worker teammate 정리 완료`, or `stale 멤버 1명 reconcile 후 정리`) and proceed to the final `PROGRESS: complete ...` line.
412
+
376
413
  ## Common Mistakes
377
414
 
378
415
  | Mistake | Fix |
379
416
  |---------|-----|
380
- | Probing `select:TeamCreate` and declaring "Agent Teams unavailable" when it comes back empty | `TeamCreate` was removed in CC v2.1.178 — it is empty on every current build. Do not probe it; the session's implicit team is always present with `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`. Dispatch workers directly with `Agent(name: ...)` |
381
- | Passing `team_name` on an `Agent(...)` dispatch | The parameter was removed in CC v2.1.178. Dispatch with `name` only — teammates auto-join the implicit team |
417
+ | Probing `select:TeamCreate`, declaring "Agent Teams unavailable", or passing `team_name` on dispatch | The implicit team is always present dispatch with `Agent(name: ...)` only, per [team-contract](./team-contract.md) Operating Rule 0 |
382
418
  | Substituting Claude lead reasoning for a worker result | Claude lead synthesizes only — spawn the worker |
383
419
  | Skipping a worker silently | Always record terminal status with reason |
384
420
  | Writing verdict before all workers report | Wait for all results or explicit terminal statuses |
@@ -395,7 +431,7 @@ After persistence, reply briefly in the resolved Report Language with: completio
395
431
  | Letting `convergence.maxRounds` default to 2 for `requirements-discovery` | Resolve effective default to `1` for discovery and record in convergence state artifact |
396
432
  | Issuing serial Read calls in Phase 1 | The intake files are independent — issue all Read calls in a single message (parallel) |
397
433
  | Flagging the claude-worker dispatch prompt as "incomplete" because it lacks `[Required reading]` / `[Error reporting]` blocks | Intentional asymmetry — see [team-contract](./team-contract.md) "Asymmetry between claude-worker and codex/antigravity-worker prompts" |
398
- | Waiting silently while the dispatched `claude-worker` Agent call returns nothing for many minutes | The claude-worker MUST append a `- PROGRESS: <stage> <ISO-UTC>` line to its audit sidecar (`runs/<task-type>/worker-results/claude-worker-audit-<task-type>-<seq>.md`) at least every 5 minutes (see `agents/workers/claude-worker.md` "Heartbeat" rule). If the sidecar is absent or its mtime is >5 minutes stale, treat the dispatch as `timeout` and redispatch once with a byte-identical prompt; after a second silent hang, record terminal status `timeout` with the missing-sidecar reason in team-state. The authoritative completion signal is the **result file's appearance**, detected via self-scheduled polling (see [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)") — NOT the Agent-call return, which under `run_in_background` dispatch is just an immediate `Spawned successfully` ack. The heartbeat sidecar is an auxiliary liveness signal layered on top: a missing sidecar after the result file appears is itself a contract violation per the heartbeat rule |
434
+ | Waiting silently while the dispatched `claude-worker` Agent call returns nothing for many minutes | The spawn ack is not completion the result file is, via self-scheduled polling; stale-heartbeat handling per [team-contract](./team-contract.md) "Claude-worker heartbeat staleness" |
399
435
  | Re-sending confirmed findings (`full-consensus`/`partial-consensus`/`worker-unique`) to a worker in Round 2 | Queue pruning rule — see [convergence](./convergence.md) "Round 1-N: Re-verification Loop (queue-pruned)" |
400
436
  | Aggregating a `timeout`/`error` reverify dispatch as `DISAGREE` | Worker failure handling — record as `verification-error` and add to `skippedWorkers[]`. See [convergence](./convergence.md) "Worker failure handling in reverify" |
401
437
  | Skipping `--substitute-data` in the Phase 7 collector run | Always pass the flag — see [report-writer](./report-writer.md) "Phase 7 token-usage collector" |
@@ -0,0 +1,240 @@
1
+ # Okstra Plan-Body Verification Contract
2
+
3
+ ## When to Use
4
+
5
+ - ONLY when the run's task-type is `implementation-planning` — no other task-type runs this round
6
+ - As a Phase 6 sub-step, AFTER the Report writer worker's draft is reviewed — see [okstra-lead-contract](./okstra-lead-contract.md) "Phase 6 sub-step: Plan-body verification". Do NOT read this file during Phase 5.5 finding convergence; it is not part of that loop.
7
+ - Companion to [convergence](./convergence.md): finding convergence reconciles worker **findings** (`F-*`); this contract verifies the **consolidated plan body** (`P-*`) authored by the Report writer worker. The two queues are disjoint — see "MUTUAL EXCLUSION" below.
8
+
9
+ This contract defines a **second, independent** verification round that fires only for `task-type = implementation-planning`. The round verifies the *consolidated plan* that the report-writer worker has authored, not the worker findings that were already reconciled earlier.
10
+
11
+ ## Lifecycle position (BLOCKING)
12
+
13
+ Plan-body verification runs **after** finding convergence and **after** the report-writer draft is written. Sequence inside a single implementation-planning run:
14
+
15
+ ```
16
+ Phase 4 workers produce independent analyses (Findings F-001…)
17
+ → Phase 5.5 FINDING convergence ([convergence](./convergence.md), sections "Convergence Algorithm" through "Convergence State Artifact")
18
+ → Phase 6 report-writer authors final-report draft (consolidated Option Candidates / Stepwise Execution Order / Dependency / Validation Checklist / Rollback)
19
+ → PLAN-BODY VERIFICATION ROUND ← this contract
20
+ → User Approval gate (top-of-report `- [ ] Approved` marker is rendered only when this round's Gate result is `passed` or `passed-with-dissent`)
21
+ → implementation phase (separate run)
22
+ ```
23
+
24
+ Plan-body verification MUST NOT replace, precede, or be conflated with the Phase 5.5 finding convergence above. They are two distinct rounds with different inputs (findings vs. consolidated plan body), different ID schemes (`F-*` vs. `P-*`), and different state files.
25
+
26
+ ## MUTUAL EXCLUSION (BLOCKING)
27
+
28
+ The finding queue (Phase 5.5, [convergence](./convergence.md)) and the plan-item queue (this contract) are **disjoint**:
29
+
30
+ - A finding-convergence reverify prompt MUST NOT contain any `P-*` item.
31
+ - A plan-body verification prompt MUST NOT contain any `F-*` finding.
32
+ - The two rounds write to **different state files**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (findings, see [convergence](./convergence.md) §"Convergence State Artifact") vs. `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (plan items, see §"`plan-body-verification.json` schema").
33
+ - Aggregation logic (verdict counting, classification) MUST NOT carry votes from one queue into the other.
34
+
35
+ Mixing the two queues — for example, parsing a Phase 6 draft's Stepwise Execution Order step as if it were an `F-*` finding — is a contract violation. Future Claude reading this contract: if you find yourself tempted to "just reuse the finding queue for plan items, they're similar enough", stop. They are not similar enough; the verdict semantics differ (see §"Plan-body verdict semantics" below).
36
+
37
+ ## Configuration
38
+
39
+ Plan-body verification is configured under `convergence.planBodyVerification` in `task-manifest.json`:
40
+
41
+ | Setting | Default | Description |
42
+ |---------|---------|-------------|
43
+ | `enabled` | `true` | If `false`, the round is skipped and the top-of-report Approval marker is rendered unconditionally (legacy behaviour). |
44
+ | `maxRounds` | `1` | Upper bound. Plan-body verification is consistency / completeness checking, not fact checking — additional rounds rarely help. Range 1–3. |
45
+ | `gating` | `true` | If `true` (default), `majority-disagree` blocks the Approval marker. If `false`, the round is advisory-only and the marker always renders. |
46
+
47
+ Default values are emitted into the manifest by `scripts/okstra_ctl/render.py` (`_build_convergence_block`). The ctx knob `OKSTRA_PLAN_VERIFICATION=false` flips `planBodyVerification.enabled` to false.
48
+
49
+ The shared Majority definition and the auto-disable rule (fewer than 2 analyser workers → advisory `gating=false` path) are owned by [convergence](./convergence.md) §"Convergence Algorithm" / §"Configuration" and apply here unchanged.
50
+
51
+ ## Plan-item extraction (Round 0 equivalent)
52
+
53
+ From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, lead extracts plan items with the following prefixes (see also `templates/reports/final-report.template.md` §5.5.9):
54
+
55
+ | Prefix | Source sub-section | One row per |
56
+ |--------|--------------------|-------------|
57
+ | `P-Opt-<N>` | `4.5.1 Option Candidates` | one Option (its File Structure list + interfaces + blast radius) |
58
+ | `P-Step-<N>` | `4.5.4 Stepwise Execution Order` | one step (path + command + success signal) |
59
+ | `P-Dep-<N>` | `4.5.5 Dependency / Migration Risk` | one dependency row |
60
+ | `P-Val-<N>` | `4.5.6 Validation Checklist` | one checklist item |
61
+ | `P-Rb-<N>` | `4.5.7 Rollback Strategy` | one rollback path |
62
+ | `P-Req-<N>` | `4.5.8 Requirement Coverage` | one requirement coverage row |
63
+
64
+ `4.5.2 Trade-off Matrix` and `4.5.3 Recommended Option` are NOT extracted as standalone plan items — the trade-off matrix is evaluated implicitly through each option's `P-Opt-*` verification, and the recommended option is one of those `P-Opt-*` rows.
65
+
66
+ Each plan item inherits the `[TICKETID: ...]` tag of its source section (per the standard ticket-tagging contract).
67
+
68
+ When extracting each item, lead also captures a **`subject`** — a plain one-line label (≤12 words) describing *what that item is* in the reader's terms, e.g. `P-Opt-1` → "Option A: upload v2 를 신규 모듈로 분리", `P-Step-1.1` → "Stage 1 Step 2: `npm run test:v2` 로 회귀 검증". This is a label-capture, not new analysis. The `subject` is what §5.5.9 renders as the per-item heading so the reader knows *what* each AGREE/DISAGREE is about without cross-referencing §4.5; a bare `P-*` ID with no subject is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_item_subject_substance` fails a subject that is a placeholder — under 3 chars, equal to the item id, or shaped like a bare `P-*` id.
69
+
70
+ ## Plan-body verdict semantics
71
+
72
+ The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their meaning is plan-specific:
73
+
74
+ - **AGREE**: the item is executable as written *and* internally consistent with other items in the plan.
75
+ - **DISAGREE(<kind>)**: the item is broken. `<kind>` MUST be one of:
76
+ - `a` — a concrete referenced file path / symbol **contradicts** a different concrete path / symbol for the same artifact in another step or option's File Structure list (a genuine mismatch between two spelled-out references). An abbreviated / ellipsis (`…`) / under-specified path is NOT kind `a` — it contradicts nothing, it is merely imprecise notation; classify it as `b`.
77
+ - `b` — a command **or a referenced path** is not executable or is ambiguous — including an abbreviated, ellipsis, or under-specified path that does not resolve as written
78
+ - `c` — validation signal is not observable
79
+ - `d` — rollback violates commit / dependency order
80
+ - `e` — item contradicts the trade-off matrix
81
+ - `f` — requirement coverage row cites no concrete option / stage / step, cites a non-existent option / stage / step, or marks a requirement `covered` while the cited plan item does not satisfy the row's stated requirement. A row that cites an existing option / stage / step is concrete for this purpose even if that option's File Structure paths are abbreviated — path imprecision inside the cited option is kind `b` on that option's own item, not `f` on the coverage row.
82
+ - **SUPPLEMENT**: the item is sound but is missing a dependency / edge case / precondition.
83
+
84
+ Worker non-result handling (`timeout`, `error`, no result file, wrapper `cli-failure`) is identical to finding convergence: do NOT aggregate as DISAGREE, record `contract-violation`, and apply the round-level abort rule below.
85
+
86
+ ## Mode constraint
87
+
88
+ Plan-body verification only supports **lightweight mode** (defined in [convergence](./convergence.md) §"Verification Mode"). `full-reanalysis` is not meaningful here because the "original source materials" for a plan item are the worker's own analysis plus the lead-mediated synthesis — there is no independent ground truth to re-read. The manifest's top-level `verificationMode` is ignored for this round; lightweight is always used.
89
+
90
+ Exception for `P-Req-*`: verifiers still MUST NOT re-open the original task brief for this round, but they MUST compare the requirement text embedded in the `Requirement Coverage` row with the cited Option / Stage / Step in the draft plan. A row is not sound merely because it says `covered`; the cited plan item must actually satisfy the row's stated requirement.
91
+
92
+ ## Adversarial plan-body posture
93
+
94
+ When `config.adversarial == true` (the default for `implementation-planning`; see [convergence](./convergence.md) §"Configuration"), the plan-body round runs with an **adversarial posture**. The classification rules and gate arithmetic in §"Round protocol" are UNCHANGED — `majority-disagree` (a *majority* of analysers DISAGREE) remains the only classification that blocks the Approval marker, and `dissent-isolated` still passes the gate. Adversarial mode changes only *how each verifier evaluates an item*:
95
+
96
+ - The burden of proof sits on the plan: an item earns `AGREE` only if the verifier actively tried to break it and could not.
97
+ - The verifier MUST open the file paths / symbols / commands the item cites and confirm they exist and are executable as written. This is the one allowed widening of the lightweight "judge from internal consistency and stated commands / paths" rule — confirming the existence of cited paths is not "re-analyzing the original requirements".
98
+ - If a cited path / command / validation signal cannot be confirmed, the verifier responds `DISAGREE(<kind>)` with the applicable breakage kind (a–e); uncertainty resolves toward DISAGREE, not AGREE.
99
+ - **Single-vote-blocking kinds.** A single `DISAGREE` is approval-blocking on its own — no majority needed — when the breakage kind is `a` (cited path/symbol mismatch) or `d` (rollback violates commit/dependency order) on *any* plan item, or `f` (requirement-coverage mismatch) on a `P-Req-*` item. These defects are concrete, safety-critical, and adversarially verifiable (the verifier confirmed the cited path / order / requirement), so one correct dissent must not be outvoted. Each creates a `majority-disagree` classification and MUST become a `Blocks=approval` clarification row. Kinds `b` / `c` / `e` still need a majority — `b` especially is prone to planning-vs-implementation environment false positives. Because `a` is reserved for a concrete contradiction between two spelled-out references (see §"Plan-body verdict semantics"), an abbreviated / ellipsis / under-specified path is raised as `b` (majority-gated), never `a` — a lone "this path is abbreviated" dissent must not single-vote-block on notation alone, and it is especially not blocking on a *rejected* option that will never be implemented. **Enforced:** `validators/validate-run.py` `_classify_plan_item_gate` (`_SINGLE_VOTE_BLOCKING_KINDS = {a, d}` + the P-Req `f` rule).
100
+
101
+ Plan-body verification stays **lightweight** even under this posture — the `verificationMode = "full-reanalysis"` forcing in [convergence](./convergence.md) §"Adversarial Verification Mode" applies to finding convergence only (see §"Mode constraint"); the adversarial posture here only changes verifier behaviour, not the mode. This raises verification *quality* (active refutation, plan-side burden). The gate *threshold* stays majority-based for the majority-gated kinds (`b`/`c`/`e`), with the single-vote-blocking exception above for the concrete, safety-critical kinds (`a`/`d`, and `f` on P-Req). A majority requires at least two participating (non-error) votes, so a lone surviving `DISAGREE` whose peer returned a non-result does NOT block on a majority-gated kind — a worker failure must not make the gate stricter than a healthy roster would.
102
+
103
+ ## Round protocol (single round at default `maxRounds=1`)
104
+
105
+ 1. Lead parses the report-writer draft and extracts the `P-*` plan items.
106
+ 2. For each analyser worker in the roster (`claude`, `codex`, and `antigravity` if opted in), lead constructs a reverify prompt using the template in §"Plan-body reverify prompt" below.
107
+ 3. Dispatch uses the same wrapper infrastructure as finding convergence. The `--role-slug` is `<role>-plan-verify-r<N>`. Result file path: `runs/<task-type>/worker-results/<role-slug>-plan-verify-r<N>-implementation-planning-<seq>.md`.
108
+ 4. After all dispatches return, lead aggregates verdicts per `P-*` item across workers and classifies each:
109
+ - `full-consensus` — all participating analysers `AGREE` (SUPPLEMENT counts as agree on the item itself).
110
+ - `partial-consensus` — majority `AGREE`, dissenting `DISAGREE` recorded.
111
+ - `dissent-isolated` — only one worker `DISAGREE`s, others `AGREE` — treat as `partial-consensus` for gate purposes; record dissent. (Distinct from finding-convergence `worker-unique`, which means the *opposite*: only one worker AGREEs. Plan-body classifications use this dedicated label to avoid the collision.)
112
+ - `majority-disagree` — a *majority* of analysers `DISAGREE` (majority needs ≥2 participating non-error votes), OR any single-vote-blocking kind fires: one `DISAGREE(a)` / `DISAGREE(d)` on any item, or one `DISAGREE(f)` on a `P-Req-*` item (see §"Single-vote-blocking kinds"). This classification **blocks the Approval marker**.
113
+ - `contested` only meaningful when `maxRounds > 1`; at default `maxRounds=1`, fold any unresolved item into `partial-consensus`.
114
+ 5. Gate result resolution:
115
+ - any `majority-disagree` item present AND `gating=true` → `blocked-by-disagreement`
116
+ - all dispatches non-result → `aborted-non-result`
117
+ - any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
118
+ - all items `full-consensus` → `passed`
119
+ 6. Lead writes `runs/<task-type>/state/plan-body-verification-<task-type>-<seq>.json` (schema below) and populates `### 5.5.9 Plan Body Verification` in the final report's data.json (`implementationPlanning.planBodyVerification`, schema `schemas/final-report-v1.0.schema.json`; template at `templates/reports/final-report.template.md`). The §5.5.9 body is **grouped by plan item**: `planItems[]`, each carrying its `id`, its plain-language `subject` (rendered as the item heading), an optional `sourceSection`, an optional `clarificationId` (the `C-<N>` this item blocks on when `majority-disagree`), and a `verdicts[]` list (`worker / verdict / breakageKind / note`) — one verdict row per worker under that item. The renderer prints three fixed legends (gate values, verdict tokens, breakage kinds a–f) so the reader can decode every cell without opening this spec. The older flat `#### Verdict details` table (`Plan item / Worker / …`, one row per plan-item × worker pair) is superseded by the grouped layout — it hid *what* each vote was about behind a bare `P-*` ID; the subject heading is the fix. The validator's `Plan Body Verification` + `Gate result:` substring checks still gate this section.
120
+ 7. For every `majority-disagree` item, lead adds a row to `## 1. Clarification Items` with:
121
+ - new `C-<N>` ID (numbering continues from any existing rows)
122
+ - `Statement` summarising the disagreement and the worker breakage `<kind>`
123
+ - `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
124
+ - `Blocks=approval`
125
+ - the item's `planItems[].clarificationId` set to that `C-<N>` (1:1 link). `validators/validate-run.py` `_validate_plan_body_clarification_matching` recomputes each item's class and fails when a majority-disagree item's `clarificationId` is missing, dangling, or points at a non-`approval` row.
126
+ 8. The top-of-report `- [ ] Approved` marker line is rendered if and only if the Gate result is `passed` or `passed-with-dissent`. `validators/validate-run.py` `validate_phase_boundary` enforces this correspondence; manually adding the marker line when the gate did not pass is a contract violation.
127
+
128
+ ## `plan-body-verification-<task-type>-<seq>.json` schema
129
+
130
+ ```json
131
+ {
132
+ "schemaVersion": "1.0",
133
+ "phase": "implementation-planning",
134
+ "round": 1,
135
+ "effectiveMaxRounds": 1,
136
+ "gating": true,
137
+ "verificationMode": "lightweight",
138
+ "gateResult": "passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result",
139
+ "planItems": [
140
+ {
141
+ "id": "P-Opt-1",
142
+ "subject": "Option A: upload v2 를 신규 모듈로 분리",
143
+ "sourceSection": "4.5.1",
144
+ "ticketId": "<id-or-unknown>",
145
+ "votes": {"claude-worker": "AGREE", "codex-worker": "AGREE"},
146
+ "classification": "full-consensus",
147
+ "clarificationId": null
148
+ },
149
+ {
150
+ "id": "P-Step-3",
151
+ "subject": "Stage 2 Step 3: 마이그레이션 실행 후 회귀 테스트",
152
+ "sourceSection": "4.5.4",
153
+ "ticketId": "TICKET-123",
154
+ "votes": {"claude-worker": "DISAGREE(a)", "codex-worker": "DISAGREE(a)"},
155
+ "classification": "majority-disagree",
156
+ "clarificationId": "C-7"
157
+ }
158
+ ],
159
+ "dispatches": [
160
+ {"role": "claude-worker", "resultPath": "...", "terminalStatus": "completed"}
161
+ ]
162
+ }
163
+ ```
164
+
165
+ `dispatches[].terminalStatus` mirrors finding convergence (`completed | timeout | error | not-run | cli-failure`).
166
+
167
+ `planItems[].classification` enum: `full-consensus | partial-consensus | dissent-isolated | majority-disagree | contested`. `contested` only appears when `maxRounds > 1`; at default `maxRounds=1` any otherwise-unresolved item folds into `partial-consensus` per the round protocol above.
168
+
169
+ `planItems[].votes.<worker>` is the verbatim verdict token emitted by the worker — `AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT` — or `verification-error` for terminal non-result dispatches. The `DISAGREE` token retains its `<kind>` suffix so the breakage class is recoverable from the state file alone.
170
+
171
+ ## Plan-body reverify prompt
172
+
173
+ Required prompt anchor headers are identical to finding convergence (see [convergence](./convergence.md) §"Required reverify-prompt anchor headers"). The prompt body changes from F-* listing to P-* listing:
174
+
175
+ ```
176
+ You are <worker-role> performing plan-body verification for <task-key> (round 1).
177
+
178
+ ## Instructions
179
+
180
+ Review the following items extracted from the consolidated implementation plan
181
+ authored after your initial analysis. For EACH item, respond with exactly one
182
+ verdict:
183
+
184
+ - **AGREE**: The item is executable as written and internally consistent with
185
+ other items in the plan.
186
+ - **DISAGREE(<kind>)**: The item is broken. Cite which kind:
187
+ (a) a concrete referenced file path / symbol contradicts a different concrete path / symbol for the same artifact elsewhere — a genuine mismatch; an abbreviated / ellipsis path is NOT (a), use (b),
188
+ (b) command or referenced path is not executable or is ambiguous — including an abbreviated / ellipsis / under-specified path that does not resolve as written,
189
+ (c) validation signal is not observable,
190
+ (d) rollback violates commit / dependency order,
191
+ (e) item contradicts the trade-off matrix,
192
+ (f) requirement coverage row does not map the stated requirement to a concrete satisfying option / stage / step — citing an existing option counts as concrete even if that option's paths are abbreviated (that is (b) on the option's item, not (f)).
193
+ - **SUPPLEMENT**: The item is sound but a dependency / edge case / precondition
194
+ is missing.
195
+
196
+ Do NOT re-analyze the original requirements. Judge solely from plan internal
197
+ consistency and stated commands / paths. Do NOT inspect the original task brief
198
+ or worker analyses for this round.
199
+
200
+ For `P-Req-*` items, compare only the requirement text embedded in the row
201
+ against the cited plan item(s). Do not open the original brief, but do reject
202
+ coverage rows that cite no concrete option/stage/step or cite a plan item that
203
+ does not satisfy the row's own requirement.
204
+
205
+ ## Plan items to verify
206
+
207
+ ### P-Step-3 [TICKETID: <id>]: <one-line summary>
208
+ **From section**: 4.5.4 Stepwise Execution Order
209
+ **Original text**:
210
+ > <verbatim quote of the step>
211
+
212
+ **Check**:
213
+ - Are referenced file paths consistent with the option's File Structure list?
214
+ - Is the named command executable as written?
215
+ - Does the success criterion produce an observable signal?
216
+
217
+ ### P-Opt-2 [TICKETID: <id>]: <one-line summary>
218
+ ...
219
+
220
+ ## Response format
221
+
222
+ ### P-Step-3
223
+ **Verdict**: AGREE | DISAGREE(<a|b|c|d|e>) | SUPPLEMENT
224
+ **Explanation**: <2-3 sentences>
225
+
226
+ ### P-Opt-2
227
+ ...
228
+ ```
229
+
230
+ When `config.adversarial == true`, the lead prepends the adversarial framing from §"Adversarial plan-body posture" to the `## Instructions` block: the burden of proof is on the plan, the verifier opens and confirms every cited path / command, and an item whose cited references cannot be confirmed is answered `DISAGREE(<kind>)` rather than `AGREE`. The verdict tokens, breakage kinds (a–e), classification, and the majority gate threshold are unchanged. This prepended framing supersedes the template's "Judge solely from plan internal consistency" instruction for the adversarial round.
231
+
232
+ The "Reverify prompt: required-reading suppression" rule in [convergence](./convergence.md) (lightweight mode does NOT inject a `[Required reading]` clause) applies here as well.
233
+
234
+ ## Worker non-result handling in plan-body round (BLOCKING)
235
+
236
+ Mirrors finding convergence ([convergence](./convergence.md) §"Worker failure handling in reverify"). Concretely:
237
+
238
+ - A dispatch that returns terminal non-result MUST NOT be aggregated as `DISAGREE`.
239
+ - If at least one dispatch was issued AND **all** plan-body dispatches return non-result, the Gate result is `aborted-non-result`. Record one `contract-violation` event per non-result dispatch.
240
+ - When the gate is `aborted-non-result`, report-writer MUST keep the frontmatter `approved: false` (publishing `approved: true` under this gate result is a validator failure). A single row is added to `## 1. Clarification Items` with `Statement="plan-body verification could not run — all workers returned non-result"`, `Kind=decision`, `Blocks=approval`, allowing the user to either retry the phase or override by manually flipping the frontmatter to `approved: true` (or running `--approve` on the resume command).
@@ -28,7 +28,7 @@ Agent(
28
28
  prompt: "<report-writer prompt: see this contract + Required reading clause + Available MCP Servers section>",
29
29
  name: "report-writer",
30
30
  subagent_type: "report-writer-worker",
31
- run_in_background: true, # no team_name — removed in CC v2.1.178; joins the session's implicit team
31
+ run_in_background: true, # no team_name — implicit team; see team-contract Operating Rule 0
32
32
  model: "<family token of Report writer worker's modelExecutionValue>", # opus/sonnet/haiku — NOT hardcoded; see below
33
33
  mode: "auto"
34
34
  )
@@ -60,13 +60,13 @@ The prompt MUST include, in this order at the top:
60
60
  12. For implementation-planning runs: a literal block listing the 8 required English section headings the validator scans for (`Option Candidates`, `Trade-off`, `Recommended Option`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `User Approval Request`). The writer must use these exact substrings as section headings (Korean translation in parentheses is allowed).
61
61
  13. An explicit instruction: `You are the author of TWO files: (a) the final-report data.json at <Result Path>, (b) the worker-results audit file at <Worker Result Path>. After writing the data.json, invoke "okstra render-final-report <Result Path>" via Bash so the markdown sibling is rendered before you return. Do not return the report inline. The validator fails the run when (a)'s schema validation fails, when the rendered markdown is absent, or when (b) is missing.`
62
62
 
63
- **Completion detection after dispatch (BLOCKING).** The `Agent(name: ..., run_in_background: true)` call returns `Spawned successfully` immediately; that ack is NOT completion. After dispatching the report-writer (async), Lead MUST detect its completion via the self-scheduled polling protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", polling for the appearance of the data.json (Result Path) and the worker-results file (Worker Result Path) — do NOT restate the algorithm here. Report-writer is a single worker, so the pending set has one entry; the SSOT protocol handles that naturally. Lead MUST NOT treat the `Spawned successfully` ack as completion and MUST NOT end its turn with a prose "waiting for the report" statement; that path stalls the run until the user manually nudges it.
63
+ **Completion detection after dispatch (BLOCKING).** The spawn ack is NOT completion detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", with a one-entry pending set covering the data.json (Result Path) and the worker-results file (Worker Result Path). Do NOT end the turn with a prose "waiting for the report" statement.
64
64
 
65
65
  ### Resume-safe dispatch
66
66
 
67
- A resumed lead session can ALWAYS dispatch a fresh Report writer worker. There is no team to revive CC v2.1.178 removed `TeamCreate`, so every session already has its implicit team:
67
+ A resumed lead session can ALWAYS dispatch a fresh Report writer worker there is no team to revive; the session's implicit team is always present ([team-contract](./team-contract.md) Operating Rule 0):
68
68
 
69
- - Dispatch with `Agent(name: "report-writer", run_in_background: true)` and no `team_name` (the parameter was removed). The worker joins the session's implicit team and its session is recoverable by `agentName: "report-writer"` in `okstra-token-usage.py`.
69
+ - Dispatch with `Agent(name: "report-writer", run_in_background: true)`; the worker's session is recoverable by `agentName: "report-writer"` in `okstra-token-usage.py`.
70
70
  - Do NOT skip dispatch because of any team-related concern. Record `teamCreate` in team-state and proceed.
71
71
 
72
72
  ### Lead-authored fallback (only if dispatch failed)
@@ -79,7 +79,7 @@ Except for `release-handoff` (which is single-lead by design and never dispatche
79
79
 
80
80
  Speculative reasons such as "session resume constraint", "team object no longer exists", or "lead can do it faster" are NOT valid.
81
81
 
82
- For `tmux-pane` backend runs, do not use the Agent template. Create a one-job jobs file for the `report-writer` with `dispatchKind: "report-writer"` and the same `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths` fields used by reverify jobs. Then call `okstra team dispatch --project-root <dir> --run-manifest <path> --jobs-file <jobs-file>` followed by `okstra team await --project-root <dir> --run-manifest <path>`. Completion requires both the report data.json and the report-writer worker-results audit file.
82
+ For `tmux-pane` backend runs, do not use the Agent template. Create a one-job jobs file for the `report-writer` with `dispatchKind: "report-writer"` and the same field set as reverify jobs files ([convergence](./convergence.md) "Re-verification Agent Dispatch", tmux-pane branch the single source for the jobs-file fields). Then call `okstra team dispatch --project-root <dir> --run-manifest <path> --jobs-file <jobs-file>` followed by `okstra team await --project-root <dir> --run-manifest <path>`. Completion requires both the report data.json and the report-writer worker-results audit file.
83
83
 
84
84
 
85
85
  ## Phase 6 → Phase 7 execution sequence (BLOCKING order)
@@ -134,7 +134,7 @@ The four steps below MUST execute in this exact order. Reordering them is the re
134
134
 
135
135
  The status file is written after step 3 completes.
136
136
 
137
- **Run-end teammate teardown follows this whole sequence.** Token-usage collection (step 1) reads the worker session jsonls, so the lead MUST NOT disband the team until every step above is done. Only then — after the combined pane/team disposition prompt and only when the user approves cleanup — does the lead dismiss the worker teammates via `SendMessage` shutdown_request per `_common-contract.md` "Run-end teammate teardown" (split-pane runs only; silent-skip when split-pane is off; if the user keeps panes/teammates, leave them intact and surface the manual Teams/FleetView cleanup path).
137
+ **Run-end teammate teardown follows this whole sequence.** Token-usage collection (step 1) reads the worker session jsonls, so the lead MUST NOT disband the team until every step above is done. Only then — after the combined pane/team disposition prompt and only when the user approves cleanup — does the lead dismiss the worker teammates via `SendMessage` shutdown_request per [okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped pane & teammate lifecycle" → "Run-end teammate teardown" (split-pane runs only; silent-skip when split-pane is off; if the user keeps panes/teammates, leave them intact and surface the manual Teams/FleetView cleanup path).
138
138
 
139
139
  ## Final Report Structure
140
140
 
@@ -337,7 +337,7 @@ Persistence steps that must be performed in Phase 7:
337
337
  - [ ] 6. **Generate final status file**: `runs/<task-type>/status/final-<task-type>-<seq>.status` (if necessary)
338
338
  - [ ] 7. **Save convergence state**: `runs/<task-type>/state/convergence-<task-type>-<seq>.json` (when convergence is enabled)
339
339
  - [ ] 8. **Spawn follow-up task stubs**: run `okstra spawn-followups` against the final-report per the canonical spawn rule defined in "Phase 7 follow-up task spawner" above. Do not restate the trigger condition here — that section is the single source of truth. The script is idempotent across reruns.
340
- - [ ] 9. **Human HTML report** (conditional): `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — produced by Phase 7 step 1.5 **only when the report has ≥1 §5 `C-*` clarification row** (self-contained, embeds `Export user response` button). Clarification-free reports legitimately have no html sibling; do not treat its absence as a missing artifact.
340
+ - [ ] 9. **Human HTML report** (conditional): `runs/<task-type>/reports/final-report-<task-type>-<seq>.html` — produced by Phase 7 step 1.5 per its generation predicate (≥1 §1 `C-*` clarification row OR an implementation-planning Plan Approval widget target; that step is the single source for the exact condition). Reports matching neither condition legitimately have no html sibling; do not treat its absence as a missing artifact.
341
341
 
342
342
  ### Response after Persistence
343
343