okstra 0.164.0 → 0.165.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +1 -1
  2. package/docs/architecture.md +12 -8
  3. package/docs/cli.md +7 -3
  4. package/docs/for-ai/README.md +2 -2
  5. package/docs/for-ai/skills/okstra-inspect.md +2 -2
  6. package/docs/for-ai/skills/okstra-user-response.md +2 -2
  7. package/docs/project-structure-overview.md +15 -9
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/agents/workers/antigravity-worker.md +9 -7
  11. package/runtime/agents/workers/codex-worker.md +9 -7
  12. package/runtime/agents/workers/grok-worker.md +6 -4
  13. package/runtime/agents/workers/kimi-worker.md +6 -4
  14. package/runtime/bin/okstra-antigravity-exec.sh +1 -340
  15. package/runtime/bin/okstra-claude-exec.sh +1 -178
  16. package/runtime/bin/okstra-codex-exec.sh +1 -467
  17. package/runtime/bin/okstra-provider-exec.py +165 -190
  18. package/runtime/bin/okstra-trace-cleanup.sh +14 -7
  19. package/runtime/bin/okstra-wrapper-status.py +26 -19
  20. package/runtime/prompts/lead/adapters/cmux.md +1 -1
  21. package/runtime/prompts/lead/convergence.md +36 -8
  22. package/runtime/prompts/lead/okstra-lead-contract.md +23 -1
  23. package/runtime/prompts/lead/plan-body-verification.md +9 -1
  24. package/runtime/prompts/lead/report-writer.md +1 -0
  25. package/runtime/prompts/lead/team-contract.md +3 -3
  26. package/runtime/prompts/profiles/_common-contract.md +9 -1
  27. package/runtime/prompts/profiles/_coverage-critic.md +1 -1
  28. package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
  29. package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
  30. package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
  31. package/runtime/prompts/profiles/implementation-planning.md +5 -3
  32. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +1 -1
  33. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
  34. package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +148 -0
  35. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +55 -0
  36. package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +41 -0
  37. package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +44 -0
  38. package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +42 -0
  39. package/runtime/python/okstra_ctl/dispatch_core.py +5 -1
  40. package/runtime/python/okstra_ctl/dispatch_state.py +10 -0
  41. package/runtime/python/okstra_ctl/domain/provider.py +5 -1
  42. package/runtime/python/okstra_ctl/domain/worker_exec.py +102 -0
  43. package/runtime/python/okstra_ctl/domain/worker_role.py +34 -0
  44. package/runtime/python/okstra_ctl/domain/worker_stream.py +261 -0
  45. package/runtime/python/okstra_ctl/incremental_scope.py +16 -4
  46. package/runtime/python/okstra_ctl/report_html/common.py +71 -25
  47. package/runtime/python/okstra_ctl/report_html/models.py +5 -0
  48. package/runtime/python/okstra_ctl/report_html/render.py +1 -1
  49. package/runtime/python/okstra_ctl/report_html/run_usage.py +19 -0
  50. package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +14 -0
  51. package/runtime/python/okstra_ctl/report_views.py +44 -16
  52. package/runtime/python/okstra_ctl/stage_citations.py +52 -15
  53. package/runtime/python/okstra_ctl/user_response.py +45 -29
  54. package/runtime/python/okstra_ctl/wizard.py +13 -9
  55. package/runtime/python/okstra_ctl/worker_prompt_policy.py +10 -3
  56. package/runtime/python/okstra_ctl/worker_request.py +140 -0
  57. package/runtime/python/okstra_ctl/worker_runner.py +622 -0
  58. package/runtime/python/okstra_token_usage/collect.py +8 -1
  59. package/runtime/python/okstra_token_usage/report.py +42 -0
  60. package/runtime/python/okstra_token_usage/task_totals.py +88 -0
  61. package/runtime/schemas/final-report-v1.0.schema.json +70 -0
  62. package/runtime/schemas/final-report-v2.0.schema.json +90 -0
  63. package/runtime/skills/okstra-inspect/SKILL.md +1 -2
  64. package/runtime/skills/okstra-inspect/facets/logs.md +5 -5
  65. package/runtime/skills/okstra-inspect/facets/run-audit.md +3 -3
  66. package/runtime/skills/okstra-run/SKILL.md +1 -1
  67. package/runtime/skills/okstra-user-response/SKILL.md +15 -5
  68. package/runtime/templates/report-writer-prompt-preamble.md +1 -0
  69. package/runtime/templates/reports/html/assets/base.css +8 -4
  70. package/runtime/templates/reports/html/base.template.html +12 -6
  71. package/runtime/templates/reports/html/i18n/en.json +29 -6
  72. package/runtime/templates/reports/html/i18n/ko.json +29 -6
  73. package/runtime/templates/reports/html/macros/forms.html +9 -3
  74. package/runtime/templates/reports/html/tasks/implementation-planning.template.html +14 -19
  75. package/runtime/templates/reports/report.js +59 -26
  76. package/runtime/templates/reports/user-response.template.md +12 -8
  77. package/runtime/validators/validate-run.py +88 -7
  78. package/runtime/validators/validate_session_conformance.py +62 -1
  79. package/src/cli-registry.mjs +0 -7
  80. package/runtime/bin/okstra-wrapper-agy-stream.py +0 -61
  81. package/runtime/python/okstra_ctl/error_issue.py +0 -640
  82. package/runtime/python/okstra_ctl/issue_signals.py +0 -186
  83. package/runtime/skills/okstra-inspect/facets/error-issue.md +0 -77
  84. package/src/commands/inspect/error-issue.mjs +0 -27
package/README.md CHANGED
@@ -183,7 +183,7 @@ Use these slash commands inside a Claude Code session:
183
183
  | `/okstra-brief-gen` | Convert a ticket, requirements document, link, or conversation into an `okstra-run` task brief |
184
184
  | `/okstra-run` | Start a new task or continue an existing task's next phase |
185
185
  | `/okstra-memory` | Store, search, and archive global conversation memory in `~/.okstra/memory-book` |
186
- | `/okstra-inspect` | Unified read side. Subcommands: `status` (phase/state and workStatus updates), `history` (past tasks, reruns, resumes), `report` (find/read final reports), `time` (elapsed-time breakdown), `logs` (wrapper log sidecar inventory and cleanup suggestions), `cost` (task bundle context/read cost), `errors` (aggregate run error logs into a report), `error-zip` (collect cross-project error logs into an anonymized zip and summarize clusters), `run-audit` (check every run's artifacts against progress invariants, catching runs that ended wrong without ever logging a failure), `error-issue` (turn those anomalies into GitHub issue candidates and, after explicit approval, file them on the okstra repo), and `recap` (run-to-run before/after summary plus free-form Q&A over a task's `.okstra` artifacts) |
186
+ | `/okstra-inspect` | Unified read side. Subcommands: `status` (phase/state and workStatus updates), `history` (past tasks, reruns, resumes), `report` (find/read final reports), `time` (elapsed-time breakdown), `logs` (wrapper log sidecar inventory and cleanup suggestions), `cost` (task bundle context/read cost), `errors` (aggregate run error logs into a report), `error-zip` (collect cross-project error logs into an anonymized zip and summarize clusters), `run-audit` (check every run's artifacts against progress invariants, catching runs that ended wrong without ever logging a failure), and `recap` (run-to-run before/after summary plus free-form Q&A over a task's `.okstra` artifacts) |
187
187
  | `/okstra-rollup` | Aggregate every task run in a task group or project, including per-task run counts, duration, errors, group totals, and a cross-task report digest |
188
188
  | `/okstra-usage` | Show the current project's recent run coverage, raw and billable-equivalent tokens, known USD cost, CPU time, and wall-clock time grouped by task type (default: last 30 days) |
189
189
  | `/okstra-schedule-gen` | Invoke as `/okstra-schedule-gen [task-group]` to generate a work schedule for an entire task group. Each non-done task is resolved through the source-aware `stage-map` response; your unfinished-stage choices are captured in a temporary selection contract, and only the same draft that passes deterministic selection validation followed by independent narrative verification is published |
@@ -805,22 +805,26 @@ Errors that occur while provider workers, the report writer, or the Okstra lead
805
805
  - Appends records through a single entry point.
806
806
  - Both worker-sidecar dumps (`append_observed`, guarded by schema version) and lead observations (`lead-observed`) use the same helper.
807
807
 
808
- ### Live-log mirror (codex / antigravity wrapper)
808
+ ### Live log and worker-pane presentation (every CLI worker)
809
809
 
810
- - On every dispatch, `scripts/okstra-codex-exec.sh` and `scripts/okstra-antigravity-exec.sh` create a `<prompt>.log` sidecar next to the prompt path and mirror stdout there through a named FIFO (which keeps the CLI a single addressable PID for the idle watchdog). stderr is appended to the same file, preserving the subagent stderr-capture contract, and the file is truncated on each dispatch. This solves the problem where a calling subagent polls `BashOutput` every 60 seconds, leaving users unable to detect a stalled state during long-running work such as large-codebase scans in analysis or cargo / pytest in implementation.
810
+ - On every dispatch, `scripts/okstra_ctl/worker_runner.py` creates a `<prompt>.log` sidecar next to the prompt path and writes the CLI's output there, truncating the file on each dispatch. All five entrypoints (`okstra-{claude,codex,antigravity,grok,kimi}-exec.sh`) are four-line shells that `exec` `scripts/okstra-provider-exec.py`, so the log contract is one implementation rather than one per provider. The runner reads the child's pipes directly with a `selectors` loop there is no FIFO and no `tee` subprocess and the child is spawned with `start_new_session=True`, which is what keeps it addressable as a process *group* for the idle watchdog's `killpg`.
811
+ - How the two streams are split depends on what the CLI speaks, and the decision lives in `_stderr_target`. A `stream-json` provider puts events on stdout and only its own error text on stderr, so the two are folded into one reader. A `text` provider (codex) splits meaning across them — the result on stdout, progress on stderr — and merging them would destroy the only way to tell the answer from the noise, so they stay apart.
812
+ - Idle is measured from **stream arrival**, never from the log file's mtime. The screen deliberately drops thinking events, so an mtime-based watchdog would SIGTERM a healthy worker in the middle of a long reasoning stretch. On a breach the runner `killpg`s the group (SIGTERM, then SIGKILL after a 5s grace), returns exit code 124, and marks the status sidecar `{timeout: true, idle_seconds, idle_at_ts, terminated_by: "idle-watchdog"}`.
811
813
  - The `.log` is an execution transcript containing wrapper/CLI output, not the original prompt. `okstra log-report` pairs it with the sibling persisted `.md` prompt and reports `transcriptBytes`, `promptBytes`, and their ratio separately. The compatibility fields `path`, `sizeBytes`, and `totalBytes` continue to mean transcript data, so existing consumers and transcript-size ordering remain stable.
812
- - **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.
813
- - 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.
814
- - **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.
815
- - **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>`.
814
+ - **Run-wide cap on the progress copy in the log** (`_LOG_PROGRESS_LINE_CAP` in `scripts/okstra_ctl/worker_runner.py`): 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 runner archives the first 5000 progress lines of the run and replaces the rest with a `[okstra log-cap] N progress line(s) elided` marker, emitted every 500 elided lines so a reader tailing the log can see the run is still producing. The cap is run-wide rather than per-block because a block boundary is a provider's own vocabulary and the shared runner has none; the cost is that a very long run keeps its opening rather than a sample throughout, which the elision notices make visible. **Only the log copy is capped** — never the screen, and never the result stream, because a truncated tool echo costs detail while a truncated answer costs the whole post-mortem.
815
+ - **Progress lands in the worker's own pane, not in a sibling tail pane.** The presentation is passed to the entrypoint as `--presentation live|quiet`; `live` is claimed only by a backend that opened a pane, and the default is `quiet`. Under `live` the runner renders each event into one readable row on the caller's own streams (`→ Bash: …` for a tool call, ` ← ok (N bytes)` for its result, `!! PERMISSION DENIED <tool>: <reason>` for a refusal); thinking events are dropped. Under `quiet` progress is withheld and only the worker's closing text is printed, which is what a `cli-wrapper` dispatch on a machine with no pane surface needs. `scripts/okstra_ctl/domain/worker_stream.py` owns all three projections (`format_live`, `format_log`, `final_text`) as pure functions over *normalised* events `Text`, `ToolCall`, `ToolResult`, `Denial`, `Result`. Providers do not share a wire format and this layer may not name one, so each adapter supplies the function that turns its own events into those, declared on `ExecCommand.normalise` beside the stream format it belongs to. Adding a provider whose stream is shaped differently is that one function; a provider that declares `stream-json` without one is failed by `tests/contract/test_provider_execution_contract.py`, which runs the schema each provider claims through its own normaliser and requires rows out.
816
+ - Because the wrappers no longer split a `tail -F` sibling, **nothing spawns a trace pane anymore**, and with it two pane tags lost their only writer: `@okstra_trace_run` (which marked a trace pane with its run) and `@okstra_status` (which pointed at that run's status sidecar). The operator watches the worker pane itself instead. Two consumers are now inert rather than wrong: `okstra-trace-cleanup.sh --reclaim-completed`, which reclaimed only trace panes whose `@okstra_status` read `stage=exited`, and `okstra-subagent-reclaim.sh`, which drives that mode from the `SubagentStop` / `TaskCompleted` hooks. Both still run and both now match nothing harmless, because the panes they closed are no longer created. The hooks stay installed on user machines, so removing this machinery is a deliberate follow-up rather than a side effect.
817
+ - **Run-scoped tagging for cleanup**: `@okstra_worker_run=<RUN_DIR>` is still written, by `tmux.tag_pane` for the tmux-pane backend's worker-compute panes. `okstra-trace-cleanup.sh` discovers panes server-wide from that tag via `tmux list-panes -a` and runs `tmux kill-pane`, needing neither tmux environment variables nor a pane-ID registry. Because the tag is run-scoped, it does not kill panes belonging to other simultaneous okstra runs. Cleanup has two entry forms: the lead invokes it with `--run-dir <RUN_DIR>` to clean that run's worker panes, or the `hooks.SessionEnd` entry in `templates/reports/settings.template.json` invokes it with `--reap` to clean everything tagged below `$CLAUDE_PROJECT_DIR/.okstra/` when no single run directory exists at session end. Missing tmux and stale pane IDs silently degrade.
818
+ - **Automatic cleanup on phase transitions, including worker-agent panes**: `okstra-trace-cleanup.sh --run-dir <RUN_DIR>` closes tagged worker-compute panes plus the 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`, and FleetView teammate prefixes `✳ ` / `⠂ `, are also treated as okstra panes. Every entry matches as a substring (`*agy-executor*`), which is how a former `agy-executor-tail` trace pane was caught even though the allowlist never named trace panes; no such pane is created now. 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>`.
816
819
  - **Cleanup survives compaction and precedes user gates**: A `SessionStart` hook with matcher `compact` (`okstra-compact-reminder.sh`, seeded in `settings.template.json`) fires after every `/compact` or auto-compaction. It reads `cwd` from the hook's stdin JSON, finds any in-progress okstra run for that project via `active.jsonl` (`pane_reclaim.active_run_dirs_for_project`), and, if one exists, prints a factual reminder naming the run dir and how completed panes/tasks are reclaimed — restoring the boundary/gate cleanup obligation that a compaction summary can otherwise drop. The hook never kills panes itself: a teammate pane is untagged and title-only, so a hook cannot tell an in-flight worker from a finished one; the actual `kill-pane` stays with the lead, which knows completion state. Separately, the lead runs the same completed-pane reclaim immediately before any user approval/clarification/decision gate (`PROGRESS: phase-gate-cleanup panes=<n>`), so a gate is never shown with finished worker panes still open. At every cleanup point the pane-kill (`trace-cleanup`) and the task-stop (`TaskStop`) are paired — a `TaskStop` alone idles the roster task but leaves the pane open.
817
820
  - **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.
818
821
  - 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.
819
822
 
820
- ### Linked-worktree `.git/` write permissions (codex / antigravity)
823
+ ### Linked-worktree `.git/` write permissions
821
824
 
822
825
  - Inside a `--executor codex|antigravity` worktree, `git add` / `git commit` must write to main-repository per-worktree metadata (`<main-repo>/.git/worktrees/<name>/index`, refs, HEAD) and the shared object database (`<main-repo>/.git/objects/`). These paths are outside the worktree directory, so opening only the worktree path in the sandbox causes index.lock creation to fail with EPERM, preventing the executor from satisfying the step-commit contract and forcing it to revert edits and exit.
823
- - The wrapper resolves the main repository's absolute `.git/` path with `git -C <worktree> rev-parse --git-common-dir` from inside the worktree, then forwards it to the sandbox by appending `--add-dir <main-repo>/.git` (Codex) or `--include-directories <main-repo>/.git` (Antigravity).
826
+ - `write_scope` in `scripts/okstra_ctl/worker_request.py` resolves the main repository's absolute `.git/` path with `git -C <worktree> rev-parse --git-common-dir` and appends it to the scope, after the project root and the stage tree. The order is contract: a strategy translates the tuple into repeated `--add-dir` positionally. Every provider is told the scope the same way — codex names the project root with `-C` and skips the repeat, and the resolution itself is shared rather than copied per wrapper.
827
+ - The scope is what a worker may *write*; where it *runs* is a separate answer that each strategy gives (`ExecCommand.cwd`). A dispatch that passed only the working directory would produce an argv with no `--add-dir` at all, and the worker would then fail by quietly not writing files rather than by erroring.
824
828
 
825
829
  ## Token usage and cost accounting
826
830
 
package/docs/cli.md CHANGED
@@ -497,7 +497,7 @@ Selects the provider that performs the Executor role for `--task-type implementa
497
497
  - The Executor is the **only worker allowed to mutate project files** in this run. The other two providers are dispatched as strict read-only verifiers in the same run.
498
498
  - The Executor reuses the provider's worker model flag. With `--executor codex`, its model comes from `--codex-model`, default `gpt-5.6-sol`; with `--executor antigravity`, it comes from `--antigravity-model`, default `gemini-3.1-pro`.
499
499
  - All three Claude, Codex, and Antigravity verifiers are always dispatched regardless of the Executor provider. Even the verifier using the same provider runs in a separate CLI session with isolated context, preserving the self-review safeguard.
500
- - Codex and Antigravity mutate files through each CLI's auto-edit mode, for example `codex exec --sandbox workspace-write`, without passing through Claude-side Edit/Write tools. Mutations occur in the task worktree described below. Both wrappers—`scripts/okstra-codex-exec.sh` and `scripts/okstra-antigravity-exec.sh`—receive the worktree path as their fourth positional argument and forward it through `--add-dir` for Codex or `--include-directories` for Antigravity. Without it, the Codex `workspace-write` sandbox rejects worktree writes with EPERM.
500
+ - Codex and Antigravity mutate files through each CLI's auto-edit mode, for example `codex exec --sandbox workspace-write`, without passing through Claude-side Edit/Write tools. Mutations occur in the task worktree described below. Every `okstra-<provider>-exec.sh` entrypoint receives the worktree path as its fourth positional argument and adds it to the worker's write scope, which each provider is told as repeated `--add-dir` (Codex names the project root with `-C` and skips the repeat). Without it, the Codex `workspace-write` sandbox rejects worktree writes with EPERM.
501
501
  - **Claude Executor cwd handling**: Claude's Bash tool has no per-call cwd argument and inherits the lead session cwd. To run cwd-sensitive toolchains such as `cargo`, `npm`, `pnpm`, `bun`, `pytest`, `make`, or `go` inside the worktree, prefix the invocation with `cd {{EXECUTOR_WORKTREE_PATH}} && <cmd>`. Keep `cd` as the leading token in a single Bash call so Claude Code permission auto-allow works; do not wrap it in `bash -lc "..."` or `bash -c "..."`, which hides `cd` and causes a permission prompt on every call. Prefer a tool's working-directory option—such as `git -C <path>`, `cargo --manifest-path`, or `pytest --rootdir`—over a `cd && ` chain. Edit/Write/Read tools already use absolute paths and need no cwd handling. This rule applies only to the Claude Executor; the Codex and Antigravity wrappers inject cwd.
502
502
  - **Task worktree (automatic isolation for every task type)**: During the first phase's preparation for any task type, `okstra-ctl` creates a `git worktree` at `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/` and branches `<work-category-namespace>/<task-id-segment>` from the resolved commit of the user-selected `--base-ref`, for example `feature/dev-9436` or `fix/dev-7311`. Later phases for the same task key reuse the path and branch and record status `reused`; no new `git worktree add` occurs during run preparation. Special characters such as `/` and `:` in every segment are normalized to `-`, and `~/.okstra/worktrees/registry.json` globally manages task-key-to-path/branch mappings under flock. Executor edits, writes, builds, tests, and commits—and verifier reads—run in this worktree. If the caller is already in another worktree or project_root is not a Git repository, provisioning is skipped and records `skipped-in-worktree` or `skipped-not-git`. Path or branch collisions fail immediately with `PrepareError`. Worktrees are not deleted after a run; remove one manually with `git worktree remove`, then `git branch -D`, then delete the registry entry. **The implementation stage isolation below is the exception.**
503
503
  - **`implementation` stage isolation (concurrent parallelism)**: The task-key worktree above applies only from `requirements-discovery` through `implementation-planning`. Each `implementation` run executes in a **stage-specific isolated worktree** at `~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>/stage-<N>/`, on branch `<work-category-namespace>/<task-id-segment>-s<N>`. The registry atomically reserves a stage key, `<task-key>#stage-<N>`, under flock. `_resolve_effective_stages` excludes `started` rows in `consumers.jsonl` and reserved stages. Stage selection, worktree creation, and registry reservation all happen in one critical section protected by the task-key provisioning mutex at `~/.okstra/.locks/worktree-provision/`, so concurrent `implementation` runs safely select different ready stages: **one run = one stage**. A stage worktree's base depends on its dependency shape: independent (`depends-on (none)`) uses the common anchor fixed once at first stage entry; a single dependency (`depends-on X`) uses the predecessor stage's completed `head_commit`; multiple dependencies (`depends-on X,Y…`) use task-worktree HEAD after all predecessors have been merged, verified with `git merge-base --is-ancestor`, and otherwise fail with `PrepareError` and merge guidance. Select the stage with `--stage <auto|N>` for `okstra.sh`/`render-bundle`, or with the okstra-run wizard's `stage_pick` step. If `project_root` is not a Git repository or is a nested worktree, stage isolation also degrades to flat operation.
@@ -757,7 +757,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
757
757
  | `okstra worker-audit-check --run-dir <runs/<task-type>/> --task-type <type> --seq <nnn> [--worker <id>]` | Apply the Phase 7 worker audit-sidecar rules mid-run, while the worker session is still alive. For each of this run's `worker-results/<worker>-<task-type>-<seq>.md` it checks that the file carries no `## 0. Reading Confirmation` heading, that the matching audit sidecar exists, and — for prompts carrying the required-v1 evidence-ledger marker — that every backticked `path:line` citation has an Evidence read row in that sidecar. `--worker` scopes it to the role that just returned. Emits `{ok, failures[]}` and exits 2 when `failures[]` is non-empty. The rules come from the `okstra_ctl.worker_audit_ledger` SSOT shared with `validate-run.py`, so an early pass and the Phase 7 pass cannot disagree. Run it right after collecting a result: the same failure at Phase 7 leaves only a retroactive edit, which breaks the audit chain, or a failed run |
758
758
  | `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 |
759
759
  | `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 |
760
- | `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 |
760
+ | `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> [--plan-decision <json>] [--task-key <key>]` writes the sidecar; the plan decision carries `status` (`approved` / `revision-requested` / `rejected`) and a `reason` that is mandatory for the latter two. 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 |
761
761
  | `okstra pr <template\|branches\|gen> …` | Backend for the okstra-pr-gen skill. Git-only—no project registration required. `template list\|show <name\|default>\|add --name <name> (--content <text>\|--file <path>)\|path` manages PR body templates under `~/.okstra/template/pr/` (bundled fallback `src/commands/pr/default.md`); `branches` recommends a base branch; `gen --base <ref> [--template <name\|default>]` emits a JSON bundle of the template plus `<base>..HEAD` commits and `<base>...HEAD` diffstat |
762
762
  | `okstra migrate [--apply] [--cwd <dir>] [--quiet]` | One-time migration of the project artifact root from `.project-docs/okstra/` to `.okstra/`. It is a dry run by default; `--apply` performs the move with `git mv` in a Git worktree, removes an empty `.project-docs/`, and synchronizes the `<PROJECT>/CLAUDE.md` import line, `.gitignore`, the project's rows in `~/.okstra/{recent,active}.jsonl`, and `~/.okstra/worktrees/registry.json`. It exits 1 if `.okstra/` already exists or the legacy directory is absent. Scheduled for removal by the end of v0.x |
763
763
  | `okstra task-list [--project-root <path>]` | Combine `list_project_tasks` and `read_latest_task` into JSON containing the task catalog and latest task |
@@ -824,4 +824,8 @@ Both `wait_for_input` and `replan` stop before a stage worktree is provisioned a
824
824
 
825
825
  ### Live-log sidecar
826
826
 
827
- 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. The lead also runs the same reclaim immediately before any user approval/clarification/decision gate (`PROGRESS: phase-gate-cleanup panes=<n>`), and an `okstra-compact-reminder.sh` `SessionStart` hook (matcher `compact`) re-injects this obligation after a `/compact`.
827
+ For every dispatch, whichever provider runs it, okstra creates a `runs/<task-type>/prompts/<worker>-prompt-<phase>-<seq>.log` sidecar and writes the CLI's output into it. All five `okstra-<provider>-exec.sh` entrypoints share one implementation for this, so the log contract does not vary by provider.
828
+
829
+ **Progress appears in the worker's own pane.** Earlier versions split a sibling `tail -F` trace pane next to each worker; they no longer do, and no trace pane is created at all. Instead the presentation is passed to the entrypoint as `--presentation live|quiet`, and only a backend that opened a pane asks for `live` — the default, and what a `cli-wrapper` subagent dispatch passes, is `quiet`. Under `live` each event becomes one readable row on the worker's own streams — `→ Bash: npm run check`, then ` ← ok (2481 bytes)`, and `!! PERMISSION DENIED — <tool>: <reason>` for a refusal. Under `quiet` progress is withheld and only the worker's closing text is printed, which is what a dispatch on a machine with no pane surface needs. The `.log` sidecar records the progress either way, so withholding it from the screen loses nothing.
830
+
831
+ Because nothing spawns a trace pane, the `@okstra_trace_run` and `@okstra_status` pane user options have no writer left, and the `okstra-trace-cleanup.sh --reclaim-completed` mode plus the `okstra-subagent-reclaim.sh` hook that drives it now match no panes. Both stay installed and are harmless — the panes they existed to close are no longer created. tmux-pane backend worker-compute panes are still tagged `@okstra_worker_run=<RUN_DIR>`. When Claude receives `/exit`, the `SessionEnd` hook cleans 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 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. The lead also runs the same reclaim immediately before any user approval/clarification/decision gate (`PROGRESS: phase-gate-cleanup panes=<n>`), and an `okstra-compact-reminder.sh` `SessionStart` hook (matcher `compact`) re-injects this obligation after a `/compact`.
@@ -20,7 +20,7 @@ This directory is a compressed manual for an AI to quickly select and precisely
20
20
  | Turn requirements, tickets, links, a codebase scan, or an error-zip into an okstra input brief | `okstra-brief-gen` | [`skills/okstra-brief-gen.md`](skills/okstra-brief-gen.md) |
21
21
  | Start an okstra run or execute the next phase in the current Claude Code session | `okstra-run` | [`skills/okstra-run.md`](skills/okstra-run.md) |
22
22
  | Manage okstra tasks across multiple projects — bundling, assignment, sync snapshots, child launch packets | `okstra-manager` | [`skills/okstra-manager.md`](skills/okstra-manager.md) |
23
- | Check status, history, report, time, logs, cost, errors, error-zip, run-audit, error-issue, recap | `okstra-inspect` | [`skills/okstra-inspect.md`](skills/okstra-inspect.md) |
23
+ | Check status, history, report, time, logs, cost, errors, error-zip, run-audit, recap | `okstra-inspect` | [`skills/okstra-inspect.md`](skills/okstra-inspect.md) |
24
24
  | Collect and aggregate the results of multiple task runs across a task-group (or the whole project) into a synthesized summary | `okstra-rollup` | [`skills/okstra-rollup.md`](skills/okstra-rollup.md) |
25
25
  | Project-wide recent run coverage, tokens, known cost, CPU, and wall-clock usage by task type | `okstra-usage` | [`skills/okstra-usage.md`](skills/okstra-usage.md) |
26
26
  | Generate a client-facing work schedule for a whole task-group | `okstra-schedule-gen` | [`skills/okstra-schedule-gen.md`](skills/okstra-schedule-gen.md) |
@@ -38,7 +38,7 @@ This directory is a compressed manual for an AI to quickly select and precisely
38
38
  4. Project artifacts go under `<PROJECT_ROOT>/.okstra/` by default. The exception is `okstra-memory`, which uses the global user memory `~/.okstra/memory-book/`.
39
39
  5. `runtime/` is build output. When fixing a source skill or template, edit the source under `skills/`, `templates/`, `validators/`, `scripts/`, `src/` and apply it via a build.
40
40
  6. Do not guess the contents of a tracker, URL, file, report, log, zip, template, or validator. Use only what you have confirmed by reading or running with a tool.
41
- 7. Read-side skills also produce some artifacts. `okstra-inspect errors` produces an error report Markdown, `okstra-inspect error-zip` produces an anonymized zip, and `okstra-inspect error-issue` produces a plan file and — only after the user approves in that session — GitHub issues on a public repo. Even in these cases, keep the CLI stdout JSON as the source of truth.
41
+ 7. Read-side skills also produce some artifacts. `okstra-inspect errors` produces an error report Markdown and `okstra-inspect error-zip` produces an anonymized zip. Even in these cases, keep the CLI stdout JSON as the source of truth.
42
42
 
43
43
  ## The Order the AI Reads In
44
44
 
@@ -17,7 +17,8 @@
17
17
 
18
18
  - `status.4`: writes the user-requested `workStatus` into `task-manifest.json`.
19
19
  - `errors`, `error-zip`, `recap record`: produce report/zip/log artifacts from the information read.
20
- - `error-issue submit`: the only sub-command that writes outside this machine. It files GitHub issues on a public repo and runs only after an explicit user approval in the same session.
20
+
21
+ No sub-command writes outside this machine.
21
22
 
22
23
  ## sub-command list
23
24
 
@@ -32,7 +33,6 @@
32
33
  | `errors` | aggregate task error logs into a timestamped markdown report | generates report |
33
34
  | `error-zip` | build an anonymized zip of cross-project error logs | generates zip |
34
35
  | `run-audit` | check every run's artifacts against progress invariants — catches a run that ended wrong without ever logging a failure | read |
35
- | `error-issue` | turn cross-project anomalies into GitHub issue candidates (`plan`), then file the approved ones (`submit`) | writes a plan file; `submit` creates/comments GitHub issues after user approval |
36
36
  | `recap` | summarize a task's before/after runs and record Q&A | appends `recap-log.jsonl` |
37
37
 
38
38
  ## Preflight
@@ -43,8 +43,8 @@ okstra preflight --runtime claude-code --json
43
43
  - **Picker: the row's `options[]` plus `Enter directly`** — slots follow array order, the `role: recommended` entry first with its label suffixed `(Recommended)`, `Enter directly` always last. Each `label` is the option's `answer`; each `description` is `<rationale> — Scope: <scopeImpact> · Added work: <addedWork> · Direction: <directionChange>`, in that fixed order. Never fold the three axes into one phrase. An empty axis is written `not stated in the report` — never inferred. More than three entries: keep the recommended one plus the two alternatives whose `scopeImpact` differs most, and say how many were left out. Never mark anything but `recommended` as recommended.
44
44
  - **Transcribe**: an `options[]` pick → `value` = that option's `answer` text, `disposition:"answer"`; `Enter directly` → the user's utterance verbatim, `disposition:"answer"`; free text asking for a re-ask → `disposition:"reframe"` (does not satisfy the approval gate). A question back from the user records nothing — **Read** the ref, explain, re-ask the same item with the same options. Echo `[n/N] C-014 → answer: …` and move on. Each item's JSON: `{id, kind, value, rationale?, disposition}`.
45
45
  4. **echo → confirmed gate**: before `write`, echo the whole collection (each `id`·`disposition`·`value`·`rationale`·approval) as-is and get explicit confirmation. Never `write` before `confirmed`. On any change, re-echo and re-confirm.
46
- 5. **approval (optional)**: only when the approval-blocking items are **all filled with an answer** and the user explicitly approved, `--approval '{"approved":true,"implementationOption":"<selected option>"}'`. If any item is unfilled/reframe, do not approve and say the gate is still open.
47
- 6. **write**: `okstra user-response write --report <reportPath> --answers '<json>' [--approval '<json>'] --task-key <taskKey>` → report the returned `{sidecar:<path>}`. (When a same-named sidecar exists, the same `id` is overwritten with the new value and merged.)
46
+ 5. **plan decision (optional)**: only when the user stated one outright. Approval also needs the approval-blocking items **all filled with an answer**: `--plan-decision '{"status":"approved","implementationOption":"<selected option>"}'`. If any item is unfilled/reframe, do not approve and say the gate is still open. A turn-down takes the same flag with a mandatory reason: `--plan-decision '{"status":"rejected","reason":"<the user's own words>"}'` (`revision-requested` when the same plan should be reworked).
47
+ 6. **write**: `okstra user-response write --report <reportPath> --answers '<json>' [--plan-decision '<json>'] --task-key <taskKey>` → report the returned `{sidecar:<path>}`. (When a same-named sidecar exists, the same `id` is overwritten with the new value and merged.)
48
48
 
49
49
  ## Output Rules
50
50
 
@@ -175,7 +175,7 @@ Runtime/install asset changes follow this checklist:
175
175
  | `resolve-task-key` | `src/commands/inspect/resolve-task-key.mjs` | Resolve a bare task-id to candidate task-keys from the project catalog |
176
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`) |
177
177
  | `time-report`, `log-report`, `error-report`, `error-zip` | `src/commands/inspect/*.mjs` | Read-side task runtime, wrapper log, and error aggregation helpers |
178
- | `run-audit`, `error-issue` | `src/commands/inspect/run-audit.mjs`, `src/commands/inspect/error-issue.mjs` | Anomaly detection and issue filing `run-audit` checks run artifacts against progress invariants (read-only), `error-issue plan/submit` turns anomalies into GitHub issue candidates and files the approved ones (Python: `okstra_ctl.run_audit`, `okstra_ctl.error_issue`) |
178
+ | `run-audit` | `src/commands/inspect/run-audit.mjs` | Anomaly detection — checks run artifacts against progress invariants and reports invariant violations, read-only (Python: `okstra_ctl.run_audit`) |
179
179
  | `worker-liveness` | `src/commands/inspect/worker-liveness.mjs` | Report whether pending workers are still alive, so the lead's poll ends a stalled wait early instead of paying the deadline (Python: `okstra_ctl.worker_liveness`) |
180
180
  | `worker-audit-check` | `src/commands/execute/worker-audit-check.mjs` | Apply the Phase 7 worker audit-sidecar rules while the worker session is still alive, so it can fix its own citations (Python: `okstra_ctl.worker_audit_check`, rules in `okstra_ctl.worker_audit_ledger`) |
181
181
  | `context-cost` | `src/commands/inspect/context-cost.mjs` | Estimate task bundle file/read context cost |
@@ -218,15 +218,16 @@ Top-level scripts:
218
218
  | `okstra.sh` | Bash CLI wrapper around `prepare_task_bundle`, optionally launches `claude` |
219
219
  | `okstra-ctl.sh` | Bash control center for list/show/open/rerun/reconcile/project commands |
220
220
  | `okstra-central.sh` | Central run index writer / reconciler entrypoint |
221
- | `okstra-claude-exec.sh`, `okstra-codex-exec.sh`, `okstra-antigravity-exec.sh` | Worker CLI wrappers with log/status sidecars |
222
- | `okstra-wrapper-status.py` | Heartbeat sidecar writer used by worker wrappers |
221
+ | `okstra-{claude,codex,antigravity,grok,kimi}-exec.sh` | Worker CLI entrypoints — four lines each, `exec`ing `okstra-provider-exec.py` with the provider id. They hold no provider logic; adding a flag to one of these instead of to the provider adapter is exactly the drift this shape exists to prevent |
222
+ | `okstra-provider-exec.py` | The one worker entrypoint: parses the shared positional contract plus `--presentation`, resolves the provider's `ExecutionStrategy` from the registry, refuses a missing CLI before any artifact is written, then hands the run to `okstra_ctl.worker_runner` |
223
+ | `okstra-wrapper-status.py` | Standalone writer for one worker status sidecar. No longer on the dispatch path — `worker_runner.py` writes the same document in-process |
223
224
  | `okstra-token-usage.py` | Token usage CLI entrypoint |
224
225
  | `okstra-render-final-report.py` | Render version-selected final-report Markdown from data.json |
225
226
  | `okstra-render-report-views.py` | Render schema v2 task-specific HTML directly from data.json, or a legacy view from schema v1 / quick Markdown |
226
227
  | `okstra-error-log.py` | Normalize worker/lead error sidecars |
227
228
  | `okstra-spawn-followups.py` | Follow-up spawning helper |
228
- | `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 |
229
- | `okstra-subagent-reclaim.sh` | entry that walks active runs and reclaims only completed trace panes (wired to the `SubagentStop`/`TaskCompleted` hooks) |
229
+ | `okstra-trace-cleanup.sh` | tmux okstra pane cleanup (worker-agent + worker-compute, 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. Its `--reclaim-completed` mode keyed on trace panes tagged `@okstra_status`; **no code writes that tag any more**, so the mode is inert rather than wrong the trace panes it reclaimed are no longer created |
230
+ | `okstra-subagent-reclaim.sh` | entry that walks active runs and drives `--reclaim-completed` (wired to the `SubagentStop`/`TaskCompleted` hooks). **Inert for the same reason** — it still runs and matches nothing. Left installed rather than removed: the hooks are already seeded on user machines, so retiring this is a deliberate follow-up |
230
231
 
231
232
  ### 4.3 `scripts/okstra_ctl/` — Python orchestration core
232
233
 
@@ -271,7 +272,7 @@ Important modules:
271
272
  | `index.py`, `jsonl.py`, `reconcile.py`, `listing.py`, `batch.py`, `backfill.py` | `~/.okstra` run index and history operations |
272
273
  | `run_index_row.py` | single reference point for creating / slimming / hydrating a `~/.okstra` run-index row — runId SSOT, preserves projectId raw |
273
274
  | `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 |
274
- | `run_audit.py`, `issue_signals.py`, `error_issue.py` | backend for the okstra-inspect run-audit/error-issue facets`run_audit` reads run-manifest / final-report / team-state artifacts and reports invariant violations (read-only, never the lead's self-report); `issue_signals` computes the verdict signals from pre-anonymization records and classifies a cluster as okstra-defect / environment-policy / target-code; `error_issue` applies the quantitative gate, fingerprints clusters, matches existing issues via `gh`, renders the outbound body, and enforces the last-gate allowlist that keeps target identity out of a public repo |
275
+ | `run_audit.py` | backend for the okstra-inspect run-audit facet — reads run-manifest / final-report / team-state artifacts and reports invariant violations (read-only, never the lead's self-report) |
275
276
  | `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`, resolving each pending worker from its team-state row (`livenessMode` picks the artifact, `startedAt` anchors the grace) and reporting `stalled` (heartbeat past the budget, or none yet for this dispatch past the grace) or `did-not-launch` (no wrapper `.log`/`.status.json` past the launch grace) |
276
277
  | `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) |
277
278
  | `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 |
@@ -282,7 +283,7 @@ Important modules:
282
283
  | `code_review_paths.py` | filesystem-layout SSOT for code-review result files — `stage_review_dir` / `branch_review_dir` plus `next_stage_review` / `next_branch_review`, which read the existing files to derive the next round's name (`stage-<NN>.md`, then `-r2`, `-r3`, …) or the next same-day sequence (`<YYYY-MM-DD>-<NN>.md`), so skill markdown never re-derives a literal review path |
283
284
  | `code_review_target.py` | `okstra code-review target` backend — argument validation and JSON shaping only. Stage mode delegates whole to `okstra_project.state.code_review_target_snapshot`; branch mode is resolved here, defaulting the diff base to the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`). Read-only: it never creates the review directory |
284
285
  | `session.py`, `tmux.py`, `seeding.py`, `locks.py`, `invocation.py`, `sequence.py`, `ids.py`, `material.py` | Supporting lifecycle helpers |
285
- | `pane_reclaim.py` | decides which completed trace panes are reclaim targets; imports the in-progress status set from the `reconcile.NON_TERMINAL_RECENT_STATUSES` SSOT |
286
+ | `pane_reclaim.py` | decides which completed trace panes are reclaim targets; imports the in-progress status set from the `reconcile.NON_TERMINAL_RECENT_STATUSES` SSOT. Inert alongside `--reclaim-completed` — no trace pane is created any more — but still the owner of that decision if the mechanism returns |
286
287
  | `improvement_lenses.py` | lens enum SSOT + cap constants for the improvement-discovery phase (DEFAULT 8, ABSOLUTE 12, MIN/MAX PRIORITY 1/4, SOURCE_WORKERS) |
287
288
  | `improvement_assignment.py` | improvement-discovery primary-pass lens assignment — round-robins the resolved `requiredWorkerRoles` order over the resolved priority lenses (`assign_primary_lenses`) and validates the resulting map (`validate_primary_lens_assignments`). Only the primary pass rotates; every analyser still confirms the full lens set afterwards |
288
289
  | `container.py` | the `okstra container` convergence entrypoint of the okstra-container-build public skill — `provision_container_group` + `up`/`status`/`logs`/`stop-watcher`/`down` dispatch, env-override synthesis, compose argv assembly, and per-container watcher startup |
@@ -321,7 +322,12 @@ Important modules:
321
322
  | `scope_provenance.py` | single source of truth for the scope-provenance grammar every phase-emitted requirement must declare, shared by `validators/validate-run.py` and `validators/validate_fanout.py` so the planning report and fan-out packets cannot drift |
322
323
  | `worker_artifact_paths.py` | canonical worker artifact path derivation (e.g. `audit_sidecar_rel` inserts `-audit-` after the first `-worker-` token), so dispatch and validation agree on non-canonical-path rejection |
323
324
  | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` in that load-bearing order, stops at the first non-zero exit and names the failing step. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
324
- | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar written by `okstra-wrapper-status.py` (the heartbeat writer) |
325
+ | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar `worker_runner.py` writes. `is_terminal` is the one question it answers for the dispatch record and the pane reclaim: does `stage` read `exited` |
326
+ | `worker_runner.py` | runs one worker CLI and records what happened — shared by every provider entrypoint. Owns the `selectors` pump over the child's streams, the stream-arrival idle watchdog (`killpg` on breach), the run-wide progress cap on the log copy, and the status sidecar's whole life. A run that dies after launch still closes its sidecar, so `worker_liveness` never reads a dead worker as running |
327
+ | `worker_request.py` | assembles the `WorkerExecRequest` every strategy then takes on trust: resolved paths, the write scope in the order the CLIs are told it (project root → stage tree → the tree's git-common-dir), the verifier's toolchain grants, and the role's idle budget |
328
+ | `domain/worker_exec.py` | the provider axis' vocabulary — `WorkerExecRequest`, `ExecCommand`, `ExecutionPolicy`, the `ExecutionStrategy` protocol, and `PolicySupport`, by which a provider that *cannot* express the policy must say so rather than silently run without it |
329
+ | `domain/worker_stream.py` | the normalised event vocabulary (`Text` / `ToolCall` / `ToolResult` / `Denial` / `Result`) plus its three pure projections: `format_live` (one readable row per event, for the pane), `format_log` (the same plus bodies, for the archive), `final_text` (the closing message alone). Also `content_block_events`, the normaliser for the wire shape keyed on `type` with `message.content` blocks, which three providers share. No files, no clock |
330
+ | `domain/worker_role.py` | per-role execution budgets — the 1500s/600s idle pair lives here once instead of being re-declared in each wrapper |
325
331
  | `task_target.py` | shared helper resolving `task-key → (task_root, project_root)` (`resolve_task_root`) |
326
332
 
327
333
  > `i18n.py` (the final-report i18n dictionary loader + Jinja2 lookup) is an intentionally undocumented internal helper — it is a render helper that users and contributors do not need to know about in the canonical docs, so it is excluded from the module map.
@@ -410,7 +416,7 @@ Boilerplate shared by several skills (bash invocation rule, outdated-CLI preflig
410
416
  | `okstra-brief-gen` | yes | Produce task brief from ticket/doc/link/conversation |
411
417
  | `okstra-run` | yes | Start/resume an okstra task in the current registered host session |
412
418
  | `okstra-memory` | yes | Store/search/archive global conversation memory under `~/.okstra/memory-book` |
413
- | `okstra-inspect` | yes | Unified read-side — sub-commands `status` (lifecycle + workStatus), `history` (past runs / re-run / resume), `report` (find final-report), `time` (elapsed-time breakdown), `logs` (wrapper log inventory + cleanup), `cost` (task bundle context/read cost), `errors` (error-log aggregation), `error-zip` (anonymized cross-project error bundle), `run-audit` (progress-invariant audit over run artifacts), `error-issue` (anomaly → GitHub issue candidates, filed only after explicit approval), `recap` (cross-run phase recap). `SKILL.md` is a thin core (preflight + dispatch table + shared rules) and each sub-command body lives in `skills/okstra-inspect/facets/<sub-command>.md`, lazily read only after dispatch resolves; the 1:1 match between dispatch rows and facet files is enforced by `tests/contract/test_okstra_inspect_facets.py` |
419
+ | `okstra-inspect` | yes | Unified read-side — sub-commands `status` (lifecycle + workStatus), `history` (past runs / re-run / resume), `report` (find final-report), `time` (elapsed-time breakdown), `logs` (wrapper log inventory + cleanup), `cost` (task bundle context/read cost), `errors` (error-log aggregation), `error-zip` (anonymized cross-project error bundle), `run-audit` (progress-invariant audit over run artifacts), `recap` (cross-run phase recap). `SKILL.md` is a thin core (preflight + dispatch table + shared rules) and each sub-command body lives in `skills/okstra-inspect/facets/<sub-command>.md`, lazily read only after dispatch resolves; the 1:1 match between dispatch rows and facet files is enforced by `tests/contract/test_okstra_inspect_facets.py` |
414
420
  | `okstra-rollup` | yes | Cross-task roll-up — aggregate runs/time/errors across a task-group (or whole project) and synthesize a digest from the report files |
415
421
  | `okstra-usage` | yes | Read-only project usage snapshot — aggregate recent run coverage, tokens, known cost, CPU, and wall-clock time by task type (default: 30 days) |
416
422
  | `okstra-schedule-gen` | yes | Generate task-group schedule |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.164.0",
3
+ "version": "0.165.0",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.164.0",
3
- "builtAt": "2026-08-10T05:52:10.109Z",
2
+ "package": "0.165.0",
3
+ "builtAt": "2026-08-11T04:29:41.048Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -27,21 +27,23 @@ Execute the Google Antigravity CLI and return the analysis result.
27
27
 
28
28
  **Required form (uses the okstra wrapper to avoid redirect-triggered permission prompts):**
29
29
  ```bash
30
- $HOME/.okstra/bin/okstra-antigravity-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
30
+ $HOME/.okstra/bin/okstra-antigravity-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>] --presentation quiet
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is folded into both the caller (worker) pane title `agy-<role>` and the sibling trace-pane title `agy-<role>-tail`. `<role>` carries the dispatched Agent `name` minus the `agy-` prefix, so the pane title equals the FleetView teammate name (`agy-worker-reverify-r1`, `agy-executor`, …) instead of a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the pane title is self-describing.
33
+ `--presentation quiet` belongs on every dispatch you make. You have no pane: your stdout is the calling agent's context window, and `live` fills it with every tool call and every tool result the CLI produces. Nothing is lost by withholding itthe same progress is written to the `.log` beside the prompt either way, and what you return is the worker's closing text. `live` is for a dispatch that opened a pane to show it in, and that dispatch passes the flag itself.
34
+
35
+ The fifth argument `<role>` selects this dispatch's idle budget and is recorded in the run's status sidecar. `executor` and `verifier` run silent build+test suites and get a longer budget (1500s) than every other role (600s), so the wrong value — or none — is what reaps a healthy build mid-suite. It carries the dispatched Agent `name` minus the `agy-` prefix, so the sidecar names the actual assignment (`worker-reverify-r1`, `executor`, …) rather than a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim — Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The default when the argument is omitted is `worker`, which carries the short budget — so pass it explicitly.
34
36
 
35
37
  The fourth argument is **mandatory for implementation phase** and optional otherwise. It must be the literal `EXECUTOR_WORKTREE_PATH` recorded in the run context; the wrapper adds it to agy's `--add-dir` workspace list so the model can both read and operate on the worktree alongside project-root.
36
38
 
37
39
  The wrapper internally runs:
38
40
  ```bash
39
- agy --print "<prompt>" --model "<model>" --add-dir "<project-root>" [--add-dir "<worktree-path>"] --dangerously-skip-permissions
41
+ agy --print "<prompt>" --model "<model>" --add-dir "<project-root>" [--add-dir "<worktree-path>"] --output-format stream-json --print-timeout 7200s --dangerously-skip-permissions
40
42
  ```
41
43
 
42
44
  The wrapper exists because agent-host Bash permission matchers can reject simple-prefix matches when the command contains stdin/stderr redirects. Calling `agy --print ... < <path> 2>/dev/null` directly may trigger a permission prompt even when `Bash(agy:*)` is allowlisted. The wrapper folds the redirects inside, so the harness sees a single non-redirect command that matches `Bash($HOME/.okstra/bin/okstra-antigravity-exec.sh:*)`.
43
45
 
44
- **Do NOT** invoke `agy --print ... 2>>log > >(tee)` directly — always go through the wrapper. agy has no `--cd` flag, so the wrapper anchors workspace correctness via `--add-dir <project-root>` regardless of inherited cwd.
46
+ **Do NOT** invoke `agy --print ...` directly — always go through the wrapper. agy has no `--cd` flag, so the wrapper anchors workspace correctness via `--add-dir <project-root>` regardless of inherited cwd, and it is what turns agy's `stream-json` events into rows a person can read; invoked directly you get raw JSON and no `.log`.
45
47
 
46
48
  ## Execution Rules
47
49
 
@@ -75,9 +77,9 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
75
77
 
76
78
  **Dispatch (background, no foreground timeout):**
77
79
  ```bash
78
- $HOME/.okstra/bin/okstra-antigravity-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>"
80
+ $HOME/.okstra/bin/okstra-antigravity-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>" --presentation quiet
79
81
  ```
80
- Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `--print`, `--model`, the repeatable `--add-dir`, inlining the prompt file as the `--print` argument (agy does not read stdin), `--dangerously-skip-permissions`, and stderr capture internally. Calling `agy` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(agy:*)` and produce a permission prompt every dispatch.
82
+ Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `--print`, `--model`, the repeatable `--add-dir`, inlining the prompt file as the `--print` argument (agy does not read stdin), `--output-format stream-json`, the `--print-timeout` wall-clock cap, and `--dangerously-skip-permissions`. The event stream is rendered into readable rows — one per tool call and one per tool result — which go to the `.log` beside the prompt, and to the screen only when a pane asked for them. Calling `agy` directly (without the wrapper) is an error in this skill: it produces a permission prompt every dispatch and leaves the raw stream unrendered.
81
83
 
82
84
  **Poll loop (BashOutput-only, 30-minute cap):**
83
85
  - Record `start_ts` at dispatch time via a single `Bash` call: `date +%s` (output captured).
@@ -103,7 +105,7 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
103
105
  b. **CLI failure first.** If the final `BashOutput` reports a non-zero `exit_code`, follow the **CLI failure** rule in §"Error reporting" before returning. Do NOT perform the result-file check on a failed exit — `cli-failure` already covers it.
104
106
 
105
107
  c. **Result-file existence check (exit 0 only).** If `exit_code == 0` BUT no file exists at the extracted Result Path, the Antigravity CLI returned 0 without producing the analysis artifact (it streamed prose, hit its token budget or a sandbox EPERM mid-`Write`, and exited 0 with the artifact never persisted). Forwarding the partial stdout degrades lead synthesis, so this path is required.
106
- 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the wrapper writes the log next to it per the §"trace pane" comment in `okstra-antigravity-exec.sh`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/agy-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
108
+ 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the live log is always written beside the prompt with the `.md` suffix replaced by `.log`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/agy-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
107
109
  2. Record a `cli-failure` event directly to the run-level error log via the exact `okstra error-log append-observed` template in §"Error reporting" — substitute `--exit-code 0`, `--duration-ms <observed-ms>`, `--message "okstra-antigravity-exec.sh exited 0 but no result file at <abs-path>"`, and `--stderr-excerpt-file <temp-tail-path>`.
108
110
  3. Return `ANTIGRAVITY_RESULT_MISSING: agy exited 0 but result file absent at <abs-path>` instead of the raw stdout. The lead is responsible for deciding redispatch per `team-contract` "Lead Redispatch Policy on Result-Missing".
109
111
 
@@ -27,21 +27,23 @@ Execute the OpenAI Codex CLI and return the analysis result.
27
27
 
28
28
  **Required form (uses the okstra wrapper to avoid redirect-triggered permission prompts):**
29
29
  ```bash
30
- $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
30
+ $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>] --presentation quiet
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is folded into both the caller (worker) pane title `codex-<role>` and the sibling trace-pane title `codex-<role>-tail`. `<role>` carries the dispatched Agent `name` minus the `codex-` prefix, so the pane title equals the FleetView teammate name (`codex-worker-reverify-r1`, `codex-executor`, …) instead of a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the pane title is self-describing.
33
+ `--presentation quiet` belongs on every dispatch you make. You have no pane: your stdout is the calling agent's context window, and `live` fills it with every tool call and every tool result the CLI produces. Nothing is lost by withholding itthe same progress is written to the `.log` beside the prompt either way, and what you return is the worker's closing text. `live` is for a dispatch that opened a pane to show it in, and that dispatch passes the flag itself.
34
+
35
+ The fifth argument `<role>` selects this dispatch's idle budget and is recorded in the run's status sidecar. `executor` and `verifier` run silent build+test suites and get a longer budget (1500s) than every other role (600s), so the wrong value — or none — is what reaps a healthy build mid-suite. It carries the dispatched Agent `name` minus the `codex-` prefix, so the sidecar names the actual assignment (`worker-reverify-r1`, `executor`, …) rather than a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim — Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The default when the argument is omitted is `worker`, which carries the short budget — so pass it explicitly.
34
36
 
35
37
  The fourth argument is **mandatory for implementation phase** and optional otherwise. It must be the literal `EXECUTOR_WORKTREE_PATH` recorded in the run context; the wrapper forwards it to codex as `--add-dir`, which grants the codex sandbox write access to the worktree (where all implementation-phase mutations occur). Without it, codex's `workspace-write` sandbox is anchored only at `<project-root>` and rejects every Edit/Write that targets the worktree (EPERM), which is the failure pattern that originally motivated this argument.
36
38
 
37
39
  The wrapper internally runs:
38
40
  ```bash
39
- codex exec -C "<project-root>" [--add-dir "<worktree-path>"] --model "<model>" --sandbox workspace-write -c approval_policy=never - < "<prompt-path>" 2>/dev/null
41
+ codex exec -C "<project-root>" [--add-dir "<worktree-path>"] --model "<model>" --sandbox workspace-write -c approval_policy=never - < "<prompt-path>"
40
42
  ```
41
43
 
42
44
  The wrapper exists because agent-host Bash permission matchers can reject simple-prefix matches when the command contains stdin/stderr redirects. Calling `codex exec ... < <path> 2>/dev/null` directly may trigger a permission prompt even when `Bash(codex exec:*)` is allowlisted. The wrapper folds the redirects inside, so the harness sees a single non-redirect command that matches `Bash($HOME/.okstra/bin/okstra-codex-exec.sh:*)`.
43
45
 
44
- **Do NOT use** the non-existent `-q` flag. The approval policy MUST be set with `-c approval_policy=never` (the `-a`/`--ask-for-approval` flag is NOT accepted by `codex exec` — it errors with `unexpected argument '-a'`); without `approval_policy=never` codex runs under the default `on-request` policy and, having no TTY to answer an approval prompt, ends the turn in a few seconds with exit 0 and no result file. **Do NOT** invoke `codex exec ... < ... 2>/dev/null` directly — always go through the wrapper.
46
+ **Do NOT use** the non-existent `-q` flag. The approval policy MUST be set with `-c approval_policy=never` (the `-a`/`--ask-for-approval` flag is NOT accepted by `codex exec` — it errors with `unexpected argument '-a'`); without `approval_policy=never` codex runs under the default `on-request` policy and, having no TTY to answer an approval prompt, ends the turn in a few seconds with exit 0 and no result file. **Do NOT** invoke `codex exec ... < ...` directly — always go through the wrapper.
45
47
 
46
48
  ## Execution Rules
47
49
 
@@ -75,9 +77,9 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
75
77
 
76
78
  **Dispatch (background, no foreground timeout):**
77
79
  ```bash
78
- $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>"
80
+ $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>" --presentation quiet
79
81
  ```
80
- Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `-C`, `--add-dir`, `--model`, `--sandbox workspace-write`, `-c approval_policy=never` (non-interactive: never block on an approval prompt the TTY-less dispatch cannot answer), the stdin redirect from the prompt file, and stderr suppression internally. Calling `codex exec` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(codex exec:*)` and produce a permission prompt every dispatch.
82
+ Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper handles `-C`, `--add-dir`, `--model`, `--sandbox workspace-write`, `-c approval_policy=never` (non-interactive: never block on an approval prompt the TTY-less dispatch cannot answer), and feeding the prompt file on stdin. codex writes its result to stdout and its progress to stderr; the wrapper keeps the two apart, so the result reaches you in full while `--presentation quiet` withholds only the progress, which is archived in the `.log` beside the prompt. Calling `codex exec` directly (without the wrapper) is an error in this skill: the redirect tokens disqualify the prefix match against `Bash(codex exec:*)` and produce a permission prompt every dispatch.
81
83
 
82
84
  **Poll loop (BashOutput-only, 30-minute cap):**
83
85
  - Record `start_ts` at dispatch time via a single `Bash` call: `date +%s` (output captured).
@@ -103,7 +105,7 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
103
105
  b. **CLI failure first.** If the final `BashOutput` reports a non-zero `exit_code`, follow the **CLI failure** rule in §"Error reporting" before returning. Do NOT perform the result-file check on a failed exit — `cli-failure` already covers it.
104
106
 
105
107
  c. **Result-file existence check (exit 0 only).** If `exit_code == 0` BUT no file exists at the extracted Result Path, the Codex CLI returned 0 without producing the analysis artifact (it streamed prose, hit its token budget or a sandbox EPERM mid-`Write`, and exited 0 with the artifact never persisted). Forwarding the partial stdout degrades lead synthesis, so this path is required.
106
- 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the wrapper writes the log next to it per the §"trace pane" comment in `okstra-codex-exec.sh`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/codex-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
108
+ 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the live log is always written beside the prompt with the `.md` suffix replaced by `.log`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/codex-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
107
109
  2. Record a `cli-failure` event directly to the run-level error log via the exact `okstra error-log append-observed` template in §"Error reporting" — substitute `--exit-code 0`, `--duration-ms <observed-ms>`, `--message "okstra-codex-exec.sh exited 0 but no result file at <abs-path>"`, and `--stderr-excerpt-file <temp-tail-path>`.
108
110
  3. Return `CODEX_RESULT_MISSING: codex exited 0 but result file absent at <abs-path>` instead of the raw stdout. The lead is responsible for deciding redispatch per `team-contract` "Lead Redispatch Policy on Result-Missing".
109
111
 
@@ -27,10 +27,12 @@ Execute the xAI Grok CLI CLI and return the analysis result.
27
27
 
28
28
  **Required form (uses the okstra wrapper to avoid redirect-triggered permission prompts):**
29
29
  ```bash
30
- $HOME/.okstra/bin/okstra-grok-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
30
+ $HOME/.okstra/bin/okstra-grok-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>] --presentation quiet
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is folded into both the caller (worker) pane title `grok-<role>` and the sibling trace-pane title `grok-<role>-tail`. `<role>` carries the dispatched Agent `name` minus the `grok-` prefix, so the pane title equals the FleetView teammate name (`grok-worker-reverify-r1`, `grok-executor`, …) instead of a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the pane title is self-describing.
33
+ `--presentation quiet` belongs on every dispatch you make. You have no pane: your stdout is the calling agent's context window, and `live` fills it with every tool call and every tool result the CLI produces. Nothing is lost by withholding itthe same progress is written to the `.log` beside the prompt either way, and what you return is the worker's closing text. `live` is for a dispatch that opened a pane to show it in, and that dispatch passes the flag itself.
34
+
35
+ The fifth argument `<role>` selects this dispatch's idle budget and is recorded in the run's status sidecar. `executor` and `verifier` run silent build+test suites and get a longer budget (1500s) than every other role (600s), so the wrong value — or none — is what reaps a healthy build mid-suite. It carries the dispatched Agent `name` minus the `grok-` prefix, so the sidecar names the actual assignment (`worker-reverify-r1`, `executor`, …) rather than a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim — Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The default when the argument is omitted is `worker`, which carries the short budget — so pass it explicitly.
34
36
 
35
37
  The fourth argument is **mandatory for implementation phase** and optional otherwise. For supported analysis and critic roles it may identify the active read target; the shared provider runner uses that directory as both process cwd and Grok `--cwd`. Grok is not registered for executor or verifier roles.
36
38
 
@@ -75,7 +77,7 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
75
77
 
76
78
  **Dispatch (background, no foreground timeout):**
77
79
  ```bash
78
- $HOME/.okstra/bin/okstra-grok-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>"
80
+ $HOME/.okstra/bin/okstra-grok-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>" --presentation quiet
79
81
  ```
80
82
  Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper passes the persisted prompt with `-p`, the assigned model with `-m`, selects `streaming-json`, anchors `--cwd` to the active project/worktree, mirrors output to the run log, and records the shared status sidecar.
81
83
 
@@ -103,7 +105,7 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
103
105
  b. **CLI failure first.** If the final `BashOutput` reports a non-zero `exit_code`, follow the **CLI failure** rule in §"Error reporting" before returning. Do NOT perform the result-file check on a failed exit — `cli-failure` already covers it.
104
106
 
105
107
  c. **Result-file existence check (exit 0 only).** If `exit_code == 0` BUT no file exists at the extracted Result Path, the Grok CLI returned 0 without producing the analysis artifact (it streamed prose, hit its token budget or a sandbox EPERM mid-`Write`, and exited 0 with the artifact never persisted). Forwarding the partial stdout degrades lead synthesis, so this path is required.
106
- 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the wrapper writes the log next to it per the §"trace pane" comment in `okstra-grok-exec.sh`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/grok-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
108
+ 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the live log is always written beside the prompt with the `.md` suffix replaced by `.log`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/grok-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
107
109
  2. Record a `cli-failure` event directly to the run-level error log via the exact `okstra error-log append-observed` template in §"Error reporting" — substitute `--exit-code 0`, `--duration-ms <observed-ms>`, `--message "okstra-grok-exec.sh exited 0 but no result file at <abs-path>"`, and `--stderr-excerpt-file <temp-tail-path>`.
108
110
  3. Return `GROK_RESULT_MISSING: grok exited 0 but result file absent at <abs-path>` instead of the raw stdout. The lead is responsible for deciding redispatch per `team-contract` "Lead Redispatch Policy on Result-Missing".
109
111
 
@@ -27,10 +27,12 @@ Execute the Moonshot Kimi CLI CLI and return the analysis result.
27
27
 
28
28
  **Required form (uses the okstra wrapper to avoid redirect-triggered permission prompts):**
29
29
  ```bash
30
- $HOME/.okstra/bin/okstra-kimi-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
30
+ $HOME/.okstra/bin/okstra-kimi-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>] --presentation quiet
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is folded into both the caller (worker) pane title `kimi-<role>` and the sibling trace-pane title `kimi-<role>-tail`. `<role>` carries the dispatched Agent `name` minus the `kimi-` prefix, so the pane title equals the FleetView teammate name (`kimi-worker-reverify-r1`, `kimi-executor`, …) instead of a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the pane title is self-describing.
33
+ `--presentation quiet` belongs on every dispatch you make. You have no pane: your stdout is the calling agent's context window, and `live` fills it with every tool call and every tool result the CLI produces. Nothing is lost by withholding itthe same progress is written to the `.log` beside the prompt either way, and what you return is the worker's closing text. `live` is for a dispatch that opened a pane to show it in, and that dispatch passes the flag itself.
34
+
35
+ The fifth argument `<role>` selects this dispatch's idle budget and is recorded in the run's status sidecar. `executor` and `verifier` run silent build+test suites and get a longer budget (1500s) than every other role (600s), so the wrong value — or none — is what reaps a healthy build mid-suite. It carries the dispatched Agent `name` minus the `kimi-` prefix, so the sidecar names the actual assignment (`worker-reverify-r1`, `executor`, …) rather than a generic `worker`. Pass the value of the dispatch prompt's `**Pane role:**` line verbatim — Lead injects it on every CLI dispatch: `**Pane role:** worker-reverify-r1` on a convergence reverify, `**Pane role:** worker-critic` on a critic pass, `**Pane role:** executor` on an `implementation` Executor dispatch, `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, and `**Pane role:** worker` on a plain analysis dispatch. The default when the argument is omitted is `worker`, which carries the short budget — so pass it explicitly.
34
36
 
35
37
  The fourth argument is **mandatory for implementation phase** and optional otherwise. For supported analysis and critic roles it may identify the active read target; the shared provider runner executes Kimi with that directory as cwd. Kimi is not registered for executor or verifier roles.
36
38
 
@@ -75,7 +77,7 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
75
77
 
76
78
  **Dispatch (background, no foreground timeout):**
77
79
  ```bash
78
- $HOME/.okstra/bin/okstra-kimi-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>"
80
+ $HOME/.okstra/bin/okstra-kimi-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" "<absolute-worktree-path>" "<pane-role>" --presentation quiet
79
81
  ```
80
82
  Call `Bash` with `run_in_background: true`. Capture the returned `bash_id` (a.k.a. `shell_id`). Pass the positional arguments verbatim — do NOT use environment variables, `cd`, `&&` chains, or pipes from `cat`. Substitute the literal extracted Project Root, model execution value, prompt-history path, and worktree path, plus the `**Pane role:**` value (`executor` / `verifier`, or `worker` when the line is absent). The fourth argument is **mandatory for implementation phase** (extract from `EXECUTOR_WORKTREE_PATH` in the lead prompt's run context or the `**Worktree:**` / `cwd for every mutating command:` line) and **may be omitted only for non-implementation analysis phases** that do not mutate the worktree. The wrapper passes the persisted prompt with `-p`, the assigned model with `-m`, selects `stream-json`, runs in the active project/worktree, mirrors output to the run log, and records the shared status sidecar.
81
83
 
@@ -103,7 +105,7 @@ The wrapper exists because agent-host Bash permission matchers can reject simple
103
105
  b. **CLI failure first.** If the final `BashOutput` reports a non-zero `exit_code`, follow the **CLI failure** rule in §"Error reporting" before returning. Do NOT perform the result-file check on a failed exit — `cli-failure` already covers it.
104
106
 
105
107
  c. **Result-file existence check (exit 0 only).** If `exit_code == 0` BUT no file exists at the extracted Result Path, the Kimi CLI returned 0 without producing the analysis artifact (it streamed prose, hit its token budget or a sandbox EPERM mid-`Write`, and exited 0 with the artifact never persisted). Forwarding the partial stdout degrades lead synthesis, so this path is required.
106
- 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the wrapper writes the log next to it per the §"trace pane" comment in `okstra-kimi-exec.sh`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/kimi-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
108
+ 1. Capture the final ~10 lines of the wrapper's live log for diagnostics — single Bash call: `tail -n 10 "${prompt_path%.md}.log"` (substitute the literal absolute prompt-history path; the live log is always written beside the prompt with the `.md` suffix replaced by `.log`). Write the captured lines to a temp file (e.g. `<errors-sidecar-dir>/kimi-result-missing-tail.txt`) so `--stderr-excerpt-file` can reference it.
107
109
  2. Record a `cli-failure` event directly to the run-level error log via the exact `okstra error-log append-observed` template in §"Error reporting" — substitute `--exit-code 0`, `--duration-ms <observed-ms>`, `--message "okstra-kimi-exec.sh exited 0 but no result file at <abs-path>"`, and `--stderr-excerpt-file <temp-tail-path>`.
108
110
  3. Return `KIMI_RESULT_MISSING: kimi exited 0 but result file absent at <abs-path>` instead of the raw stdout. The lead is responsible for deciding redispatch per `team-contract` "Lead Redispatch Policy on Result-Missing".
109
111