okstra 0.145.0 → 0.146.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/architecture.md +4 -2
- package/docs/cli.md +15 -5
- package/docs/project-structure-overview.md +3 -5
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +5 -6
- package/runtime/bin/okstra-trace-cleanup.sh +28 -2
- package/runtime/prompts/lead/adapters/claude-code.md +3 -3
- package/runtime/prompts/lead/convergence.md +20 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +20 -14
- package/runtime/prompts/lead/team-contract.md +3 -3
- package/runtime/python/okstra_ctl/analysis_packet.py +4 -10
- package/runtime/python/okstra_ctl/codex_dispatch.py +117 -58
- package/runtime/python/okstra_ctl/convergence_engine.py +3 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +19 -56
- package/runtime/python/okstra_ctl/dispatch_state.py +167 -3
- package/runtime/python/okstra_ctl/path_hints.py +6 -0
- package/runtime/python/okstra_ctl/paths.py +7 -44
- package/runtime/python/okstra_ctl/render.py +2 -0
- package/runtime/python/okstra_ctl/wizard.py +34 -0
- package/runtime/python/okstra_ctl/worker_liveness.py +84 -21
- package/runtime/python/okstra_ctl/worker_prompt_body.py +24 -4
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +57 -0
- package/runtime/python/okstra_ctl/worker_state.py +65 -0
- package/runtime/python/okstra_token_usage/antigravity.py +3 -0
- package/runtime/python/okstra_token_usage/codex.py +54 -23
- package/runtime/python/okstra_token_usage/collect.py +141 -33
- package/runtime/python/okstra_token_usage/paths.py +27 -0
- package/runtime/python/okstra_vendor/__init__.py +15 -2
- package/runtime/schemas/convergence-groups-v1.0.schema.json +0 -1
- package/runtime/skills/okstra-run/SKILL.md +14 -4
- package/runtime/skills/okstra-setup/references/project-config.md +13 -4
- package/runtime/validators/lib/fixtures.sh +1 -1
- package/runtime/validators/validate-run.py +52 -1
- package/runtime/validators/validate_analysis_report.py +34 -3
- package/src/cli-registry.mjs +7 -10
- package/src/commands/execute/worker-state.mjs +29 -0
- package/src/commands/inspect/worker-liveness.mjs +5 -3
- package/src/commands/lifecycle/preflight.mjs +13 -3
- package/src/lib/runtime-readiness.mjs +90 -0
- package/runtime/python/okstra_ctl/phase_cleanup.py +0 -235
- package/src/commands/execute/phase-cleanup.mjs +0 -38
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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.
|
|
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 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. 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,
|
|
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-
|
|
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 `✳ ` / `⠂ `.
|
|
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-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 `✳ ` / `⠂ `. 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
package/runtime/BUILD.json
CHANGED
|
@@ -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
|
|
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:**`.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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>
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
# `--list` (alias `--dry-run`) prints `<pane_id>\t<pane_title>` per pane instead
|
|
34
34
|
# of killing — only meaningful with `--run-dir`.
|
|
35
35
|
#
|
|
36
|
+
# `--keep <substr>` (repeatable) spares any pane whose current title contains
|
|
37
|
+
# <substr>, in both the kill and the list set. Used to preserve an in-flight
|
|
38
|
+
# report-writer at a round boundary.
|
|
39
|
+
#
|
|
36
40
|
# Failures are tolerated silently — a stale pane id, no tmux, or a locked tmux
|
|
37
41
|
# client must never prevent Claude from exiting cleanly.
|
|
38
42
|
|
|
@@ -57,6 +61,7 @@ MODE="kill" # kill | list
|
|
|
57
61
|
RECLAIM=0 # 1: trace pane 은 @okstra_status 가 완료(exited)일 때만 회수 (--reclaim-completed)
|
|
58
62
|
REAP=0
|
|
59
63
|
run_dir=""
|
|
64
|
+
KEEP_PATTERNS=() # --keep <substr>: panes whose title contains substr are spared from kill/list
|
|
60
65
|
while [[ $# -gt 0 ]]; do
|
|
61
66
|
case "$1" in
|
|
62
67
|
--list|--dry-run) MODE="list" ;;
|
|
@@ -64,13 +69,18 @@ while [[ $# -gt 0 ]]; do
|
|
|
64
69
|
--reap) REAP=1 ;;
|
|
65
70
|
--run-dir) shift; run_dir="${1-}" ;;
|
|
66
71
|
--run-dir=*) run_dir="${1#--run-dir=}" ;;
|
|
72
|
+
--keep) shift; KEEP_PATTERNS+=("${1-}") ;;
|
|
73
|
+
--keep=*) KEEP_PATTERNS+=("${1#--keep=}") ;;
|
|
67
74
|
-h|--help)
|
|
68
75
|
cat <<'USAGE'
|
|
69
|
-
usage: okstra-trace-cleanup.sh (--run-dir <RUN_DIR> [--list] [--reclaim-completed] | --reap)
|
|
76
|
+
usage: okstra-trace-cleanup.sh (--run-dir <RUN_DIR> [--list] [--reclaim-completed] [--keep <substr>]... | --reap)
|
|
70
77
|
|
|
71
78
|
--run-dir okstra run directory; closes that run's trace + worker-agent panes.
|
|
72
79
|
--list with --run-dir: print "<pane_id>\t<pane_title>" per pane; no kill.
|
|
73
80
|
--dry-run alias for --list.
|
|
81
|
+
--keep <substr> exclude any pane whose title contains <substr> from the
|
|
82
|
+
kill/list set (repeatable). Used to spare an in-flight
|
|
83
|
+
report-writer at a round boundary.
|
|
74
84
|
--reclaim-completed with --run-dir: restrict trace panes to those whose
|
|
75
85
|
@okstra_status sidecar is terminal (stage=exited); in-flight
|
|
76
86
|
and teammate panes are preserved. Skips the title-allowlist
|
|
@@ -149,6 +159,18 @@ _title_in_okstra_scope() {
|
|
|
149
159
|
return 1
|
|
150
160
|
}
|
|
151
161
|
|
|
162
|
+
# A collected pane whose current title contains any --keep substring is spared.
|
|
163
|
+
# Applied at the final emit so both the tag scan and the title scan honour it.
|
|
164
|
+
_keep_excluded() {
|
|
165
|
+
local pid="$1" title pat
|
|
166
|
+
(( ${#KEEP_PATTERNS[@]} )) || return 1
|
|
167
|
+
title=$(tmux display-message -p -t "$pid" '#{pane_title}' 2>/dev/null || true)
|
|
168
|
+
for pat in "${KEEP_PATTERNS[@]}"; do
|
|
169
|
+
[[ -n "$pat" && "$title" == *"$pat"* ]] && return 0
|
|
170
|
+
done
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
173
|
+
|
|
152
174
|
collect_okstra_panes() {
|
|
153
175
|
local -a panes=()
|
|
154
176
|
local pid trace_tag worker_tag status_tag title
|
|
@@ -195,8 +217,12 @@ collect_okstra_panes() {
|
|
|
195
217
|
fi
|
|
196
218
|
|
|
197
219
|
# Dedupe — a live trace pane can match both the tag scan and the title scan.
|
|
220
|
+
# Then drop any pane a --keep pattern spares (in-flight report-writer).
|
|
198
221
|
if (( ${#panes[@]} )); then
|
|
199
|
-
printf '%s\n' "${panes[@]}" | awk 'NF && !seen[$0]++'
|
|
222
|
+
printf '%s\n' "${panes[@]}" | awk 'NF && !seen[$0]++' | while IFS= read -r _pid; do
|
|
223
|
+
_keep_excluded "$_pid" && continue
|
|
224
|
+
printf '%s\n' "$_pid"
|
|
225
|
+
done
|
|
200
226
|
fi
|
|
201
227
|
}
|
|
202
228
|
|
|
@@ -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, `--
|
|
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
|
|
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
|
|
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
|
|
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).
|
|
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
|
|
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,
|
|
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.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The final-report data.json is authored by `Report writer worker` when that role is in the roster. The lead reviews both rendered artifacts but does not write them. Lead-authored fallback is legal only after a real `dispatch_worker` attempt records `error`, `timeout`, or `not-run` with a concrete reason. `release-handoff` remains the intentional single-lead exception.
|
|
6
6
|
|
|
7
|
-
The JSON SSOT path is `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`. The user-facing markdown at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md` is produced by `scripts/okstra-render-final-report.py` from the data.json
|
|
7
|
+
The JSON SSOT path is `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`. The user-facing markdown at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md` is produced by `scripts/okstra-render-final-report.py` from the data.json. The worker-result pointer at `**Worker Result Path:**` records those two paths and the reconciled convergence input. These three completion artifacts land on disk before the worker returns; the heartbeat audit sidecar remains a separate required audit artifact.
|
|
8
8
|
|
|
9
9
|
The data.json schema is `schemas/final-report-v1.0.schema.json`. The renderer + the run-validator both consume that schema, so a data.json that validates is guaranteed to render into a markdown that passes the contract checks.
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ Two `frontmatter` approval fields are always emitted with their unset default
|
|
|
12
12
|
|
|
13
13
|
**As the report-writer worker:** YOU write the data.json and invoke the renderer; the files on disk are the canonical record, so do not return either artifact inline.
|
|
14
14
|
|
|
15
|
-
**As the lead:** prepare the report-writer prompt, dispatch the Report writer worker per the Phase 6 dispatch template in okstra-lead-contract.md, and review
|
|
15
|
+
**As the lead:** prepare the report-writer prompt, dispatch the Report writer worker per the Phase 6 dispatch template in okstra-lead-contract.md, and review the three completion artifacts plus the separate audit sidecar in Phase 7. Do not call `write_artifact` against the report paths or worker-result pointer yourself when Report writer worker is in the roster.
|
|
16
16
|
|
|
17
17
|
## When to Use
|
|
18
18
|
|
|
@@ -26,7 +26,7 @@ Two `frontmatter` approval fields are always emitted with their unset default
|
|
|
26
26
|
2. Persist the exact prompt history with the required anchor headers and audience-specific reading list.
|
|
27
27
|
3. Emit the Phase 6 checkpoint.
|
|
28
28
|
4. Call `dispatch_worker(report_writer_assignment, prompt)` through the selected adapter.
|
|
29
|
-
5. Call `await_workers([handle])` and verify
|
|
29
|
+
5. Call `await_workers([handle])` and verify the data.json Result Path, rendered Markdown sibling, and worker-result pointer at Worker Result Path. Verify the separate heartbeat audit sidecar before accepting the run. **Enforced:** both dispatch adapters keep the three completion paths in `WorkerJob.completion_paths`, and `validators/validate_session_conformance.py` validates the audit sidecar.
|
|
30
30
|
|
|
31
31
|
The assignment's `modelExecutionValue` feeds both adapter dispatch and the prompt header in item 9 below, so the execution model and recorded `**Model:**` header always agree. Missing or unsupported model resolution is a pre-dispatch contract failure; the common contract does not choose a runtime fallback.
|
|
32
32
|
|
|
@@ -35,8 +35,8 @@ The prompt MUST include, in this order at the top:
|
|
|
35
35
|
1. `**Project Root:** <absolute-path>`
|
|
36
36
|
2. `**Prompt History Path:** <project-relative-path>` (under current run `prompts/`)
|
|
37
37
|
3. `**Result Path:** runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` — canonical JSON SSOT. The renderer produces the sibling `.md` automatically.
|
|
38
|
-
4. `**
|
|
39
|
-
5. `**
|
|
38
|
+
4. `**Worker Result Path:** runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md` — canonical three-path worker-result pointer and source for the audit-path derivation.
|
|
39
|
+
5. `**Audit sidecar path:** <absolute-path>` — the generated report-writer heartbeat/read-confirmation destination derived from the Markdown `**Worker Result Path:**`, never from Result Path.
|
|
40
40
|
6. `Assigned worker prompt history path: <absolute-path>`
|
|
41
41
|
7. The four BLOCKING dispatch anchor headers generated from the report-writer audience (the worker cannot synthesize any of these paths):
|
|
42
42
|
- `**Worker Preamble Path:** <absolute-path>` — selects `templates/report-writer-prompt-preamble.md`.
|
|
@@ -49,18 +49,18 @@ The prompt MUST include, in this order at the top:
|
|
|
49
49
|
- `<instruction-set>/final-report-schema.json` — a task-type excerpt of the data.json schema (the other task-types' deliverable blocks and their unreachable `$defs` are stripped; ~38% of the full schema is `$defs` alone). This is your authoring aid for the data.json shape — the installed schema, not the excerpt, is what the run is judged against. Do **NOT** pull the full `schemas/final-report-v1.0.schema.json` — it carries all task-types and its `schemas/...` path is not part of the task bundle. (Validation still runs against the full schema post-hoc via the renderer, so the excerpt never relaxes the contract.)
|
|
50
50
|
- `<instruction-set>/final-report-template.md` — the **phase-stripped** template (every other task-type's §5.x deliverable block removed by `render.py`'s `_strip_phase_blocks`, leaving only your run's §5.x). Do **NOT** also pull the full `templates/reports/final-report.template.md` source (it re-adds ~330 lines of other phases' deliverables and is not in the task bundle).
|
|
51
51
|
11. A one-line MCP pointer instead of the verbatim block (redundant — the brief is already in the report-writer's Required reading, item 10): `**MCP servers:** follow the task brief's "## Available MCP Servers" section (already in your Required reading).`
|
|
52
|
-
12.
|
|
52
|
+
12. `Convergence state: runs/<task-type>/state/convergence-<task-type>-<seq>.json`, followed by pointers to all analysis-worker result files under `worker-results/`. The convergence path is deterministic and is listed even before Phase 5.5 creates the file. Read its classifications (Full/Partial/Contested/Worker-Unique), `roundHistory[]`, `round2SkippedReason`, and `finalClassificationCounts`; populate `crossVerification.roundHistory` in data.json so Section 6 can show which rounds executed, queue sizes, and why Round 2 was (or was not) skipped. The renderer prints the full per-round table only when more than one round ran; single-round or zero-round histories are auto-collapsed to a one-line summary.
|
|
53
53
|
13. `**Report Language:** <en|ko>` — must be either `en` or `ko`; `auto`
|
|
54
54
|
has been resolved by the lead from project.json / global config
|
|
55
55
|
before the dispatch is constructed. The worker copies this verbatim
|
|
56
56
|
into `data.json.meta.reportLanguage`.
|
|
57
57
|
14. For implementation-planning runs: a literal block listing the 12 required English section headings — `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. This list is `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`; that tuple is the SSOT and this block must match it exactly. The writer uses these exact substrings as section headings (Korean translation in parentheses is allowed), and the `Plan Body Verification` section carries its required `Gate result:` line.
|
|
58
|
-
15. An explicit instruction: `You are the author of
|
|
58
|
+
15. An explicit instruction: `You are the author of THREE files: (a) the final-report data.json at <Result Path>, (b) its rendered Markdown sibling produced through "okstra render-final-report <Result Path>", and (c) the worker-result pointer at <Worker Result Path>. Maintain the separate heartbeat audit sidecar at <Audit sidecar path>. Do not return the report inline. The dispatch fails when any of the three completion artifacts is missing, and session conformance fails when the audit sidecar is missing or invalid.`
|
|
59
59
|
16. The prose budget (dedup contract): `verdictCard.finalConclusion` is the conclusion SSOT — at most 3 sentences. `rationale.*` fields stay within 2 sentences each and reference the verdict card / row IDs instead of restating their prose; `readerSummary` fields are one line each; `summary` stays at 3-5 rows unless the run covers multiple tickets. The schema field descriptions carry the same budgets (`tests/contract/test_report_prose_budget.py` guards both surfaces). Generation time scales with output volume, so exceeding the budget is a cost bug, not extra diligence.
|
|
60
60
|
|
|
61
61
|
**Fix-run incremental authoring (applies when the run's profile carries a "Fix-Run Carry" block).** Do not author the data.json from scratch. Start by copying the previous run's data.json (the `Previous report` path in the Fix-Run Carry block) to this run's Result Path, then update ONLY the blocks the fix run changed: `meta`/`header` (run seq, dates), `executionStatus`, `implementation.verifierResults`, `implementation.validationEvidence`, `implementation.commitList` / `diffSummary`, `crossVerification`, `verdictCard`, `finalVerdict`, and any `evidence` rows the fix touched. Deliverable prose for unchanged sections is carried forward verbatim — do not re-generate it. Then invoke the renderer exactly as in a full run. The schema validation and renderer contract are unchanged, so an incrementally-authored data.json passes the same post-hoc gates. The lead's dispatch prompt MUST include the previous data.json path when the carry block is present.
|
|
62
62
|
|
|
63
|
-
**Completion detection after dispatch (BLOCKING).** A dispatch acknowledgement is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection", with a
|
|
63
|
+
**Completion detection after dispatch (BLOCKING).** A dispatch acknowledgement is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection", with a pending set covering the data.json (Result Path), rendered Markdown sibling, and worker-result pointer (Worker Result Path). Check the separate audit sidecar before accepting conformance. Do NOT end the turn with a prose "waiting for the report" statement. **Enforced:** the adapters reject a completed transition while any `completionPaths` entry is absent; `validators/validate_session_conformance.py` owns the audit check.
|
|
64
64
|
|
|
65
65
|
### Resume-safe dispatch
|
|
66
66
|
|
|
@@ -273,19 +273,25 @@ When the run's `task-type` is `release-handoff`, the final report MUST include S
|
|
|
273
273
|
|
|
274
274
|
The final-report template `templates/reports/final-report.template.md` Section 5.6 already encodes this contract — copy that block verbatim and fill in. For non-`release-handoff` runs, omit Section 5.6 entirely.
|
|
275
275
|
|
|
276
|
-
### Mandatory worker-
|
|
276
|
+
### Mandatory worker-result pointer and audit sidecar (BLOCKING)
|
|
277
277
|
|
|
278
|
-
You (the report-writer worker) MUST
|
|
278
|
+
You (the report-writer worker) MUST write the worker-result pointer at `**Worker Result Path:**`, defaulting to:
|
|
279
279
|
|
|
280
280
|
```
|
|
281
|
-
runs/<task-type>/worker-results/report-writer-worker
|
|
281
|
+
runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md
|
|
282
282
|
```
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
Its body contains exactly the project-relative data.json path, rendered Markdown path, and convergence-state input path. Analysis-worker result files stay in `## Inputs`; do not copy their list into the pointer. **Enforced:** both dispatch adapters include this pointer in `WorkerJob.completion_paths` and refuse `completed` while it is absent.
|
|
285
|
+
|
|
286
|
+
The pointer's frontmatter and header follow `team-contract` "Result Frontmatter" and the standard worker-result header sections. Use `workerId: "report-writer"` and copy the remaining canonical values from `analysis-material.md`; do not duplicate the final-report body.
|
|
287
|
+
|
|
288
|
+
You MUST also write the separate heartbeat/read-confirmation audit file at `**Audit sidecar path:**`, derived from Worker Result Path and defaulting to:
|
|
285
289
|
|
|
286
|
-
|
|
290
|
+
```
|
|
291
|
+
runs/<task-type>/worker-results/report-writer-worker-audit-<task-type>-<seq>.md
|
|
292
|
+
```
|
|
287
293
|
|
|
288
|
-
|
|
294
|
+
The selected report-writer preamble defines that audit shape. **Enforced:** `validators/validate_session_conformance.py` checks its reading confirmation, progress stages, timestamps, and cadence whenever the role completes.
|
|
289
295
|
|
|
290
296
|
### Main Body Section
|
|
291
297
|
|