okstra 0.121.1 → 0.123.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 (67) hide show
  1. package/README.md +4 -2
  2. package/docs/architecture/storage-model.md +14 -0
  3. package/docs/architecture.md +38 -8
  4. package/docs/cli.md +45 -5
  5. package/docs/for-ai/README.md +2 -2
  6. package/docs/for-ai/skills/okstra-rollup.md +1 -1
  7. package/docs/for-ai/skills/{okstra-schedule.md → okstra-schedule-gen.md} +3 -3
  8. package/docs/project-structure-overview.md +4 -3
  9. package/docs/task-process/implementation-planning.md +33 -9
  10. package/docs/task-process/implementation.md +21 -2
  11. package/package.json +1 -1
  12. package/runtime/BUILD.json +2 -2
  13. package/runtime/agents/workers/claude-worker.md +1 -1
  14. package/runtime/bin/lib/okstra/usage.sh +3 -3
  15. package/runtime/prompts/launch.template.md +9 -6
  16. package/runtime/prompts/lead/adapters/claude-code.md +104 -0
  17. package/runtime/prompts/lead/adapters/codex.md +45 -0
  18. package/runtime/prompts/lead/adapters/external.md +46 -0
  19. package/runtime/prompts/lead/context-loader.md +5 -5
  20. package/runtime/prompts/lead/convergence.md +11 -28
  21. package/runtime/prompts/lead/okstra-lead-contract.md +44 -94
  22. package/runtime/prompts/lead/plan-body-verification.md +50 -5
  23. package/runtime/prompts/lead/report-writer.md +59 -54
  24. package/runtime/prompts/lead/team-contract.md +33 -106
  25. package/runtime/prompts/profiles/_common-contract.md +3 -3
  26. package/runtime/prompts/profiles/_implementation-deliverable.md +3 -1
  27. package/runtime/prompts/profiles/_implementation-executor.md +6 -1
  28. package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
  29. package/runtime/prompts/profiles/final-verification.md +2 -0
  30. package/runtime/prompts/profiles/implementation-planning.md +14 -4
  31. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  32. package/runtime/prompts/wizard/prompts.ko.json +44 -0
  33. package/runtime/python/okstra_ctl/codex_dispatch.py +23 -1
  34. package/runtime/python/okstra_ctl/design_prep.py +1462 -0
  35. package/runtime/python/okstra_ctl/design_surfaces.py +243 -0
  36. package/runtime/python/okstra_ctl/final_report_schema.py +33 -1
  37. package/runtime/python/okstra_ctl/implementation_stage.py +35 -0
  38. package/runtime/python/okstra_ctl/incremental_carry.py +294 -21
  39. package/runtime/python/okstra_ctl/incremental_scope.py +51 -5
  40. package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
  41. package/runtime/python/okstra_ctl/material.py +1 -1
  42. package/runtime/python/okstra_ctl/model_discovery.py +98 -0
  43. package/runtime/python/okstra_ctl/models.py +8 -3
  44. package/runtime/python/okstra_ctl/render.py +81 -194
  45. package/runtime/python/okstra_ctl/report_views.py +22 -7
  46. package/runtime/python/okstra_ctl/run.py +53 -5
  47. package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
  48. package/runtime/python/okstra_ctl/user_response.py +67 -2
  49. package/runtime/python/okstra_ctl/wizard.py +283 -3
  50. package/runtime/python/okstra_token_usage/report.py +11 -0
  51. package/runtime/schemas/final-report-v1.0.schema.json +339 -0
  52. package/runtime/skills/okstra-inspect/SKILL.md +2 -2
  53. package/runtime/skills/okstra-rollup/SKILL.md +1 -1
  54. package/runtime/skills/{okstra-schedule → okstra-schedule-gen}/SKILL.md +62 -20
  55. package/runtime/skills/okstra-user-response/SKILL.md +8 -6
  56. package/runtime/templates/reports/final-report.template.md +71 -4
  57. package/runtime/templates/reports/i18n/en.json +32 -1
  58. package/runtime/templates/reports/i18n/ko.json +32 -1
  59. package/runtime/validators/validate-run.py +515 -5
  60. package/runtime/validators/validate-schedule.py +11 -7
  61. package/runtime/validators/validate_session_conformance.py +58 -33
  62. package/src/cli-registry.mjs +14 -0
  63. package/src/commands/inspect/design-prep.mjs +23 -0
  64. package/src/commands/inspect/stage-map.mjs +100 -0
  65. package/src/commands/lifecycle/install.mjs +3 -0
  66. package/src/commands/lifecycle/uninstall.mjs +8 -23
  67. package/src/lib/skill-catalog.mjs +10 -1
@@ -12,7 +12,7 @@ Work like a senior engineer who owns this result, not a commentator on it.
12
12
 
13
13
  ## Overview
14
14
 
15
- Claude lead orchestrates multiple AI workers (Claude, Codex, Antigravity) against a prepared task bundle, collects their independent outputs, supervises convergence, and ensures the final report is produced. When `Report writer worker` is in the selected roster, the final report file is **authored by the Report writer worker** — Claude lead reviews and approves but does not write the file itself. Claude lead never substitutes its own reasoning for a worker result, and never bypasses a rostered Report writer worker by writing the report directly.
15
+ The lead orchestrates the selected AI workers against a prepared task bundle, collects their independent outputs, supervises convergence, and ensures the final report is produced. When `Report writer worker` is in the selected roster, that worker authors the final-report artifacts; the lead reviews and approves them. The lead never substitutes its own reasoning for a worker result and never bypasses a rostered report writer.
16
16
 
17
17
  ## When to Use
18
18
 
@@ -34,7 +34,7 @@ This document is the operating contract and phase index. Detailed procedures liv
34
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 |
35
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 |
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
+ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`) are user-invoked skills, not lead support contracts — the lead does not consult them during a run.
38
38
 
39
39
  ## Quick Reference
40
40
 
@@ -44,18 +44,18 @@ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule`) are
44
44
  | 1.5 Graph hint | Build shared knowledge graph (optional) | this contract — see "Optional shared knowledge graph" below |
45
45
  | 2. Prompts | Prepare shared + role-specific prompts | `team-contract` |
46
46
  | 2.5 Team contract | Load operating rules for selected roster | `team-contract` |
47
- | 3. Teammate setup | Record team-state for the session's implicit team; gate split-pane on tmux | this contract see "Phase 3" below |
48
- | 4. Execution | Spawn analysis workers (`Agent(name: ..., run_in_background: true)`) | `team-contract` |
49
- | 5. Completion poll | Self-scheduled poll for worker result files | `team-contract` |
47
+ | 3. Dispatch setup | Execute the selected adapter's pre-dispatch setup and record its state | selected runtime adapter + this contract |
48
+ | 4. Execution | Call `dispatch_worker` once per selected assignment | selected runtime adapter + `team-contract` |
49
+ | 5. Completion wait | Call `await_workers` and verify terminal state plus required artifacts | selected runtime adapter + `team-contract` |
50
50
  | 5.5 Convergence | Cross-verify findings across workers | `convergence` |
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
+ | 5.6 Critic pass | (opt-in) fresh one-shot critic pass through `redispatch_worker`: coverage gaps (discovery/error-analysis/impl-planning) or acceptance devil's-advocate (final-verification). The critic dispatch fires concurrently with the first 5.5 reverify round (its input is fixed at Round 0); gap/blocker verification (one round) completes here | `convergence` "Coverage critic pass" / "Acceptance critic pass" |
52
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
+ | 7. Persist | Call `collect_usage`, update manifests, run the cleanup approval gate, then call `shutdown_workers` only on approval | selected runtime adapter + `report-writer` + this contract |
54
54
 
55
55
  ## Core operating contract
56
56
 
57
- - `Claude lead` owns orchestration, convergence supervision, and final-report ownership (review/approval). It does NOT author the final-report file when `Report writer worker` is in the roster.
58
- - Standard worker roles: `Claude worker`, `Codex worker`, `Antigravity worker`, `Report writer worker`. If the current run selects a subset, use only that subset with exact role labels from the task bundle.
57
+ - The `lead` owns orchestration, convergence supervision, and final-report review/approval. It does not author the final-report file when `Report writer worker` is in the roster.
58
+ - Standard worker roles remain `Claude worker`, `Codex worker`, `Antigravity worker`, and `Report writer worker`; these are provider/functional-role identities, not lead-runtime primitives.
59
59
  - `Report writer worker`, when in the roster, is the **author** of the final-report file. Lead reviews the draft and may request a revision via a follow-up dispatch, but MUST NOT write the report itself as a "shortcut". The only legal lead-authored fallback is when a Report writer worker dispatch was actually attempted and recorded a terminal status of `error`/`timeout`/`not-run` with an explicit reason in team-state — see [report-writer](./report-writer.md) "Lead-authored fallback".
60
60
  - "Session resume", "team is no longer alive", and similar are NOT valid reasons to skip Report writer worker dispatch — see [report-writer](./report-writer.md) "Resume-safe dispatch".
61
61
  - If the brief is incomplete, continue with explicit uncertainty markers rather than fabricating confidence.
@@ -95,38 +95,38 @@ Required checkpoints:
95
95
  - `PROGRESS: phase-1-intake reading task bundle` — at the start of Phase 1, before issuing parallel Read calls.
96
96
  - `PROGRESS: phase-1-intake complete` — after all intake reads return.
97
97
  - `PROGRESS: phase-2-prompts preparing <N> worker prompts` — at the start of Phase 2, before any `Write` to the assigned prompt paths.
98
- - `PROGRESS: phase-3-team-create using implicit team (split-pane=<on|off>)`emitted in Phase 3 after recording `teamCreate` in team-state (`on` when `$TMUX` is set, else `off`). When the launch prompt's "Concurrent-run marker" gate applies, emit `PROGRESS: phase-3-team-create skipped (concurrent-run)` instead, immediately after recording `teamCreate: { attempted: false, status: "skipped", reason: "concurrent-run" }` in team-state. The `phase-3-team-create` phase-id is required on every run; only the verb phrase varies.
99
- - `PROGRESS: phase-4-dispatch worker=<role> model=<model>` — once per worker, immediately before the `Agent` / wrapper call.
98
+ - `PROGRESS: phase-3-team-create <adapter-specific-status>` — after selected-adapter setup is recorded in team-state. The stable phase id is retained for artifact compatibility.
99
+ - `PROGRESS: phase-4-dispatch worker=<role> model=<model>` — once per worker, immediately before `dispatch_worker`.
100
100
  - `PROGRESS: phase-5-poll pending=<n> done=<m>` — emitted on each wakeup while the pending set is non-empty.
101
101
  - `PROGRESS: phase-5-collect worker=<role> status=<terminal-status>` — once per worker, immediately after the result file is verified.
102
102
  - `PROGRESS: phase-5.5-convergence round=<N> queue=<count>` — at the start of each convergence round (Phase 5.5).
103
103
  - `PROGRESS: phase-5.6-critic provider=<provider> gaps=<n>` — after the critic result is collected (Phase 5.6, opt-in; the critic dispatch itself fires concurrently with the first 5.5 reverify round). Omitted when `convergence.critic.enabled == false`.
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
+ - `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 worker handle 은 절대 노출하지 않는다. 첫 배치(분석 워커 dispatch) 직전엔 치울 게 없어 no-op 이며 마커도 생략.
105
105
  - `PROGRESS: phase-6-synthesis dispatching report-writer-worker` — at the start of Phase 6.
106
106
  - `PROGRESS: phase-7-persist updating manifests` — at the start of Phase 7.
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
+ - `PROGRESS: phase-7-teardown shutting-down-workers` — only after usage collection and user approval, immediately before `shutdown_workers`; omitted when no cleanup resource exists or the user keeps it.
108
108
  - `PROGRESS: complete final-report=<relative-path>` — final summary line, after all persistence.
109
109
 
110
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.
111
111
 
112
- `okstra-run` (in-session) surfaces these lines to the user directly; the bash-spawned path leaves them in the session jsonl for post-hoc retrieval. Neither path requires any additional formatting from Lead — emit the literal `PROGRESS:` prefix and the rest of the line as plain text.
112
+ `okstra-run` surfaces these lines to the user directly; other launch paths persist them in the selected adapter's declared conformance evidence/event source for post-hoc retrieval. No path requires additional formatting from Lead — emit the literal `PROGRESS:` prefix and the rest of the line as plain text.
113
113
 
114
- **Enforcement:** the Phase 7 validator (`validators/validate-run.py` → `validate_session_conformance.py`) scans the lead session jsonl (run-window-scoped, assistant text blocks only) and fails the run as `contract-violated` when a required checkpoint is missing — including the per-worker `phase-4-dispatch` / `phase-5-collect` lines (which must name each worker's role) and the `phase-batch-cleanup` lines that MUST precede the first `phase-5.5-convergence` round and the `phase-6-synthesis` report-writer dispatch. `phase-7-teardown` and `complete` fire after validation and are not checked.
114
+ **Enforcement:** the Phase 7 validator (`validators/validate-run.py` → `validate_session_conformance.py`) reads the selected adapter's declared conformance evidence/event source within the run window and fails the run as `contract-violated` when a required checkpoint is missing — including the per-worker `phase-4-dispatch` / `phase-5-collect` lines (which must name each worker's role) and the `phase-batch-cleanup` lines that MUST precede the first `phase-5.5-convergence` round and the `phase-6-synthesis` report-writer dispatch. `phase-7-teardown` and `complete` fire after validation and are not checked.
115
115
 
116
116
  ## Model assignments
117
117
 
118
118
  **The lead never invents a model.** Every role's model is read from `task-manifest.json` → `resultContract.requiredWorkerRoles[*].modelExecutionValue` (and the lead model metadata). A missing assignment is a manifest defect, not a license to fall back — see [team-contract](./team-contract.md) "Model Assignment Rules". The manifest is always populated at run-prep time by the CLI, which seeds these values from `OKSTRA_DEFAULT_*_MODEL` (`scripts/okstra_ctl/run.py`).
119
119
 
120
- **Reading the assignment is not enough — it must be applied at spawn.** For the in-process Claude subagents (`Claude worker`, `Report writer worker`), the manifest value is bound only when lead passes it as the `Agent(...)` `model:` parameter; their `model: inherit` frontmatter otherwise follows the lead's own model. See "Model Assignment Rules" #3–#5 in [team-contract](./team-contract.md) for the spawn rule, the family-token mapping, and the Codex/Antigravity exemption.
120
+ **Reading an assignment is not enough — the selected adapter must apply it at dispatch.** `dispatch_worker` receives the manifest assignment, and the selected runtime adapter maps `modelExecutionValue` to its native invocation without changing provider, role, or model. A missing/unsupported mapping is a pre-dispatch contract failure, never a silent fallback.
121
121
 
122
122
  The table below documents those prep-time seed values **for reference only** — it is NOT a lead-applied fallback:
123
123
 
124
- | Role | Seed model | subagent_type | Source definition |
124
+ | Role | Seed model | Worker assignment | Source definition |
125
125
  |------|-----------|---------------|-------------------|
126
- | Claude lead | opus | -- | orchestration + convergence supervision + final-report review/approval |
126
+ | Lead role | opus | -- | runtime-specific role label; orchestration + convergence supervision + final-report review/approval |
127
127
  | Report writer worker | opus | report-writer-worker | `agents/workers/report-writer-worker.md` |
128
128
  | Claude worker | opus | claude-worker | `agents/workers/claude-worker.md` |
129
- | Codex worker | gpt-5.6 | codex-worker | generated from `agents/workers/_cli-wrapper-template.md` + `codex-worker.params.json` |
129
+ | Codex worker | gpt-5.6-sol | codex-worker | generated from `agents/workers/_cli-wrapper-template.md` + `codex-worker.params.json` |
130
130
  | Antigravity worker | auto | antigravity-worker | generated from `agents/workers/_cli-wrapper-template.md` + `antigravity-worker.params.json` |
131
131
 
132
132
  All three analysis workers use dedicated agent definitions; Codex/Antigravity wrappers handle external CLI invocation internally; Claude worker runs as an in-process subagent with explicitly registered MCP tools so it does not fall back to `claude --mcp-cli` Bash invocations.
@@ -140,7 +140,7 @@ For `--task-type implementation` runs, the task bundle additionally pins one of
140
140
 
141
141
  Lead MUST dispatch Edit/Write-bearing work only through the `workerAgent` declared there. The other two providers still run as read-only verifiers in the same run; the executor's own provider is *also* dispatched separately as a verifier (a fresh CLI session) so the diff is reviewed by a context-isolated session. Session isolation is the primary self-review safeguard — same-model executor and same-provider verifier is acceptable when running in distinct sessions. Selecting a different model variant (e.g. executor=opus / Claude verifier=sonnet) is recommended but no longer mandatory.
142
142
 
143
- Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`); the model used by the executor is taken from the corresponding worker model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). For Codex/Antigravity executors, the underlying file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`), not through Claude-side Edit/Write tools.
143
+ Executor is chosen at run-prep time via `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`); the model used by the executor is taken from the corresponding worker model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). For CLI-backed executors, the underlying file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`), not through the lead runtime's `write_artifact` operation.
144
144
 
145
145
  #### Task worktree (BLOCKING for every task-type)
146
146
 
@@ -186,7 +186,7 @@ After context-loader completes, read **only the compact intake files below** in
186
186
 
187
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.
188
188
 
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.
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 recorded in the selected adapter's conformance evidence/event source, 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 selected adapter's declared source within this run's window, and that they precede the `phase-6-synthesis` / `phase-7-persist` checkpoints respectively.
190
190
 
191
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.
192
192
 
@@ -208,35 +208,19 @@ These phases are governed by [team-contract](./team-contract.md). It is the cano
208
208
 
209
209
  `Report writer worker` is NOT an analysis worker. Do not dispatch it in Phase 4/5 alongside analysis workers. It is invoked only in Phase 6 — see [report-writer](./report-writer.md).
210
210
 
211
- ### Phase 3 — Teammate setup (BLOCKING)
211
+ ### Phase 3 — Runtime adapter setup (BLOCKING)
212
212
 
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.
213
+ 1. Read the selected adapter contract path from the rendered launch prompt.
214
+ 2. Verify the run manifest's `leadRuntime`, `leadAdapter`, dispatch backend, concurrency metadata, and artifact paths.
215
+ 3. Execute the adapter's pre-dispatch setup without substituting another adapter's primitive.
216
+ 4. Persist the setup outcome in team-state using the existing fields required by that backend.
217
+ 5. Emit the canonical `PROGRESS: phase-3-team-create <adapter-specific-status>` checkpoint. The phase id remains stable for artifact compatibility; only the adapter-owned verb phrase varies.
214
218
 
215
- 1. Record team-state before any dispatch:
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`.
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.
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.
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:
220
- - `$TMUX` set → workers render as split-pane teammates under `teammateMode: auto`. Record `teamCreate.splitPane: true`.
221
- - `$TMUX` unset → teammates run in-process (no split-pane possible — a host limitation, not a degraded run). Record `teamCreate.splitPane: false`.
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.
219
+ ### Phase 4 / Phase 5 — Dispatch, await, and error-log dump
223
220
 
224
- Use agent and subagent names that map cleanly to the selected worker roles. Do not create ambiguous role names that differ from `Claude worker`, `Codex worker`, `Antigravity worker`, or `Report writer worker`.
221
+ For each selected worker assignment, persist the exact prompt history, emit the per-worker Phase 4 checkpoint, and call `dispatch_worker(assignment, prompt)` through the selected adapter. Then call `await_workers(handles)`. A dispatch acknowledgement or process/pane creation is never completion: verify the terminal status, Result Path, worker-results audit path, and error sidecar required by `team-contract` before emitting the Phase 5 collection checkpoint.
225
222
 
226
- If the launch prompt contains `Tmux Worker Dispatch Gate`, Phase 3 is recorded as skipped with `reason: "tmux-pane"` and Phase 4 dispatch is performed only through `okstra team dispatch` followed by `okstra team await`. In this branch, calling `Agent(...)` is a contract violation; worker completion is read from `workerDispatches[]` plus result files.
227
-
228
-
229
- ### Phase 4 / Phase 5 — Execution and error-log dump
230
-
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.
232
-
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.
234
-
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).
236
-
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.
238
-
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.
223
+ Retries and convergence re-verification always call `redispatch_worker` to create a fresh one-shot session. Never reuse a worker conversation or switch adapters/providers to hide a failed assignment.
240
224
 
241
225
  ### Errors log path wiring (BLOCKING)
242
226
 
@@ -258,7 +242,7 @@ okstra error-log append-from-worker \
258
242
  --task-key <taskKey> --agent <agent> --agent-role <role> --model <model>
259
243
  ```
260
244
 
261
- For Codex/Antigravity wrappers: if the CLI returns non-zero, times out, or hits a rate limit, immediately call `okstra error-log append-observed --error-type cli-failure ...` with the captured exit code, duration, message, and stderr excerpt. The wrapper subagent does this from inside its own Bash tool — Lead does NOT need to re-record. Token usage is NOT available from Agent tool results in real time; it is collected post-hoc at the start of Phase 7.
245
+ For Codex/Antigravity wrappers: if the CLI returns non-zero, times out, or hits a rate limit, immediately call `okstra error-log append-observed --error-type cli-failure ...` with the captured exit code, duration, message, and stderr excerpt. The wrapper subagent records this through its selected adapter — Lead does NOT need to re-record. Token usage is not inferred from dispatch return values; call `collect_usage` at the start of Phase 7.
262
246
 
263
247
  ## Phase 5.5: Convergence loop
264
248
 
@@ -277,7 +261,7 @@ When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolv
277
261
 
278
262
  **Confirmed findings are pruned from the queue.** Findings classified as `full-consensus`, `partial-consensus`, or `worker-unique` MUST NOT appear in any subsequent round's reverify prompt for any worker. `contested` is a final classification assigned only when the last executed round completes and the queue is still non-empty — it is NEVER an intermediate queue label.
279
263
 
280
- If any re-verification batch yields a `verification-error` terminal status, or a worker result fails the contract, Lead MUST record one event per violation via `okstra error-log append-observed --error-type contract-violation --agent <offending-agent> ...`. Use `agent: "claude-lead"` only when the violation is detected internally without a specific worker.
264
+ If any re-verification batch yields a `verification-error` terminal status, or a worker result fails the contract, Lead MUST record one event per violation via `okstra error-log append-observed --error-type contract-violation --agent <offending-agent> ...`. For an internally detected violation without a specific worker, use the selected adapter's lead-role event identity.
281
265
 
282
266
  If convergence is disabled, proceed directly to Phase 6 with the raw worker results.
283
267
 
@@ -368,54 +352,20 @@ The errors log is informational. Its presence/absence does not affect the final
368
352
 
369
353
  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. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first, so the reader knows which task the reply is about. **Every run-artifact path in this reply MUST be task-qualified** — report the final report as `.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/reports/final-report-<task-type>-<seq>.md` (the full path rooted at the task bundle), NOT the bare `runs/<task-type>/reports/...` form. The bare `runs/...` prefix is byte-for-byte identical across every task-group / task-id of the same task-type, so it cannot tell one task's report from another's — always emit the `.okstra/tasks/<task-group>/<task-id>/` prefix. The same task-qualified rule applies to the team-state path, resume command path, and any other run-artifact path this reply cites.
370
354
 
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.
355
+ ## Run-scoped worker-resource lifecycle
412
356
 
357
+ - At run start, call the selected adapter's setup required to distinguish lead-owned resources from worker-owned resources.
358
+ - Before every new worker batch, clean only confirmed-complete resources from the prior batch, call `record_lead_event` for the batch-cleanup checkpoint, and never terminate the lead or an incomplete worker.
359
+ - After Phase 7 persistence and `collect_usage`, enumerate residual adapter-owned resources. If none remain, skip the question.
360
+ - If resources remain, call `prompt_user` once with a binary keep-or-clean choice. The answer controls the entire residual set; do not ask a second backend-specific cleanup question.
361
+ - On keep, preserve all resources and provide the selected adapter's manual-cleanup instruction.
362
+ - On clean, emit the teardown checkpoint and call `shutdown_workers` through the selected adapter. Never execute another adapter's cleanup primitive.
413
363
  ## Common Mistakes
414
364
 
415
365
  | Mistake | Fix |
416
366
  |---------|-----|
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 |
418
- | Substituting Claude lead reasoning for a worker result | Claude lead synthesizes only — spawn the worker |
367
+ | Calling a host primitive from an unselected adapter | Read the selected adapter path from the launch prompt and execute only its semantic-operation mappings |
368
+ | Substituting lead reasoning for a worker result | The lead synthesizes only — call `dispatch_worker` for the selected assignment |
419
369
  | Skipping a worker silently | Always record terminal status with reason |
420
370
  | Writing verdict before all workers report | Wait for all results or explicit terminal statuses |
421
371
  | Ignoring task bundle model assignments | Task bundle overrides are canonical |
@@ -425,13 +375,13 @@ Lead-only operating duties spanning the whole run — recorded once here; the pr
425
375
  | Using `/tmp/*prompt*.txt` for worker prompt persistence | Persist the exact worker prompt to the assigned run-level `prompts/` path |
426
376
  | Lead writes `final-report-<suffix>.md` itself when `Report writer worker` is in the roster | Dispatch the Report writer worker per [report-writer](./report-writer.md) "Phase 6 dispatch template" |
427
377
  | Skipping Report writer worker dispatch citing "session resume constraint" or "team is gone" | No such constraint exists — see [report-writer](./report-writer.md) "Resume-safe dispatch" |
428
- | Using `subagent_type: "general-purpose"` for Report writer worker | Use `subagent_type: "report-writer-worker"` |
378
+ | Selecting a generic worker assignment for Report writer worker | Use the rostered `report-writer-worker` assignment; the selected adapter owns its native field mapping |
429
379
  | Including `final-report-template.md` in analysis worker `[Required reading]` | Template belongs only in the report-writer prompt — see [team-contract](./team-contract.md) audience-scoped enumeration |
430
380
  | Injecting `[Required reading]` into lightweight reverify prompts | Lightweight reverify forbids re-reading source materials — see [convergence](./convergence.md) "Reverify prompt: required-reading suppression" |
431
381
  | Letting `convergence.maxRounds` default to 2 for `requirements-discovery` | Resolve effective default to `1` for discovery and record in convergence state artifact |
432
382
  | Issuing serial Read calls in Phase 1 | The intake files are independent — issue all Read calls in a single message (parallel) |
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" |
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" |
383
+ | Flagging an adapter-shortened dispatch prompt as "incomplete" because it omits host-loaded material | The Worker Preamble pointer and core inputs remain mandatory; the selected adapter may omit duplicate host-loaded definitions |
384
+ | Waiting silently after `dispatch_worker` returns without a completed worker artifact | A dispatch acknowledgement is not completion — call `await_workers` and enforce the selected adapter's liveness policy |
435
385
  | 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)" |
436
386
  | 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" |
437
387
  | Skipping `--substitute-data` in the Phase 7 collector run | Always pass the flag — see [report-writer](./report-writer.md) "Phase 7 token-usage collector" |
@@ -60,11 +60,14 @@ From the report-writer's draft of `## 5.4 Implementation Plan Deliverables`, lea
60
60
  | `P-Val-<N>` | `4.5.6 Validation Checklist` | one checklist item |
61
61
  | `P-Rb-<N>` | `4.5.7 Rollback Strategy` | one rollback path |
62
62
  | `P-Req-<N>` | `4.5.8 Requirement Coverage` | one requirement coverage row |
63
+ | `P-Prep-S<stage>-<kind>` | Stage `designSurfaceCoverage` + `5.5.10 Implementation Design Preparation` | exactly one detector-produced `(stage, kind)` |
63
64
 
64
65
  `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
 
66
67
  Each plan item inherits the `[TICKETID: ...]` tag of its source section (per the standard ticket-tagging contract).
67
68
 
69
+ For every detector-produced `(stage, kind)`, extract exactly one plan item named `P-Prep-S<stage>-<kind>`. The V1 detector in `scripts/okstra_ctl/design_surfaces.py` owns the set: extraction consumes its output and never reruns a free-form requirements or keyword analysis. The worker receives the stage trigger evidence and its single `designSurfaceCoverage` row, plus only the referenced `designPreparation` PREP items needed to judge that row. `validators/validate-run.py` `_validate_expected_prep_plan_items` enforces the exact ID set and `_validate_detector_coverage` enforces exactly-once coverage/evidence.
70
+
68
71
  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
72
 
70
73
  ## Plan-body verdict semantics
@@ -80,6 +83,19 @@ The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their mea
80
83
  - `e` — item contradicts the trade-off matrix
81
84
  - `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
85
  - **SUPPLEMENT**: the item is sound but is missing a dependency / edge case / precondition.
86
+ - **fixability** (DISAGREE 전용, 필수): 각 `DISAGREE(<kind>)` 는 결함을 지금 가진 코드 + 이 계획 draft + brief 만으로 고칠 수 있는지 판정한다.
87
+ - `planner-fixable` — 외부 정보 없이 계획 자체를 교정하면 해소된다(경로 축약, prose command, placeholder, requirement-coverage 재매핑, 존재하지 않는 stage 인용 등).
88
+ - `needs-user-input` — 교정에 열린 사용자 clarification(인프라·계약 결정) 또는 외부 정보가 필요하다.
89
+ 기준 한 줄: "지금 가진 코드 + 계획 + brief 만으로 이 결함을 고칠 수 있는가?" — 그렇다면 `planner-fixable`. **Enforced:** `validators/validate-run.py` `_validate_disagree_has_fixability` 는 verification round 가 돈(`roundCount >= 1`) run 에서 `fixability` 가 없거나 허용값(`planner-fixable` / `needs-user-input`)이 아닌 `DISAGREE` verdict 를 실패시킨다 — 그래야 `_validate_self_fix_before_clarification` 가 mislabel 로 planner-fixable 결함을 self-fix 없이 승격시키지 못한다.
90
+
91
+ `P-Prep-S<stage>-<kind>` applies the same verdict tokens and adds these disposition checks:
92
+
93
+ - `inline-contract`: AGREE only when the contract contains the kind-specific fields needed to implement the stage; a label or generic prose is insufficient.
94
+ - `prep-item`: AGREE only when every referenced PREP item has a concrete `aiProposal`, correct `stageRefs`/`kind`, and state-specific safeguards required by `schemas/final-report-v1.0.schema.json`.
95
+ - `not-applicable`: AGREE only when the rationale is consistent with the stage action; otherwise DISAGREE with `fixability` (`planner-fixable` when the plan can supply the missing contract, `needs-user-input` only for genuinely external facts).
96
+ - A declared `blocked` item is not itself a plan-body failure. Missing or duplicate coverage, an empty proposal, a mismatched reference, or an unjustified disposition is a failure and receives `DISAGREE(<kind>)` with `fixability`.
97
+
98
+ The semantic checks above are the plan-body enforcement layer for the schema-valid structures; `validators/validate-run.py` enforces detector coverage and references, while this worker verdict decides whether the content is implementable.
83
99
 
84
100
  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
101
 
@@ -95,7 +111,7 @@ When `config.adversarial == true` (the default for `implementation-planning`; se
95
111
 
96
112
  - 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
113
  - 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.
114
+ - If a cited path / command / validation signal cannot be confirmed, the verifier responds `DISAGREE(<kind>)` with the applicable breakage kind (a–f); uncertainty resolves toward DISAGREE, not AGREE.
99
115
  - **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
116
 
101
117
  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.
@@ -117,13 +133,21 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
117
133
  - any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
118
134
  - all items `full-consensus` → `passed`
119
135
  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:
136
+ 7. **Self-fix round (최대 1회, planner-fixable 결함 대상).** aggregation 후 `majority-disagree` 항목 `DISAGREE` verdict 다수가 `fixability == planner-fixable` 인 항목이 하나 이상이면, lead 는 사용자에게 승격하기 **전에** 자가수정을 1회 시도한다:
137
+ - lead 가 report-writer 에게 그 planner-fixable 항목들만 교정 재작성을 지시한다(전체 draft 재생성 아님 — 해당 `P-*` 가 가리키는 계획 섹션만 patch; 절차는 [report-writer](./report-writer.md) §"Self-fix rewrite").
138
+ - missing or weak `P-Prep-*` contracts are repaired by adding kind-specific inline detail or an AI-prepared PREP item with a concrete proposal. Facts that require user or external authority remain `blocked` and keep their request material; never invent those facts during self-fix.
139
+ - lead 가 plan-body 검증을 1회 재실행한다(교정된 항목 + 재작성이 건드린 인접 항목 중심). 재검증 후 verdict 로 `planItems[].verdicts` 를 덮어쓴다.
140
+ - self-fix 로 `majority-disagree` 가 해소된 항목은 `planItems[].selfFixNote` 에 `self-fixed in round <N>: <무엇을 고쳤는지>` 를 기록한다. 해소된 항목은 clarification 을 만들지 않는다.
141
+ - 이 라운드는 **최대 1회**다(무한루프 상한). self-fix 라운드를 시도했으면 `planBodyVerification.selfFixRoundApplied = true` 로 기록한다.
142
+ - `needs-user-input` 다수인 `majority-disagree` 항목은 self-fix 대상이 아니다 — 곧바로 다음 step 의 clarification 승격으로 간다.
143
+ 8. For every `majority-disagree` item **that remains after the self-fix round** (self-fix 로 해소되지 않았거나 처음부터 `needs-user-input` 다수인 항목), lead adds a row to `## 1. Clarification Items` with:
121
144
  - new `C-<N>` ID (numbering continues from any existing rows)
122
145
  - `Statement` summarising the disagreement and the worker breakage `<kind>`
123
146
  - `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
124
147
  - `Blocks=approval`
125
148
  - 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.
149
+ - self-fix 라운드를 거치고도 남은 `planner-fixable` 항목은 승격하되 `Statement` "planner self-fix 시도했으나 미해소" 명시한다(상한 때문에 재작성을 반복하지 않는다). `validators/validate-run.py` `_validate_self_fix_before_clarification` `selfFixRoundApplied != true` 인데 planner-fixable 다수 항목이 승격되면 실패시킨다.
150
+ 9. 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
151
 
128
152
  ## `plan-body-verification-<task-type>-<seq>.json` schema
129
153
 
@@ -136,6 +160,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
136
160
  "gating": true,
137
161
  "verificationMode": "lightweight",
138
162
  "gateResult": "passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result",
163
+ "selfFixRoundApplied": false,
139
164
  "planItems": [
140
165
  {
141
166
  "id": "P-Opt-1",
@@ -168,6 +193,8 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
168
193
 
169
194
  `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
195
 
196
+ `planBodyVerification.selfFixRoundApplied` (bool, 기본 false): self-fix 라운드를 1회라도 시도했는지. `planItems[].verdicts[].fixability` 는 각 `DISAGREE` 의 `planner-fixable | needs-user-input` 판정으로, 최종 data.json(`§5.5.9`)에 기록된다.
197
+
171
198
  ## Plan-body reverify prompt
172
199
 
173
200
  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:
@@ -190,6 +217,7 @@ verdict:
190
217
  (d) rollback violates commit / dependency order,
191
218
  (e) item contradicts the trade-off matrix,
192
219
  (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)).
220
+ DISAGREE 를 낼 때는 **Fixability** 도 함께 답하라 — 이 결함을 코드 + 이 계획 draft + brief 만으로 고칠 수 있으면 `planner-fixable`, 열린 사용자 clarification / 외부 정보가 필요하면 `needs-user-input`.
193
221
  - **SUPPLEMENT**: The item is sound but a dependency / edge case / precondition
194
222
  is missing.
195
223
 
@@ -202,6 +230,11 @@ against the cited plan item(s). Do not open the original brief, but do reject
202
230
  coverage rows that cite no concrete option/stage/step or cite a plan item that
203
231
  does not satisfy the row's own requirement.
204
232
 
233
+ For each `P-Prep-S<stage>-<kind>` item, use only the supplied detector trigger
234
+ evidence, its single `designSurfaceCoverage` row, and referenced PREP items.
235
+ Apply the disposition-specific AGREE/DISAGREE rules in this contract; do not
236
+ rerun a free-form requirements analysis. Every DISAGREE includes Fixability.
237
+
205
238
  ## Plan items to verify
206
239
 
207
240
  ### P-Step-3 [TICKETID: <id>]: <one-line summary>
@@ -217,17 +250,29 @@ does not satisfy the row's own requirement.
217
250
  ### P-Opt-2 [TICKETID: <id>]: <one-line summary>
218
251
  ...
219
252
 
253
+ ### P-Prep-S2-external-interface [TICKETID: <id>]: <one-line summary>
254
+ **From section**: Stage 2 designSurfaceCoverage + 5.5.10 Implementation Design Preparation
255
+ **Trigger evidence**: <detector-produced evidence rows>
256
+ **Coverage row**: <the single designSurfaceCoverage row>
257
+ **Referenced PREP items**: <verbatim referenced items, or none>
258
+
220
259
  ## Response format
221
260
 
222
261
  ### P-Step-3
223
- **Verdict**: AGREE | DISAGREE(<a|b|c|d|e>) | SUPPLEMENT
262
+ **Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT
263
+ **Fixability** (DISAGREE 일 때만): planner-fixable | needs-user-input — "코드 + 이 계획 + brief 만으로 고칠 수 있으면 planner-fixable"
224
264
  **Explanation**: <2-3 sentences>
225
265
 
226
266
  ### P-Opt-2
227
267
  ...
268
+
269
+ ### P-Prep-S2-external-interface
270
+ **Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT
271
+ **Fixability** (DISAGREE only): planner-fixable | needs-user-input
272
+ **Explanation**: <2-3 sentences applying the disposition-specific rule>
228
273
  ```
229
274
 
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.
275
+ 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–f), 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
276
 
232
277
  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
278