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.
- package/README.kr.md +2 -2
- package/README.md +2 -2
- package/bin/okstra +7 -1
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-brief.md +2 -3
- package/docs/for-ai/skills/okstra-container-build.md +2 -3
- package/docs/for-ai/skills/okstra-inspect.md +5 -12
- package/docs/for-ai/skills/okstra-rollup.md +3 -4
- package/docs/for-ai/skills/okstra-run.md +3 -5
- package/docs/for-ai/skills/okstra-schedule.md +2 -7
- package/docs/for-ai/skills/okstra-setup.md +1 -1
- package/docs/kr/architecture/storage-model.md +1 -2
- package/docs/kr/architecture.md +2 -2
- package/docs/kr/cli.md +4 -2
- package/docs/project-structure-overview.md +5 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/coding-preflight/overview.md +3 -1
- package/runtime/prompts/launch.template.md +1 -5
- package/runtime/prompts/lead/context-loader.md +2 -4
- package/runtime/prompts/lead/convergence.md +11 -240
- package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
- package/runtime/prompts/lead/plan-body-verification.md +240 -0
- package/runtime/prompts/lead/report-writer.md +7 -7
- package/runtime/prompts/lead/team-contract.md +15 -17
- package/runtime/prompts/profiles/_common-contract.md +1 -37
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +2 -2
- package/runtime/prompts/profiles/implementation.md +1 -1
- package/runtime/python/okstra_ctl/path_hints.py +1 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/render.py +8 -0
- package/runtime/python/okstra_ctl/set_work_status.py +147 -0
- package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
- package/runtime/skills/okstra-brief/SKILL.md +32 -176
- package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
- package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
- package/runtime/skills/okstra-container-build/SKILL.md +7 -20
- package/runtime/skills/okstra-graphify/SKILL.md +8 -8
- package/runtime/skills/okstra-inspect/SKILL.md +27 -43
- package/runtime/skills/okstra-rollup/SKILL.md +6 -6
- package/runtime/skills/okstra-run/SKILL.md +7 -16
- package/runtime/skills/okstra-schedule/SKILL.md +64 -419
- package/runtime/skills/okstra-setup/SKILL.md +25 -223
- package/runtime/skills/okstra-setup/references/project-config.md +188 -0
- package/runtime/templates/reports/schedule.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/validate-run.py +7 -7
- package/src/cli-registry.mjs +14 -0
- package/src/commands/inspect/set-work-status.mjs +31 -0
- package/src/commands/lifecycle/check-project.mjs +68 -56
- package/src/commands/lifecycle/install.mjs +1 -0
- 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
|
|
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
|
-
|
|
36
|
-
|
|
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` + `
|
|
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` +
|
|
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
|
|
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
|
|
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
|
-
-
|
|
149
|
-
-
|
|
150
|
-
- Branch: `<work-category-
|
|
151
|
-
- A global registry at `~/.okstra/worktrees/registry.json` (flock-guarded)
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
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
|
|
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`**
|
|
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
|
-
|
|
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
|
|
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 [
|
|
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 [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|