okstra 0.121.1 → 0.122.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/docs/architecture.md +4 -2
- package/docs/project-structure-overview.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/prompts/launch.template.md +4 -4
- package/runtime/prompts/lead/adapters/claude-code.md +104 -0
- package/runtime/prompts/lead/adapters/codex.md +45 -0
- package/runtime/prompts/lead/adapters/external.md +46 -0
- package/runtime/prompts/lead/context-loader.md +5 -5
- package/runtime/prompts/lead/convergence.md +11 -28
- package/runtime/prompts/lead/okstra-lead-contract.md +42 -92
- package/runtime/prompts/lead/plan-body-verification.md +21 -5
- package/runtime/prompts/lead/report-writer.md +50 -51
- package/runtime/prompts/lead/team-contract.md +33 -106
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
- package/runtime/python/okstra_ctl/render.py +76 -194
- package/runtime/python/okstra_ctl/report_views.py +22 -7
- package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
- package/runtime/schemas/final-report-v1.0.schema.json +3 -0
- package/runtime/skills/okstra-schedule/SKILL.md +60 -18
- package/runtime/templates/reports/final-report.template.md +4 -4
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/i18n/ko.json +1 -1
- package/runtime/validators/validate-run.py +89 -0
- package/runtime/validators/validate-schedule.py +7 -3
- package/runtime/validators/validate_session_conformance.py +58 -33
- package/src/cli-registry.mjs +7 -0
- package/src/commands/inspect/stage-map.mjs +100 -0
- package/src/commands/lifecycle/install.mjs +3 -0
- package/src/commands/lifecycle/uninstall.mjs +8 -23
- package/src/lib/skill-catalog.mjs +8 -0
|
@@ -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
|
-
|
|
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
|
|
|
@@ -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.
|
|
48
|
-
| 4. Execution |
|
|
49
|
-
| 5. Completion
|
|
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)
|
|
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 |
|
|
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
|
-
- `
|
|
58
|
-
- Standard worker roles
|
|
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,35 +95,35 @@ 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
|
|
99
|
-
- `PROGRESS: phase-4-dispatch worker=<role> model=<model>` — once per worker, immediately before
|
|
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
|
|
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
|
|
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`
|
|
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`)
|
|
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
|
|
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 |
|
|
124
|
+
| Role | Seed model | Worker assignment | Source definition |
|
|
125
125
|
|------|-----------|---------------|-------------------|
|
|
126
|
-
|
|
|
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
129
|
| Codex worker | gpt-5.6 | codex-worker | generated from `agents/workers/_cli-wrapper-template.md` + `codex-worker.params.json` |
|
|
@@ -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
|
|
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
|
|
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 —
|
|
211
|
+
### Phase 3 — Runtime adapter setup (BLOCKING)
|
|
212
212
|
|
|
213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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> ...`.
|
|
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
|
|
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
|
-
|
|
|
418
|
-
| Substituting
|
|
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
|
-
|
|
|
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
|
|
434
|
-
| Waiting silently
|
|
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" |
|
|
@@ -80,6 +80,10 @@ The verdict tokens `AGREE` / `DISAGREE` / `SUPPLEMENT` are reused, but their mea
|
|
|
80
80
|
- `e` — item contradicts the trade-off matrix
|
|
81
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
82
|
- **SUPPLEMENT**: the item is sound but is missing a dependency / edge case / precondition.
|
|
83
|
+
- **fixability** (DISAGREE 전용, 필수): 각 `DISAGREE(<kind>)` 는 결함을 지금 가진 코드 + 이 계획 draft + brief 만으로 고칠 수 있는지 판정한다.
|
|
84
|
+
- `planner-fixable` — 외부 정보 없이 계획 자체를 교정하면 해소된다(경로 축약, prose command, placeholder, requirement-coverage 재매핑, 존재하지 않는 stage 인용 등).
|
|
85
|
+
- `needs-user-input` — 교정에 열린 사용자 clarification(인프라·계약 결정) 또는 외부 정보가 필요하다.
|
|
86
|
+
기준 한 줄: "지금 가진 코드 + 계획 + 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 없이 승격시키지 못한다.
|
|
83
87
|
|
|
84
88
|
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
89
|
|
|
@@ -95,7 +99,7 @@ When `config.adversarial == true` (the default for `implementation-planning`; se
|
|
|
95
99
|
|
|
96
100
|
- 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
101
|
- 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–
|
|
102
|
+
- 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
103
|
- **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
104
|
|
|
101
105
|
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 +121,20 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
117
121
|
- any `partial-consensus` / `dissent-isolated` present, no `majority-disagree` → `passed-with-dissent`
|
|
118
122
|
- all items `full-consensus` → `passed`
|
|
119
123
|
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.
|
|
124
|
+
7. **Self-fix round (최대 1회, planner-fixable 결함 대상).** aggregation 후 `majority-disagree` 항목 중 그 `DISAGREE` verdict 의 다수가 `fixability == planner-fixable` 인 항목이 하나 이상이면, lead 는 사용자에게 승격하기 **전에** 자가수정을 1회 시도한다:
|
|
125
|
+
- lead 가 report-writer 에게 그 planner-fixable 항목들만 교정 재작성을 지시한다(전체 draft 재생성 아님 — 해당 `P-*` 가 가리키는 계획 섹션만 patch; 절차는 [report-writer](./report-writer.md) §"Self-fix rewrite").
|
|
126
|
+
- lead 가 plan-body 검증을 1회 재실행한다(교정된 항목 + 재작성이 건드린 인접 항목 중심). 재검증 후 verdict 로 `planItems[].verdicts` 를 덮어쓴다.
|
|
127
|
+
- self-fix 로 `majority-disagree` 가 해소된 항목은 `planItems[].selfFixNote` 에 `self-fixed in round <N>: <무엇을 고쳤는지>` 를 기록한다. 해소된 항목은 clarification 을 만들지 않는다.
|
|
128
|
+
- 이 라운드는 **최대 1회**다(무한루프 상한). self-fix 라운드를 시도했으면 `planBodyVerification.selfFixRoundApplied = true` 로 기록한다.
|
|
129
|
+
- `needs-user-input` 다수인 `majority-disagree` 항목은 self-fix 대상이 아니다 — 곧바로 다음 step 의 clarification 승격으로 간다.
|
|
130
|
+
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
131
|
- new `C-<N>` ID (numbering continues from any existing rows)
|
|
122
132
|
- `Statement` summarising the disagreement and the worker breakage `<kind>`
|
|
123
133
|
- `Kind` chosen per the standard policy (usually `decision` for option-level conflicts, `data-point` for path/symbol mismatches)
|
|
124
134
|
- `Blocks=approval`
|
|
125
135
|
- 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
|
-
|
|
136
|
+
- self-fix 라운드를 거치고도 남은 `planner-fixable` 항목은 승격하되 `Statement` 에 "planner self-fix 시도했으나 미해소" 를 명시한다(상한 때문에 재작성을 반복하지 않는다). `validators/validate-run.py` `_validate_self_fix_before_clarification` 는 `selfFixRoundApplied != true` 인데 planner-fixable 다수 항목이 승격되면 실패시킨다.
|
|
137
|
+
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
138
|
|
|
128
139
|
## `plan-body-verification-<task-type>-<seq>.json` schema
|
|
129
140
|
|
|
@@ -136,6 +147,7 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
136
147
|
"gating": true,
|
|
137
148
|
"verificationMode": "lightweight",
|
|
138
149
|
"gateResult": "passed | passed-with-dissent | blocked-by-disagreement | aborted-non-result",
|
|
150
|
+
"selfFixRoundApplied": false,
|
|
139
151
|
"planItems": [
|
|
140
152
|
{
|
|
141
153
|
"id": "P-Opt-1",
|
|
@@ -168,6 +180,8 @@ Plan-body verification stays **lightweight** even under this posture — the `ve
|
|
|
168
180
|
|
|
169
181
|
`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
182
|
|
|
183
|
+
`planBodyVerification.selfFixRoundApplied` (bool, 기본 false): self-fix 라운드를 1회라도 시도했는지. `planItems[].verdicts[].fixability` 는 각 `DISAGREE` 의 `planner-fixable | needs-user-input` 판정으로, 최종 data.json(`§5.5.9`)에 기록된다.
|
|
184
|
+
|
|
171
185
|
## Plan-body reverify prompt
|
|
172
186
|
|
|
173
187
|
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 +204,7 @@ verdict:
|
|
|
190
204
|
(d) rollback violates commit / dependency order,
|
|
191
205
|
(e) item contradicts the trade-off matrix,
|
|
192
206
|
(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)).
|
|
207
|
+
DISAGREE 를 낼 때는 **Fixability** 도 함께 답하라 — 이 결함을 코드 + 이 계획 draft + brief 만으로 고칠 수 있으면 `planner-fixable`, 열린 사용자 clarification / 외부 정보가 필요하면 `needs-user-input`.
|
|
193
208
|
- **SUPPLEMENT**: The item is sound but a dependency / edge case / precondition
|
|
194
209
|
is missing.
|
|
195
210
|
|
|
@@ -220,14 +235,15 @@ does not satisfy the row's own requirement.
|
|
|
220
235
|
## Response format
|
|
221
236
|
|
|
222
237
|
### P-Step-3
|
|
223
|
-
**Verdict**: AGREE | DISAGREE(<a|b|c|d|e>) | SUPPLEMENT
|
|
238
|
+
**Verdict**: AGREE | DISAGREE(<a|b|c|d|e|f>) | SUPPLEMENT
|
|
239
|
+
**Fixability** (DISAGREE 일 때만): planner-fixable | needs-user-input — "코드 + 이 계획 + brief 만으로 고칠 수 있으면 planner-fixable"
|
|
224
240
|
**Explanation**: <2-3 sentences>
|
|
225
241
|
|
|
226
242
|
### P-Opt-2
|
|
227
243
|
...
|
|
228
244
|
```
|
|
229
245
|
|
|
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–
|
|
246
|
+
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
247
|
|
|
232
248
|
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
249
|
|