okstra 0.128.0 → 0.129.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 (47) hide show
  1. package/docs/architecture/storage-model.md +15 -2
  2. package/docs/architecture.md +19 -3
  3. package/docs/cli.md +8 -0
  4. package/docs/project-structure-overview.md +14 -5
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/agents/workers/antigravity-worker.md +3 -3
  8. package/runtime/agents/workers/claude-worker.md +4 -4
  9. package/runtime/agents/workers/codex-worker.md +3 -3
  10. package/runtime/agents/workers/report-writer-worker.md +5 -5
  11. package/runtime/prompts/lead/adapters/claude-code.md +2 -1
  12. package/runtime/prompts/lead/adapters/codex.md +1 -0
  13. package/runtime/prompts/lead/adapters/external.md +1 -0
  14. package/runtime/prompts/lead/convergence.md +42 -84
  15. package/runtime/prompts/lead/okstra-lead-contract.md +10 -8
  16. package/runtime/prompts/lead/report-writer.md +3 -2
  17. package/runtime/prompts/lead/team-contract.md +11 -9
  18. package/runtime/prompts/profiles/_common-contract.md +1 -1
  19. package/runtime/prompts/profiles/error-analysis.md +1 -1
  20. package/runtime/prompts/profiles/implementation-planning.md +5 -0
  21. package/runtime/prompts/profiles/improvement-discovery.md +11 -1
  22. package/runtime/prompts/profiles/requirements-discovery.md +8 -1
  23. package/runtime/python/okstra_ctl/analysis_packet.py +7 -13
  24. package/runtime/python/okstra_ctl/codex_dispatch.py +42 -97
  25. package/runtime/python/okstra_ctl/context_cost.py +82 -7
  26. package/runtime/python/okstra_ctl/convergence.py +223 -0
  27. package/runtime/python/okstra_ctl/convergence_engine.py +1152 -0
  28. package/runtime/python/okstra_ctl/convergence_migration.py +184 -0
  29. package/runtime/python/okstra_ctl/convergence_store.py +85 -0
  30. package/runtime/python/okstra_ctl/dispatch_core.py +53 -14
  31. package/runtime/python/okstra_ctl/improvement_assignment.py +61 -0
  32. package/runtime/python/okstra_ctl/path_hints.py +23 -1
  33. package/runtime/python/okstra_ctl/paths.py +10 -1
  34. package/runtime/python/okstra_ctl/render.py +56 -11
  35. package/runtime/python/okstra_ctl/worker_prompt_body.py +107 -0
  36. package/runtime/python/okstra_ctl/worker_prompt_contract.py +102 -9
  37. package/runtime/python/okstra_ctl/worker_prompt_headers.py +79 -9
  38. package/runtime/python/okstra_ctl/worker_prompt_policy.py +158 -0
  39. package/runtime/templates/implementation-worker-preamble.md +51 -0
  40. package/runtime/templates/operating-standard.md +4 -4
  41. package/runtime/templates/report-writer-prompt-preamble.md +37 -0
  42. package/runtime/templates/worker-error-contract.md +46 -0
  43. package/runtime/templates/worker-prompt-preamble.md +28 -234
  44. package/runtime/validators/lib/fixtures.sh +27 -12
  45. package/runtime/validators/validate-run.py +201 -72
  46. package/src/cli-registry.mjs +7 -0
  47. package/src/commands/execute/convergence.mjs +34 -0
@@ -47,7 +47,7 @@ Read-side inspection (`/okstra-inspect`) and scheduling (`/okstra-schedule-gen`)
47
47
  | 3. Dispatch setup | Execute the selected adapter's pre-dispatch setup and record its state | selected runtime adapter + this contract |
48
48
  | 4. Execution | Call `dispatch_worker` once per selected assignment | selected runtime adapter + `team-contract` |
49
49
  | 5. Completion wait | Call `await_workers` and verify terminal state plus required artifacts | selected runtime adapter + `team-contract` |
50
- | 5.5 Convergence | Cross-verify findings across workers | `convergence` |
50
+ | 5.5 Convergence | Semantically group findings, then drive deterministic state transitions through `ConvergenceEngine` via `okstra convergence` | `convergence` |
51
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
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 |
@@ -210,6 +210,8 @@ These phases are governed by [team-contract](./team-contract.md). It is the cano
210
210
 
211
211
  For `final-verification`, Lead persists initial analysis prompts with one shared semantic body. Any genuine run delta appears under exactly one `## Run-specific directive` heading and applies to every selected analysis worker; Lead never shards verification requirements by worker, provider, or model. If the shared directive would exceed 40 nonblank lines, Lead writes it into the instruction set and adds the same reference to `analysis-packet.md` before dispatch. Phase 7 validates the persisted initial analysis prompts through the shared prompt contract before the run can pass.
212
212
 
213
+ For `improvement-discovery`, Lead records `## Primary Pass Assignments` in the Phase 1.5 grilling log before worker prompt generation. Enumerate selected analyser worker instances in run-manifest `requiredWorkerRoles` order and rotate them over the resolved lenses in log order; provider and model names never determine assignment position. Every analyser still inspects every resolved lens. Phase 7 recomputes this rotation from the persisted roster and fails a missing, extra, duplicate, out-of-order, or out-of-scope assignment.
214
+
213
215
  `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).
214
216
 
215
217
  ### Phase 3 — Runtime adapter setup (BLOCKING)
@@ -282,15 +284,15 @@ Convergence is enabled by default. Configure via task-manifest.json:
282
284
  - `convergence.verificationMode`: `"lightweight"` | `"full-reanalysis"` (default: `"lightweight"`; the adversarial phases below force `"full-reanalysis"`)
283
285
  - `convergence.adversarial`: true/false — **phase-aware default**: `true` for `requirements-discovery` / `error-analysis` / `implementation-planning`, `false` otherwise. When `true`, Phase 5.5 runs in adversarial mode (verifiers refute findings; burden of proof on the claim). See [convergence](./convergence.md) "Adversarial Verification Mode".
284
286
 
285
- When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolve the effective value via the phase-aware default above before entering Phase 5.5, and record the resolved value in the convergence state artifact at `config.effectiveMaxRounds`.
287
+ When `task-manifest.json` does not set `convergence.maxRounds`, lead MUST resolve the effective value via the phase-aware default above before entering Phase 5.5 and put it in the grouped input at `config.effectiveMaxRounds`.
286
288
 
287
- **Round 2 is gated, not unconditional.** Even when `effectiveMaxRounds == 2`, Round 2 runs only when (a) the verification queue is non-empty after Round 1, AND (b) at least one Round 1 reverify dispatch terminated as `completed`. Otherwise lead writes `round2SkippedReason` to the convergence state and proceeds to final classification. See [convergence](./convergence.md) "Round 2 gate" for the predicate.
289
+ The lead judges semantic similarity, ticket-set equality, and evidence. After writing the grouped input, it drives `okstra convergence seed plan-round apply-round finalize validate` and MUST NOT calculate queue membership, classification, history arithmetic, skip reasons, final state, or counts itself. `ConvergenceEngine` owns all of those deterministic transitions.
288
290
 
289
- **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.
291
+ For every dispatch plan, create only the worker batches returned by the engine and send them through the selected adapter. Confirmed findings cannot reappear because queue pruning is engine-owned and monotonic. Reverify terminal failures are structured outcomes for `apply-round`, never lead-authored `DISAGREE` votes.
290
292
 
291
293
  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.
292
294
 
293
- If convergence is disabled, proceed directly to Phase 6 with the raw worker results.
295
+ If convergence is disabled, `seed`/`finalize` produce the auto-disabled final state and Phase 6 uses the raw worker results for synthesis.
294
296
 
295
297
  ## Phase 6: Final report assembly
296
298
 
@@ -403,10 +405,10 @@ After persistence, reply briefly in the resolved Report Language with: completio
403
405
  | Selecting a generic worker assignment for Report writer worker | Use the rostered `report-writer-worker` assignment; the selected adapter owns its native field mapping |
404
406
  | 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 |
405
407
  | Injecting `[Required reading]` into lightweight reverify prompts | Lightweight reverify forbids re-reading source materials — see [convergence](./convergence.md) "Reverify prompt: required-reading suppression" |
406
- | Letting `convergence.maxRounds` default to 2 for `requirements-discovery` | Resolve effective default to `1` for discovery and record in convergence state artifact |
408
+ | Letting `convergence.maxRounds` default to 2 for `requirements-discovery` | Resolve effective default to `1` for discovery and put it in the grouped input |
407
409
  | Issuing serial Read calls in Phase 1 | The intake files are independent — issue all Read calls in a single message (parallel) |
408
410
  | 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 |
409
411
  | 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 |
410
- | 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)" |
411
- | 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" |
412
+ | Re-sending a finding absent from the persisted round plan | Dispatch exactly the engine-returned `findingIds`; see [convergence](./convergence.md) "Re-verification Dispatch" |
413
+ | Aggregating a `timeout`/`error` reverify dispatch as `DISAGREE` | Put the terminal outcome in round results; `apply-round` records `verification-error`. See [convergence](./convergence.md) "Worker failure handling in reverify" |
412
414
  | Skipping `--substitute-data` in the Phase 7 collector run | Always pass the flag — see [report-writer](./report-writer.md) "Phase 7 token-usage collector" |
@@ -37,8 +37,9 @@ The prompt MUST include, in this order at the top:
37
37
  3. `**Result Path:** runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` — canonical JSON SSOT. The renderer produces the sibling `.md` automatically.
38
38
  4. `**Worker Result Path:** runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md` — mandatory validator-checked worker-results audit file
39
39
  5. `Assigned worker prompt history path: <absolute-path>`
40
- 6. The three BLOCKING dispatch anchor headers the lead MUST inject (per [team-contract](./team-contract.md) headers #5 / #7 / #8 — the worker cannot synthesize the two error paths and must not dead-end when they are absent):
41
- - `**Worker Preamble Path:** <absolute-path>` — the canonical Required Reading + Error Reporting SSOT at `~/.okstra/templates/worker-prompt-preamble.md`; the worker Reads it end-to-end before producing output.
40
+ 6. The four BLOCKING dispatch anchor headers generated from the report-writer audience (the worker cannot synthesize any of these paths):
41
+ - `**Worker Preamble Path:** <absolute-path>` — selects `templates/report-writer-prompt-preamble.md`.
42
+ - `**Worker Error Contract Path:** <absolute-path>` — selects `templates/worker-error-contract.md`.
42
43
  - `**Errors log path:** <absolute-path>` — run-level errors JSONL (`logs/errors-<task-type>-<seq>.jsonl`).
43
44
  - `**Errors sidecar path:** <absolute-path>` — this worker's per-run sidecar JSON (`worker-results/report-writer-worker-errors-<task-type>-<seq>.json`).
44
45
  7. `**Model:** Report writer worker, <modelExecutionValue>` (resolved per Phase 5.5 anchor-header rules)
@@ -65,13 +65,15 @@ Only workers selected from `recommendedWorkers` in `task-manifest.json` and `res
65
65
 
66
66
  Every worker prompt MUST start with the anchor headers rendered by `okstra_ctl.worker_prompt_headers.worker_prompt_headers()` (the generating SSOT — never hand-author or reorder them). Their meaning and extraction rules for workers are documented in the worker preamble §"Anchor headers". Phase-specific extra headers (implementation worktree, final-verification target snapshot, improvement-discovery grilling log) are listed there too.
67
67
 
68
- The `**Coding preflight pack:**` anchor is emitted only when `taskType == implementation`. A pane display role named `verifier` during `final-verification` is still an initial analysis worker; it neither receives implementation conventions nor becomes a Phase 5.5 reverify dispatch. Reverify is identified by the `-reverify-r<N>-` prompt/result path contract in [convergence](./convergence.md).
68
+ The `**Coding preflight pack:**` anchor is emitted only for the `implementation-executor` and `implementation-verifier` audiences. An implementation report-writer never receives it. A pane display role named `verifier` during `final-verification` is still an initial analysis worker; it neither receives implementation conventions nor becomes a Phase 5.5 reverify dispatch. Reverify is identified by the `-reverify-r<N>-` prompt/result path contract in [convergence](./convergence.md).
69
+
70
+ For `improvement-discovery`, `worker_prompt_headers()` resolves and validates the Phase 1.5 grilling log before dispatch and emits its absolute path as `**Phase 1.5 Grilling Log:**`. The lead does not hand-author or guess that path.
69
71
 
70
72
  The body must include: role name, task type, task key, assigned model, output contract, evidence handling rules, and `analysis-packet.md` as the single primary analysis input. For `final-verification`, the compact target anchors and that packet path are sufficient; do not copy the full diff stat, source/fallback path inventory, profile sections, report-output sections, or lead self-review into the initial prompt.
71
73
 
72
- A `final-verification` prompt may contain one optional `## Run-specific directive` section for a true run delta. It is limited to 40 nonblank lines; the nonblank prompt body excluding common/target anchors is limited to 96 lines. Larger shared material belongs in the instruction set and analysis packet. Before initial dispatch, the selected adapter validates each prompt and requires byte-identical normalized analysis bodies after removing only worker/model/role and worker-specific artifact-path metadata.
74
+ A `final-verification` prompt may contain exactly one optional `## Run-specific directive` section for a true run delta. It is limited to 40 nonblank lines; the nonblank prompt body excluding common/target anchors is limited to 96 lines. Larger shared material belongs in the instruction set and analysis packet. Before initial dispatch, the selected adapter validates each prompt and requires byte-identical normalized analysis bodies after removing only worker/model/role and worker-specific artifact-path metadata.
73
75
 
74
- The Phase 7 run validator enforces the same rule against the persisted prompt paths recorded by the run manifest and team-state. `validators/validate-run.py` calls `_validate_final_verification_initial_prompts`, which delegates parsing and normalized equality to `okstra_ctl.worker_prompt_contract`; it ignores the report writer and `-reverify-r<N>-` prompts because they belong to different phases of the protocol. A dispatch-time bypass therefore remains a run-level contract violation.
76
+ The Phase 7 run validator enforces the same cross-task rule against the persisted prompt paths recorded by the run manifest, dispatch records, and team-state. Both dispatch adapters and `validators/validate-run.py` call `okstra_ctl.worker_prompt_contract.validate_initial_prompt_records()`. The validator resolves each record through `PromptPlan`, applies audience-specific anchors, compares every equality group with at least two prompts, validates report-writer common anchors without adding it to an analysis group, and ignores `-reverify-r<N>-` prompts because they belong to the lightweight convergence protocol. A dispatch-time bypass therefore remains a run-level contract violation.
75
77
 
76
78
  When a worker reads any project-relative path from the prompt, it MUST resolve it against `Project Root` (e.g. `<Project Root>/<Result Path>`) — never use bare relative paths that depend on cwd.
77
79
 
@@ -81,13 +83,13 @@ Persist the exact worker prompt before dispatch per Operating Rule 6; never use
81
83
 
82
84
  Send byte-identical dispatch prompts to every analysis worker per the "Dispatch-prompt invariant" (Role Definitions). Specialization lives in Section 6 of the worker output, not in the dispatch prompt body.
83
85
 
84
- ### Required Reading + Error Reporting via Worker Preamble (SSOT)
86
+ ### Audience preambles + shared error contract (SSOT)
85
87
 
86
- The lead does NOT inline `[Required reading]` or `[Error reporting]` blocks into worker prompts. Both contracts live in a single canonical file at `~/.okstra/templates/worker-prompt-preamble.md` (source: `templates/worker-prompt-preamble.md`). The lead injects the path via the `**Worker Preamble Path:**` anchor header and each worker Reads that file end-to-end before producing output.
88
+ The lead does not inline reading or error blocks. It resolves `PromptPlan.audience` and injects exactly one `**Worker Preamble Path:**`: analysis `templates/worker-prompt-preamble.md`, implementation executor/verifier → `templates/implementation-worker-preamble.md`, report-writer → `templates/report-writer-prompt-preamble.md`. Every initial worker also receives `**Worker Error Contract Path:**` pointing to `templates/worker-error-contract.md`, the sole owner of error schema and write rules. Reverify prompts use neither preamble.
87
89
 
88
90
  What the lead MUST still do per dispatch:
89
91
  - Inject the input file enumeration into the dispatch prompt body via an `## Inputs` section (or any heading the recipient agent expects), listing the actual project-relative primary inputs derived from the run's `instruction-set/`. For `final-verification` analysis workers, list only `analysis-packet.md` as the primary input; source files are reached on demand through that packet. Other phases may list source/fallback paths when useful. The preamble describes the rules; the lead provides the specific paths for THIS run.
90
- - Inject the absolute `**Errors log path:**` and `**Errors sidecar path:**` anchor headers — workers cannot synthesize these paths.
92
+ - Inject `**Worker Error Contract Path:**` plus the absolute `**Errors log path:**` and `**Errors sidecar path:**` headers — workers cannot synthesize these paths.
91
93
  - Omit the preamble pointer for reverify dispatches (Phase 5.5 lightweight mode) — see [convergence](./convergence.md) "Reverify prompt: required-reading suppression".
92
94
 
93
95
  Audience-scoped file enumeration (performance optimization — mandatory):
@@ -149,7 +151,7 @@ After each worker subagent returns (regardless of role), Lead MUST verify the ca
149
151
 
150
152
  ## Worker Output Contract
151
153
 
152
- The canonical worker output contract — result frontmatter schema, section ordering (1–5 + optional Section 6), item-ID rules, ticket tagging, and the worker result header standard — lives in the worker preamble (`templates/worker-prompt-preamble.md`), the file every worker reads before producing output.
154
+ The canonical analysis-worker output contract — frontmatter, sections 1–5 plus optional Section 6, item IDs, and ticket tagging — lives in `templates/worker-prompt-preamble.md`. Implementation output behavior remains in its executor/verifier sidecars; report authoring remains in the report-writer preamble and Phase 6 contract.
153
155
 
154
156
  Lead-facing duties that stay here:
155
157
 
@@ -223,8 +225,8 @@ without proceeding.
223
225
  2. Re-verification workers follow a constrained response format (verdict + brief explanation).
224
226
  3. Workers cannot vote on their own findings (only verify other workers’ work).
225
227
  4. The `report writer worker` does not participate in re-verification voting. It is responsible only for generating the final report.
226
- 5. Division of labor: the lead performs **finding-to-finding matching** (deciding which worker-A finding maps to which worker-B finding for cross-review) and mediates the round protocol; **workers cast the AGREE / DISAGREE / SUPPLEMENT votes** that determine consensus. The lead does NOT vote on substance and does NOT collapse worker disagreements by fiat disagreements flow into the `contested` / `partial-consensus` classifications defined in `prompts/lead/convergence.md`.
227
- 6. Batch processing is performed with one spawn per worker per round (not one spawn per finding).
228
+ 5. Division of labor: the lead performs **finding-to-finding matching** (semantic similarity plus ticket-set equality); workers cast AGREE / DISAGREE / SUPPLEMENT verdicts; `ConvergenceEngine` owns queue membership, classifications, round arithmetic, pruning, skip reasons, and final counts. The lead does not vote or hand-edit engine state.
229
+ 6. Batch processing is performed with one fresh spawn per persisted `plan-round` worker row (not one spawn per finding). The selected adapter transports that exact batch and terminal result without changing membership.
228
230
  7. These rules do not apply if Convergence is disabled.
229
231
 
230
232
  ## Re-verification Terminal Statuses
@@ -75,7 +75,7 @@ profile document.
75
75
  - **Worker-side item IDs (free-form but unique within the worker).** Every row item in sections 1–5 (and any optional section 6) of an analysis worker's output MUST carry an item ID that is unique within that one worker's result file. The ID convention is the worker's choice — `F-001` / `F-002` per the suggested schema, `1.1` / `1.2` / `1.3` as Codex tends to use, or any other shape — but it MUST appear as the leading column of the row (for table-form items) or as a `[<ID>]` prefix (for bullet/numbered items). Workers that emit findings without IDs make cross-worker reconciliation impossible.
76
76
  - **Lead-side ID assignment + source preservation.** When the lead (or `report-writer-worker`) synthesises `## 6.1 Consensus` / `## 6.2 Differences` / `## 2.1 Primary Evidence` rows from worker outputs, the lead assigns a fresh `C-NNN` / `D-NNN` / `E-NNN` row ID. The `Source items` column (or, where the template still calls it `Supporting workers` / `Workers (position)` / `Source`, that same column) MUST list every contributing worker:item pair (e.g. `claude:F-001, codex:1.1, antigravity:F-3`) so a reviewer can trace the synthesised row back to each worker's original wording without re-reading every worker-results file. Bare worker names without item IDs (e.g. `claude, codex, antigravity`) are deprecated for these tables; the validator does not yet fail on them but the readability pass treats it as a contract violation.
77
77
  - **Why this matters.** A real run had `claude=F-1..F-11`, `codex=1.1..1.8`, `antigravity=F-3..F-9` — three incompatible ID schemes. When the lead synthesised `C-1..C-8`, the link from `C-3` back to "which sentence in which worker file" was lost. Source-item preservation restores that link without forcing every worker to adopt a single ID prefix, which would over-constrain worker output style.
78
- - Audit sidecar (shared): Reading Confirmation placement and audit-sidecar rules are canonical in the worker preamble (`templates/worker-prompt-preamble.md` §"Reading rules"). Profiles do not restate them; the main worker-results body starts at section 1.
78
+ - Audit sidecar (shared): Reading Confirmation placement follows the audience-selected preamble named by `**Worker Preamble Path:**`. Profiles do not restate it; the main worker-results body starts at section 1.
79
79
 
80
80
  - Markdown authoring (shared — applies to markdown documents not already governed by an okstra template/schema):
81
81
  - ad-hoc markdown documents should begin with an `Index` section. Template-governed artifacts such as final-reports, worker-results, and briefs follow their own schema first.
@@ -14,7 +14,7 @@
14
14
  - read `Related Task Graph` before forming root-cause candidates. `depends-on`, `blocks` / `blocked-by`, parent/child, follow-up, and split edges define upstream/downstream boundaries for the symptom: identify whether this task's failure is caused by, blocks, or merely relates to another task before merging causes.
15
15
  - any `intent-inference` augmentation that re-characterises the symptom (e.g. classifying a vague reporter phrase like "it sometimes doesn't work" as "intermittent failure on a specific code path") is a **hypothesis**, not a confirmed symptom. If `[CONFIRMED …]` appears on the matching `intent-check:` row, treat that confirmation as the symptom. Otherwise follow the precondition's `skipped` branch above and keep the inference labelled as a hypothesis in the root-cause analysis.
16
16
  - `conversion-block:` rows mean the brief could not map a reporter statement to project vocabulary; never invent the missing mapping in this phase.
17
- - Diagnosis loop:
17
+ - Worker diagnosis procedure:
18
18
  - **Symptom lock:** state the reporter's symptom verbatim, then translate it into one observable failure condition. If no observable condition can be derived from the brief, record that gap as the first blocker instead of guessing.
19
19
  - **Reproduction status:** classify the run as `reproduced`, `not-reproduced`, or `blocked-before-repro`. Cite the command/log/file evidence used. If no command can be run safely in this phase, explain the read-only evidence path and the exact material needed next.
20
20
  - **Falsifiable cause candidates:** every root-cause candidate must include supporting evidence, the strongest falsifying evidence checked, confidence, and the next diagnostic action that would disprove it. A candidate that cannot be falsified is too vague for this phase.
@@ -13,6 +13,11 @@
13
13
  - Apply the shared reporter-confirmation precondition exactly as written. In this phase, unresolved `intent-check:` / `conversion-block:` rows carry `Blocks=approval`, so the approval frontmatter stays `approved: false` until they are resolved.
14
14
  - never plan around an unconfirmed `intent-inference` augmentation as if it were a settled requirement. Treat the inference as settled ONLY when a `[CONFIRMED …]` marker sits on the matching `intent-check:` row after the precondition runs; absent the marker it stays a `Blocks=approval` clarification item per the precondition's `skipped` branch.
15
15
  - `conversion-block:` rows are handled by the precondition; planning around an untranslated reporter phrase is forbidden until it is resolved.
16
+ - Worker planning procedure:
17
+ - identify requirement gaps and affected interfaces with file:line evidence, resolving codebase-answerable ambiguity before returning findings
18
+ - compare at least two feasible options unless the brief carries a confirmed decision; record concrete trade-offs and current-pattern evidence for every option
19
+ - propose stages with real dependency edges, validation signals, rollback order, and change-locality evidence; do not serialize independent work
20
+ - surface migration, deployment, cross-project, and approval risks without drafting final-report headings or schema rows
16
21
  - Pre-planning context exploration (mandatory before option drafting):
17
22
  - read the task brief, related-task briefs, and any cited spec / design doc end-to-end
18
23
  - inspect the current state of every file the task names (or the closest matching files if names are stale) — record current responsibilities, public interfaces, and known coupling points
@@ -21,8 +21,13 @@
21
21
  - per-candidate evidence (path:line) and scope mapping
22
22
  - per-candidate severity / effort / recommended-next-phase
23
23
  - convergence classification (full / partial / contested / worker-unique) across workers
24
+ - Worker candidate procedure:
25
+ - inspect every resolved priority lens inside the resolved scan scope; the assigned primary lens changes only the first pass, never total coverage
26
+ - for the primary pass, return an evidence-backed candidate or a no-candidate rationale citing the highest-signal path:line inspected
27
+ - for every candidate, record lens, scope, severity, effort, recommended next phase, evidence, and a worker-local source item ID
28
+ - identify overlap with other findings or linked tasks as duplicate, broader/narrower, conflicting, blocked-by, or follow-up instead of silently merging it
24
29
  - Worker diversity rule:
25
- - every analyser inspects every priority lens, but each starts with a different primary pass. Order lenses exactly as they appear in the brief; `claude` starts at lens 1, `codex` at lens 2, and `antigravity` at lens 3, wrapping around when fewer lenses are present.
30
+ - every analyser inspects every resolved priority lens. The Phase 1.5 grilling log assigns only the first pass: enumerate selected analyser worker instances in `requiredWorkerRoles` order and rotate them over resolved priority lenses in log order. Provider/model names never determine the position.
26
31
  - each worker, before broadening to the remaining lenses, must do one of: (a) produce at least one candidate from its primary pass, or (b) record a no-candidate rationale citing the highest-signal path:line evidence it inspected.
27
32
  - two workers' candidates are the same candidate only when they cite the same underlying design/code problem and the same remediation direction. Shared evidence paths alone are not enough to merge; keep distinct failure modes distinct.
28
33
  - when a candidate from one worker overlaps another worker's evidence, convergence must classify the relationship as one of: duplicate, broader/narrower, or conflicting. Do not collapse contested candidates just to meet the candidate cap.
@@ -33,10 +38,15 @@
33
38
  - For each open question Lead asks ONE `AskUserQuestion` with a `(Recommended)` answer drawn from a codebase-first inspection. Budget: at most 12 questions in this phase.
34
39
  - Stop conditions (OR): all questions resolved / budget exhausted / user signals proceed.
35
40
  - Lead persists the round at `<RUN_DIR>/state/phase-1.5-grilling.md` with one section per question (question / recommended / user answer) and a closing `Resolved scope` / `Resolved lenses` block. Worker prompts use this resolved block as the authoritative scope and lens definition.
41
+ - The same log includes `## Primary Pass Assignments` with a `Worker ID | Primary lens` table. It contains every selected analyser exactly once in `requiredWorkerRoles` order; the lead derives it from the resolved roster and lenses rather than provider catalog order.
36
42
  - After writing the log and before Phase 4 dispatch, the lead injects its **absolute path** into every analyser prompt as the `**Phase 1.5 Grilling Log:** <absolute-path>` anchor header (see `templates/worker-prompt-preamble.md` §"Anchor headers"). This is the improvement-discovery counterpart to the `**Worktree:**` / `**Verification …:**` anchors that implementation / final-verification inject: workers read the log from this explicit path rather than re-deriving `<RUN_DIR>`. The path is byte-identical across all analysers, so it does not break the dispatch-prompt invariant.
37
43
  - Decision-tree walk (bounded):
38
44
  - When candidates branch on a structural question (e.g. "is module X meant to own this responsibility?"), resolve via `Read` / `Grep` first. Only escalate to the user inside the Phase 1.5 budget.
39
45
  - Expected output emphasis:
46
+ - evidence-backed candidate or explicit no-candidate result for every resolved lens
47
+ - worker-local candidate IDs that convergence can trace back to their source worker
48
+ - uncertainty and overlap relationships kept explicit for downstream consensus classification
49
+ - Report assembly instructions:
40
50
  - the `## 5.9 Improvement Candidates` table populated with rows that obey the 10-column schema from `validators/validate_improvement_report.py` (Cand ID `I-NNN`, Lens from whitelist, Title, Scope ⊆ scan-scope, Severity, Effort, Consensus, Source workers `<worker>:<id>` from {claude, codex, antigravity}, Recommended next-phase ∈ {requirements-discovery, implementation-planning, error-analysis}, Evidence as path:line list)
41
51
  - `Consensus` cells in `## 5.9 Improvement Candidates` use the table enum exactly: `full`, `partial`, `contested`, `worker-unique`. Map convergence's `full-consensus` / `partial-consensus` labels to `full` / `partial` before writing the table.
42
52
  - `## 7. Final Verdict` Verdict Token ∈ {`candidates-ready`, `no-candidates`, `blocked`}; Direction `routing`; Next Step "ask the user to select K candidates (see the ## 5.9 table)"
@@ -14,6 +14,11 @@
14
14
  - before deciding whether to fan out, read `Related Task Graph`. Use `depends-on`, `blocks` / `blocked-by`, parent/child, follow-up, and split edges as seed ordering constraints. Do not flatten a directed graph into unrelated tasks.
15
15
  - `intent-inference` augmentations whose paired `intent-check:` row carries `[CONFIRMED …]` are treated as **confirmed**; trust the confirmation text in `## Reporter Confirmations` over the original inference if they differ. Unconfirmed `intent-inference` rows under `reporter-confirmations: skipped` follow the precondition's `skipped` branch above.
16
16
  - `conversion-block:` rows are explicit "translation failed" signals — never attempt to resolve them by inference here; the precondition above already handled them.
17
+ - Worker discovery procedure:
18
+ - classify the request and cite the evidence that determines both its work category and safest next phase
19
+ - identify independently startable decomposition candidates without publishing or rendering fan-out artifacts; preserve every directed dependency from `Related Task Graph`
20
+ - resolve codebase-answerable ambiguity by inspection and record file:line evidence; return only human-owned decisions as clarification candidates with the evidence already checked
21
+ - state the reporter's rejection criteria, missing routing inputs, and the evidence boundary behind each recommendation
17
22
  - Primary focus areas:
18
23
  - classify the work as bugfix, feature, improvement, refactor, or ops
19
24
  - determine whether `error-analysis` or `implementation-planning` is the next safe step. Direct `implementation` handoff is never a valid routing target — implementation requires an approved `implementation-planning` report
@@ -49,7 +54,9 @@
49
54
  - evidence-backed routing decision
50
55
  - uncertainty boundaries and missing inputs
51
56
  - next recommended phase and safe resume guidance
52
- - canonical-term resolution for every `terminology:*` brief item, written as a one-line `<term> = <definition>` line in a new `Domain Alignment` subsection of the final report; alongside each, propose whether `<PROJECT_ROOT>/.okstra/glossary.md` should be updated (proposal only — actual writes happen via `okstra-brief-gen` Step 4.5 on a subsequent run)
57
+ - canonical-term resolution for every `terminology:*` brief item as `<term> = <definition>`, plus whether `<PROJECT_ROOT>/.okstra/glossary.md` should be updated
58
+ - Report assembly instructions:
59
+ - write canonical-term resolutions in a new `Domain Alignment` subsection of the final report; actual glossary writes happen via `okstra-brief-gen` Step 4.5 on a subsequent run
53
60
  - Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
54
61
  - if any blocking input is missing at the time of writing the final report, populate `## 1. Clarification Items` in `final-report-template.md` (a single unified table; `Blocks=next-phase` for items the next run cannot start without)
55
62
  - prefer concrete questions whose answers map directly to a routing decision (`bugfix` vs `feature`, `error-analysis` vs `implementation-planning`, etc.). State each option in plain language with one sentence describing what choosing it would mean for the next phase.
@@ -24,29 +24,23 @@ PROFILE_SECTIONS = (
24
24
  "Primary focus areas",
25
25
  "Expected output emphasis",
26
26
  "Non-goals",
27
- "Clarification request policy",
28
27
  )
29
- PROFILE_SECTIONS_BY_TASK_TYPE = {
28
+ WORKER_PROFILE_SECTIONS_BY_TASK_TYPE = {
30
29
  "requirements-discovery": (
31
- "Fan-out",
32
- "Decision-tree walk",
30
+ "Worker discovery procedure",
33
31
  ),
34
32
  "error-analysis": (
35
- "Diagnosis loop",
33
+ "Worker diagnosis procedure",
36
34
  ),
37
35
  "implementation-planning": (
38
- "Section heading contract",
39
- "Required deliverable shape",
40
- "No-placeholder rule",
41
- "Self-review pass before finalising the report",
36
+ "Worker planning procedure",
37
+ "Design principles applied when scoring options",
42
38
  ),
43
39
  "final-verification": (
44
40
  "Worker verification procedure",
45
41
  ),
46
42
  "improvement-discovery": (
47
- "Phase 1.5 — Lead reflect-back grilling",
48
- "Worker diversity rule",
49
- "Decision-tree walk",
43
+ "Worker candidate procedure",
50
44
  ),
51
45
  }
52
46
  CLARIFICATION_SECTIONS = (
@@ -156,7 +150,7 @@ def _brief_block(brief_text: str) -> list[str]:
156
150
  def _profile_block(task_type: str, profile_text: str) -> list[str]:
157
151
  section_names = (
158
152
  PROFILE_SECTIONS
159
- + PROFILE_SECTIONS_BY_TASK_TYPE.get(task_type, ())
153
+ + WORKER_PROFILE_SECTIONS_BY_TASK_TYPE.get(task_type, ())
160
154
  )
161
155
  profile_sections = _extract_sections(profile_text, section_names)
162
156
  profile_bullets = _extract_bullet_sections(profile_text, section_names)
@@ -27,7 +27,18 @@ from .path_hints import hydrate_active_run_context
27
27
  from .wrapper_status import status_path_for_prompt
28
28
  from .paths import task_dir
29
29
  from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
30
- from .worker_prompt_contract import validate_final_verification_prompt_paths
30
+ from .worker_prompt_contract import PromptRecord, validate_initial_prompt_records
31
+ from .worker_prompt_body import (
32
+ analysis_input_lines as _analysis_input_lines,
33
+ analysis_prompt_body as _analysis_prompt_body,
34
+ existing_input_lines as _existing_input_lines,
35
+ instruction_path as _instruction_path,
36
+ mcp_pointer_line as _mcp_pointer_line,
37
+ )
38
+ from .worker_prompt_policy import (
39
+ PromptPlan,
40
+ resolve_prompt_plan_for_manifest,
41
+ )
31
42
 
32
43
 
33
44
  SUPPORTED_CLI_WORKERS = {
@@ -39,10 +50,6 @@ BACKEND_MIXED = "mixed"
39
50
  MAX_WORKER_ATTEMPTS = 2
40
51
  REPORT_WRITER_WORKER_ID = "report-writer"
41
52
  REPORT_LANGUAGE_VALUES = {"en", "ko", "auto"}
42
- ANALYSIS_WORKER_LABELS = {
43
- "codex": "Codex worker",
44
- "antigravity": "Antigravity worker",
45
- }
46
53
 
47
54
 
48
55
  class DispatchError(Exception):
@@ -183,7 +190,7 @@ def build_dispatch_plan(
183
190
  )
184
191
  for worker_id in selected_workers
185
192
  )
186
- _validate_initial_final_verification_prompts(manifest, workers)
193
+ _validate_initial_prompts(manifest, workers)
187
194
  return DispatchPlan(
188
195
  project_root=project_root,
189
196
  workspace_root=workspace_root,
@@ -195,21 +202,18 @@ def build_dispatch_plan(
195
202
  )
196
203
 
197
204
 
198
- def _validate_initial_final_verification_prompts(
205
+ def _validate_initial_prompts(
199
206
  manifest: Mapping[str, Any],
200
207
  workers: Sequence[WorkerDispatch],
201
208
  ) -> None:
202
- if manifest.get("taskType") != "final-verification":
203
- return
204
- prompt_paths = {
205
- worker.worker_id: worker.prompt_path
209
+ records = [
210
+ PromptRecord(worker.worker_id, "initial", worker.prompt_path)
206
211
  for worker in workers
207
- if worker.worker_id != REPORT_WRITER_WORKER_ID
208
- and "-reverify-r" not in worker.prompt_path.name
209
- }
210
- errors = validate_final_verification_prompt_paths(prompt_paths)
212
+ ]
213
+ errors = validate_initial_prompt_records(manifest=manifest, records=records)
211
214
  if errors:
212
- raise DispatchError("final-verification prompt contract: " + "; ".join(errors))
215
+ task_type = _require_string(manifest, "taskType")
216
+ raise DispatchError(f"{task_type} prompt contract: " + "; ".join(errors))
213
217
 
214
218
 
215
219
  def dispatch_plan(plan: DispatchPlan) -> int:
@@ -460,6 +464,7 @@ def _render_missing_analysis_worker_prompt(
460
464
  prompt_rel = _worker_prompt_path(manifest, worker_id)
461
465
  model = _require_string(worker_state, "modelExecutionValue")
462
466
  role = _analysis_pane_role(manifest, active_context, worker_id)
467
+ plan = _initial_prompt_plan(manifest, worker_id)
463
468
  lines = _base_prompt_headers(
464
469
  project_root=project_root,
465
470
  manifest=manifest,
@@ -469,7 +474,16 @@ def _render_missing_analysis_worker_prompt(
469
474
  result_rel=result_rel,
470
475
  )
471
476
  lines.extend(_worktree_headers(manifest, active_context, role))
472
- lines.extend(_analysis_prompt_body(manifest, active_context, worker_id, model, role))
477
+ lines.extend(
478
+ _analysis_prompt_body(
479
+ manifest,
480
+ active_context,
481
+ worker_id,
482
+ model,
483
+ role,
484
+ plan,
485
+ )
486
+ )
473
487
  executor_tail = _implementation_executor_tail(
474
488
  manifest,
475
489
  active_context,
@@ -614,43 +628,6 @@ def _build_report_writer_dispatch(
614
628
  )
615
629
 
616
630
 
617
- def _analysis_prompt_body(
618
- manifest: Mapping[str, Any],
619
- active_context: Mapping[str, Any],
620
- worker_id: str,
621
- model: str,
622
- role: str,
623
- ) -> list[str]:
624
- label = ANALYSIS_WORKER_LABELS[worker_id]
625
- return [
626
- f"**Model:** {label}, {model}",
627
- f"**Pane role:** {role}",
628
- "",
629
- f"# {label} Dispatch",
630
- "",
631
- "## Role",
632
- (
633
- f"You are the {label} for okstra cross-verification. "
634
- "Produce an independent worker result."
635
- ),
636
- "",
637
- "## Task",
638
- f"- Task key: `{_require_string(manifest, 'taskKey')}`",
639
- f"- Task type: `{_require_string(manifest, 'taskType')}`",
640
- "",
641
- "## Inputs",
642
- *_analysis_input_lines(manifest, active_context),
643
- "",
644
- _mcp_pointer_line(),
645
- "",
646
- "## Output Contract",
647
- "- Read the Worker Preamble Path end-to-end before analysis.",
648
- "- Write the worker result to Result Path and no other canonical result path.",
649
- "- Write the audit sidecar and any tool-failure entries as the preamble requires.",
650
- "- Cite evidence with file paths and line numbers whenever you make a claim.",
651
- ]
652
-
653
-
654
631
  def _report_writer_prompt_body(
655
632
  manifest: Mapping[str, Any],
656
633
  active_context: Mapping[str, Any],
@@ -688,13 +665,6 @@ def _report_writer_prompt_body(
688
665
  ]
689
666
 
690
667
 
691
- def _mcp_pointer_line() -> str:
692
- return (
693
- '**MCP servers:** follow the task brief\'s "## Available MCP Servers" '
694
- "section (already in your Required reading)."
695
- )
696
-
697
-
698
668
  def _base_prompt_headers(
699
669
  *,
700
670
  project_root: Path,
@@ -710,6 +680,7 @@ def _base_prompt_headers(
710
680
  prompt_rel=prompt_rel,
711
681
  result_rel=result_rel,
712
682
  worker_id=worker_id,
683
+ dispatch_kind="initial",
713
684
  manifest=manifest,
714
685
  active_context=active_context,
715
686
  )
@@ -717,26 +688,18 @@ def _base_prompt_headers(
717
688
  raise DispatchError(str(exc)) from exc
718
689
 
719
690
 
720
- def _analysis_input_lines(
691
+ def _initial_prompt_plan(
721
692
  manifest: Mapping[str, Any],
722
- active_context: Mapping[str, Any],
723
- ) -> list[str]:
724
- if manifest.get("taskType") == "final-verification":
725
- packet_path = _instruction_path(
726
- manifest,
727
- active_context,
728
- "analysisPacketPath",
693
+ worker_id: str,
694
+ ) -> PromptPlan:
695
+ try:
696
+ return resolve_prompt_plan_for_manifest(
697
+ manifest=manifest,
698
+ worker_id=worker_id,
699
+ dispatch_kind="initial",
729
700
  )
730
- return _existing_input_lines((("Primary analysis packet", packet_path),))
731
- inputs = [
732
- ("Primary analysis packet", _instruction_path(manifest, active_context, "analysisPacketPath")),
733
- ("Task brief", _instruction_path(manifest, active_context, "taskBriefPath")),
734
- ("Analysis profile", _instruction_path(manifest, active_context, "analysisProfilePath")),
735
- ("Analysis material", _instruction_path(manifest, active_context, "analysisMaterialPath")),
736
- ("Reference expectations", _instruction_path(manifest, active_context, "referenceExpectationsPath")),
737
- ("Clarification response", _instruction_path(manifest, active_context, "clarificationResponsePath")),
738
- ]
739
- return _existing_input_lines(inputs)
701
+ except ValueError as exc:
702
+ raise DispatchError(str(exc)) from exc
740
703
 
741
704
 
742
705
  def _report_writer_input_lines(
@@ -759,11 +722,6 @@ def _report_writer_input_lines(
759
722
  return lines or ["- No report-writer inputs were recorded in active-run-context."]
760
723
 
761
724
 
762
- def _existing_input_lines(inputs: Sequence[tuple[str, str]]) -> list[str]:
763
- lines = [f"- {label}: `{path}`" for label, path in inputs if path]
764
- return lines or ["- No input paths were recorded in active-run-context."]
765
-
766
-
767
725
  def _analysis_worker_result_lines(team_state: Mapping[str, Any]) -> list[str]:
768
726
  lines = []
769
727
  workers = team_state.get("workers")
@@ -781,19 +739,6 @@ def _analysis_worker_result_lines(team_state: Mapping[str, Any]) -> list[str]:
781
739
  return lines
782
740
 
783
741
 
784
- def _instruction_path(
785
- manifest: Mapping[str, Any],
786
- active_context: Mapping[str, Any],
787
- key: str,
788
- ) -> str:
789
- instruction_set = active_context.get("instructionSet")
790
- if isinstance(instruction_set, Mapping):
791
- value = _string_value(instruction_set.get(key))
792
- if value:
793
- return value
794
- return _string_value(manifest.get(key))
795
-
796
-
797
742
  def _analysis_pane_role(
798
743
  manifest: Mapping[str, Any],
799
744
  active_context: Mapping[str, Any],