okstra 0.145.0 → 0.146.1

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 (43) hide show
  1. package/docs/architecture.md +4 -2
  2. package/docs/cli.md +15 -5
  3. package/docs/project-structure-overview.md +3 -5
  4. package/package.json +1 -1
  5. package/runtime/BUILD.json +2 -2
  6. package/runtime/agents/workers/report-writer-worker.md +5 -6
  7. package/runtime/bin/okstra-trace-cleanup.sh +41 -11
  8. package/runtime/prompts/lead/adapters/claude-code.md +3 -3
  9. package/runtime/prompts/lead/convergence.md +20 -3
  10. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  11. package/runtime/prompts/lead/report-writer.md +20 -14
  12. package/runtime/prompts/lead/team-contract.md +3 -3
  13. package/runtime/python/okstra_ctl/analysis_packet.py +4 -10
  14. package/runtime/python/okstra_ctl/codex_dispatch.py +117 -58
  15. package/runtime/python/okstra_ctl/convergence_engine.py +3 -1
  16. package/runtime/python/okstra_ctl/dispatch_core.py +19 -56
  17. package/runtime/python/okstra_ctl/dispatch_state.py +167 -3
  18. package/runtime/python/okstra_ctl/path_hints.py +6 -0
  19. package/runtime/python/okstra_ctl/paths.py +7 -44
  20. package/runtime/python/okstra_ctl/render.py +2 -0
  21. package/runtime/python/okstra_ctl/wizard.py +34 -0
  22. package/runtime/python/okstra_ctl/worker_liveness.py +84 -21
  23. package/runtime/python/okstra_ctl/worker_prompt_body.py +24 -4
  24. package/runtime/python/okstra_ctl/worker_prompt_contract.py +57 -0
  25. package/runtime/python/okstra_ctl/worker_state.py +65 -0
  26. package/runtime/python/okstra_token_usage/antigravity.py +3 -0
  27. package/runtime/python/okstra_token_usage/codex.py +54 -23
  28. package/runtime/python/okstra_token_usage/collect.py +141 -33
  29. package/runtime/python/okstra_token_usage/paths.py +27 -0
  30. package/runtime/python/okstra_vendor/__init__.py +15 -2
  31. package/runtime/schemas/convergence-groups-v1.0.schema.json +0 -1
  32. package/runtime/skills/okstra-run/SKILL.md +14 -4
  33. package/runtime/skills/okstra-setup/references/project-config.md +13 -4
  34. package/runtime/validators/lib/fixtures.sh +1 -1
  35. package/runtime/validators/validate-run.py +52 -1
  36. package/runtime/validators/validate_analysis_report.py +34 -3
  37. package/src/cli-registry.mjs +7 -10
  38. package/src/commands/execute/worker-state.mjs +29 -0
  39. package/src/commands/inspect/worker-liveness.mjs +5 -3
  40. package/src/commands/lifecycle/preflight.mjs +13 -3
  41. package/src/lib/runtime-readiness.mjs +90 -0
  42. package/runtime/python/okstra_ctl/phase_cleanup.py +0 -235
  43. package/src/commands/execute/phase-cleanup.mjs +0 -38
@@ -302,6 +302,7 @@ The standard `okstra` workflow applies the following team contract consistently
302
302
  - Because `Antigravity worker` is optional, it is attempted only in runs where it is explicitly included.
303
303
  - Before the final judgment, each required role in the current run's worker roster must have either a result or an explicit terminal status (`completed`, `timeout`, `error`, `not-run`).
304
304
  - Every attempted worker (`completed`, `timeout`, `error`) must have an assigned worker prompt history file under the current run's `prompts/` directory.
305
+ - Worker timing begins at the atomic transition to `in-progress`, which records `workers[].startedAt` in `team-state.json`; prompt creation time is not a dispatch proxy. `okstra worker-state transition` and both dispatch adapters share `dispatch_state.transition_worker_status`, while `okstra worker-liveness --team-state ... --worker ...` reads that timestamp as the launch-grace authority.
305
306
  - An unnamed generic parallel worker is not accepted as a substitute for a required role.
306
307
 
307
308
  ### Cross-task worker prompt policy and final-verification boundaries
@@ -328,7 +329,7 @@ The complete artifact lifecycle is: worker results → Round 0 grouping → redu
328
329
 
329
330
  Cross-verification does not mean that worker A reviews worker B's entire result. Round 0 records multi-source agreement immediately, and the reducer asks independent analyser instances to vote only on single-source or still-unresolved findings selected in the persisted queue. The report writer is never a voter. It organizes the validated result, while the later plan-body round verifies the consolidated `P-*` plan items rather than reopening the `F-*` finding queue.
330
331
 
331
- The lead writes the grouped input, then advances it through the internal admin CLI operations `okstra convergence seed`, `plan-round`, `apply-round`, optional `apply-critic-gaps`, `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 and completed per-finding `UNVERIFIABLE` responses become `verification-error`; the engine never fabricates a `DISAGREE` vote. The report writer does not vote and consumes only a validated terminal state. Newly finalized convergence output is schema v1.3; under the compatibility path, valid historical final schema versions v1.0, v1.1, or v1.2 are reused and consumed without rewrite.
332
+ The lead writes the grouped input, then advances it through the internal admin CLI operations `okstra convergence seed`, `plan-round`, `apply-round`, optional `apply-critic-gaps`, `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. Its prompt carries an exact task type and active-phase forbidden-actions block; dispatch validates those phase anchors, and the run validator rejects a recorded phase-boundary violation. Terminal worker non-results and completed per-finding `UNVERIFIABLE` responses become `verification-error`; the engine never fabricates a `DISAGREE` vote. The report writer does not vote and consumes the validated terminal convergence state as a named input alongside every analysis-worker result. It completes three artifacts—the final-report data.json, its rendered Markdown sibling, and a worker-result pointer that lists those two outputs plus the convergence state—while the heartbeat/read-confirmation audit remains separate. Newly finalized convergence output is schema v1.3; under the compatibility path, valid historical final schema versions v1.0, v1.1, or v1.2 are reused and consumed without rewrite.
332
333
 
333
334
  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.
334
335
 
@@ -795,7 +796,7 @@ Errors that occur while workers (Claude/Codex/Antigravity worker, Report writer,
795
796
  - **Per-block cap on the codex log copy** (`okstra_log_mirror` in `scripts/okstra-codex-exec.sh`): workers read their required inputs end-to-end per the Worker Preamble's *Reading rules*, so a single report read can dump 170KB+ into the log and observed sidecars reach 8MB. The mirror keeps the first `log_block_line_cap` (120) lines of each output block and replaces the remainder with a `[okstra log-mirror] N line(s) elided` marker, draining every 500 elided lines so the idle watchdog keeps seeing writes. **Only the log copy is capped** — the stdout passthrough stays byte-identical, so the dispatching subagent's `BashOutput` and Phase 5 synthesis are unaffected (`tests-js/codex-log-mirror.test.mjs` asserts that byte-identity). The marker set is codex-specific; the claude wrapper emits `--output-format=stream-json` and does not share this filter.
796
797
  - When tmux is reachable in the lead environment, the wrapper automatically splits a sibling pane and runs `tail -F <log-path>`. The trace-pane title appends `-tail` to the caller (worker) pane title: `<cli>-<role>-<pid>-tail` (for example, `codex-worker-93421-tail`). At the same time, the caller (worker) pane title is set to `<cli>-<role>-<pid>`. `<pid>` is the wrapper's own PID, so multiple workers with the same role spawned concurrently remain distinguishable, and operators can visually map `<caller> ↔ <caller>-tail`. **Caller-pane resolution**—because the Claude Code Bash tool now removes both `$TMUX` and `$TMUX_PANE` from the environment, the wrapper does not depend on environment variables. It (1) derives `<RUN_DIR>` as `dirname(dirname(prompt_path))` from the prompt path (paths.py SSOT), and (2) reads `<RUN_DIR>/state/lead-pane.id`, written once by the lead in its foreground pane, as the split anchor. This remains reliable for background dispatches, unlike active-pane guessing, even if the user changes panes. If the file is absent or the pane is stale, it falls back to `tmux display-message -p '#{pane_id}'` (the active pane). The trace split explicitly anchors to that caller pane with `-t`. The role is the wrapper's fifth optional positional argument and defaults to `worker`. The caller pane title is captured and restored by an EXIT trap, preventing stale titles across dispatches. Focus returns to the caller pane, and the trace pane remains after CLI exit so its scrollback is available. All paths silently degrade when tmux is unreachable, splitting fails, or tmux is outdated.
797
798
  - **Run-scoped tagging for cleanup**: A trace pane's `tail -F` is a child of the tmux shell and survives Claude's exit. The wrapper tags each spawned pane with `tmux set-option -p @okstra_trace_run=<RUN_DIR>`, and `okstra-trace-cleanup.sh` discovers panes server-wide from that tag via `tmux list-panes -a` and runs `tmux kill-pane`. It requires neither tmux environment variables nor a pane-ID registry. Because the tag is run-scoped, it does not kill trace panes from other simultaneous okstra runs. Cleanup has two entry forms: the lead invokes it with `--run-dir <RUN_DIR>` to clean traces and worker-agent panes for that run, or the `hooks.SessionEnd` entry in `templates/reports/settings.template.json` invokes it with `--reap` to clean all trace panes tagged below `$CLAUDE_PROJECT_DIR/.okstra/` when no single run directory exists at session end. Missing tmux and stale pane IDs silently degrade.
798
- - **Automatic cleanup on phase transitions, including worker-agent panes**: `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` closes not only tagged trace panes but also worker-agent panes occupied by dispatched subagents. These harness-owned panes cannot be tagged, so the script identifies them within the lead session (`tmux list-panes -s -t <lead-pane>`) through a title allowlist: `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`. Implementation role titles such as `claude-executor` / `codex-verifier` / `agy-executor-tail`, and FleetView teammate prefixes `✳ ` / `⠂ `, are also treated as okstra panes. Session scoping and exclusion of the lead's own pane are determined by `<RUN_DIR>/state/lead-pane.id`; the lead pane is never killed even if its title matches. Immediately before dispatching workers for a new phase (before the `PROGRESS: phase-5.5-convergence` / `phase-6-synthesis` marker), the lead calls this script with `--run-dir` to clean previous-phase panes without prompting.
799
+ - **Automatic cleanup on phase transitions, including worker-agent panes**: `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` closes not only tagged trace panes but also worker-agent panes occupied by dispatched subagents. These harness-owned panes cannot be tagged, so the script identifies them within the lead's window (`tmux list-panes -t <lead-pane>`, no `-s`) through a title allowlist: `claude-worker` / `codex-worker` / `antigravity-worker` / `report-writer-worker`. Implementation role titles such as `claude-executor` / `codex-verifier` / `agy-executor-tail`, and FleetView teammate prefixes `✳ ` / `⠂ `, are also treated as okstra panes. Split-pane teammates always land in the lead's window, so window scope still catches all of this run's worker panes while leaving a second okstra lead running in another window of the same tmux session out of range. Window scoping and exclusion of the lead's own pane are determined by `<RUN_DIR>/state/lead-pane.id`; the lead pane is never killed even if its title matches. At every worker round boundary after collecting that round's results and token usage, immediately before the next dispatch and before the `PROGRESS: phase-5.5-convergence` / `phase-6-synthesis` marker the lead calls this script with `--run-dir` to reclaim the prior round's completed panes without prompting. `--keep <substr>` (repeatable) excludes panes whose title contains the substring, which is how an in-flight `report-writer-worker` survives the boundary. The lead first runs the same command with `--list` to count the panes it is about to reclaim and reports that count as `PROGRESS: phase-batch-cleanup panes=<n>`.
799
800
  - **User confirmation at phase end**: At the final step of the run, the lead calls `okstra-trace-cleanup.sh --list --run-dir <RUN_DIR>` to show remaining okstra panes (worker-agent + trace), then asks once whether to "close all and clean up teammates / keep them." It follows the response (see *Phase wrap-up* in `prompts/profiles/_common-contract.md`). If approved, the lead cleans the panes. For a split-pane run, it then uses `okstra-team-reconcile.sh` to mark dead-pane members inactive and sends each completed teammate a `SendMessage` shutdown_request (`TeamDelete` was removed in v2.1.178; the implicit team disappears with the session). The lead does not gate this pane step by interpreting `lead-pane.id`; it **always** invokes the script, which safely returns an empty pane list and no-ops outside tmux. The teammate step is determined by the existence of an on-disk team configuration whose `leadSessionId` matches (`~/.claude/teams/session-*/config.json`), not by `teamCreate.status`. `--list` does not kill panes and prints only `<pane_id>\t<pane_title>`, so the user can see exactly what would be closed.
800
801
  - Disk accumulation is handled by the `okstra-inspect logs` flow, which offers a read-only inventory and suggests cleanup commands for the user to copy and paste.
801
802
 
@@ -813,6 +814,7 @@ Tokens used in each run are collected from lead/worker session transcripts and w
813
814
  - Claude lead/workers: per-message `message.usage` in `~/.claude/projects/<cwd-as-dashes>/<sessionId>.jsonl` or `~/.claude/projects/<cwd-as-dashes>/<lead-session>/subagents/agent-a<worker-name>-<hash>.jsonl`. Worker names are recovered from nested-subagent filenames, and only the directory for the current run's `team-state.lead.sessionId` is counted.
814
815
  - Codex CLI: final `total_token_usage.total_tokens` in `~/.agent/sessions/Y/M/D/rollout-*.jsonl`
815
816
  - Antigravity CLI: per-message `tokens.total` in `~/.antigravity/tmp/*/chats/session-*.json`
817
+ - CLI execution evidence and token attribution are independent. A wrapper `.status.json` proves `not-started`, `started`, `exited`, `timeout`, or `failed` and supplies the worker's collection window; only a matching transcript with a final token snapshot proves attributable usage. If a wrapper exited successfully but no attributable transcript exists, the worker remains `source: "unavailable"` with `cliExecutionStatus: "exited"` and a reason instead of becoming zero usage or being described as never invoked.
816
818
  - Records billable-equivalent token math and USD cost estimates. It applies Anthropic billing ratios (`cache_creation_5m=1.25x`, `cache_creation_1h=2.0x`, `cache_read=0.1x`, `output=5x`). When the transcript provides separate `usage.cache_creation.ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens` values, they are counted separately.
817
819
  - Pricing is centrally managed in `scripts/okstra_token_usage/pricing.py`. Update it when model prices change. Model IDs that fail price matching are exposed to the user in `usageSummary.unmatchedModels`, preventing silent-zero incidents.
818
820
  - Project-wide historical usage is exposed through the read-only `okstra usage-report` command (`src/commands/inspect/usage-report.mjs` → `scripts/okstra_ctl/usage_report.py`) and the `okstra-usage` skill. It defaults to the whole current project's last 30 days and returns run coverage, raw and billable-equivalent tokens, known USD cost, CPU-sum milliseconds, and wall-clock milliseconds grouped by task type. Runs without usable Phase 7 usage are excluded from resource totals and reported through unavailable reason counts rather than treated as zero usage; unmatched model names remain visible when their tokens and time are included but their cost is not. Use `okstra-inspect` for one task's elapsed/context detail and `okstra-rollup` for task-group or project status/report digests.
package/docs/cli.md CHANGED
@@ -375,6 +375,16 @@ Lead runtime independence boundary:
375
375
 
376
376
  The current Claude Code independence boundary covers the external lead prompt and `okstra team *` worker dispatch. non-render `okstra_ctl.run --lead-runtime external` remains blocked; a complete external lead driver remains separate future work. `--runtime external` only selects the runtime adapter. `okstra install` creates `~/.agents/skills/` by default and also installs Claude skills and agents when `~/.claude` exists. Selecting the `claude` worker still requires the local Claude CLI wrapper.
377
377
 
378
+ Host-runtime readiness is independent of worker selection. When `/okstra-setup`
379
+ creates `<PROJECT_ROOT>/.claude/settings.local.json` in an already-open Claude
380
+ Code session, that session may not have accepted workspace trust yet. The next
381
+ `/okstra-run` preflight reports `runtimeReadiness.checks[id=workspace-trust]`
382
+ and stops before the wizard when trust is required or cannot be verified. The
383
+ user reopens the project, accepts the one Claude Code workspace prompt, and
384
+ reruns the command. This check applies only to a `claude-code` host; `codex` and
385
+ `external` hosts do not inspect Claude Code state, regardless of whether the
386
+ worker roster contains Claude, Codex, or Antigravity.
387
+
378
388
  ### Runtime auto-detection (`auto`)
379
389
 
380
390
  `okstra run` defaults to `auto`. `auto` resolves to one of `claude-code`, `codex`, or `external` based on the host through `src/lib/runtime-resolver.mjs`. Precedence: explicit runtime > the `OKSTRA_RUNTIME_HOST` environment variable > Claude Code skill handoff > external when tmux is available > fail fast otherwise. The safe fallback never silently selects a runtime different from the user's intent.
@@ -686,7 +696,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
686
696
  | `okstra doctor [--runtime claude-code\|codex\|external\|all] [--phase <phase>] [--json]` | Diagnose the runtime, Python imports, and skill/agent installation. The `codex` and `external` runtimes omit Claude skill checks. `--phase` adds readiness checks for `implementation`, `final-verification`, `release-handoff`, or `improvement-discovery` |
687
697
  | `okstra setup --project-id <id>` | Create or update `.okstra/project.json` in the current project |
688
698
  | `okstra check-project [--json]` | Verify that the current project is registered |
689
- | `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 |
699
+ | `okstra preflight [--runtime <name>] [--cwd <dir>] [--json]` | Single skill-preflight call combining `ensure-installed`, with silent reinstall when stale, `check-project`, and host-specific `runtimeReadiness` into one JSON response. A `claude-code` host checks project workspace trust; `codex` and `external` hosts return ready without reading Claude Code state. Step 0 of every project-scoped skill converges on this command |
690
700
  | `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 |
691
701
  | `okstra convergence plan-round --work-state <path> --plan <path>` | Persist the next roster-aware dispatch plan without mutating working state |
692
702
  | `okstra convergence apply-round --work-state <path> --plan <path> --results <path>` | Validate one complete structured result set and atomically reduce it into working state |
@@ -696,13 +706,13 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
696
706
  | `okstra convergence example --kind <groups\|round-results\|critic-results>` | Print one deterministic valid input example as JSON |
697
707
  | `okstra plan-items extract --data <data.json> --output <items.json>` | Deterministically extract the complete implementation-planning `P-*` queue from report-writer data.json |
698
708
  | `okstra plan-items validate --data <data.json> --items <items.json>` | Require the persisted `P-*` queue to match a fresh deterministic extraction exactly |
699
- | `okstra phase-cleanup --project-root <dir> [--task-key <k>] [--run-dir <dir>] [--fallback-team <label>] [--json]` | Reclaim the resources the previous phase or worker batch finished with, so the next phase does not inherit them. It is tmux-aware: inside a tmux pane it reclaims the prior run's **completed** worker panes through `okstra-trace-cleanup.sh --reclaim-completed`, and outside tmux there are no panes, so it skips pane reclaim entirely. Either way it reconciles teammates through `okstra-team-reconcile.sh` and prints the dismissible teammate names for the lead to shut down — it names them, it never dismisses them itself. Only completed resources are touched: the lead pane and any in-flight worker are preserved. `--project-root` is required and is enough on its own for the teammate half. `--fallback-team <label>` passes the live team label (`session-<lead-session-prefix>`) that the reconcile falls back to when the live session directory is gone: Claude Code re-issues the session id on resume or compaction, and without the label the roster resolves to nothing and the dismissible-teammate list comes back empty — so every caller inside a run should pass it. The prior run is located by `--run-dir` when given, otherwise auto-discovered from `--task-key` — auto-discovery walks both flat `runs/<type>/reports/` and staged `runs/<type>/stage-N/reports/` (`implementation` / `final-verification`), so a staged prior run is found without `--run-dir`; pass `--run-dir` only to override the discovery with a specific run. Output is the `mode` / `panes-reclaimed` / `dismissible-teammates` triple, or the same values as JSON under `--json`. A cleanup failure never blocks the next phase: a missing script, a failed helper, or an undiscoverable prior run still exits 0 (only a malformed invocation exits non-zero) |
700
709
  | `okstra config <get\|set\|unset\|show> [key] [value] [--scope project\|global\|all]` | Manage persistent settings such as `pr-template-path` with atomic JSON writes |
701
710
  | `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 |
702
711
  | `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 |
703
712
  | `okstra rollup [--task-group <group>] [--project-root <dir>] [--cwd <dir>]` | Read-only backend for the okstra-rollup skill. For every catalog task, or one task group, it emits JSON with per-task run counts, raw duration in ms, error counts, latest report paths, group totals, and status/category/phase distributions. Omitting `--task-group` targets the whole project catalog. The caller skill formats raw ms as HH:MM:SS and synthesizes report prose. Use the `okstra inspect` family for a single-task drill-down |
704
713
  | `okstra usage-report [--days <positive-int>] [--project-root <dir>] [--cwd <dir>] [--json]` | Read-only backend for the okstra-usage skill. Defaults to the whole current project's last 30 days and emits task-type run coverage, raw/billable tokens, known USD cost, CPU-sum milliseconds, wall-clock milliseconds, unavailable reason counts, and unmatched pricing models |
705
- | `okstra worker-liveness [--audit <path>]… [--prompt <path>]… [--max-idle <seconds>] [--launch-grace <seconds>] [--json]` | Judge whether pending workers are still alive so the lead's poll ends a stalled wait early instead of paying the full deadline. Both selectors repeat and may be mixed in one call. `--audit` takes a claude-worker audit sidecar and reports `stalled` when its `- PROGRESS:` heartbeat is past the idle budget; `--prompt` takes a CLI-wrapper prompt-history path and reports `did-not-launch` when no sibling `.log`/`.status.json` appeared past the launch grace. The two selectors are not interchangeable: only the `okstra-*-exec.sh` wrappers write the `.log`/`.status.json` pair, so pointing `--prompt` at an in-process claude-worker reports `did-not-launch` for a healthy worker every time past the grace — probe those with `--audit`. Healthy probes report `live`. It only judges—it never kills or re-dispatches. Exit 1 on a problem verdict, so a poll loop can branch without parsing JSON. The heartbeat line shape and the 5-minute (+60s grace) budget come from the `okstra_ctl.worker_heartbeat` SSOT shared with the Phase 7 audit (`validators/validate_session_conformance.py`), so a worker the live probe passes cannot fail the post-hoc audit on cadence |
714
+ | `okstra worker-state transition --team-state <path> --worker <id> --status <in-progress\|completed\|timeout\|error\|not-run> [--reason <text>] [--model <execution-value>]` | Atomically update one persisted worker row. `in-progress` records the authoritative `startedAt` and clears `endedAt`; terminal states record `endedAt`; `timeout`, `error`, and `not-run` require a reason. Dispatch adapters use this same transition path, so CLI-backed and in-process orchestration share the status timestamp contract |
715
+ | `okstra worker-liveness [--audit <path>]… [--team-state <path> --worker <id>]… [--max-idle <seconds>] [--launch-grace <seconds>] [--json]` | Judge whether pending workers are still alive so the lead's poll ends a stalled wait early instead of paying the full deadline. Both selectors repeat and may be mixed in one call. `--audit` takes an in-process worker audit sidecar and reports `stalled` when its `- PROGRESS:` heartbeat is past the idle budget. Each `--team-state` must have a paired `--worker`; that selector resolves the worker's prompt and starts launch grace from its persisted `startedAt`, then reports `did-not-launch` when neither the wrapper `.log` nor `.status.json` appears. Healthy probes report `live`. It only judges—it never kills or re-dispatches. Exit 1 on an unhealthy verdict, so a poll loop can branch without parsing JSON. The heartbeat line shape and budget come from the `okstra_ctl.worker_heartbeat` SSOT shared with the Phase 7 audit (`validators/validate_session_conformance.py`) |
706
716
  | `okstra log-report [--project-root <dir>] [--cwd <dir>] [--top <N>] [--json]` | Read-only inventory of wrapper transcript `.log` files and their sibling prompt `.md` files. Each ranked entry preserves `path` / `sizeBytes` for compatibility and also reports `transcriptPath`, `transcriptBytes`, `promptPath`, `promptBytes`, and `transcriptToPromptRatio`; totals distinguish prompt bytes from transcript bytes and count paired files. Ranking remains transcript-size descending |
707
717
  | `okstra recap <assemble\|record\|note> <task-root\|task-key> …` | Backend for the okstra-inspect `recap` facet. `assemble` is read-only and prints a JSON summary of phase transitions across a task's runs. `record --kind <summary\|qa> --mode <artifact\|code> --answer <text> [--question <text>] [--citation <path:line> …]` appends one line to `<task-root>/recap/recap-log.jsonl` and never mutates other artifacts. `note --kind <verification-evidence\|decision-draft\|analysis-note> --slug <topic> --purpose <text> --scope-note <text> (--body <markdown>\|--body-file <path>)` writes an agent-authored note to `<task-root>/notes/` and prints its path plus the `--clarification-response` argument for feeding it into a later run |
708
718
  | `okstra user-response <list\|show\|write> …` | Backend for the `/okstra-user-response` skill: answer a task's open clarification questions in-session and write the response sidecar. `list --home <dir> --project <id> [--limit <n>]` finds reports with open questions; `show --report <md>` reads one report's questions; `write --report <md> --answers <json> [--approval <json>] [--task-key <key>]` writes the sidecar. Each answer carries a `disposition` of `answer` or `reframe`; a `reframe` is carried into the next run as a re-scoped brief. JSON output; exit 0 ok / 1 error |
@@ -724,7 +734,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
724
734
  | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run the whole Phase 7 post-report sequence in its contractual order: `token-usage` → `render-views` → `spawn-followups` → `validate-run`. Stops at the first non-zero exit and names the failing step, then prints a per-step `[ok]` / `[FAIL]` / `[skip]` summary on stderr so the outcome is legible without parsing the JSON payload. Every step is idempotent, so re-running after a fix is safe — but `--only <step>` (repeatable) reruns just the named steps in contractual order, which matters because `validate-run` is the step that usually fails and retrying it otherwise repeats the three steps before it at full token and wall-clock cost. This is the same code path (`scripts/okstra_ctl/report_finalize.py`) the Codex lead adapter runs automatically after its report-writer completes, so a Claude-led and a Codex-led run finalize identically. `--workspace-root` is owned by the Node wrapper. Prefer this over invoking the four steps individually |
725
735
  | `okstra render-views <final-report.md>` | The Phase 7 `render-views` step, runnable on its own: deterministically create a human-facing self-contained sibling `*.html` view from one final-report Markdown file after token substitution. The source Markdown is unchanged. The Node delegation wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies form-control placement, absence of external URLs, stale source digests, and Response ID parity |
726
736
  | `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
727
- | `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, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle 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 |
737
+ | `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. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `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, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle 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 |
728
738
  | `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 |
729
739
 
730
740
  The convergence state lifecycle is `groups v1.0 → work v1.0 → final v1.3`; round-plan, round-results, and optional critic-results v1.0 artifacts provide the auditable transitions between those endpoints.
@@ -771,4 +781,4 @@ Both `wait_for_input` and `replan` stop before a stage worktree is provisioned a
771
781
 
772
782
  ### Live-log sidecar
773
783
 
774
- For every dispatch, the Codex and Antigravity wrappers create a `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` sidecar and mirror stdout and stderr into it. When the lead runs inside tmux, the wrapper automatically splits a `tail -F` pane. The trace pane title is `<cli>-<role>-<pid>-tail`, and the caller/worker pane title is `<cli>-<role>-<pid>`; the wrapper PID distinguishes concurrent dispatches with the same role. Split trace panes are tagged with the `@okstra_trace_run=<RUN_DIR>` pane user option, and tmux-pane backend worker-compute panes with `@okstra_worker_run=<RUN_DIR>`. When Claude receives `/exit`, the `SessionEnd` hook automatically cleans them up within `$CLAUDE_PROJECT_DIR/.okstra/` scope by running `okstra-trace-cleanup.sh --reap`. When the lead calls the same script with `--run-dir <RUN_DIR>`, it removes the run's trace panes, worker-compute panes, and dispatched worker-agent panes within the lead-session scope, while excluding the lead's own pane. Worker-agent titles include `claude-worker`, `codex-worker`, `antigravity-worker`, `report-writer-worker`, implementation role titles, and FleetView teammate prefixes `✳ ` / `⠂ `. Before entering a new phase, the lead runs `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` once to clear stale panes.
784
+ For every dispatch, the Codex and Antigravity wrappers create a `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` sidecar and mirror stdout and stderr into it. When the lead runs inside tmux, the wrapper automatically splits a `tail -F` pane. The trace pane title is `<cli>-<role>-<pid>-tail`, and the caller/worker pane title is `<cli>-<role>-<pid>`; the wrapper PID distinguishes concurrent dispatches with the same role. Split trace panes are tagged with the `@okstra_trace_run=<RUN_DIR>` pane user option, and tmux-pane backend worker-compute panes with `@okstra_worker_run=<RUN_DIR>`. When Claude receives `/exit`, the `SessionEnd` hook automatically cleans them up within `$CLAUDE_PROJECT_DIR/.okstra/` scope by running `okstra-trace-cleanup.sh --reap`. When the lead calls the same script with `--run-dir <RUN_DIR>`, it removes the run's trace panes, worker-compute panes, and dispatched worker-agent panes within the lead-window scope (title scan uses `tmux list-panes -t <lead-pane>`, no `-s`, so a second lead in another window of the same session is out of range), while excluding the lead's own pane. Worker-agent titles include `claude-worker`, `codex-worker`, `antigravity-worker`, `report-writer-worker`, implementation role titles, and FleetView teammate prefixes `✳ ` / `⠂ `. The lead runs `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` at every worker round boundary — after collecting that round's results and before the next dispatch, not once per phase — to reclaim the completed panes. `--keep <substr>` (repeatable) excludes panes whose title contains the substring, which is how an in-flight report writer is preserved (`--keep report-writer-worker`), and `--list` prints the same set without killing so the lead can count what it is about to reclaim.
@@ -145,7 +145,7 @@ Runtime/install asset changes follow this checklist:
145
145
 
146
146
  `--link <repo>` mode is for development and symlinks installed files back to repo sources.
147
147
 
148
- `src/lib/runtime-resolver.mjs` is the single reference point for runtime auto-detection. `okstra install` defaults to `--runtime auto`, records the request and any successful resolution in `installed-runtimes.json` schemaVersion 2, and still copies the shared runtime payload from the installed package `runtime/` tree even when host detection is unavailable. Skill targets always include the default Agent-compatible `~/.agents/skills` target, with `~/.claude/skills` also populated when `~/.claude` exists. Dynamic capabilities such as `tmux` and `codex` CLI availability are checked by `doctor` and `run`, not frozen into the install manifest. Claude Code skills pass explicit `--runtime claude-code` / `--lead-runtime claude-code` so they never depend on host auto-detection.
148
+ `src/lib/runtime-resolver.mjs` is the single reference point for runtime auto-detection. `src/lib/runtime-readiness.mjs` owns host-specific pre-dispatch readiness behind one provider-neutral result shape; its Claude Code adapter checks project workspace trust, while Codex and external hosts do not inspect Claude state. `okstra install` defaults to `--runtime auto`, records the request and any successful resolution in `installed-runtimes.json` schemaVersion 2, and still copies the shared runtime payload from the installed package `runtime/` tree even when host detection is unavailable. Skill targets always include the default Agent-compatible `~/.agents/skills` target, with `~/.claude/skills` also populated when `~/.claude` exists. Dynamic capabilities such as `tmux` and `codex` CLI availability are checked by `doctor` and `run`, not frozen into the install manifest. Claude Code skills pass explicit `--runtime claude-code` / `--lead-runtime claude-code` so they never depend on host auto-detection.
149
149
 
150
150
  ---
151
151
 
@@ -165,13 +165,12 @@ Runtime/install asset changes follow this checklist:
165
165
  | `doctor` | `src/commands/lifecycle/doctor.mjs` | Diagnose runtime and Python imports |
166
166
  | `setup` | `src/commands/lifecycle/setup.mjs` | Create/update `<PROJECT_ROOT>/.okstra/project.json` |
167
167
  | `check-project` | `src/commands/lifecycle/check-project.mjs` | Verify project registration |
168
- | `preflight` | `src/commands/lifecycle/preflight.mjs` | One-call skill preflight: ensure-installed + check-project (single JSON) |
168
+ | `preflight` | `src/commands/lifecycle/preflight.mjs` | One-call skill preflight: ensure-installed + check-project + host-specific runtime readiness (single JSON) |
169
169
  | `config` | `src/commands/lifecycle/config.mjs` | Read/write project/global settings such as PR template path |
170
170
  | `migrate` | `src/commands/lifecycle/migrate.mjs` | One-shot legacy `.project-docs/okstra` → `.okstra` migration helper |
171
171
  | `git-reconcile` | `src/commands/execute/git-reconcile.mjs` | Reconcile stale stage SHAs after external git history changes |
172
172
  | `handoff` | `src/commands/execute/handoff.mjs` | Stage-group release-handoff eligibility / assemble / record helpers |
173
173
  | `integrate-stages` | `src/commands/execute/integrate-stages.mjs` | Merge verified stages into the task worktree and clean stage worktrees |
174
- | `phase-cleanup` | `src/commands/execute/phase-cleanup.mjs` | Reclaim the prior phase/batch's completed panes and teammates before the next phase starts; tmux-aware, and it preserves the lead pane and in-flight workers (Python: `okstra_ctl.phase_cleanup`) |
175
174
  | `task-list`, `task-show` | `src/commands/inspect/task-list.mjs`, `src/commands/inspect/task-show.mjs` | Task/run introspection for skills; `task-show` consumes the Python task read-side snapshot |
176
175
  | `resolve-task-key` | `src/commands/inspect/resolve-task-key.mjs` | Resolve a bare task-id to candidate task-keys from the project catalog |
177
176
  | `set-work-status` | `src/commands/inspect/set-work-status.mjs` | Set a task's user-managed `workStatus` in task-manifest.json (Python: `okstra_ctl.set_work_status`) |
@@ -223,7 +222,7 @@ Top-level scripts:
223
222
  | `okstra-render-report-views.py` | Render self-contained HTML views from final-report Markdown |
224
223
  | `okstra-error-log.py` | Normalize worker/lead error sidecars |
225
224
  | `okstra-spawn-followups.py` | Follow-up spawning helper |
226
- | `okstra-trace-cleanup.sh` | tmux okstra pane cleanup (worker-agent + trace, excluding the lead pane); the `--reclaim-completed` mode reclaims only trace panes whose `@okstra_status` is terminated (stage=exited) and preserves in-progress panes |
225
+ | `okstra-trace-cleanup.sh` | tmux okstra pane cleanup (worker-agent + trace, excluding the lead pane), called by the lead at every worker round boundary — not once per phase; `--keep <substr>` (repeatable) spares panes whose title contains the substring, which is how an in-flight `report-writer-worker` survives a boundary; `--list` prints what would be reclaimed without killing; the `--reclaim-completed` mode reclaims only trace panes whose `@okstra_status` is terminated (stage=exited) and preserves in-progress panes |
227
226
  | `okstra-subagent-reclaim.sh` | entry that walks active runs and reclaims only completed trace panes (wired to the `SubagentStop`/`TaskCompleted` hooks) |
228
227
 
229
228
  ### 4.3 `scripts/okstra_ctl/` — Python orchestration core
@@ -269,7 +268,6 @@ Important modules:
269
268
  | `run_index_row.py` | single reference point for creating / slimming / hydrating a `~/.okstra` run-index row — runId SSOT, preserves projectId raw |
270
269
  | `error_report.py`, `error_log_core.py`, `error_zip.py` | backend for the okstra-inspect errors/error-zip facets — `error_log_core` is the read-only core that globs/parses/aggregates `errors-*.jsonl`, `error_report` renders the errors facet, and `error_zip` collects cross-project run directories, allowlist-anonymizes, aggregates clusters, and produces a zip |
271
270
  | `worker_heartbeat.py`, `worker_liveness.py` | `worker_heartbeat` is the single definition of the `- PROGRESS:` heartbeat line shape and its 5-minute (+60s grace) cadence budget, shared by the Phase 7 audit (`validators/validate_session_conformance.py`) and the live probe; `worker_liveness` backs `okstra worker-liveness`, reporting a pending worker as `stalled` (heartbeat past the budget) or `did-not-launch` (no wrapper `.log`/`.status.json` past the launch grace) |
272
- | `phase_cleanup.py` | backs `okstra phase-cleanup` — decides tmux vs in-process mode, resolves the prior run dir (explicit `--run-dir`, else the newest FLAT run for a `--task-key`), and sequences the existing `okstra-trace-cleanup.sh --reclaim-completed` and `okstra-team-reconcile.sh` primitives. It never re-implements pane kill or completion detection, and it degrades to "nothing to report" instead of propagating a helper failure, so cleanup cannot block the next phase |
273
271
  | `log_report.py`, `time_report.py` | read-side backend for the okstra-inspect logs/time facets (`okstra log-report` pairs each wrapper transcript `.log` with its sibling prompt `.md` and reports both byte counts without changing legacy transcript-size fields; `okstra time-report` is per-task time aggregation) |
274
272
  | `rollup.py` | read-side backend for the okstra-rollup skill — fans the catalog out per task-group (or the whole project) and deterministically aggregates each task's run count, elapsed time (raw ms), error count, and latest report path, plus group-level totals/status, category, and phase distribution. Reuses the `time_report`/`error_log_core` functions and delegates report-body synthesis to the skill |
275
273
  | `usage_report.py` | Read-only okstra-usage backend — scans the whole current project's recent run timelines, defaults to 30 days, and returns task-type coverage, raw/billable tokens, known USD cost, CPU-sum and wall-clock milliseconds, unavailable reason counts, and unmatched pricing models |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.145.0",
3
+ "version": "0.146.1",
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.145.0",
3
- "builtAt": "2026-08-02T12:19:03.119Z",
2
+ "package": "0.146.1",
3
+ "builtAt": "2026-08-03T06:04:45.516Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -14,7 +14,7 @@ model: inherit
14
14
  tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "TodoWrite", "WebFetch", "WebSearch"]
15
15
  ---
16
16
 
17
- **Author the final-report data.json** (the JSON SSOT) at the assigned `Result Path`, plus an audit sidecar that is the `Report writer worker`'s sole responsibility for okstra cross-verification. You are NOT an analysis worker — you do not produce independent findings, you do not vote in convergence, and you do not re-do the workers' analysis.
17
+ **Author the three report completion artifacts**: the final-report data.json (the JSON SSOT) at the assigned `Result Path`, its rendered Markdown sibling, and the worker-result pointer at `Worker Result Path`. Maintain the separate heartbeat audit sidecar at `Audit sidecar path`. That is the `Report writer worker`'s sole responsibility for okstra cross-verification. You are NOT an analysis worker — you do not produce independent findings, you do not vote in convergence, and you do not re-do the workers' analysis.
18
18
 
19
19
  - The `**Report Language:**` header in your dispatch prompt is already
20
20
  resolved to `en` or `ko` by the lead. Copy it verbatim into
@@ -30,14 +30,13 @@ If you find yourself thinking "I'll just write the markdown directly" — stop.
30
30
 
31
31
  ## Worker Result File (MANDATORY)
32
32
 
33
- Write the required worker-result record at the lead-registered `**Worker Result Path:**`. The validator checks this file exists whenever the role's terminal status is `completed`. Schema: short YAML frontmatter (`workerId: "report-writer"`, plus the canonical fields copied verbatim from `analysis-material.md` per `team-contract`) followed by:
33
+ Write the required worker-result record at the lead-registered `**Worker Result Path:**`. Both dispatch adapters include it in `WorkerJob.completion_paths` and refuse `completed` while it is absent. Schema: short YAML frontmatter (`workerId: "report-writer"`, plus the canonical fields copied verbatim from `analysis-material.md` per `team-contract`) followed by:
34
34
 
35
35
  1. The canonical data.json path you wrote (project-relative).
36
36
  2. The rendered markdown path produced by the renderer (project-relative).
37
- 3. Inputs reconciled (analysis-worker result files + convergence-state file).
38
- 4. Any structural deviations from the `<instruction-set>/final-report-schema.json` excerpt and the reason.
37
+ 3. The convergence-state input path from the prompt (project-relative).
39
38
 
40
- Do NOT duplicate the data.json contents here the data.json is the canonical artifact; this sidecar is the validator-required pointer / audit record.
39
+ Keep the data.json contents and analysis-worker result list out of this file: the data.json is the canonical artifact and the analysis results remain prompt inputs. This file is the dispatch-required three-path pointer record.
41
40
 
42
41
  ## Heartbeat (BLOCKING)
43
42
 
@@ -123,7 +122,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
123
122
  - For `implementation-planning`, populate `implementationPlanning.variationPointAnalysis` — a `hasMultipleImplementations` judgement synthesized from the analysis workers' output, not a field filled in last. When it is `true`, write one `points[]` row per varying behavior carrying `behavior`, the two or more `implementations` that serve it, `evidence` (a `path:line`, or the sibling task / stage that already implements that behavior), and an `extractionDecision` of `extract` / `interfaceKind` / `coveredBy` (the Stage Map stage that builds the interface) / `rationale`; when it is `false`, write a non-empty `noVariationRationale` and leave `points` empty (the two branches are mutually exclusive). Do NOT pass a boilerplate rationale — `false` is the cheaper field to fill, and a `false` declaration the brief or the sibling code in the workers' evidence contradicts is a `P-Var` DISAGREE, not a saving. Also populate `implementationPlanning.recommendedOption.testSeams`: one row per boundary a test injects at and replaces, each carrying `boundary` / `injectedAs` / `replacedInTest`. An empty list is a conscious "no seam needed" claim, never a default for a field nobody filled. The schema excerpt enumerates both row shapes — author against it. (Maintainer SSOT for these two rules: the `Required deliverable shape` bullet in `prompts/profiles/implementation-planning.md` in the okstra repo; that path is not resolvable here, so it is provenance, not a file to open.) **Enforced:** `schemas/final-report-v1.0.schema.json` `$defs.VariationPointAnalysis` / `$defs.VariationPoint` (the block is in `implementationPlanning.required`) plus `testSeams` in `$defs.RecommendedOption`'s `required`; `validators/validate-run.py` `_validate_variation_point_analysis` rejects a rationale-less `false`, a `false` carrying points, a `true` with no point, an `extract: true` decision leaving `interfaceKind` or `coveredBy` empty, and a hexagonal project extracting as anything but a port; and every point becomes a `P-Var-*` plan item judged in §5.5.9.
124
123
  - When the `Task Type` is `improvement-discovery`, populate `## 5.9 Improvement Candidates` with the 11-column schema enforced by `validators/validate_improvement_report.py`. The `Expected behavior after` cell states in one observable sentence what becomes different once the candidate is applied — it seeds the downstream brief's `EB-NNN` / `EO-NNN`, and an empty cell fails the run. Source the row IDs (`I-NNN`), lens whitelist, and Source workers patterns from `scripts/okstra_ctl/improvement_lenses.py` — do NOT introduce new lens names or worker prefixes. `improvement-discovery` is NOT in the data.json schema enum, so author its markdown directly (not via `okstra-render-final-report.py`). Immediately after writing the markdown, run (`Bash`): `okstra inject-report-index <markdown path> --report-language <en|ko>`. That adds the top-of-report Index plus `I-NNN` / `C-NNN` scroll anchors; the run validator fails the report when the Index anchor is absent.
125
124
 
126
- Write the data.json (and the audit sidecar `.md`) with your `Write` tool — that is the canonical authoring path, and okstra ships no hook that blocks `.md` writes (its only settings hook is the `SessionEnd` trace-cleanup; the coding-preflight hook emits reminders but never blocks). A Bash heredoc is acceptable ONLY when a specific `Write` call is genuinely rejected by the host environment, and it MUST produce byte-identical content — do not reach for it pre-emptively. Then invoke the renderer (`Bash`): `okstra render-final-report <data.json path>`. Confirm both files exist and respond with a short status line prefixed by your model identity, per the preamble §"Return message to the lead":
125
+ Write the three completion artifacts and the separate audit sidecar with your `Write` tool — that is the canonical authoring path, and okstra ships no hook that blocks `.md` writes (its only settings hook is the `SessionEnd` trace-cleanup; the coding-preflight hook emits reminders but never blocks). A Bash heredoc is acceptable ONLY when a specific `Write` call is genuinely rejected by the host environment, and it MUST produce byte-identical content — do not reach for it pre-emptively. After writing data.json, invoke the renderer (`Bash`): `okstra render-final-report <data.json path>`, then write the Worker Result Path pointer. Confirm data.json, rendered Markdown, the pointer, and the audit sidecar exist before responding with a short status line prefixed by your model identity, per the preamble §"Return message to the lead". **Enforced:** dispatch `completionPaths` requires the first three files and `validators/validate_session_conformance.py` validates the audit sidecar.
127
126
 
128
127
  ```
129
128
  **Model:** Report writer worker, <modelExecutionValue>
@@ -17,12 +17,13 @@
17
17
  # (b) worker-agent panes the harness gives to dispatched
18
18
  # subagents (`claude-worker` / `codex-worker` /
19
19
  # `antigravity-worker` / `report-writer-worker`), identified
20
- # by a title allowlist scoped to the LEAD's session. The
20
+ # by a title allowlist scoped to the LEAD's WINDOW. The
21
21
  # lead pane is read from `<RUN_DIR>/state/lead-pane.id`
22
22
  # (recorded once by the lead in its own foreground pane —
23
23
  # reliable even though Claude Code's Bash tool strips
24
24
  # `$TMUX`/`$TMUX_PANE`); it scopes the title scan and is
25
- # NEVER killed.
25
+ # NEVER killed. Window scope keeps a second lead running
26
+ # in another window of the same session out of range.
26
27
  #
27
28
  # --reap Used by the `SessionEnd` hook, where no single run-dir
28
29
  # applies. Closes every trace pane whose tag points under
@@ -33,6 +34,10 @@
33
34
  # `--list` (alias `--dry-run`) prints `<pane_id>\t<pane_title>` per pane instead
34
35
  # of killing — only meaningful with `--run-dir`.
35
36
  #
37
+ # `--keep <substr>` (repeatable) spares any pane whose current title contains
38
+ # <substr>, in both the kill and the list set. Used to preserve an in-flight
39
+ # report-writer at a round boundary.
40
+ #
36
41
  # Failures are tolerated silently — a stale pane id, no tmux, or a locked tmux
37
42
  # client must never prevent Claude from exiting cleanly.
38
43
 
@@ -57,6 +62,7 @@ MODE="kill" # kill | list
57
62
  RECLAIM=0 # 1: trace pane 은 @okstra_status 가 완료(exited)일 때만 회수 (--reclaim-completed)
58
63
  REAP=0
59
64
  run_dir=""
65
+ KEEP_PATTERNS=() # --keep <substr>: panes whose title contains substr are spared from kill/list
60
66
  while [[ $# -gt 0 ]]; do
61
67
  case "$1" in
62
68
  --list|--dry-run) MODE="list" ;;
@@ -64,13 +70,18 @@ while [[ $# -gt 0 ]]; do
64
70
  --reap) REAP=1 ;;
65
71
  --run-dir) shift; run_dir="${1-}" ;;
66
72
  --run-dir=*) run_dir="${1#--run-dir=}" ;;
73
+ --keep) shift; KEEP_PATTERNS+=("${1-}") ;;
74
+ --keep=*) KEEP_PATTERNS+=("${1#--keep=}") ;;
67
75
  -h|--help)
68
76
  cat <<'USAGE'
69
- usage: okstra-trace-cleanup.sh (--run-dir <RUN_DIR> [--list] [--reclaim-completed] | --reap)
77
+ usage: okstra-trace-cleanup.sh (--run-dir <RUN_DIR> [--list] [--reclaim-completed] [--keep <substr>]... | --reap)
70
78
 
71
79
  --run-dir okstra run directory; closes that run's trace + worker-agent panes.
72
80
  --list with --run-dir: print "<pane_id>\t<pane_title>" per pane; no kill.
73
81
  --dry-run alias for --list.
82
+ --keep <substr> exclude any pane whose title contains <substr> from the
83
+ kill/list set (repeatable). Used to spare an in-flight
84
+ report-writer at a round boundary.
74
85
  --reclaim-completed with --run-dir: restrict trace panes to those whose
75
86
  @okstra_status sidecar is terminal (stage=exited); in-flight
76
87
  and teammate panes are preserved. Skips the title-allowlist
@@ -149,6 +160,18 @@ _title_in_okstra_scope() {
149
160
  return 1
150
161
  }
151
162
 
163
+ # A collected pane whose current title contains any --keep substring is spared.
164
+ # Applied at the final emit so both the tag scan and the title scan honour it.
165
+ _keep_excluded() {
166
+ local pid="$1" title pat
167
+ (( ${#KEEP_PATTERNS[@]} )) || return 1
168
+ title=$(tmux display-message -p -t "$pid" '#{pane_title}' 2>/dev/null || true)
169
+ for pat in "${KEEP_PATTERNS[@]}"; do
170
+ [[ -n "$pat" && "$title" == *"$pat"* ]] && return 0
171
+ done
172
+ return 1
173
+ }
174
+
152
175
  collect_okstra_panes() {
153
176
  local -a panes=()
154
177
  local pid trace_tag worker_tag status_tag title
@@ -177,12 +200,15 @@ collect_okstra_panes() {
177
200
  done < <(tmux list-panes -a \
178
201
  -F '#{pane_id}'$'\t''x#{@okstra_trace_run}'$'\t''x#{@okstra_worker_run}'$'\t''x#{@okstra_status}' \
179
202
  2>/dev/null || true)
180
- # (2) Title-allowlisted worker-agent panes in the lead's session. Only for a
181
- # run (reap leaves these harness-owned panes to the harness). `list-panes -s
182
- # -t <pane>` resolves the session containing that pane, so the scan never
183
- # reaches other sessions (no `-a`). Skipped when the lead pane is unknown.
184
- # reclaim 모드는 teammate pane 회수하지 않으므로(완료 판정 불가, trace-only)
185
- # 스캔을 건너뛴다.
203
+ # (2) Title-allowlisted worker-agent panes in the lead's WINDOW. Only for a
204
+ # run (reap leaves these harness-owned panes to the harness). `list-panes -t
205
+ # <pane>` (no `-s`, no `-a`) resolves the window containing that pane and
206
+ # lists only that window's panes. Split-pane teammates always land in the
207
+ # lead's window, so this catches all of THIS run's worker panes while a second
208
+ # okstra lead in another window of the same tmux session — whose in-flight
209
+ # worker panes are untagged and title-only — is left untouched. Skipped when
210
+ # the lead pane is unknown. reclaim 모드는 teammate pane 을 회수하지 않으므로
211
+ # (완료 판정 불가, trace-only) 이 스캔을 건너뛴다.
186
212
  if [[ "$REAP" -eq 0 && "$RECLAIM" -eq 0 && -n "$lead_pane" ]]; then
187
213
  while IFS=$'\t' read -r pid title; do
188
214
  [[ -n "$pid" ]] || continue
@@ -190,13 +216,17 @@ collect_okstra_panes() {
190
216
  if _title_in_okstra_scope "$title"; then
191
217
  panes+=("$pid")
192
218
  fi
193
- done < <(tmux list-panes -s -t "$lead_pane" \
219
+ done < <(tmux list-panes -t "$lead_pane" \
194
220
  -F '#{pane_id}'$'\t''#{pane_title}' 2>/dev/null || true)
195
221
  fi
196
222
 
197
223
  # Dedupe — a live trace pane can match both the tag scan and the title scan.
224
+ # Then drop any pane a --keep pattern spares (in-flight report-writer).
198
225
  if (( ${#panes[@]} )); then
199
- printf '%s\n' "${panes[@]}" | awk 'NF && !seen[$0]++'
226
+ printf '%s\n' "${panes[@]}" | awk 'NF && !seen[$0]++' | while IFS= read -r _pid; do
227
+ _keep_excluded "$_pid" && continue
228
+ printf '%s\n' "$_pid"
229
+ done
200
230
  fi
201
231
  }
202
232
 
@@ -64,7 +64,7 @@ This adapter maps the neutral Okstra lead operations to Claude Code host primiti
64
64
  - Follow the core Result Path + terminal-status completion contract. The Claude adapter's wake mechanism is one `Bash(run_in_background: true)` poll covering every pending Result Path, not foreground sleep or an idle-notification dependency. A spawn acknowledgement is never completion.
65
65
  - The background poll uses a per-worker deadline of twice the expected duration: 20 minutes for `requirements-discovery`, 30 for `error-analysis`, 40 for `implementation-planning`, 40 for `implementation`, and 20 for `final-verification`. On timeout, record terminal status and apply the core's single shared retry budget.
66
66
  - Each in-process worker heartbeat audit sidecar must update at least every five minutes while its result is pending. A missing or stale heartbeat consumes the same one-retry budget; after the second silent hang, record `timeout`. The result file remains the authoritative completion signal.
67
- - **The background poll checks liveness, not only Result Paths.** Result Paths change once, at the very end, so polling them alone pays the full deadline for a worker that died at minute three. Each poll iteration MUST also run, in the same background shell, one `okstra worker-liveness` call covering every pending worker — `--audit <audit-sidecar-path>` for each in-process worker, `--prompt <prompt-history-path>` for each CLI-wrapper worker. The dispatch record's `livenessMode` selects the flag; never infer it from provider or filename. It exits non-zero when a worker is `stalled` (heartbeat older than the cadence budget) or `did-not-launch`; either verdict ends the wait for that worker immediately and spends the core's one-retry budget, rather than waiting out the deadline. The command reports only — it never kills or re-dispatches. It shares its heartbeat budget with the Phase 7 audit (`okstra_ctl.worker_heartbeat`), so a worker the live probe passes cannot fail the post-hoc one for cadence.
67
+ - **The background poll checks liveness, not only Result Paths.** Result Paths change once, at the very end, so polling them alone pays the full deadline for a worker that died at minute three. Each poll iteration MUST also run, in the same background shell, one `okstra worker-liveness` call covering every pending worker — `--audit <audit-sidecar-path>` for each in-process worker, and a paired `--team-state <path> --worker <id>` for each CLI-wrapper worker. The dispatch record's `livenessMode` selects the selector; never infer it from provider or filename. The wrapper selector resolves its prompt path and authoritative dispatch `startedAt` from team-state. It exits non-zero when a worker is `stalled` (heartbeat older than the cadence budget) or `did-not-launch`; either verdict ends the wait for that worker immediately and spends the core's one-retry budget, rather than waiting out the deadline. The command reports only — it never kills or re-dispatches. It shares its heartbeat budget with the Phase 7 audit (`okstra_ctl.worker_heartbeat`), so a worker the live probe passes cannot fail the post-hoc one for cadence.
68
68
  - The Claude Code harness blocks long foreground sleeps and shorter-sleep circumvention loops. Keep the result poll in a single background shell and let wrapper agents use their documented `BashOutput` loop.
69
69
  - On approved cleanup, reconcile the current live session roster before sending shutdown requests. Never target the lead session.
70
70
  - Collect usage before teardown. Resume through the recorded Claude session id and keep all run artifacts authoritative.
@@ -88,7 +88,7 @@ This adapter maps the neutral Okstra lead operations to Claude Code host primiti
88
88
  - At run start, record `teamName` as the audit label and `teamCreate: { attempted: false, status: "implicit", splitPane: <bool> }` in team-state. A concurrent run records `status: "skipped", reason: "concurrent-run"`. Populate `lead.sessionId`; the session transcript lives under `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`.
89
89
  - Record the lead pane once with `mkdir -p "<RUN_DIR>/state" && { . "$HOME/.okstra/bin/lib/okstra/tmux-pane.sh" 2>/dev/null && okstra_resolve_caller_pane; } > "<RUN_DIR>/state/lead-pane.id" 2>/dev/null || true`. This is silent setup and must not gate cleanup; the cleanup script protects the lead pane itself.
90
90
  - Collect and persist token usage before any live-roster cleanup, including cleanup between batches and the run-end shutdown sequence.
91
- - Before each new worker batch (and before the next phase's render-bundle), the batch/phase-boundary reclaim is `okstra phase-cleanup --run-dir "<RUN_DIR>" --project-root "<PROJECT_ROOT>" --fallback-team "session-<lead.sessionId-prefix>"` (tmux-aware: it skips pane reclaim in a non-tmux session). Pass `--fallback-team` every time: after a resume or compaction the session id is re-issued, and without the label the reconcile finds no live roster and prints nothing to dismiss. It reclaims only completed panes and prints `dismissible-teammates`; send `SendMessage(to: <name>, message: { type: "shutdown_request" })` only to those confirmed-complete teammates. Never target the lead or an incomplete critic/reverify worker. (This is the batch/phase boundary reclaim; the run-end keep/clean sequence below is a separate, final step for when no next phase follows.)
91
+ - Before each new worker batch (and before the next phase's render-bundle), reclaim the prior round's completed teammate panes in two passes, adding `--keep report-writer-worker` to **both** passes while the report writer is in flight. First source the count: `$HOME/.okstra/bin/okstra-trace-cleanup.sh --list --run-dir "<RUN_DIR>" [--keep report-writer-worker]` never kills and prints one `<pane_id>\t<pane_title>` line per pane it would reclaim count those lines as `<n>`. Then perform the reclaim by running the same command **without** `--list`, and emit the neutral contract's `PROGRESS: phase-batch-cleanup panes=<n>` checkpoint with that count. Call both passes after collecting that round's results and token usage and before the next dispatch, so no in-flight worker pane is caught. This `tmux kill-pane`s the harness teammate panes; `shutdown_request` only idles the agent and never frees the pane, so it stays part of the run-end sequence for roster/token hygiene. In a non-tmux session there are no panes, both passes no-op, and `<n>` is `0` — still emit the checkpoint. The lead pane (read from `<RUN_DIR>/state/lead-pane.id`) is always preserved.
92
92
  - After batch cleanup, record the current live session generation with `okstra token-usage "<TEAM_STATE_PATH>" --record-observed-session --project-root "<PROJECT_ROOT>"`. This protects usage accounting when Claude Code re-issues the session id after resume or compaction.
93
93
  - Claude Code cannot delete the implicit team or surgically remove an idle roster entry. Explain that teammates may remain visible until session end and, when needed, give the manual action `Delete team <teamName> in Teams/FleetView`.
94
94
  - The `SessionEnd` hook runs `$HOME/.okstra/bin/okstra-team-reconcile.sh --session-end` as the safety net for the current live session.
@@ -102,6 +102,6 @@ This adapter maps the neutral Okstra lead operations to Claude Code host primiti
102
102
  > This phase is ending. The following Okstra panes and worker teammates remain — close and clean them up?
103
103
  > <quoted `--list` output>
104
104
  > (Yes) Close everything and clean up teammates / (No) Keep everything
105
- 5. On `keep`, preserve every residual resource and show `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"` plus the manual Teams/FleetView action. Tell the user that `keep` holds only until the next boundary: if this session goes on to another phase/batch, that transition's `okstra phase-cleanup` reclaims the kept **completed** panes unattended (in-flight resources and the lead pane are never touched).
105
+ 5. On `keep`, preserve every residual resource and show `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"` plus the manual Teams/FleetView action. Tell the user that `keep` holds only until the next boundary: if this session goes on to another phase/batch, that transition's round-boundary cleanup reclaims the kept **completed** panes unattended (in-flight resources and the lead pane are never touched).
106
106
  6. On approved `clean`, emit the teardown checkpoint, run `$HOME/.okstra/bin/okstra-trace-cleanup.sh --run-dir "<RUN_DIR>"`, then run `$HOME/.okstra/bin/okstra-team-reconcile.sh --project-root "<PROJECT_ROOT>" --fallback-team "session-<lead.sessionId-prefix>"` exactly once. The resolver reads the current live session's `~/.claude/teams/session-<live>/config.json`, falling back to the snapshot directory only when the live directory is absent, and prints `dismissible-member: <name>` records.
107
107
  7. Send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to each printed, confirmed-complete non-lead member. The `message` MUST be the object literal shown, NEVER a JSON string in a text field. Never target the lead or use `TaskStop`; teammates are not background tasks.
@@ -84,7 +84,7 @@ Read the worker result files generated in Phase 4/5 and extract individual findi
84
84
  - Same semantics but disjoint ticket sets → separate groups (do NOT over-merge across tickets).
85
85
  - Only one worker confirms a finding → one single-source group.
86
86
  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.
87
- 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`. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` — an `implementation` run's verifiers included — and only the report author uses `report-writer`. There is no `implementation-verifier` audience here; map the verifier roster to `analysis`.
87
+ 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`. For analysis sidetracks where ticket tagging is not required, `ticketIds: []` is the canonical value; never synthesize `"unknown"` or another placeholder. `scripts/okstra_ctl/convergence_engine.py` and `schemas/convergence-groups-v1.0.schema.json` enforce the required array field and reject non-string or blank entries while allowing the empty array. When a live command or external read produced reproducible evidence, also include `evidenceArtifacts[]` with its `.okstra/` path, SHA-256 digest, command, and environment. The field is optional because historical or inaccessible evidence may not have a captured artifact. The lead and verifier MUST NOT infer live or external evidence from wording or keyword matching; they use the finding's explicit claim, provenance, and supplied artifacts. Include the resolved worker roster in order with functional `audience` values; do not derive scope from provider or model identity. The `audience` enum is a convergence role, not a phase label: every finding-producing worker uses `analysis` — an `implementation` run's verifiers included — and only the report author uses `report-writer`. There is no `implementation-verifier` audience here; map the verifier roster to `analysis`.
88
88
  6. Do not write a queue or classification in this grouped-input artifact. `okstra convergence seed` classifies Round 0 by mode:
89
89
  - Collaborative mode: multi-source groups become `full-consensus` immediately; only single-source groups enter the working queue.
90
90
  - Adversarial mode: every finding enters the working queue regardless of source count. Semantic grouping merges provenance only; it does not decide a finding is reliable.
@@ -166,7 +166,7 @@ Active only when `config.adversarial == true` (default for `requirements-discove
166
166
 
167
167
  For `project-analysis`, `feature-analysis`, and `change-impact-analysis`, every analysis worker independently analyses the full confirmed target. Provider or model diversity is an independent evidence source, never a reason to split the target into disjoint worker assignments. Only the `project-analysis` first exploration pass may divide navigation by component; every worker then returns to the whole confirmed target before producing findings.
168
168
 
169
- A single evidence-backed refutation makes the affected finding `contested` while that refutation remains unresolved. Lead MUST NOT use majority voting to override it and MUST NOT promote a lead-only finding into confirmed facts. The report writer records the disagreement and the lead may only retain the claim with explicit counter-evidence through `analysisReviewResolution`; a `still-unresolved` item cannot appear in `analysisCommon.confirmedFacts`. **Enforcement:** `validators/validate_analysis_report.py` rejects a still-unresolved reviewed ID that appears in confirmed facts, and the convergence-state validator preserves the engine's `contested` classification.
169
+ A single evidence-backed refutation makes the affected finding `contested` while that refutation remains unresolved. Lead MUST NOT use majority voting to override it and MUST NOT promote a lead-only finding into confirmed facts. The report writer records current-run refutations and resolutions from the convergence state under `crossVerification`. `analysisReviewResolution` is reserved for a prior report's `## ANALYSIS REVIEW` carry-in and MUST remain empty without that carry-in. A `still-unresolved` carry-in item cannot appear in `analysisCommon.confirmedFacts`. **Enforcement:** `validators/validate_analysis_report.py` rejects non-empty `analysisReviewResolution` without a prior review and rejects a still-unresolved reviewed ID that appears in confirmed facts; the convergence-state validator preserves the engine's `contested` classification.
170
170
 
171
171
  If every required analysis worker produces a non-result, the run verdict is `blocked`; Lead synthesis is not a worker result. A partial worker failure stays in `executionStatus`, but it does not by itself change the deterministic `analysis-complete` / `analysis-partial` scope verdict. **Enforcement:** `validators/validate_analysis_report.py` recomputes these verdict conditions from structured `data.json`.
172
172
 
@@ -268,6 +268,23 @@ outside the common 9-header count above. Other providers do not receive it.
268
268
 
269
269
  The rationale for both drops is §"Reverify prompt: required-reading suppression" below.
270
270
 
271
+ Immediately after the anchor headers (and the provider-specific plain-file
272
+ header when present), copy this phase-boundary block before any reverify
273
+ instructions:
274
+
275
+ ```markdown
276
+ **Task Type:** <task-manifest taskType>
277
+ **Forbidden actions:**
278
+ <active-run-context workflow.forbiddenActions, verbatim>
279
+ ```
280
+
281
+ Do not summarize, shorten, or reconstruct the forbidden-actions text. The
282
+ selected adapter validates the task type and exact block through
283
+ `okstra_ctl.worker_prompt_contract.validate_reverify_prompt()` before starting
284
+ the wrapper. `validators/validate-run.py` separately fails the run when the
285
+ run-level error log records a phase-boundary `contract-violation`; a correct
286
+ finding does not make evidence obtained across the phase boundary admissible.
287
+
271
288
  `<modelExecutionValue>` MUST be resolved from one of these canonical sources, in priority order:
272
289
 
273
290
  1. `task-manifest.json` → `resultContract.requiredWorkerRoles[].modelExecutionValue` for the receiving role
@@ -502,7 +519,7 @@ Schema rules:
502
519
  - `schemaVersion`: literal string `"1.3"` for all new runs — both adversarial and collaborative. Historical readers accept `"1.0"` / `"1.1"` / `"1.2"` unchanged and never rewrite those artifacts during validation. v1.3 adds the strict coverage-critic ledger and rejects unknown top-level fields; work-state remains v1.0.
503
520
  - `config.adversarial`: boolean. `true` when this run used adversarial verification (default for `requirements-discovery` / `error-analysis` / `implementation-planning` / `project-analysis` / `feature-analysis` / `change-impact-analysis`). When `true`, `config.verificationMode` is `"full-reanalysis"` (scoped) and every `disagree` vote carries a non-null `disagreeBasis`.
504
521
  - `config.effectiveMaxRounds`: the integer the lead actually used after resolving the phase-aware default (`1` for `requirements-discovery`, `2` otherwise). MUST equal `config.maxRounds` when the manifest explicitly set it.
505
- - `findings[].ticketIds`: array of ticket keys from Phase 4 grouping (parsed per the Round 0 step 5 rule). MAY be empty when the discovering worker tagged the finding `unknown`.
522
+ - `findings[].ticketIds`: array of ticket keys from Phase 4 grouping (parsed per the Round 0 step 5 rule). It is empty when the phase does not require ticket tagging; `"unknown"` is not a ticket key and must not be synthesized.
506
523
  - `findings[].rounds[].votes.<worker>.verdict`: enum, one of `agree | disagree | supplement | verification-error`. Lower-case tokens; map upper-case AGREE/DISAGREE/SUPPLEMENT verdicts emitted by workers to their lower-case form and map the input alias `unverifiable` to persisted `verification-error`. The latter represents either a terminal non-result dispatch or a completed dispatch that could not verify a particular finding (§"Worker failure handling in reverify"). Every vote has a non-empty `explanation`.
507
524
  - `findings[].rounds[].votes.<worker>.disagreeBasis`: enum `counter-evidence | burden-not-met | null`. Non-null only when `verdict == "disagree"` AND `config.adversarial == true`; `null` (or absent, treated as null) otherwise. See §"Adversarial Verification Mode".
508
525
  - `findings[].classification`: enum, one of `full-consensus | partial-consensus | worker-unique | contested`. No other value is permitted.
@@ -103,7 +103,7 @@ Required checkpoints:
103
103
  - `PROGRESS: phase-5-collect worker=<role> status=<terminal-status>` — once per worker, immediately after the result file is verified.
104
104
  - `PROGRESS: phase-5.5-convergence round=<N> queue=<count>` — at the start of each convergence round (Phase 5.5).
105
105
  - `PROGRESS: phase-5.6-critic provider=<provider> gaps=<n>` — after the critic result is collected (Phase 5.6, opt-in; the critic dispatch itself fires concurrently with the first 5.5 reverify round). Omitted when `convergence.critic.enabled == false`.
106
- - `PROGRESS: phase-batch-cleanup panes=<n> teammates=<m>` — immediately after cleaning up the previous batch's panes and completed teammates, at each batch boundary (① just before the first `phase-5.5-convergence` round ② just before the `phase-6-synthesis` report-writer dispatch). Expose only the counts and NEVER expose `%NNN`/lead-pane.id/raw worker handles. Just before the first batch (analysis-worker dispatch) there is nothing to clean up, so it is a no-op and the marker is omitted.
106
+ - `PROGRESS: phase-batch-cleanup panes=<n>` — immediately after cleaning up the previous batch's panes, at each batch boundary (① just before the first `phase-5.5-convergence` round ② just before the `phase-6-synthesis` report-writer dispatch). `<n>` is the number of panes reclaimed at that boundary — trace panes plus completed teammate panes, which are panes too — read from the cleanup's `--list` pass taken immediately before the reclaim, never estimated. Expose only the counts and NEVER expose `%NNN`/lead-pane.id/raw worker handles. Just before the first batch (analysis-worker dispatch) there is nothing to clean up, so it is a no-op and the marker is omitted.
107
107
  - `PROGRESS: phase-6-synthesis dispatching report-writer-worker` — at the start of Phase 6.
108
108
  - `PROGRESS: phase-7-persist updating manifests` — at the start of Phase 7.
109
109
  - `PROGRESS: phase-7-teardown shutting-down-workers` — only after usage collection and user approval, immediately before `shutdown_workers`; omitted when no cleanup resource exists or the user keeps it.
@@ -397,7 +397,7 @@ After persistence, reply briefly in the resolved Report Language with: completio
397
397
  ## Run-scoped worker-resource lifecycle
398
398
 
399
399
  - At run start, call the selected adapter's setup required to distinguish lead-owned resources from worker-owned resources.
400
- - Before every new worker batch, clean only confirmed-complete resources from the prior batch, call `record_lead_event` for the batch-cleanup checkpoint, and never terminate the lead or an incomplete worker; the batch-reclaim primitive is the selected adapter's.
400
+ - Before every new worker batch, and between worker rounds within a phase, close the prior round's completed teammate resources before the next dispatch never the lead and never an in-flight worker; call `record_lead_event` for the batch-cleanup checkpoint. The round-boundary teammate reclaim primitive is the selected adapter's.
401
401
  - After Phase 7 persistence and `collect_usage`, enumerate residual adapter-owned resources. If none remain, skip the question.
402
402
  - If resources remain, call `prompt_user` once with a binary keep-or-clean choice. The answer controls the entire residual set; do not ask a second backend-specific cleanup question.
403
403
  - On keep, preserve all resources and provide the selected adapter's manual-cleanup instruction.