okstra 0.84.0 → 0.85.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 (22) hide show
  1. package/package.json +1 -1
  2. package/runtime/BUILD.json +2 -2
  3. package/runtime/agents/workers/codex-worker.md +1 -1
  4. package/runtime/agents/workers/gemini-worker.md +1 -1
  5. package/runtime/bin/okstra-claude-exec.sh +61 -2
  6. package/runtime/bin/okstra-codex-exec.sh +10 -9
  7. package/runtime/bin/okstra-gemini-exec.sh +11 -10
  8. package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/scripts/preedit-check.sh +3 -3
  9. package/runtime/prompts/lead/convergence.md +3 -2
  10. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  11. package/runtime/templates/reports/settings.template.json +1 -1
  12. package/src/doctor.mjs +6 -1
  13. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/architectures/hexagonal.md +0 -0
  14. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/clean-code.md +0 -0
  15. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/frameworks/node-server.md +0 -0
  16. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/languages/java.md +0 -0
  17. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/languages/javascript-typescript.md +0 -0
  18. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/languages/kotlin.md +0 -0
  19. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/languages/python.md +0 -0
  20. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/languages/rust.md +0 -0
  21. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/languages/sql.md +0 -0
  22. /package/runtime/prompts/{okstra-coding-preflight → coding-preflight}/overview.md +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.84.0",
3
+ "version": "0.85.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.84.0",
3
- "builtAt": "2026-06-15T18:39:25.947Z",
2
+ "package": "0.85.0",
3
+ "builtAt": "2026-06-15T19:37:36.878Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -30,7 +30,7 @@ You are a Codex worker agent. Your job is to execute the OpenAI Codex CLI and re
30
30
  $HOME/.okstra/bin/okstra-codex-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is folded into both the caller (worker) pane title `codex-<role>-<pid>` and the sibling trace-pane title `codex-<role>-<pid>-tail` (`<pid>` = the wrapper's PID, present so concurrent dispatches of the same role can be told apart). Pass the value of the dispatch prompt's `**Pane role:**` line verbatim — Lead injects `**Pane role:** executor` on an `implementation` Executor dispatch and `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, so the trace pane names the actual job (`codex-executor-<pid>` / `codex-verifier-<pid>`). When the prompt carries no `**Pane role:**` line (analysis phases), pass the literal `worker`. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the pane title is self-describing.
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.
34
34
 
35
35
  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
36
 
@@ -30,7 +30,7 @@ You are a Gemini worker agent. Your job is to execute the Google Gemini CLI and
30
30
  $HOME/.okstra/bin/okstra-gemini-exec.sh "<absolute-project-root>" "<assigned-model-execution-value>" "<absolute-prompt-history-path>" [<absolute-worktree-path>] [<role>]
31
31
  ```
32
32
 
33
- The fifth argument `<role>` is folded into both the caller (worker) pane title `gemini-<role>-<pid>` and the sibling trace-pane title `gemini-<role>-<pid>-tail` (`<pid>` = the wrapper's PID, present so concurrent dispatches of the same role can be told apart). Pass the value of the dispatch prompt's `**Pane role:**` line verbatim — Lead injects `**Pane role:** executor` on an `implementation` Executor dispatch and `**Pane role:** verifier` on an `implementation` / `final-verification` verifier dispatch, so the trace pane names the actual job (`gemini-executor-<pid>` / `gemini-verifier-<pid>`). When the prompt carries no `**Pane role:**` line (analysis phases), pass the literal `worker`. The wrapper defaults to `worker` when the argument is omitted, but pass it explicitly so the pane title is self-describing.
33
+ The fifth argument `<role>` is folded into both the caller (worker) pane title `gemini-<role>` and the sibling trace-pane title `gemini-<role>-tail`. `<role>` carries the dispatched Agent `name` minus the `gemini-` prefix, so the pane title equals the FleetView teammate name (`gemini-worker-reverify-r1`, `gemini-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.
34
34
 
35
35
  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 appends it to gemini's `--include-directories` list so the model can both read and operate on the worktree alongside project-root.
36
36
 
@@ -39,6 +39,25 @@ if ! command -v claude >/dev/null 2>&1; then
39
39
  exit 127
40
40
  fi
41
41
 
42
+ # Grant tool access to the implementation worktree (and the main repo's shared
43
+ # .git for linked-worktree commits) when one is supplied. The executor mutates
44
+ # a git worktree that lives outside project-root; without --add-dir Claude
45
+ # Code's Edit/Write tools cannot reach it. Mirrors okstra-codex-exec.sh.
46
+ extra_args=()
47
+ if [[ -n "$worktree_path" ]]; then
48
+ extra_args+=(--add-dir "$worktree_path")
49
+ if command -v git >/dev/null 2>&1; then
50
+ common_git_dir=$(git -C "$worktree_path" rev-parse --git-common-dir 2>/dev/null || true)
51
+ if [[ -n "$common_git_dir" ]]; then
52
+ [[ "$common_git_dir" != /* ]] && common_git_dir="$worktree_path/$common_git_dir"
53
+ if [[ -d "$common_git_dir" ]]; then
54
+ common_git_dir=$(cd "$common_git_dir" && pwd -P)
55
+ extra_args+=(--add-dir "$common_git_dir")
56
+ fi
57
+ fi
58
+ fi
59
+ fi
60
+
42
61
  log_path="${prompt_path%.md}.log"
43
62
  [[ "$log_path" == "$prompt_path" ]] && log_path="${prompt_path}.log"
44
63
  : > "$log_path"
@@ -62,10 +81,50 @@ trap _okstra_status_finish EXIT
62
81
 
63
82
  cd "$project_root"
64
83
  set +e
65
- claude -p --model "$model" --output-format=stream-json < "$prompt_path" \
84
+ claude -p --model "$model" ${extra_args[@]+"${extra_args[@]}"} --output-format=stream-json \
85
+ < "$prompt_path" \
66
86
  > >(tee -a "$log_path") \
67
- 2> >(tee -a "$log_path" >&2)
87
+ 2> >(tee -a "$log_path" >&2) &
88
+ claude_pid=$!
89
+
90
+ # Idle watchdog: if the live log sees no write for $idle_timeout_secs, SIGTERM
91
+ # claude (5s grace, then SIGKILL) and mark the status sidecar timed out, so a
92
+ # silent hang is bounded instead of stalling the lead. Pass 0 to disable.
93
+ watchdog_pid=""
94
+ if (( idle_timeout_secs > 0 )); then
95
+ poll_interval=$(( idle_timeout_secs / 20 ))
96
+ (( poll_interval < 5 )) && poll_interval=5
97
+ (( poll_interval > 30 )) && poll_interval=30
98
+ (
99
+ while kill -0 "$claude_pid" 2>/dev/null; do
100
+ sleep "$poll_interval"
101
+ kill -0 "$claude_pid" 2>/dev/null || exit 0
102
+ last_mtime=$(stat -f %m "$log_path" 2>/dev/null || stat -c %Y "$log_path" 2>/dev/null || printf '0')
103
+ now=$(date +%s)
104
+ idle=$(( now - last_mtime ))
105
+ if (( idle >= idle_timeout_secs )); then
106
+ printf '\n[okstra wrapper] idle-watchdog: %ds without stdout — terminating claude (pid=%d)\n' \
107
+ "$idle" "$claude_pid" >> "$log_path" 2>&1 || true
108
+ python3 "$script_dir/okstra-wrapper-status.py" \
109
+ timeout "$status_path" "$now" "$idle" >>"$log_path" 2>&1 || true
110
+ kill -TERM "$claude_pid" 2>/dev/null || true
111
+ sleep 5
112
+ kill -KILL "$claude_pid" 2>/dev/null || true
113
+ exit 0
114
+ fi
115
+ done
116
+ ) &
117
+ watchdog_pid=$!
118
+ fi
119
+
120
+ wait "$claude_pid"
68
121
  claude_exit=$?
69
122
  set -e
70
123
 
124
+ if [[ -n "$watchdog_pid" ]]; then
125
+ kill "$watchdog_pid" 2>/dev/null || true
126
+ wait "$watchdog_pid" 2>/dev/null || true
127
+ fi
128
+ wait 2>/dev/null || true
129
+
71
130
  exit "$claude_exit"
@@ -211,11 +211,12 @@ if type okstra_resolve_caller_pane >/dev/null 2>&1; then
211
211
  caller_pane="$(okstra_resolve_caller_pane)"
212
212
  fi
213
213
 
214
- # Pane titles: worker (caller) pane gets `codex-<role>-<pid>`; the sibling
215
- # trace pane is that same caller title with a `-tail` suffix, so the
216
- # operator can visually pair `<caller> ↔ <caller>-tail`. The wrapper PID in
217
- # the caller title disambiguates concurrent dispatches of the same role.
218
- pane_label="codex-${role}-$$"
214
+ # Pane titles: the caller (worker) pane gets `codex-<role>`; the sibling trace
215
+ # pane is that same caller title with a `-tail` suffix, so the operator can
216
+ # visually pair `<caller> ↔ <caller>-tail`. `role` carries the dispatched Agent
217
+ # name minus the `codex-` prefix (e.g. `worker-reverify-r1`, `executor`), so the
218
+ # pane title equals the FleetView teammate name instead of a generic `worker`.
219
+ pane_label="codex-${role}"
219
220
  trace_label="${pane_label}-tail"
220
221
 
221
222
  # Capture the caller pane's current title so the EXIT trap can restore it
@@ -251,13 +252,13 @@ fi
251
252
  # for the wrapper to exit. This fires in every phase the wrapper is invoked
252
253
  # from (analysis, error-analysis, implementation-planning, implementation,
253
254
  # …) — long-running codex dispatches are not implementation-specific. The
254
- # new pane carries the title `codex-<role>-<pid>-tail` so the operator can
255
- # pair it with its caller pane (`codex-<role>-<pid>`). The split is
255
+ # new pane carries the title `codex-<role>-tail` so the operator can
256
+ # pair it with its caller pane (`codex-<role>`). The split is
256
257
  # explicitly anchored to the caller pane (`-t "$caller_pane"`) to avoid
257
258
  # attaching to tmux's idle active pane. `role` is the optional 5th
258
259
  # positional arg (defaults to `worker`); callers that dispatch a different
259
- # role (e.g. `executor`) must pass it explicitly. The `<pid>` suffix is the
260
- # wrapper's PID and disambiguates concurrent dispatches of the same role.
260
+ # role (e.g. `executor`, `worker-reverify-r1`) must pass it explicitly so the
261
+ # pane title names the actual job.
261
262
  # The pane uses `tail -F` (follow-by-name) so it survives any truncation a
262
263
  # re-dispatch performs on the same log path. We gate on a resolved
263
264
  # `$caller_pane` (non-empty only when tmux is reachable) rather than the
@@ -153,11 +153,12 @@ if type okstra_resolve_caller_pane >/dev/null 2>&1; then
153
153
  caller_pane="$(okstra_resolve_caller_pane)"
154
154
  fi
155
155
 
156
- # Pane titles: worker (caller) pane gets `gemini-<role>-<pid>`; the sibling
157
- # trace pane is that same caller title with a `-tail` suffix, so the
158
- # operator can visually pair `<caller> ↔ <caller>-tail`. The wrapper PID in
159
- # the caller title disambiguates concurrent dispatches of the same role.
160
- pane_label="gemini-${role}-$$"
156
+ # Pane titles: the caller (worker) pane gets `gemini-<role>`; the sibling trace
157
+ # pane is that same caller title with a `-tail` suffix, so the operator can
158
+ # visually pair `<caller> ↔ <caller>-tail`. `role` carries the dispatched Agent
159
+ # name minus the `gemini-` prefix (e.g. `worker-reverify-r1`, `executor`), so the
160
+ # pane title equals the FleetView teammate name instead of a generic `worker`.
161
+ pane_label="gemini-${role}"
161
162
  trace_label="${pane_label}-tail"
162
163
 
163
164
  # Capture the caller pane's current title so the EXIT trap can restore it
@@ -191,13 +192,13 @@ fi
191
192
  # When a tmux session is reachable, split a sibling pane tailing the log so
192
193
  # the operator can watch progress live. This fires in every phase the
193
194
  # wrapper is invoked from — long-running gemini dispatches are not
194
- # implementation-specific. Title `gemini-<role>-<pid>-tail` so the operator
195
- # can pair it with its caller pane (`gemini-<role>-<pid>`). The split is
195
+ # implementation-specific. Title `gemini-<role>-tail` so the operator
196
+ # can pair it with its caller pane (`gemini-<role>`). The split is
196
197
  # explicitly anchored to the caller pane to avoid attaching to tmux's idle
197
198
  # active pane. `role` is the optional 5th positional arg (defaults to
198
- # `worker`); callers that dispatch a different role must pass it explicitly.
199
- # The `<pid>` suffix is the wrapper's PID and disambiguates concurrent
200
- # dispatches of the same role. We gate on a resolved `$caller_pane`
199
+ # `worker`); callers that dispatch a different role (e.g. `worker-reverify-r1`)
200
+ # must pass it explicitly so the pane title names the actual job.
201
+ # We gate on a resolved `$caller_pane`
201
202
  # (non-empty only when tmux is reachable) rather than the now-stripped
202
203
  # `$TMUX`. See the codex wrapper for the full design rationale and the
203
204
  # silent-degrade failure model.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # PreToolUse hook for okstra-coding-preflight.
2
+ # PreToolUse hook for coding-preflight.
3
3
  #
4
4
  # Inspects the target file path of a Write/Edit/MultiEdit/NotebookEdit
5
5
  # call. If the extension matches a language covered by this skill,
@@ -68,8 +68,8 @@ fi
68
68
 
69
69
  : > "$marker"
70
70
 
71
- skill_root="$HOME/.okstra/prompts/okstra-coding-preflight"
72
- msg="[okstra-coding-preflight] About to edit a ${lang} file (${file_path}). Before writing, you MUST Read ${skill_root}/${ref} plus ${skill_root}/clean-code.md (the skill is user-invocable:false — read the files directly).${extra_hint} If the project uses ports-and-adapters (domain/ + ports/ + adapters/, *.port.* files), also Read ${skill_root}/architecture/hexagonal.md. (Fires once per session per language.)"
71
+ skill_root="$HOME/.okstra/prompts/coding-preflight"
72
+ msg="[coding-preflight] About to edit a ${lang} file (${file_path}). Before writing, you MUST Read ${skill_root}/${ref} plus ${skill_root}/clean-code.md (the skill is user-invocable:false — read the files directly).${extra_hint} If the project uses ports-and-adapters (domain/ + ports/ + adapters/, *.port.* files), also Read ${skill_root}/architecture/hexagonal.md. (Fires once per session per language.)"
73
73
 
74
74
  jq -nc \
75
75
  --arg event "PreToolUse" \
@@ -266,8 +266,9 @@ Agent(
266
266
 
267
267
  - Agent Teams mode: Spawn within an existing team
268
268
  - Fallback mode: Spawn with `run_in_background: true` and no `team_name`
269
+ - CLI (Codex / Gemini) worker: inject `**Pane role:** worker-reverify-r<N>` (the Agent `name` minus the `<cli>-` prefix) into the reverify prompt body so the tmux trace pane reads `<cli>-worker-reverify-r<N>` per [okstra-lead-contract](./okstra-lead-contract.md) "Agent `name` on dispatch".
269
270
 
270
- For `tmux-pane` backend runs, do not use the Agent snippet. For each reverify round, write a jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json` with `dispatchKind: "reverify-r<N>"` and worker entries containing `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`; then run `okstra team dispatch --project-root <dir> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>` followed by `okstra team await --project-root <dir> --run-manifest <path>`.
271
+ For `tmux-pane` backend runs, do not use the Agent snippet. For each reverify round, write a jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json` with `dispatchKind: "reverify-r<N>"` and worker entries containing `workerId`, `provider`, `role` (set to `worker-reverify-r<N>` for the pane title), `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`; then run `okstra team dispatch --project-root <dir> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>` followed by `okstra team await --project-root <dir> --run-manifest <path>`.
271
272
 
272
273
 
273
274
  **Completion detection per round (BLOCKING).** Each round dispatches a variable set (1..N) of reverify workers asynchronously; the `Agent(... team_name ...)` calls return `Spawned successfully` immediately, which is NOT completion. Lead MUST detect each round's completion via the self-scheduled polling protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", with the pending set reconstructed from that round's dispatched workers' Result Paths — do NOT restate the algorithm here. Lead MUST NOT treat the spawn ack as completion and MUST NOT end its turn with a prose "waiting" statement.
@@ -523,7 +524,7 @@ The critic input is the Round 0 consolidated finding list. Reverify rounds only
523
524
  - **Gap verification + merge**: only after BOTH the finding-convergence loop has exited AND the critic result is collected, and BEFORE the Phase 6 report-writer dispatch. If the loop exited `aborted-non-result`, do NOT dispatch a gap-verification round — surface the gaps as unverified `clarification` items per §"Gap verification" and record that fact.
524
525
 
525
526
  ### Dispatch (reused worker)
526
- Dispatch ONE pass to the `config.critic.provider`'s existing subagent (`claude-worker` / `codex-worker` / `gemini-worker`) with `model = config.critic.modelExecutionValue` — no new agent type. If `config.critic.modelExecutionValue` is null/empty (model could not be resolved), skip the critic pass and record `critic-skipped: model-unresolved` in the convergence state rather than dispatching with no model. Result path: `runs/<task-type>/worker-results/<provider>-critic-<task-type>-<seq>.md`. The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
527
+ Dispatch ONE pass to the `config.critic.provider`'s existing subagent (`claude-worker` / `codex-worker` / `gemini-worker`) with `model = config.critic.modelExecutionValue` — no new agent type, `name: "<provider>-worker-critic"`. If `config.critic.modelExecutionValue` is null/empty (model could not be resolved), skip the critic pass and record `critic-skipped: model-unresolved` in the convergence state rather than dispatching with no model. For a CLI (Codex / Gemini) critic dispatch, inject `**Pane role:** worker-critic` into the prompt body so the trace pane reads `<cli>-worker-critic` per [okstra-lead-contract](./okstra-lead-contract.md) "Agent `name` on dispatch". Result path: `runs/<task-type>/worker-results/<provider>-critic-<task-type>-<seq>.md`. The critic prompt seeds the consolidated findings and asks ONLY for coverage gaps:
527
528
 
528
529
  ```
529
530
  You are the coverage critic for <task-key>. Below are the consolidated findings
@@ -222,7 +222,7 @@ If the launch prompt contains `Tmux Worker Dispatch Gate`, Phase 3 is recorded a
222
222
 
223
223
  Spawn **analysis workers only** in the same turn (Phase 4 in Teams mode; Phase 5 with `run_in_background: true` and no `team_name` when Teams unavailable). Preserve exact roster, role labels, assigned models from the task bundle.
224
224
 
225
- **Agent `name` on dispatch (BLOCKING — token-usage attribution depends on it).** Every analysis-worker `Agent(...)` call MUST set `name: "<workerId>-worker"` — `name: "claude-worker"` / `name: "codex-worker"` / `name: "gemini-worker"` — exactly as the report-writer dispatch sets `name: "report-writer"` ([report-writer](./report-writer.md)). The Agent harness records this `name` as `agentName` in the subagent session jsonl, and the Phase 7 token collector matches each worker's session by that `agentName` (`okstra_token_usage/collect.py`). A worker dispatched **without** `name` produces a session with no `agentName`; the collector cannot attribute it and records the worker as `source: "unavailable"` even though the session exists and is team-tagged (observed in `dev-9692` error-analysis: `claude`/`codex` workers dispatched without `name` → both `unavailable`, while the named `report-writer` collected normally). Convergence reverify dispatches keep the prefix (`<workerId>-worker-reverify-r<N>`). For `implementation` and `final-verification` dispatches the `name` carries the **functional role** so the FleetView teammate pill and trace panes name the actual job instead of a generic `worker`: the Executor dispatch sets `name: "<workerId>-executor"` (e.g. `codex-executor`) and every verifier dispatch sets `name: "<workerId>-verifier"` (e.g. `claude-verifier`). These role suffixes still attribute correctly — the collector matches any `agentName` beginning `<workerId>-` (`okstra_token_usage/collect.py` `match_prefixes`). **For CLI (Codex / Gemini) executor/verifier dispatches, Lead MUST also inject a `**Pane role:** executor` (or `verifier`) line into the dispatched prompt body** — the wrapper subagent reads it and passes it as the wrapper's 5th `<role>` argument so the tmux trace pane title reads `<cli>-<role>-<pid>` instead of `<cli>-worker-<pid>` (see `agents/workers/_cli-wrapper-template.md`). Analysis-phase worker dispatches are unchanged: `name: "<workerId>-worker"`, no `**Pane role:**` line (the wrapper defaults to `worker`).
225
+ **Agent `name` on dispatch (BLOCKING — token-usage attribution depends on it).** Every analysis-worker `Agent(...)` call MUST set `name: "<workerId>-worker"` — `name: "claude-worker"` / `name: "codex-worker"` / `name: "gemini-worker"` — exactly as the report-writer dispatch sets `name: "report-writer"` ([report-writer](./report-writer.md)). The Agent harness records this `name` as `agentName` in the subagent session jsonl, and the Phase 7 token collector matches each worker's session by that `agentName` (`okstra_token_usage/collect.py`). A worker dispatched **without** `name` produces a session with no `agentName`; the collector cannot attribute it and records the worker as `source: "unavailable"` even though the session exists and is team-tagged (observed in `dev-9692` error-analysis: `claude`/`codex` workers dispatched without `name` → both `unavailable`, while the named `report-writer` collected normally). Convergence reverify dispatches keep the prefix (`<workerId>-worker-reverify-r<N>`). For `implementation` and `final-verification` dispatches the `name` carries the **functional role** so the FleetView teammate pill and trace panes name the actual job instead of a generic `worker`: the Executor dispatch sets `name: "<workerId>-executor"` (e.g. `codex-executor`) and every verifier dispatch sets `name: "<workerId>-verifier"` (e.g. `claude-verifier`). These role suffixes still attribute correctly — the collector matches any `agentName` beginning `<workerId>-` (`okstra_token_usage/collect.py` `match_prefixes`). **For every CLI (Codex / Gemini) dispatch, Lead MUST inject a `**Pane role:**` line into the dispatched prompt body whose value is the Agent `name` with the leading `<cli>-` prefix removed** — the wrapper subagent reads it and passes it as the wrapper's 5th `<role>` argument so the tmux trace pane title reads `<cli>-<role>` (which equals the FleetView teammate name) instead of a generic `<cli>-worker` (see `agents/workers/_cli-wrapper-template.md`). So `name: "codex-worker"` `**Pane role:** worker`, `name: "codex-worker-reverify-r1"` → `**Pane role:** worker-reverify-r1`, `name: "codex-executor"` → `**Pane role:** executor`, `name: "codex-verifier"` → `**Pane role:** verifier`. The wrapper defaults to `worker` when the line is absent, but always inject it so the pane names the actual job.
226
226
 
227
227
  **Agent `model:` on dispatch (BLOCKING — assignment is otherwise ignored).** The `Claude worker` `Agent(...)` call MUST set `model: "<family token of that role's modelExecutionValue>"` (`fable` / `opus` / `sonnet` / `haiku`), per [team-contract](./team-contract.md) "Model Assignment Rules" #3–#4. The claude-worker definition is `model: inherit`, so omitting this parameter makes the worker silently run on the lead's model instead of its manifest assignment — the assigned-vs-actual deviation. `Codex worker` / `Gemini worker` are exempt: their CLI model is applied via the wrapper's own `--model` argument, so leave their Agent `model:` at `inherit` (rule #5).
228
228
 
@@ -34,7 +34,7 @@
34
34
  "hooks": [
35
35
  {
36
36
  "type": "command",
37
- "command": "bash \"$HOME/.okstra/prompts/okstra-coding-preflight/scripts/preedit-check.sh\""
37
+ "command": "bash \"$HOME/.okstra/prompts/coding-preflight/scripts/preedit-check.sh\""
38
38
  }
39
39
  ]
40
40
  }
package/src/doctor.mjs CHANGED
@@ -146,9 +146,14 @@ async function loadPhaseDiagnostics(phase, paths) {
146
146
  ")",
147
147
  "print(json.dumps(payload, ensure_ascii=False))",
148
148
  ].join("\n");
149
+ // `home` locates the agent install root (`<home>/.claude/agents`), which is
150
+ // the user home — NOT the okstra home (`paths.home` = ~/.okstra). Agents are
151
+ // installed under `homedir()/.claude/agents` (see install.mjs), so passing
152
+ // paths.home made every worker-agent phase check resolve to a path that
153
+ // never exists and report a false "missing agent".
149
154
  const result = await runPythonSnippet({
150
155
  script,
151
- args: [phase, process.cwd(), paths.workspace, paths.home],
156
+ args: [phase, process.cwd(), paths.workspace, homedir()],
152
157
  });
153
158
  if (result.code !== 0 && !result.stdout.trim()) {
154
159
  return {