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
@@ -105,6 +105,8 @@ By contrast, `sessions/claude-resume-<task-type>-<seq>.sh` is an interruption-re
105
105
 
106
106
  The resolved run directory collects execution history. It divides its contents into type-specific subdirectories such as `manifests/`, `state/`, `prompts/`, `reports/`, `status/`, `sessions/`, and `worker-results/`, then distinguishes each run-level artifact and result file with a `-<task-type>-<seq>` suffix (a three-digit, zero-padded per-category counter, such as `001` or `002`).
107
107
  Worker prompt history is retained not under `/tmp`, but always as a canonical artifact under `prompts/` for the current run.
108
+
109
+ The persisted prompt's `**Worker Preamble Path:**` records the selected functional audience contract: analysis uses `templates/worker-prompt-preamble.md`, implementation executor/verifier uses `templates/implementation-worker-preamble.md`, and report writing uses `templates/report-writer-prompt-preamble.md`. Every initial prompt also persists `**Worker Error Contract Path:** templates/worker-error-contract.md`. Active-run and run-context runtime resources expose the same audience map plus the shared error-contract path, so dispatch replay and context-cost accounting do not infer an audience from a provider/model name.
108
110
  Unlike before, `analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, skill copies, and `final-report-template.md` are not duplicated for every run.
109
111
  These materials retain canonical copies in `instruction-set/` under the stable task root.
110
112
  For `final-verification`, `verification-target.md` is also stable-task instruction-set state. Prepare owns the snapshot and stores its project-relative path plus SHA-256 digest in task/run manifests and active-run context. Initial analysis prompts carry the compact worktree/scope/base/head/path/digest identity and read this sidecar on demand instead of copying its diff stat.
@@ -309,12 +311,23 @@ After `okstra` runs, Claude should follow this default sequence when reading the
309
311
  8. Consult `history/timeline.json` and previous run results if necessary.
310
312
  9. As `Claude lead`, organize roles according to the current run's worker roster (`Claude worker`, `Codex worker`, and `Report writer worker` by default; `Antigravity worker` only when explicitly included).
311
313
  10. Save each selected worker prompt under the current run's `prompts/` directory at the assigned worker prompt history path before dispatching the worker.
312
- Every selected final-verification initial analysis worker receives the same full-core semantic body. Dispatch and Phase 7 validate compactness and normalized equality; report-writer and `-reverify-r<N>-` prompts are separate audiences.
313
- 11. In convergence Round 0, classify matching semantic/ticket-set findings as full consensus and queue only single-worker findings. Reverify each queued finding through other workers only; lightweight reverify does not reload the original bundle.
314
+ Every selected initial analyser receives the same full-core semantic body for its task, including final-verification. Dispatch and Phase 7 validate the same normalized equality policy; implementation executor, report-writer, and `-reverify-r<N>-` prompts are separate audiences.
315
+ 11. In convergence Round 0, the lead groups findings by semantic meaning and ticket set, then writes `state/convergence-groups-<task-type>-<seq>.json`. The deterministic engine seeds the working queue and generates each roster-aware reverify plan; lightweight reverify reads only that persisted batch and its embedded evidence.
314
316
  12. Collect a result or terminal status for each required worker.
315
317
  13. Unless the brief requires a more specific format, write the final Markdown report using the `final-report-template.md` structure.
316
318
  14. Save the result directly to `reports/final-report-<task-type>-<seq>.md` for the current run and, if necessary, update `status/final-<task-type>-<seq>.status`, `manifests/run-manifest-<task-type>-<seq>.json`, `task-manifest.json`, and `task-index.md` to match the current state.
317
319
 
320
+ Convergence keeps its decision trail under the run's `state/` directory:
321
+
322
+ - `state/convergence-groups-<task-type>-<seq>.json` — lead-authored semantic groups and resolved analyser roster.
323
+ - `state/convergence-work-<task-type>-<seq>.json` — engine-owned queue, finding state, and round history.
324
+ - `state/convergence-round-<N>-plan-<task-type>-<seq>.json` — immutable roster-aware dispatch plan for one round.
325
+ - `state/convergence-round-<N>-results-<task-type>-<seq>.json` — adapter outcomes and parsed votes supplied to the reducer.
326
+ - `state/convergence-<task-type>-<seq>.json` — validated terminal schema v1.2 state consumed by reporting and Phase 7.
327
+ - `state/migrations/` — byte-for-byte archives of invalid or partial legacy state replaced during an explicit Round 0 restart.
328
+
329
+ A valid terminal legacy final is reused unchanged, and a matching valid working state resumes. Invalid new-engine working state fails closed unless seeding receives `--restart-from-round0`; an existing final can be replaced only when its migration archive still matches its original bytes.
330
+
318
331
  Recommended worker status values:
319
332
 
320
333
  - `completed`
@@ -304,15 +304,31 @@ The standard `okstra` workflow applies the following team contract consistently
304
304
  - Every attempted worker (`completed`, `timeout`, `error`) must have an assigned worker prompt history file under the current run's `prompts/` directory.
305
305
  - An unnamed generic parallel worker is not accepted as a substitute for a required role.
306
306
 
307
- ### Final-verification prompt and re-verification boundaries
307
+ ### Cross-task worker prompt policy and final-verification boundaries
308
+
309
+ `PromptPlan` is the generating SSOT for functional prompt audience, equality group, packet-only input, coding-preflight eligibility, required headers, and size limits. It resolves only from task type, worker ID, the manifest's executor worker ID, and dispatch kind; provider and model identity never assign scope. The resolved analyser order used for improvement primary-pass rotation is a separate roster operation and is not a `PromptPlan` input.
310
+
311
+ The same analysis-core rule applies to `requirements-discovery`, `error-analysis`, `implementation-planning`, `improvement-discovery`, and `final-verification`: every selected initial analyser receives the same normalized semantic body and independently covers the whole common scope. In `implementation`, the implementation executor is excluded from verifier equality; all selected implementation verifiers form their own equality group. If the roster contains one verifier, individual header rules still apply, while one verifier makes normalized equality a deliberate no-op. Report-writer and reverify prompts are excluded from analysis equality groups because they author or adjudicate existing findings instead of producing an initial independent analysis.
312
+
313
+ The policy selects one audience preamble: analysis uses `templates/worker-prompt-preamble.md`; executor and verifier use `templates/implementation-worker-preamble.md`; report writing uses `templates/report-writer-prompt-preamble.md`. Every initial audience also reads the shared `templates/worker-error-contract.md`. Only implementation executor/verifier prompts receive `**Coding preflight pack:**`; a report writer never loads implementation coding instructions.
314
+
315
+ `dispatch_core.py`, `codex_dispatch.py`, and Phase 7 `validators/validate-run.py` all call `worker_prompt_contract.validate_initial_prompt_records()` so launch-time and persisted-artifact validation use the same `PromptPlan`. Report-writer prompts still receive individual common-anchor validation, while `-reverify-r<N>-` prompts keep their separate lightweight convergence contract.
308
316
 
309
317
  Final-verification uses one shared full-core responsibility for every selected initial analysis worker. Provider/model diversity supplies independent observations of the same requirements; it does not split acceptance criteria into disjoint worker scopes. `analysis-packet.md` carries the worker-facing verification procedure, while report-only deliverable and self-review guidance stays with the report writer.
310
318
 
311
319
  At final-verification entry, `prepare_task_bundle()` snapshots the resolved worktree, verification scope, base ref, head ref, implementation/stage report mapping, and diff stat into `instruction-set/verification-target.md`. It records the sidecar's project-relative path and `sha256:<hex>` digest in the task/run manifest and active-run context. Initial prompts carry only six compact target identity headers plus one `analysis-packet.md` path; workers open the target sidecar on demand. The coding-preflight anchor remains implementation-only, regardless of whether an initial pane is displayed with the role `verifier`.
312
320
 
313
- All selected initial prompt bodies must normalize to the same semantic content. One optional shared `## Run-specific directive` is bounded to 40 nonblank lines, and larger material is placed in the instruction set. `dispatch_core.py` and `codex_dispatch.py` enforce this before process launch; Phase 7 `validators/validate-run.py` applies the same `worker_prompt_contract.py` rules to the persisted initial prompt paths. Report-writer and `-reverify-r<N>-` prompts are outside this initial-prompt gate.
321
+ All selected initial prompt bodies must normalize to the same semantic content. One optional shared `## Run-specific directive` is bounded to 40 nonblank lines, and larger material is placed in the instruction set. Final-verification adds compact target identity and size limits to the common cross-task policy.
322
+
323
+ ### Deterministic convergence engine
324
+
325
+ `ConvergenceEngine` owns deterministic state transitions after Round 0 grouping: queue membership, roster-aware dispatch plans, vote reduction, classification, round history and limits, skip reasons, final state, and classification counts. The lead retains semantic grouping and evidence interpretation because those operations require judgment. Runtime adapters are transport-only: they dispatch the persisted batch and return structured terminal outcomes without recalculating engine state.
326
+
327
+ The lead writes the grouped input, then advances it through `okstra convergence seed`, `plan-round`, `apply-round`, `finalize`, and `validate`. For worker W, each generated dispatch excludes findings originating from W; resolved findings leave the queue permanently. Lightweight reverify receives only its current persisted batch and embedded evidence, not the original analysis packet, profile, brief, or instruction set. Terminal worker non-results become `verification-error`; the engine never fabricates a `DISAGREE` vote. The report writer does not vote and may consume only a validated terminal v1.2 final state.
328
+
329
+ Coverage critic and plan-body verification remain separate from finding convergence. The critic audits the integrated Round 0 analysis, while implementation-planning's plan-body gate validates the later report draft through its own `P-*` queue and state file. Neither path changes the engine's `F-*` queue.
314
330
 
315
- The convergence algorithm is unchanged: Round 0 immediately classifies findings with matching semantics and ticket sets from two or more workers as `full-consensus`; only single-worker findings enter the verification queue. For worker W, `items_for_W` excludes findings originating from W, and resolved findings are removed permanently. Lightweight reverify receives only that current batch and its embedded evidence, not the original analysis packet/profile/brief/instruction set. The report writer does not vote, and terminal worker non-results are recorded as `verification-error`, never fabricated as `DISAGREE`.
331
+ Seeding provides the migration boundary for in-progress runs. A valid terminal legacy final is reused unchanged. A matching valid working state resumes. A malformed or partial legacy state is archived byte-for-byte under `state/migrations/` before an explicit Round 0 restart; an invalid new-engine working state fails closed until `--restart-from-round0` is supplied. Replacement of an existing final is allowed only while its recorded archive still matches the original bytes.
316
332
 
317
333
  ## Stable task identity
318
334
 
package/docs/cli.md CHANGED
@@ -128,6 +128,7 @@ For standard values and phase-specific responsibilities, see [Task type](#--task
128
128
  - Verdict Token: `candidates-ready` / `no-candidates` / `blocked`.
129
129
  - Routing: there is no automatic spin-off. The user selects candidates and starts each under a new task ID with `requirements-discovery`, `implementation-planning`, or `error-analysis`.
130
130
  - Workers: claude + codex + antigravity + report-writer are all required.
131
+ - Primary-pass assignment: selected analyser instances are enumerated in `requiredWorkerRoles` order, then the lead rotates the primary pass across the resolved priority lenses. Provider/model names do not affect the order, and every analyser still covers every resolved lens after its primary pass.
131
132
  - Two bidirectional grilling points: an enhanced Step 4 in `okstra-brief-gen` with a budget of 8, and the lead's Phase 1.5 reflect-back with a budget of 12.
132
133
  - Validator: `validators/validate_improvement_report.py` enforces the 11-part contract for an `improvement-discovery` final report.
133
134
  - Because an `improvement-discovery` run is not in `PHASE_SEQUENCE`, the `--task-key` short form does not automatically populate `nextRecommendedPhase` for it.
@@ -634,6 +635,11 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
634
635
  | `okstra setup --project-id <id>` | Create or update `.okstra/project.json` in the current project |
635
636
  | `okstra check-project [--json]` | Verify that the current project is registered |
636
637
  | `okstra preflight [--runtime <name>] [--cwd <dir>] [--json]` | Single skill-preflight call combining `ensure-installed`, with silent reinstall when stale, and `check-project` into one JSON response. Step 0 of every project-scoped skill converges on this command |
638
+ | `okstra convergence seed --groups <path> --work-state <path> --final-state <path> --migration-dir <dir> [--restart-from-round0]` | Create, resume, reuse, or explicitly recover deterministic convergence state |
639
+ | `okstra convergence plan-round --work-state <path> --plan <path>` | Persist the next roster-aware dispatch plan without mutating working state |
640
+ | `okstra convergence apply-round --work-state <path> --plan <path> --results <path>` | Validate one complete structured result set and atomically reduce it into working state |
641
+ | `okstra convergence finalize --work-state <path> --output <path>` | Materialize the terminal schema v1.2 convergence state |
642
+ | `okstra convergence validate --state <path> --kind <working\|final>` | Validate replayable working state or a terminal final state |
637
643
  | `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
638
644
  | `okstra memory <add\|list\|search\|show\|archive>` | Manage global conversation memory in `~/.okstra/memory-book`, a user-home store separate from project `.okstra/` and the CLI basis of the `save this in okstra` natural-language skill |
639
645
  | `okstra manager <init\|discover-projects\|new\|task>` | Public CLI for grouping cross-project okstra tasks into manager-owned context. `new project`, `new task-group`, and `new task` create manager plans; `task assign`, `task note`, `task sync`, `task status`, and `task run` manage per-project assignments and snapshots. `new project --project-root` accepts only existing directories and performs setup-equivalent registration only if `.okstra/project.json` is absent. Public documentation uses the full `project-id:task-group:task-id` child task key; when child task IDs differ within the same manager task, select the exact child with `--child-task-id`. `task run` does not execute the child lead directly; it returns `prepared` launch metadata/event and a child launch-context packet as JSON |
@@ -661,6 +667,8 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
661
667
  | `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, and improvement-discovery. Downstream phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
662
668
  | `okstra token-usage ...` | Wrap the installed `okstra-token-usage.py` to collect and substitute run token usage. Session JSONL is incrementally scanned by default through a byte-cursor cache at `$OKSTRA_HOME/cache/token-usage/`; `--no-cache` bypasses the cache and forces a full rescan as an accuracy fallback |
663
669
 
670
+ `okstra convergence` is an internal admin CLI used by the lead protocol, not a user-facing skill. The former `okstra-convergence` skill remains obsolete; the installed `prompts/lead/convergence.md` contract tells the lead when to invoke these operations.
671
+
664
672
  > Every subcommand is wired to `PYTHONPATH` and `~/.okstra/lib/python` by the Python helper (`src/lib/python-helper.mjs`) spawned by `bin/okstra`. When invoking `python3 -m okstra_ctl.*` directly, you must configure `PYTHONPATH` yourself.
665
673
 
666
674
  #### `okstra design-prep`
@@ -268,9 +268,9 @@ Important modules:
268
268
  | `manager_store.py` | Manager-owned state mutation — project membership, task planning, assignment, directives, event append |
269
269
  | `manager_sync.py` | One-way child project `.okstra` snapshot reader; corrupt child state becomes row-level `error` so other children continue |
270
270
  | `manager_launch.py` | Child launch packet and manager child context renderer; records `prepared` launch metadata/events without changing project-local task state |
271
- | `dispatch_core.py` | Backend-neutral worker dispatch core — worker execution/collection logic shared by any lead runtime (Claude/Codex/external); gates selected final-verification initial prompts through the shared compact/equality contract before launch |
272
- | `codex_dispatch.py` | Codex lead CLI-worker dispatcher — the `okstra codex-dispatch` backend. Reads the run manifest to run the Codex-side supported worker subset, applies the same final-verification initial-prompt gate, and performs token-usage substitution, view render, follow-up, and validation |
273
- | `analysis_packet.py` | assembles the compact analysis-worker input packet for a task run; final-verification packets include worker verification procedure but omit report-only deliverable/self-review guidance |
271
+ | `dispatch_core.py` | Backend-neutral worker dispatch core — worker execution/collection logic shared by any lead runtime (Claude/Codex/external); gates selected initial prompts through the shared cross-task contract before launch |
272
+ | `codex_dispatch.py` | Codex lead CLI-worker dispatcher — the `okstra codex-dispatch` backend. Reads the run manifest to run the Codex-side supported worker subset, applies the same cross-task initial-prompt gate, and performs token-usage substitution, view render, follow-up, and validation |
273
+ | `analysis_packet.py` | assembles the compact analysis-worker input packet for a task run from worker-owned profile sections; report/lead procedure stays outside the packet |
274
274
  | `context_cost.py` | read-side context-cost estimator for a prepared okstra task bundle (the `okstra context-cost` backend) |
275
275
  | `schema_excerpt.py` | generates a task-type-scoped excerpt of the final-report schema — a schema reduction to inject into the worker/lead prompt |
276
276
  | `work_categories.py` | requirements-discovery work-category (domain) **SSOT** (`is_valid_category`) — the work-category allowlist is defined only here |
@@ -279,7 +279,12 @@ Important modules:
279
279
  | `lead_events.py` | structured JSONL events emitted by non-Claude lead runtimes |
280
280
  | `team_reconcile.py` | stale team-member reconciliation at run-end teardown |
281
281
  | `worker_prompt_headers.py` | shared rendering of phase-aware worker prompt anchors (`worker_prompt_headers`): coding-preflight only for implementation and compact target identity for final-verification |
282
- | `worker_prompt_contract.py` | deterministic compact initial final-verification prompt validator and normalized cross-worker equality SSOT, reused by dispatch adapters and the Phase 7 persisted-artifact validator |
282
+ | `worker_prompt_body.py` | provider-neutral initial analysis body/input renderer shared by Codex and external/team dispatch paths |
283
+ | `worker_prompt_policy.py` | `PromptPlan` generating SSOT — resolves functional audience, equality group, packet-only input, coding-preflight eligibility, required headers, and size limits without consulting provider/model identity |
284
+ | `worker_prompt_contract.py` | deterministic cross-task initial-prompt validator and normalized cross-worker equality SSOT, reused by dispatch adapters and the Phase 7 persisted-artifact validator |
285
+ | `convergence_engine.py` | pure `ConvergenceEngine` reducer — seeds Round 0 working state, plans roster-aware rounds, applies structured outcomes, finalizes schema v1.2, and validates replayable state without dispatch or filesystem ownership |
286
+ | `convergence_store.py`, `convergence_migration.py` | atomic JSON persistence plus legacy/new-engine seed decisions; valid terminal finals are reused, while invalid state requires byte-preserving archival before restart |
287
+ | `convergence.py` | `okstra convergence` internal CLI orchestration for `seed`, `plan-round`, `apply-round`, `finalize`, and `validate`; it composes the reducer, store, and migration policy without duplicating their decisions |
283
288
  | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar written by `okstra-wrapper-status.py` (the heartbeat writer) |
284
289
  | `task_target.py` | shared helper resolving `task-key → (task_root, project_root)` (`resolve_task_root`) |
285
290
  | `graphify_cmd.py` | scope wrapper for the okstra-graphify skill — `resolve_scope` + `assemble` + `okstra graphify` subcommand dispatch. Forces the corpus to `.okstra` and outputs to `.okstra/graph/` |
@@ -321,6 +326,10 @@ Token/cost accounting:
321
326
  | `templates/reports/report.css`, `report.js` | Inline assets for self-contained HTML report view |
322
327
  | `templates/reports/*.template.md` | Inputs, schedule, user-response, settings templates |
323
328
  | `templates/project-docs/task-index.template.md` | Project task index template |
329
+ | `templates/worker-prompt-preamble.md` | Initial analysis audience procedure and output contract |
330
+ | `templates/implementation-worker-preamble.md` | Shared implementation executor/verifier procedure, including coding-preflight and worktree rules |
331
+ | `templates/report-writer-prompt-preamble.md` | Report-writer input and authoring procedure without analysis or implementation instructions |
332
+ | `templates/worker-error-contract.md` | Audience-neutral error-path, sidecar schema, and write protocol shared by every initial worker |
324
333
 
325
334
  ### 4.8 `schemas/`
326
335
 
@@ -430,7 +439,7 @@ Single-stage `final-verification --stage <N>` reuses the matching implementation
430
439
  Current report pipeline:
431
440
 
432
441
  1. Analysis workers write worker result files.
433
- 2. Convergence writes `state/convergence-<task-type>-<seq>.json` when applicable.
442
+ 2. The lead writes semantic groups; the convergence engine persists working state, per-round plans/results, and then a validated `state/convergence-<task-type>-<seq>.json` v1.2 terminal state.
434
443
  3. Report-writer worker writes `reports/final-report-<task-type>-<seq>.data.json`.
435
444
  4. `scripts/okstra-render-final-report.py` renders Markdown.
436
445
  5. Token usage substitution fills usage/cost cells.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.128.0",
3
+ "version": "0.129.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.128.0",
3
- "builtAt": "2026-07-20T15:52:59.156Z",
2
+ "package": "0.129.0",
3
+ "builtAt": "2026-07-20T18:48:35.313Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -149,14 +149,14 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Ant
149
149
 
150
150
  Before invoking the Antigravity CLI, you MUST:
151
151
 
152
- 1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and verify the CLI run will Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Output sections contract). The lead's prompt body — which you persist verbatim and feed into Antigravity via stdin already contains this anchor; do not strip it. **Exception — reverify dispatches**: a Phase 5.5 re-verification prompt (its `**Prompt History Path:**` carries a `-reverify-r<N>-` segment) deliberately omits this anchor, because lightweight mode judges the supplied findings without re-reading source materials. Proceed without it; do NOT return a sentinel and do NOT synthesize the preamble path. The `**Errors log path:**` / `**Errors sidecar path:**` gate under "Error reporting" still applies to reverify prompts.
152
+ 1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` and verify the CLI run will Read both selected files end-to-end. The first owns audience procedure; the second owns sidecar schema and write rules. Persist and forward both anchors unchanged; never substitute the analysis preamble for an implementation audience. **Exception — `-reverify-r<N>-` dispatches**: a Phase 5.5 re-verification prompt deliberately omits both reading contracts and uses its lightweight prompt contract; do NOT return a sentinel for those two omitted reading anchors. The `**Errors log path:**` / `**Errors sidecar path:**` gate still applies.
153
153
  2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
154
154
 
155
- The CLI writes a Reading Confirmation block to the **audit sidecar** at `runs/<task-type>/worker-results/antigravity-worker-audit-<task-type>-<seq>.md`. The sidecar's body begins with `# Antigravity Worker Audit — <task-key>` followed by one short line per input file confirming end-to-end reading. Section-0 placement follows the worker preamble §"Reading rules" (canonical). If any file was skipped, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
155
+ The CLI writes a Reading Confirmation block to the **audit sidecar** at `runs/<task-type>/worker-results/antigravity-worker-audit-<task-type>-<seq>.md`. The sidecar's body begins with `# Antigravity Worker Audit — <task-key>` followed by one short line per input file confirming end-to-end reading. Placement follows the selected audience preamble's `Required reading` section. If any file was skipped, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
156
156
 
157
157
  ## Worker Output Structure
158
158
 
159
- The Antigravity CLI — not this wrapper — produces the worker result. Its required structure is defined in the preamble (the lead injects the `**Worker Preamble Path:**` anchor into the dispatched prompt you persist and forward, and the CLI Reads it): YAML frontmatter with `workerId: "antigravity"`, sections 1–5 (common core) plus optional Section 6, a unique item ID on every item, and ticket tagging on `requirements-discovery` / `error-analysis` / `implementation-planning` / `implementation` runs. This wrapper forwards the CLI's output unmodified except for the single `**Model:**` line (step 8d) — never reshape or summarize it.
159
+ The Antigravity CLI — not this wrapper — produces the worker result. It follows the audience-selected preamble and, for implementation, the executor/verifier role sidecar. Analysis output uses sections 1–5 plus optional Section 6; implementation output follows its sidecar. This wrapper forwards output unmodified except for the single `**Model:**` line (step 8d).
160
160
 
161
161
  ## Error reporting
162
162
 
@@ -58,8 +58,8 @@ Unlike the Codex / Antigravity workers, you are an in-process Claude subagent
58
58
 
59
59
  Before producing any output, you MUST:
60
60
 
61
- 1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and Read that file end-to-end with a single `Read` call (no `offset`, no `limit`). This is the canonical SSOT for the Required Reading + Error Reporting + Output sections contract.
62
- 2. Read every primary input file the lead enumerated under `## Inputs` (or equivalent heading) in the dispatch prompt body, end-to-end, following the rules stated in the preamble. For analysis workers this is normally `analysis-packet.md`; the source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` that file is for the report writer only.
61
+ 1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` from the lead prompt and Read both selected files end-to-end with one full-file `Read` each. The preamble owns audience procedure; the error contract owns sidecar schema and write rules. Never replace the selected path with a hard-coded analysis preamble.
62
+ 2. Read every primary input file the lead enumerated under `## Inputs` (or equivalent heading) end-to-end, following the selected preamble. Analysis workers normally receive `analysis-packet.md`; implementation workers receive their role sidecar and approved deliverable inputs.
63
63
 
64
64
  **Heartbeat — write the audit sidecar EARLY and APPEND per stage (BLOCKING).** This worker runs as an in-process Agent or a fresh-session tmux pane, so the lead has no `BashOutput`-style liveness signal while it waits for your return — the audit sidecar is the only signal that survives a silent hang.
65
65
 
@@ -71,7 +71,7 @@ Before producing any output, you MUST:
71
71
 
72
72
  ## Worker Output Structure
73
73
 
74
- Follow the **Worker output contract** in the preamble you Read via the `**Worker Preamble Path:**` anchor — it is the canonical source for the frontmatter schema, sections 1–5 + optional Section 6 ordering, item-ID conventions, and ticket tagging. Set `workerId: "claude"`.
74
+ Follow the output contract selected for your audience. Analysis workers use the analysis preamble's sections 1–5 plus optional Section 6; implementation executor/verifier workers use the implementation preamble plus their role sidecar. Set `workerId: "claude"`.
75
75
 
76
76
  ## Stop Condition (BLOCKING)
77
77
 
@@ -93,7 +93,7 @@ If you find yourself thinking "let me double-check section 3" or "I should read
93
93
 
94
94
  ## Error reporting
95
95
 
96
- Record your own tool failures per the **Error reporting** contract in the preamble: extract the `**Errors sidecar path:**` from the dispatch prompt (return `CLAUDE_WORKER_ERRORS_PATH_MISSING` without proceeding if it is absent — do NOT synthesize a `runs/...` path), then append a `tool-failure` entry there for any failed pre-/in-analysis tool call (creating the file with `{"schemaVersion": 1, "errors": []}` if missing). The lead dumps the sidecar to the run error log after this subagent terminates. This worker has no external CLI, so there is no `cli-failure` category — treat MCP and Bash errors uniformly as `tool-failure`.
96
+ Record your own tool failures per the file selected by `**Worker Error Contract Path:**`: extract `**Errors sidecar path:**` from the dispatch prompt (return `CLAUDE_WORKER_ERRORS_PATH_MISSING` without proceeding if absent), then append `tool-failure` entries exactly as that shared contract requires. This worker has no external CLI, so MCP and Bash failures use the same sidecar protocol.
97
97
 
98
98
  ## Notes
99
99
 
@@ -149,14 +149,14 @@ This wrapper does NOT invoke MCP tools directly. MCP availability inside the Cod
149
149
 
150
150
  Before invoking the Codex CLI, you MUST:
151
151
 
152
- 1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and verify the CLI run will Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Output sections contract). The lead's prompt body — which you persist verbatim and feed into Codex via stdin already contains this anchor; do not strip it. **Exception — reverify dispatches**: a Phase 5.5 re-verification prompt (its `**Prompt History Path:**` carries a `-reverify-r<N>-` segment) deliberately omits this anchor, because lightweight mode judges the supplied findings without re-reading source materials. Proceed without it; do NOT return a sentinel and do NOT synthesize the preamble path. The `**Errors log path:**` / `**Errors sidecar path:**` gate under "Error reporting" still applies to reverify prompts.
152
+ 1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**` and verify the CLI run will Read both selected files end-to-end. The first owns audience procedure; the second owns sidecar schema and write rules. Persist and forward both anchors unchanged; never substitute the analysis preamble for an implementation audience. **Exception — `-reverify-r<N>-` dispatches**: a Phase 5.5 re-verification prompt deliberately omits both reading contracts and uses its lightweight prompt contract; do NOT return a sentinel for those two omitted reading anchors. The `**Errors log path:**` / `**Errors sidecar path:**` gate still applies.
153
153
  2. Verify the lead's prompt body lists the per-run primary input files under `## Inputs` (normally `analysis-packet.md` for analysis workers). The source files named inside that packet are fallback/evidence paths to open when needed. Analysis workers do NOT read `final-report-template.md` — that file is for the report writer only.
154
154
 
155
- The CLI writes a Reading Confirmation block to the **audit sidecar** at `runs/<task-type>/worker-results/codex-worker-audit-<task-type>-<seq>.md`. The sidecar's body begins with `# Codex Worker Audit — <task-key>` followed by one short line per input file confirming end-to-end reading. Section-0 placement follows the worker preamble §"Reading rules" (canonical). If any file was skipped, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
155
+ The CLI writes a Reading Confirmation block to the **audit sidecar** at `runs/<task-type>/worker-results/codex-worker-audit-<task-type>-<seq>.md`. The sidecar's body begins with `# Codex Worker Audit — <task-key>` followed by one short line per input file confirming end-to-end reading. Placement follows the selected audience preamble's `Required reading` section. If any file was skipped, record a `tool-failure` in the errors sidecar instead of fabricating Findings.
156
156
 
157
157
  ## Worker Output Structure
158
158
 
159
- The Codex CLI — not this wrapper — produces the worker result. Its required structure is defined in the preamble (the lead injects the `**Worker Preamble Path:**` anchor into the dispatched prompt you persist and forward, and the CLI Reads it): YAML frontmatter with `workerId: "codex"`, sections 1–5 (common core) plus optional Section 6, a unique item ID on every item, and ticket tagging on `requirements-discovery` / `error-analysis` / `implementation-planning` / `implementation` runs. This wrapper forwards the CLI's output unmodified except for the single `**Model:**` line (step 8d) — never reshape or summarize it.
159
+ The Codex CLI — not this wrapper — produces the worker result. It follows the audience-selected preamble and, for implementation, the executor/verifier role sidecar. Analysis output uses sections 1–5 plus optional Section 6; implementation output follows its sidecar. This wrapper forwards output unmodified except for the single `**Model:**` line (step 8d).
160
160
 
161
161
  ## Error reporting
162
162
 
@@ -62,8 +62,8 @@ Do NOT duplicate the data.json contents here — the data.json is the canonical
62
62
 
63
63
  Before writing the data.json, you MUST:
64
64
 
65
- 1. Extract the absolute path from the lead's `**Worker Preamble Path:**` anchor header and Read that file end-to-end (canonical SSOT for the Required Reading + Error Reporting + Anchor contract — this overrides per-spec restatements).
66
- 2. Read every input file the lead enumerated under `## Inputs` (or equivalent heading) in the dispatch prompt body, end-to-end (single `Read` call with no `offset`/`limit`; page through with explicit offsets only when a file is too large for one read).
65
+ 1. Extract `**Worker Preamble Path:**` and `**Worker Error Contract Path:**`; Read the selected report-writer preamble and shared error contract end-to-end. Do not substitute the analysis or implementation preamble.
66
+ 2. Read every input file the lead enumerated under `## Inputs` (or equivalent heading) end-to-end (single `Read` call with no `offset`/`limit`; page explicitly only when required).
67
67
 
68
68
  For the report writer specifically, the `## Inputs` list always includes:
69
69
 
@@ -75,7 +75,7 @@ For the report writer specifically, the `## Inputs` list always includes:
75
75
 
76
76
  For the carry-in `clarification-response.md` (if present), walk every row of `## 1. Clarification Items` including rows whose `User input` cell is blank — a blank cell with `Status=open` is a signal you must surface in the conditional `## 0. Clarification Response Carried In From Previous Run` section (the template's `RENDER_IF` guard activates it when the carry-in path is non-empty). When no carry-in path was provided, OMIT the `## 0.` heading entirely — do NOT write an empty-state stub.
77
77
 
78
- Write a Reading Confirmation block to your **audit sidecar** at `runs/<task-type>/worker-results/report-writer-worker-audit-<task-type>-<seq>.md`, per the worker preamble §"Reading rules" (canonical — the main final-report and worker-results files carry no Section 0 heading). If you cannot truthfully confirm a file end-to-end, record a `tool-failure` in the errors sidecar instead of fabricating the report.
78
+ Write a Reading Confirmation block to your **audit sidecar** at `runs/<task-type>/worker-results/report-writer-worker-audit-<task-type>-<seq>.md`, per the selected report-writer preamble's `Required reading` section (the main final-report and worker-results files carry no Section 0 heading). If you cannot truthfully confirm a file end-to-end, record a `tool-failure` in the errors sidecar instead of fabricating the report.
79
79
 
80
80
  ## Authoring Contract
81
81
 
@@ -92,7 +92,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
92
92
  - **§7 phase-continuation row (mandatory for non-terminal task-types).** When `header.taskType` is one of `requirements-discovery` / `implementation-planning` / `error-analysis` / `implementation` / `final-verification`, `followUpTasks` MUST contain at least one row whose `origin` is `phase-continuation`, `suggestedTaskType` equals the next phase (byte-identical to `finalVerdict.nextStep`'s referenced phase), `newTaskId` reuses the current task-id, `autoSpawn` is `"no"`, and `priority` is `"P0"`. For `release-handoff` runs, omit the phase-continuation row. Schema `allOf` clause enforces this via `contains`.
93
93
  - **No deprecated sections.** The schema has no `4.5.8 User Approval Request` body field, no `4.5.9 Open Questions`, no `5.1 Additional Material Request`, no `5.2 User Confirmation Questions` — clarifications go under the unified `clarificationItems[]` array.
94
94
  - **Optional Section 0.** Include `clarificationCarryIn` ONLY when the lead's prompt provides a non-empty carry-in path. Omit the key entirely otherwise (do NOT set it to `null` or an empty object).
95
- - **Reading Confirmation** goes in the audit sidecar per the preamble §"Reading rules" — never in the data.json or the main worker-results file.
95
+ - **Reading Confirmation** goes in the audit sidecar per the selected report-writer preamble's `Required reading` section — never in the data.json or the main worker-results file.
96
96
  - Include all four convergence categories. The schema's `crossVerification.consensus` / `.differences` arrays carry full / partial / contested / worker-unique items; do not omit any.
97
97
  - Convergence round history goes in `crossVerification.roundHistory.rounds[]` with `round2SkippedReason`. When convergence is disabled, set `crossVerification.roundHistory` to `{"disabled": true}`. Values come verbatim from `state/convergence-<task-type>-<seq>.json` — do not recompute.
98
98
  - `verification-error` votes are their own verdict (`planItems[].verdicts[].verdict` enum); they are NOT folded into AGREE / DISAGREE counts.
@@ -114,7 +114,7 @@ data.json written to <abs path>; markdown rendered to <abs path>. Sections popul
114
114
 
115
115
  ## Error reporting
116
116
 
117
- Record tool failures per the **Error reporting** contract in the preamble: extract the `**Errors sidecar path:**` from the dispatch prompt (return `REPORT_WRITER_ERRORS_PATH_MISSING` without proceeding if it is absent — do NOT synthesize a `runs/...` path), then append a `tool-failure` entry there for any failed tool call (Write of the prompt history file, mkdir, MCP call, Read of an input file), creating the file with `{"schemaVersion": 1, "errors": []}` if missing. The lead dumps the sidecar to the run error log after this subagent terminates. This worker has no external CLI, so there is no `cli-failure` category — treat MCP and Bash errors uniformly as `tool-failure`.
117
+ Record tool failures through the file selected by `**Worker Error Contract Path:**`. If `**Errors sidecar path:**` is absent, return `REPORT_WRITER_ERRORS_PATH_MISSING` and stop; otherwise use the shared schema and append protocol exactly. This worker has no external CLI.
118
118
 
119
119
  ## Notes
120
120
 
@@ -46,12 +46,13 @@ This adapter maps the neutral Okstra lead operations to Claude Code host primiti
46
46
  - Missing or unsupported family-token mapping is a pre-dispatch contract failure. Never inherit the lead model, choose a nearby alias, or switch provider silently.
47
47
  - Every analysis dispatch sets `name: "<workerId>-worker"`; convergence retries append `-reverify-r<N>`, implementation uses the functional `-executor` / `-verifier` suffix, and report writing uses `report-writer`. These values are retained as `agentName` in session JSONL for usage attribution.
48
48
  - Every Codex / Antigravity prompt includes `**Pane role:** <functional-role>` so the wrapper's optional fifth argument names both its caller pane and trace pane.
49
- - The Agent SDK auto-loads the in-process `claude-worker` definition, so that worker's prompt body may be shorter than CLI-wrapper prompt bodies; every worker still receives the same Worker Preamble pointer and core inputs.
49
+ - The Agent SDK may supply transport metadata through the in-process worker definition, but the persisted semantic prompt body and primary analysis-packet input remain identical to the CLI-wrapper workers after permitted identity/path normalization.
50
50
  - A retry keeps the same Agent `name`. When logging a twice-failed CLI-wrapper attempt, reference both attempts' `bash_ids` and prompt-history paths.
51
51
  - An internally detected contract violation without a specific worker uses `--agent "claude-lead"` in the error-log event.
52
52
 
53
53
  ### Reverify, critic, and report-writer assignments
54
54
 
55
+ - For convergence reverify, consume the persisted round plan exactly. This adapter may map and transport each returned batch, but it cannot change batch membership and does not classify findings or branch on task type, provider, or model identity.
55
56
  - Reverify dispatch uses a fresh one-shot `Agent(...)` call named `<workerId>-worker-reverify-r<N>`. Preserve the initial worker's definition and map an in-process Claude assignment's `modelExecutionValue` to its exact family token; CLI-wrapper assignments remain `inherit` at the Agent layer and apply the exact model in their wrapper.
56
57
  - Critic dispatch uses `name: "<provider>-worker-critic"`, `dispatchKind: "critic"`, and the exact mapped model from `config.critic.modelExecutionValue`. If that value cannot be mapped, record `critic-skipped: model-unresolved` and do not dispatch.
57
58
  - Report-writer dispatch uses `name: "report-writer"` and maps the roster assignment's `modelExecutionValue` to the supported family token. The prompt's `**Model:**` header must carry the same execution value.
@@ -33,6 +33,7 @@ This adapter maps the neutral Okstra lead operations to the Codex artifact-first
33
33
 
34
34
  ## Codex dispatch details
35
35
 
36
+ - For convergence reverify, consume the persisted round plan exactly. This adapter may map and transport each returned batch, but it cannot change batch membership and does not classify findings or branch on task type, provider, or model identity.
36
37
  - Do not invoke Claude Code team or subagent tools.
37
38
  - The prepared run manifest and team-state are the dispatch authority. Unsupported explicitly requested workers fail; an adapter must not silently change the roster.
38
39
  - Report-writer execution keeps the existing explicit opt-in policy in this milestone: dispatch requires `--enable-codex-report-writer` and an explicit `--report-writer-codex-model` value.
@@ -33,6 +33,7 @@ This adapter maps the neutral Okstra lead operations to a generic host using Oks
33
33
 
34
34
  ## External dispatch details
35
35
 
36
+ - For convergence reverify, consume the persisted round plan exactly. This adapter may map and transport each returned batch, but it cannot change batch membership and does not classify findings or branch on task type, provider, or model identity.
36
37
  - Do not invoke Claude Code team tools or `okstra codex-dispatch`.
37
38
  - Worker completion is valid only from `workerDispatches[]`, terminal status sidecars, and required Result Paths. Pane creation alone is not completion.
38
39
  - Reverify uses a fresh jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json`, sets `dispatchKind: "reverify-r<N>"`, and dispatches with `okstra team dispatch --project-root <root> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>`.
@@ -76,92 +76,48 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
76
76
  - Items tagged `unknown` keep the literal `unknown` as their ticket key.
77
77
  2. For each finding, record the summary, evidence (file path, line number, basis), the discovering worker, **the worker-internal item ID that worker assigned** (e.g. `F-001`, `1.1`, `F-3` — see `prompts/profiles/_common-contract.md` "Cross-worker traceability" SSOT), and the parsed ticket set. Persist the item ID as `findings[].discoveredBy.<worker>.itemId` and each cross-worker confirmation as `findings[].sourceItems[]` (one entry per contributing `<worker>:<item-id>` pair). The final-report `## 6.1 Consensus` / `## 6.2 Differences` / `## 2.1 Primary Evidence` tables read this verbatim into their `Source items` columns; without it the synthesised `C-NNN` row loses its link back to the original worker wording.
78
78
  3. The lead groups findings based on semantic similarity AND ticket-set equality:
79
- - Same semantics + same ticket set across 2+ workers → immediately reach `full consensus`.
80
- - Same semantics but disjoint ticket sets → keep as separate groups (do NOT over-merge across tickets).
81
- - Only one worker confirms a finding → `unique`, enter the verification queue.
82
- 4. When grouping is ambiguous, prefer splitting over merging (avoid over-merging).
83
- 5. Persist each finding's ticket set in the convergence state artifact under a `ticketIds` field on the finding record. Re-verification rounds carry the same field forward.
84
- 6. After grouping, the verification queue contains EXACTLY the `unique`-marked findings (Step 3 case "Only one worker confirms"). `full-consensus` findings reached in Step 3 are recorded immediately in the convergence state with `classification: "full-consensus"` and DO NOT enter the queue.
79
+ - Same semantics + same ticket set across 2+ workers → one multi-source group.
80
+ - Same semantics but disjoint ticket sets → separate groups (do NOT over-merge across tickets).
81
+ - Only one worker confirms a finding → one single-source group.
82
+ 4. When grouping is ambiguous, prefer splitting over merging (avoid over-merging). Semantic matching, ticket-set equality, and evidence interpretation remain lead judgments; the engine does not perform fuzzy matching or decide whether evidence is credible.
83
+ 5. Write `runs/<task-type>/state/convergence-groups-<task-type>-<seq>.json`. Each group carries its `ticketIds`, `originWorker`, `originEvidence`, `discoveredBy`, and every `<worker>:<item-id>` source in `sourceItems`. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity.
84
+ 6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` deterministically marks multi-source groups `full-consensus` and puts only single-source groups in the working queue. Section 6 never enters the grouped input.
85
85
 
86
86
  ### Round 1-N: Re-verification Loop (queue-pruned)
87
87
 
88
- The verification queue holds only findings that are not yet classified. Confirmed items are *removed* from the queue and never re-sent.
88
+ The `ConvergenceEngine` reducer owns the working queue, classification strategy, pruning, round arithmetic, gate precedence, skip reason, final state, and classification counts. The lead owns only semantic grouping plus translation of observed worker outcomes into the structured round-results schema. Runtime adapters transport persisted batches and terminal outcomes only.
89
+
90
+ Working artifacts are stored beside the final state:
89
91
 
90
92
  ```text
91
- roundIndex = 0
92
- WHILE roundIndex < effectiveMaxRounds AND queue is non-empty:
93
- roundIndex += 1
94
-
95
- # Round 2 gate (only evaluated when entering round 2 or higher)
96
- IF roundIndex > 1 AND NOT round_gate_open(queue, roundHistory[-1].dispatches):
97
- record round2SkippedReason in convergence state
98
- BREAK
99
-
100
- inputQueueSize = len(queue)
101
- dispatches = []
102
- skippedWorkers = []
103
-
104
- FOR each analysis worker W (excluding report-writer-worker):
105
- items_for_W = [f for f in queue if W != f.originWorker]
106
- IF items_for_W is empty:
107
- skippedWorkers.append({worker: W, reason: "no items to verify"})
108
- CONTINUE
109
- dispatch = send_reverify_request(W, items_for_W, roundIndex)
110
- dispatches.append(dispatch)
111
-
112
- IF len(dispatches) > 0 AND all dispatches in this round are terminal non-result (timeout/error/no-result-file):
113
- # Per "Worker failure handling in reverify" below — do NOT treat as DISAGREE.
114
- record verification-error evidence on each finding in the queue for this round
115
- record round2SkippedReason = "all-reverify-non-result" for any subsequent round
116
- BREAK
117
-
118
- resolvedCount = 0
119
- carriedForwardCount = 0
120
-
121
- FOR each finding F in queue (snapshot):
122
- votes = aggregate_votes(F, dispatches) # AGREE / DISAGREE / SUPPLEMENT / verification-error
123
- IF all non-error votes are AGREE or SUPPLEMENT:
124
- F.classification = "full-consensus"
125
- queue.remove(F); resolvedCount += 1
126
- ELIF majority non-error votes are AGREE or SUPPLEMENT:
127
- F.classification = "partial-consensus"
128
- queue.remove(F); resolvedCount += 1
129
- ELIF all non-error votes are DISAGREE:
130
- F.classification = "worker-unique"
131
- queue.remove(F); resolvedCount += 1
132
- ELSE:
133
- # mixed / insufficient non-error votes, or all-error votes → carry forward
134
- carriedForwardCount += 1
135
-
136
- record roundHistory entry { round: roundIndex, inputQueueSize, resolvedCount,
137
- carriedForwardCount, dispatches, skippedWorkers }
138
-
139
- # Final classification — runs after the WHILE loop exits (queue empty OR roundIndex == effectiveMaxRounds OR Round 2 gate closed)
140
- FOR each finding F still in queue:
141
- IF config.adversarial AND F was carried forward by the adversarial round
142
- resolution (a surviving `counter-evidence` refute, or a burden-not-met
143
- majority — see §"Adversarial Verification Mode"):
144
- F.classification = "contested" # one evidence-backed refute denies consensus, whatever the AGREE tally
145
- ELIF majority AGREE-or-SUPPLEMENT across all executed rounds:
146
- F.classification = "partial-consensus"
147
- ELSE:
148
- F.classification = "contested"
93
+ convergence-groups-<task-type>-<seq>.json
94
+ convergence-work-<task-type>-<seq>.json
95
+ convergence-round-<N>-plan-<task-type>-<seq>.json
96
+ convergence-round-<N>-results-<task-type>-<seq>.json
97
+ convergence-<task-type>-<seq>.json
149
98
  ```
150
99
 
151
- The lead MUST construct the per-worker reverify prompt body from `items_for_W` only — confirmed findings from earlier rounds MUST NOT appear in the prompt, even as background. The dispatch-prompt invariant (every worker gets the same prompt content modulo their own findings) continues to apply to the per-round prompt body.
100
+ Follow this protocol exactly:
152
101
 
153
- #### Round 2 gate (`round_gate_open` predicate)
102
+ 1. Run `okstra convergence seed --groups <groups> --work-state <work> --final-state <final> --migration-dir <state/migrations>`. A `reuse-final` action means validate the existing final and continue to Phase 6. `create-work`, `resume-work`, and `restart-round0` continue with planning.
103
+ 2. Run `okstra convergence plan-round --work-state <work> --plan <round-plan>`. This is read-only with respect to the working state.
104
+ 3. When the plan action is `dispatch`, create exactly one reverify prompt for each `dispatches[]` row and dispatch it through the selected runtime adapter. Its findings are exactly that row's `findingIds`.
105
+ 4. Convert parsed verdicts and every terminal dispatch outcome into `convergence-round-<N>-results-<task-type>-<seq>.json`; then run `okstra convergence apply-round --work-state <work> --plan <round-plan> --results <round-results>`.
106
+ 5. Repeat `plan-round` and `apply-round` until the plan action is `finalize`.
107
+ 6. Run `okstra convergence finalize --work-state <work> --output <final>`.
108
+ 7. Run `okstra convergence validate --state <final> --kind final`. Only this validated final schema-v1.2 artifact is delivered to the report-writer, which does not vote.
154
109
 
155
- `round_gate_open(queue, previous_dispatches)` returns `true` iff ALL three conditions hold (here `previous_dispatches` is the most recent entry's `dispatches` array in `roundHistory`); otherwise the lead records `round2SkippedReason` and breaks out of the loop:
110
+ The planner preserves roster and queue order, excludes that finding's origin worker, emits at most one batch per analysis worker per round, and ensures the report-writer never appears in `dispatches` or `skippedWorkers`. Queue pruning is monotonic: a finding absent from the current queue cannot reappear in a later plan.
156
111
 
157
- | Condition | Required value | `round2SkippedReason` if not met |
158
- |---|---|---|
159
- | `effectiveMaxRounds >= 2` | true | `"max-rounds-1"` |
160
- | `len(queue) > 0` after round 1 | true | `"queue-empty"` |
112
+ After `seed`, the lead MUST NOT hand-edit queue IDs, classifications, `roundHistory` arithmetic, `round2SkippedReason`, `finalState`, `totalRounds`, or `finalClassificationCounts`. The persisted plan is the assignment audit record; adapters and leads may not recalculate it. `scripts/okstra_ctl/convergence_engine.py` and `okstra convergence validate` enforce these rules.
113
+
114
+ #### Resume and legacy-state recovery (BLOCKING)
161
115
 
162
- The third gate condition "all reverify dispatches terminated as non-result" is handled inline by the WHILE-loop body (see the `BREAK` on `all dispatches ... terminal non-result` and §"Worker failure handling in reverify" rule 4) which records `round2SkippedReason = "all-reverify-non-result"` and aborts before the predicate is re-evaluated. It is therefore not duplicated as a gate row here.
116
+ A valid terminal legacy final is reused unchanged. A malformed, unreadable, or partial legacy final is archived byte-for-byte under `state/migrations/` and restarted from Round 0 because its queue provenance cannot be reconstructed. A matching valid working state resumes. A malformed new-engine working state fails closed and names the explicit `--restart-from-round0` recovery flag; when supplied, every invalid existing state is archived before replacement. The final path can be replaced only when its recorded migration archive still matches the original bytes.
163
117
 
164
- When all conditions hold the predicate returns `true` and `round2SkippedReason` is set to `"not-skipped"`. The field is mandatory on every convergence state artifact — write `"not-skipped"` rather than omitting the key.
118
+ #### Engine-owned Round 2 gate
119
+
120
+ `plan-round` applies gate precedence in one place: auto-disabled, all reverify non-result, effective maximum of one, empty queue, then maximum rounds reached. `finalize` maps that internal reason to the public `round2SkippedReason` and `finalState`. The lead and adapters never reproduce this predicate.
165
121
 
166
122
  #### Worker failure handling in reverify (BLOCKING)
167
123
 
@@ -169,17 +125,17 @@ A reverify dispatch that returns a **terminal non-result** (`timeout`, `error`,
169
125
 
170
126
  Rules:
171
127
 
172
- 1. For each affected finding, append a `votes[W].verdict = "verification-error"` entry instead of `disagree`, plus the wrapper's captured exit reason in `votes[W].explanation`.
128
+ 1. For each failed dispatch, put its actual terminal status and duration in the round-results `dispatches[]`; do not invent a vote. `okstra convergence apply-round` appends `votes[W].verdict = "verification-error"` with the terminal reason for every affected finding.
173
129
  2. Record one event per failed dispatch via `okstra error-log append-observed --error-type cli-failure --agent <worker> ...` (the worker wrapper does this for wrapper failures; for in-process worker timeouts the lead does it).
174
- 3. Add an entry to the round's `skippedWorkers[]` with `{worker: <W>, reason: "dispatch-non-result", terminalStatus: <timeout|error|not-run>}`.
175
- 4. If at least one dispatch was issued AND all reverify dispatches in a round terminate as non-result (mirroring the pseudocode's `len(dispatches) > 0` guard), the round is treated as gate-closed: write `round2SkippedReason: "all-reverify-non-result"` (even if the round in question is round 1 — i.e. round 2 never runs because round 1 produced no usable votes), record one `contract-violation` event per non-result dispatch, and exit the WHILE loop.
130
+ 3. `apply-round` adds the worker to the persisted round's `skippedWorkers[]` with `{worker: <W>, reason: "dispatch-non-result", terminalStatus: <timeout|error|not-run>}`.
131
+ 4. If at least one dispatch was issued and every dispatch terminates as non-result, `apply-round` records the `all-reverify-non-result` stop state. The next `plan-round` returns `action: "finalize"`; record one `contract-violation` event per non-result dispatch.
176
132
  5. Section 6 (Specialization Lens) of a worker output is OUT of convergence scope per "Convergence scope" above — its absence is NEVER a `verification-error`.
177
133
 
178
- The final classifier (`FOR each finding F still in queue` block) treats `verification-error` as "no usable vote" — it counts neither toward AGREE nor toward DISAGREE.
134
+ The engine's classifiers treat `verification-error` as "no usable vote" — it counts neither toward AGREE nor toward DISAGREE.
179
135
 
180
136
  ### Convergence Test
181
137
 
182
- The exit conditions and final-classification rules are defined by the §"Convergence Algorithm" pseudocode (the `WHILE` exit, the post-loop `FOR each finding F still in queue` block, and the `finalState` mapping in §"Convergence State Artifact"). This is the single source no separate prose copy is maintained here to prevent drift.
138
+ The executable source is `scripts/okstra_ctl/convergence_engine.py`; `okstra convergence validate --kind final` replays its persisted-state invariants. This document defines orchestration and semantic boundaries, not a second implementation of the reducer.
183
139
 
184
140
  ## Verification Mode
185
141
 
@@ -249,7 +205,9 @@ Design intent: one `counter-evidence` refute denies a claim consensus (it cannot
249
205
 
250
206
  ### Sponsorship Optimization
251
207
 
252
- For each analysis worker with a non-empty current round's `items_for_W` batch, build one reverify prompt and call `redispatch_worker(assignment, prompt, reason)` exactly once per worker through the selected runtime adapter. The batch contains only that worker's eligible findings from the current verification queue; final classification labels never select dispatch targets. The assignment, model, prompt path, Result Path, worker-results path, errors paths, and `dispatchKind` come from the current run artifacts. Every reverify is a fresh one-shot session.
208
+ For each persisted round plan, build exactly one prompt per `dispatches[]` row and call `redispatch_worker(assignment, prompt, reason)` once through the selected runtime adapter. The prompt contains exactly that row's `findingIds` in plan order and MUST NOT add, remove, or reorder findings. This excludes Section 6, every resolved finding, and every finding owned by the receiving origin worker because none can appear in the engine row. The assignment, model, prompt path, Result Path, worker-results path, errors paths, and `dispatchKind` come from the current run artifacts. Every reverify is a fresh one-shot session.
209
+
210
+ The persisted round plan is the audit record for batch membership. The lead and adapter do not branch on task type, provider, model identity, classification labels, or their own view of the queue. They dispatch only the engine-returned row through the selected runtime adapter.
253
211
 
254
212
  Call `await_workers(handles)` through the same adapter and apply the shared terminal-status/completion-path contract before counting a vote. The selected adapter owns native invocation spelling and any jobs-file/CLI fields. This contract owns only the reverify payload and verdict semantics.
255
213
 
@@ -290,7 +248,7 @@ If none of the three is available, **abort the reverify dispatch for that role**
290
248
 
291
249
  Reverify prompts MUST NOT inject the Phase 2 `[Required reading]` clause:
292
250
 
293
- Lightweight reverify does not require the original `analysis-packet.md`, `analysis-profile.md`, `task-brief.md`, or instruction set. Its complete input is the receiving worker's current `items_for_W` batch and the evidence embedded in those findings.
251
+ Lightweight reverify does not require the original `analysis-packet.md`, `analysis-profile.md`, `task-brief.md`, or instruction set. Its complete input is the receiving worker's current engine-planned `findingIds` batch and the evidence embedded in those findings.
294
252
 
295
253
  - **Lightweight mode**: the clause directly contradicts the "Do NOT re-analyze the original source materials" instruction below. Including it forces workers to re-read the entire instruction-set per round per worker (3 workers × 2 rounds × 5+ files in the worst case) for no quality gain.
296
254
  - **Full-reanalysis mode**: workers DO need to re-read source materials, but only the analysis-worker file list (no `final-report-template.md`). If lead chooses to inject a reading clause here, it MUST mirror the audience-scoped enumeration in [okstra-lead-contract](./okstra-lead-contract.md) Phase 2 (no template).
@@ -500,9 +458,9 @@ Schema rules:
500
458
  - `roundHistory[].carriedForwardCount`: queue size at the END of this round — the single definition. In-round insertions into the queue are forbidden, so this always equals `inputQueueSize - resolvedCount`. The pseudocode's per-item `carriedForwardCount += 1` accumulator is a counting convenience that lands on the same value; persist the post-round queue length, not the loop accumulator, if the two ever diverge.
501
459
  - `roundHistory[].dispatches[]`: one entry per worker that was actually dispatched in this round. Each entry is `{worker, status, durationMs}`. `status ∈ {completed, timeout, error, not-run}`. `durationMs` is integer milliseconds and is always present, even for terminal-non-result dispatches (use the elapsed time before the wrapper gave up).
502
460
  - `roundHistory[].skippedWorkers[]`: per-worker `{worker, reason}` for workers with no items to verify OR with a non-result dispatch.
503
- - `round2SkippedReason`: literal enum `queue-empty | max-rounds-1 | all-reverify-non-result | not-skipped | auto-disabled`. Always present. Precedence when more than one condition holds, highest first: `"auto-disabled"` when the auto-disable rule fired (see §"Configuration" — the loop never built a queue; `roundHistory` is empty); `"max-rounds-1"` when `effectiveMaxRounds == 1` (Round 2 was never attempted — this wins over `queue-empty` even when Round 1 drained the queue, so a clean `requirements-discovery` run records `max-rounds-1`); `"all-reverify-non-result"` when all Round 1 dispatches terminated as non-result; `"queue-empty"` when Round 1 fully drained the queue (`effectiveMaxRounds >= 2` only, by the precedence above); `"not-skipped"` when Round 2 actually ran.
461
+ - `round2SkippedReason`: literal enum `queue-empty | max-rounds-1 | all-reverify-non-result | not-skipped | auto-disabled`. Always present and derived by `finalize`; the engine owns precedence so the lead never writes it manually.
504
462
  - `finalClassificationCounts`: post-loop counts. Required field with keys `fullConsensus`, `partialConsensus`, `contested`, `workerUnique`.
505
- - `finalState ∈ {converged, max-rounds-reached, aborted-non-result}`. Assigned by the lead at WHILE-loop exit: `converged` when the queue is empty at the end of any round; `max-rounds-reached` when the loop exits because `roundIndex == effectiveMaxRounds` with the queue still non-empty; `aborted-non-result` when the loop exits via the Worker-failure BREAK (per the "Worker failure handling in reverify" section, rule 4). `aborted-non-result` is the new v1.1 value.
463
+ - `finalState ∈ {converged, max-rounds-reached, aborted-non-result}`. Derived by `finalize` from the validated queue, history, and stop reason; the lead does not assign it.
506
464
  - `totalRounds`: count of rounds actually executed (not `effectiveMaxRounds`). May be `0` when Round 0 produced no queue items (all findings reached consensus during grouping).
507
465
 
508
466
  ## Coverage critic pass
@@ -575,7 +533,7 @@ Critic output lives in the run's `worker-results/` directory (`runs/final-verifi
575
533
 
576
534
  Information to be passed to Phase 6 after completing this contract:
577
535
 
578
- - Final classification of all findings (4-category)
536
+ - Validated final schema-v1.2 artifact containing the four-category classification of all findings; the report-writer consumes it and does not vote
579
537
  - Round history and votes per worker for each finding
580
538
  - Path to the convergence state artifact
581
539
  - Convergence summary (count per category)