okstra 0.167.0 → 0.169.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 (102) hide show
  1. package/README.md +6 -5
  2. package/docs/architecture/storage-model.md +57 -1
  3. package/docs/architecture.md +70 -2
  4. package/docs/cli.md +8 -4
  5. package/docs/for-ai/skills/okstra-code-review.md +3 -2
  6. package/docs/for-ai/skills/okstra-schedule-gen.md +3 -1
  7. package/docs/pr-template-usage.md +10 -6
  8. package/docs/project-structure-overview.md +14 -11
  9. package/package.json +1 -1
  10. package/runtime/BUILD.json +2 -2
  11. package/runtime/agents/workers/claude-worker.md +6 -5
  12. package/runtime/agents/workers/report-writer-worker.md +9 -4
  13. package/runtime/agents/workers/translator-worker.md +6 -4
  14. package/runtime/prompts/coding-preflight/architectures/hexagonal.md +27 -1
  15. package/runtime/prompts/coding-preflight/clean-code.md +13 -0
  16. package/runtime/prompts/duties/acceptance-critic.md +24 -0
  17. package/runtime/prompts/duties/acceptance-verifier.md +24 -0
  18. package/runtime/prompts/duties/analysis-worker.md +24 -0
  19. package/runtime/prompts/duties/code-reviewer.md +24 -0
  20. package/runtime/prompts/duties/common.md +35 -0
  21. package/runtime/prompts/duties/implementation-executor.md +24 -0
  22. package/runtime/prompts/duties/implementation-verifier.md +24 -0
  23. package/runtime/prompts/duties/lead.md +24 -0
  24. package/runtime/prompts/duties/report-writer.md +24 -0
  25. package/runtime/prompts/duties/reverification-worker.md +24 -0
  26. package/runtime/prompts/duties/schedule-verifier.md +24 -0
  27. package/runtime/prompts/duties/scope-critic.md +24 -0
  28. package/runtime/prompts/duties/translator.md +24 -0
  29. package/runtime/prompts/lead/convergence.md +51 -7
  30. package/runtime/prompts/lead/okstra-lead-contract.md +10 -20
  31. package/runtime/prompts/lead/plan-body-verification.md +16 -1
  32. package/runtime/prompts/lead/report-writer.md +20 -5
  33. package/runtime/prompts/lead/team-contract.md +13 -13
  34. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  35. package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
  36. package/runtime/prompts/profiles/_implementation-executor.md +1 -1
  37. package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
  38. package/runtime/prompts/profiles/implementation.md +4 -2
  39. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +6 -0
  40. package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +3 -2
  41. package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +8 -0
  42. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +33 -0
  43. package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +13 -12
  44. package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +6 -0
  45. package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +3 -2
  46. package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +2 -0
  47. package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
  48. package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +6 -0
  49. package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -1
  50. package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +6 -0
  51. package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -1
  52. package/runtime/python/okstra_ctl/agent_invocation.py +1502 -0
  53. package/runtime/python/okstra_ctl/agent_prompt_cli.py +788 -0
  54. package/runtime/python/okstra_ctl/codex_dispatch.py +2 -107
  55. package/runtime/python/okstra_ctl/context_cost.py +46 -5
  56. package/runtime/python/okstra_ctl/dispatch_core.py +312 -37
  57. package/runtime/python/okstra_ctl/dispatch_state.py +461 -36
  58. package/runtime/python/okstra_ctl/doctor.py +150 -16
  59. package/runtime/python/okstra_ctl/entrypoints/hosts.py +87 -9
  60. package/runtime/python/okstra_ctl/initial_prompt_materialization.py +214 -23
  61. package/runtime/python/okstra_ctl/path_hints.py +26 -0
  62. package/runtime/python/okstra_ctl/paths.py +20 -0
  63. package/runtime/python/okstra_ctl/ports/__init__.py +8 -0
  64. package/runtime/python/okstra_ctl/ports/host.py +3 -0
  65. package/runtime/python/okstra_ctl/ports/host_model.py +60 -0
  66. package/runtime/python/okstra_ctl/pr_template.py +3 -6
  67. package/runtime/python/okstra_ctl/registry/host_registry.py +5 -0
  68. package/runtime/python/okstra_ctl/render.py +217 -12
  69. package/runtime/python/okstra_ctl/report_finalize.py +44 -0
  70. package/runtime/python/okstra_ctl/run.py +368 -51
  71. package/runtime/python/okstra_ctl/session.py +16 -12
  72. package/runtime/python/okstra_ctl/team.py +11 -11
  73. package/runtime/python/okstra_ctl/worker_dispatch.py +104 -0
  74. package/runtime/python/okstra_ctl/worker_prompt_body.py +5 -38
  75. package/runtime/python/okstra_ctl/worker_prompt_contract.py +32 -2
  76. package/runtime/python/okstra_ctl/worker_prompt_policy.py +38 -1
  77. package/runtime/skills/okstra-code-review/SKILL.md +22 -3
  78. package/runtime/skills/okstra-run/SKILL.md +16 -1
  79. package/runtime/skills/okstra-schedule-gen/SKILL.md +15 -1
  80. package/runtime/templates/implementation-worker-preamble.md +0 -10
  81. package/runtime/templates/report-writer-prompt-preamble.md +0 -9
  82. package/runtime/templates/reports/settings.template.json +0 -11
  83. package/runtime/templates/worker-prompt-preamble.md +0 -10
  84. package/runtime/validators/lib/fixtures.sh +93 -0
  85. package/runtime/validators/lib/validate-assets.sh +0 -8
  86. package/runtime/validators/validate-run.py +182 -0
  87. package/src/cli-registry.mjs +14 -0
  88. package/src/commands/execute/agent-prompt.mjs +25 -0
  89. package/src/commands/execute/codex-dispatch.mjs +6 -63
  90. package/src/commands/execute/worker-dispatch.mjs +76 -0
  91. package/src/commands/lifecycle/doctor.mjs +18 -3
  92. package/src/commands/lifecycle/install.mjs +33 -15
  93. package/src/commands/lifecycle/uninstall.mjs +4 -3
  94. package/src/lib/install-assets.mjs +9 -0
  95. package/runtime/agents/workers/antigravity-worker.md +0 -259
  96. package/runtime/agents/workers/codex-worker.md +0 -259
  97. package/runtime/agents/workers/grok-worker.md +0 -259
  98. package/runtime/agents/workers/kimi-worker.md +0 -259
  99. package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +0 -79
  100. package/runtime/templates/operating-standard.md +0 -22
  101. package/src/lib/worker-agent-render.mjs +0 -50
  102. /package/runtime/templates/{prd → pr}/pr-body.template.md +0 -0
@@ -19,8 +19,8 @@ Okstra tasks use one lead plus the exact worker assignments selected in the prep
19
19
  |------|------|------|---------------|------|
20
20
  | Lead | orchestration + convergence supervision + final-report review/approval | runtime-specific | -- | Does not author the final report when `Report writer worker` is rostered |
21
21
  | Claude worker | Answer every brief question across feasibility, requirement interpretation, hidden assumptions, and alternatives — with file:line evidence | broad reasoning depth, hidden assumptions, execution-risk surfacing | claude-worker | `agents/workers/claude-worker.md` |
22
- | Codex worker | Same core responsibility as Claude worker — identical questions, identical sections 1–5 | implementation realism, code-path implications, edge cases, technical trade-offs | codex-worker | generated from `agents/workers/_cli-wrapper-template.md` + `codex-worker.params.json` |
23
- | Antigravity worker | Same core responsibility as Claude worker — identical questions, identical sections 1–5 | requirement interpretation, consistency, safety, alternative viewpoints | antigravity-worker | generated from `agents/workers/_cli-wrapper-template.md` + `antigravity-worker.params.json` |
22
+ | Codex worker | Same core responsibility as Claude worker — identical questions, identical sections 1–5 | implementation realism, code-path implications, edge cases, technical trade-offs | codex-worker | final prompt composed from the invocation duty and task instructions; CLI execution uses `worker-dispatch` |
23
+ | Antigravity worker | Same core responsibility as Claude worker — identical questions, identical sections 1–5 | requirement interpretation, consistency, safety, alternative viewpoints | antigravity-worker | final prompt composed from the invocation duty and task instructions; CLI execution uses `worker-dispatch` |
24
24
  | Report writer worker | **Authors** the final-report file in Phase 6. NOT an analysis worker. | — | report-writer-worker | `agents/workers/report-writer-worker.md`. Excluded from Phase 4/5 and convergence |
25
25
 
26
26
  **Model assignment has no default.** The model for every role comes from `resultContract.requiredWorkerRoles[*].modelExecutionValue` in `task-manifest.json` (and lead model metadata). There is no per-role hard-coded fallback — see "Model Assignment Rules" below.
@@ -32,8 +32,8 @@ Disjoint initial scopes are invalid triangulation. Every selected analysis worke
32
32
  ### Model Assignment Rules
33
33
 
34
34
  1. `resultContract.requiredWorkerRoles` in `task-manifest.json` (and the lead model metadata) is the canonical source. There is no role-level fallback — a missing assignment is a manifest defect, not a license to invent one.
35
- 2. If `modelExecutionValue` differs from `model`, use `modelExecutionValue` during execution.
36
- 3. **Dispatch-time enforcement (BLOCKING).** The selected adapter receives each role's `modelExecutionValue` and must apply its documented native mapping. The adapter must fail before dispatch if it cannot apply the exact assignment; it must not inherit the lead model, change provider, or choose a nearby alias silently.
35
+ 2. Select the execution value from `runner`: `native-session` passes only `hostModelValue` to the host primitive, while `cli-wrapper` passes `modelExecutionValue` to the provider process. Both values remain recorded in the invocation contract; neither may be substituted for the other.
36
+ 3. **Dispatch-time enforcement (BLOCKING).** The selected adapter receives the complete assignment and must apply the runner-specific value above. The adapter must fail before dispatch if it cannot apply the exact assignment; it must not inherit the lead model, change provider, or choose a nearby alias silently.
37
37
 
38
38
  ### Dynamic Worker Role Determination
39
39
 
@@ -51,7 +51,7 @@ Only workers selected from `recommendedWorkers` in `task-manifest.json` and `res
51
51
  0. **Adapter-owned dispatch (BLOCKING).** Every worker start, await, retry, and shutdown goes through the selected runtime adapter. Core state records the outcome but never guesses a host primitive.
52
52
  1. The lead is responsible for orchestration, convergence supervision, and final-report review/approval. It never overrides worker analysis and never bypasses a rostered Report writer worker.
53
53
  2. `Report writer worker` is NOT an analysis worker. It is excluded from Phase 4/5 (initial analysis) and Phase 5.5 (convergence re-verification). It is spawned only in Phase 6 and is the **author** of the final-report file at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md`.
54
- 3. When `Report writer worker` is in the roster, Lead MUST dispatch it in Phase 6. The only legal lead-authored fallback is when a dispatch was attempted and recorded a terminal status of `error` / `timeout` / `not-run` with a concrete logged reason. Speculative reasons such as "session resume constraint" or "team is no longer alive" are NOT valid — `dispatch_worker` can start a fresh one-shot assignment through the selected adapter.
54
+ 3. When `Report writer worker` is in the roster, Lead MUST dispatch it in Phase 6 as a separate invocation after convergence. Omit it from Phase 4/5 analysis selection and pass `--workers report-writer` for a CLI-backed Phase 6 call. The only legal lead-authored fallback is when a dispatch was attempted and recorded a terminal status of `error` / `timeout` / `not-run` with a concrete logged reason. Speculative reasons such as "session resume constraint" or "team is no longer alive" are NOT valid — `dispatch_worker` can start a fresh one-shot assignment through the selected adapter. **Enforced:** `dispatch_core._validate_report_writer_isolation()` rejects every mixed analysis/report plan before process creation, and the default roster selectors exclude `report-writer`.
55
55
  4. The assigned model for each role is maintained based on `resultContract.requiredWorkerRoles` in task-manifest.json and the lead model metadata.
56
56
  5. Required roles must not be replaced by unnamed generic parallel workers.
57
57
  6. Before dispatching any required worker, persist the exact worker prompt to the assigned current-run prompt history path under `runs/<task-type>/prompts/`.
@@ -152,11 +152,11 @@ Branch on the exit code, not the JSON: without `--wait`, `0` = every probe healt
152
152
 
153
153
  ## Lead Redispatch Policy on Result-Missing
154
154
 
155
- After each worker subagent returns (regardless of role), Lead MUST verify the canonical result file exists at the absolute path resolved from the `**Result Path:**` anchor header (against `**Project Root:**`). The check is identical for in-process workers (claude-worker) and CLI-wrapper workers (codex-worker / antigravity-worker).
155
+ After each worker attempt returns (regardless of role), Lead MUST verify the canonical result file exists at the absolute path resolved from the `**Result Path:**` anchor header (against `**Project Root:**`). The check is identical for host-native workers and deterministic CLI processes.
156
156
 
157
157
  **Triggers (any of):**
158
158
 
159
- - The wrapper subagent returned an explicit `*_RESULT_MISSING` sentinel (codex-worker / antigravity-worker step 8c — `CODEX_RESULT_MISSING` / `ANTIGRAVITY_RESULT_MISSING`).
159
+ - The deterministic provider process returned an explicit `*_RESULT_MISSING` sentinel.
160
160
  - The result file is absent at the resolved absolute path even though the worker returned without a `*_RESULT_MISSING` sentinel — for example, claude-worker returned its final assistant message but never persisted the artifact, or the wrapper exited 0 and the codex/antigravity sub-agent forwarded raw stdout despite the contract.
161
161
  - The result file exists but cannot be parsed (frontmatter unreadable, sections 1–5 entirely missing). A truncated file in the middle of section 5 is NOT covered here — it goes to the validator's regular `error` path, not the retry path.
162
162
  - `okstra worker-liveness --team-state <path> --worker <id>` reports a **CLI-wrapper** worker (`codex` / `antigravity`) `did-not-launch` — neither `<prompt-path>.log` nor `<prompt-path>.status.json` exists after the persisted `startedAt` plus the launch grace (default 60s). The wrapper writes its status sidecar before invoking the CLI and hard-fails loudly with a distinct exit code on every argument check before that, so the absence of BOTH artifacts means the dispatch itself never reached the script. Without this trigger the only evidence was a lead noticing two missing files by eye, and the run paid the full polling cap for a worker that never started.
@@ -175,7 +175,7 @@ After each worker subagent returns (regardless of role), Lead MUST verify the ca
175
175
  - Lead MUST log the deviation with the normal `contract-deviation` entry naming the removed or reordered reads and the measured evidence that motivated it (byte counts, elapsed time). An unlogged reading-plan change is a contract violation, not an exception.
176
176
  - This exception never licenses changing what the worker is asked to *produce*. Narrowing the deliverable to make it finish is a contract violation.
177
177
 
178
- **Logging.** Lead records the first attempt's `cli-failure` (already emitted by the wrapper sub-agent) as-is. The retry, on success, is logged via the normal worker-completion path; on failure (second `*_RESULT_MISSING`), Lead records a single `contract-violation` entry with `--message "result-missing after 1 retry"` referencing both adapter dispatch-attempt ids and prompt-history paths.
178
+ **Logging.** Lead records the first attempt's `cli-failure` (already emitted by `worker-dispatch`) as-is. The retry, on success, is logged via the normal worker-completion path; on failure (second `*_RESULT_MISSING`), Lead records a single `contract-violation` entry with `--message "result-missing after 1 retry"` referencing both adapter dispatch-attempt ids and prompt-history paths.
179
179
 
180
180
  **Diagnostic sidecar (advisory).** Every CLI-worker dispatch writes a heartbeat sidecar at `<prompt-path>.status.json` recording `started_ts`, `ended_ts`, `exit_code`, `duration_ms`, and the canonical `log_path` (written by `scripts/okstra_ctl/worker_runner.py`, which every provider entrypoint shares). Lead MAY read this sidecar when deciding whether the first attempt actually launched the CLI (stage=`exited`, `exit_code=0`, non-zero `duration_ms`) versus failed before reaching it (sidecar absent, or stage=`started` with no exit fields). A run that ended abnormally after launch — an error, a Ctrl-C, or the SIGTERM/SIGHUP a pane kill or session teardown sends — closes as stage=`exited` with a `failure` string and **no** `exit_code`; read that as a failed attempt, not as a success. A SIGKILL cannot be closed by anything, so a sidecar still reading stage=`started` is not evidence that the worker is alive. The sidecar is best-effort — its absence is NOT by itself a reason to skip the retry; the canonical trigger remains the missing result file.
181
181
 
@@ -240,14 +240,14 @@ wiring)` section (resolved by the okstra runtime via `paths.py`). If Lead
240
240
  omits either header, the worker MUST return `<WORKER>_ERRORS_PATH_MISSING`
241
241
  without proceeding.
242
242
 
243
- - `cli-failure` events are recorded by the wrapper subagent itself (Codex / Antigravity), but **directly to the run-level error log** via `okstra error-log append-observed --error-type cli-failure ...` — NOT via the sidecar. The sidecar is an in-process tool-failure channel only.
244
- - **Wrapper invocation arity.** Every `okstra-<provider>-exec.sh` entrypoint takes the same three required positional arguments plus three optional ones: `<project-root> <model-execution-value> <prompt-path> [worktree-path] [role] [idle-timeout-seconds]`, optionally followed by the flag `--presentation live|quiet`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise — when passed it must be an existing directory (preflight hard-fails otherwise, exit 68). It is added to the worker's write scope, which every provider receives as repeated `--add-dir` (codex names the project root with `-C` instead of repeating it). Omitting it during implementation causes the codex sandbox to reject every Edit/Write targeting the worktree with EPERM. Workers extract the path from the `**Worktree:**` / `EXECUTOR_WORKTREE_PATH` / `cwd for every mutating command:` line in the lead prompt. The optional fifth `<role>` names the dispatched role (`executor`, `verifier`, `worker-reverify-r1`, …); the selected adapter maps the functional assignment identity to this value, and when it is absent, pass the literal `worker`. The optional sixth `<idle-timeout-seconds>` overrides the role's idle budget (default 600s, or 1500s when `<role>` is `executor` / `verifier` those roles run silent build+test suites — see "No external timeout on wrapper subagents" below). `--presentation` names the surface the progress has to land on. `live` renders it into the worker's own pane and is passed only by a backend that opened one; `quiet` withholds it and prints only the closing text, and is what a `cli-wrapper` subagent dispatch passes because its stdout is the calling agent's context window. Omitting the flag means `quiet` — the flag is never a way to ask for a pane that does not exist. Do not drop it when relaying a command line.
245
- - **Background dispatch + polling contract (Codex / Antigravity wrappers).** Both wrapper subagents MUST start their CLI through the selected adapter's asynchronous execution mapping and await the same handle until it reports terminal completion, capped at 30 minutes (1800s) of wall-clock elapsed time. The adapter's await operation is the wait primitive; do not add a standalone sleep or build shorter-sleep loops to bypass a host constraint. This rule applies in **every phase**. Recording responsibilities:
246
- - Successful completion: return the wrapper's accumulated stdout from the terminal await result. No log entry.
243
+ - `cli-failure` events are recorded by `worker-dispatch` directly to the run-level error log via `okstra error-log append-observed --error-type cli-failure ...` — NOT via the sidecar. The sidecar is a worker tool-failure channel only.
244
+ - **Provider-process invocation arity.** Every `okstra-<provider>-exec.sh` entrypoint takes the same three required positional arguments plus three optional ones: `<project-root> <model-execution-value> <prompt-path> [worktree-path] [role] [idle-timeout-seconds]`, optionally followed by `--presentation live|quiet`. `worker-dispatch` alone constructs this invocation from the verified `WorkerJob`; leads and host adapters do not assemble it. The fourth argument is mandatory for implementation, the fifth names the functional role, and the sixth controls the shared idle budget (1500s for executor/verifier, 600s otherwise). `live` is reserved for a pane backend; deterministic dispatch uses `quiet`.
245
+ - **Background dispatch + polling contract (CLI processes).** `worker-dispatch` starts the selected provider CLI through the adapter's asynchronous execution mapping and awaits the same handle until it reports terminal completion, capped at 30 minutes (1800s) of wall-clock elapsed time. The adapter's await operation is the wait primitive; do not add a standalone sleep or build shorter-sleep loops to bypass a host constraint. This rule applies in **every phase**. Recording responsibilities:
246
+ - Successful completion: return the provider process's accumulated stdout from the terminal await result. No log entry.
247
247
  - Non-zero `exit_code`: record a `cli-failure` to the run-level error log with the real `exit_code` and observed `duration-ms`.
248
248
  - Polling cap reached: perform a one-shot **mtime-grace check** on the wrapper's live log (`<prompt>.log`). If the log was written within the last 90 seconds and grace has not yet been applied, extend the cap from 1800s to 2100s and continue awaiting. Otherwise call the selected adapter's termination mapping, record `cli-failure` with `--exit-code 124 --duration-ms <observed_ms> --message "<wrapper> exceeded polling cap (grace=<applied|not-applied>, last_mtime_age=<n>s)"`, then return the language-specific `*_CLI_TIMEOUT` sentinel.
249
249
  - The selected adapter owns runtime-session accounting for the full wrapper window; core retains only the observed start/end event boundaries.
250
- - **No external timeout on wrapper subagents.** The wrapper dispatch owns BOTH of its timeout mechanisms, and they are the only two: (1) the subagent's polling cap (30min + optional 5min mtime grace), and (2) the shared runner's stream-idle watchdog if the CLI produces nothing on either stream for `<idle-timeout-seconds>` (6th positional arg, default 600s, or 1500s for the build-running `executor` / `verifier` roles), the runner TERM/KILLs the CLI's whole process group and marks the status sidecar timed out (`scripts/okstra_ctl/worker_runner.py`). Idle is measured from stream arrival, not from the log file's mtime. A long-running but *chatty* CLI is never idle-killed; a silent hang is reaped at the idle cap instead of burning the full 30 minutes. Lead MUST NOT impose a separate `dispatch_worker` timeout, an outer `Bash` wall-clock deadline, or any other mechanism that terminates the subagent before the wrapper's own caps fire. Doing so reproduces the historical failure mode that motivated this rule: Lead aborts the subagent at e.g. 18 minutes, the subagent returns nothing, and Lead classifies the role as "no response" while the underlying CLI was actively working. The caps are calibrated so that, combined with Lead's redispatch policy (see "Lead Redispatch Policy on Result-Missing"), a recoverable single-run failure costs at most ~70 minutes of wall-clock — predictable enough to plan around. If a specific run requires a tighter cap, pass a lower `<idle-timeout-seconds>` or lower the polling cap in the wrapper subagent's polling contract (single source of truth), NOT by layering Lead-side timeouts.
250
+ - **No external timeout around `worker-dispatch`.** The deterministic dispatch owns BOTH timeout mechanisms: (1) the process polling cap (30min + optional 5min mtime grace), and (2) the shared runner's stream-idle watchdog. If the CLI produces nothing for `<idle-timeout-seconds>`, the runner terminates the process group and marks the status sidecar timed out. Lead MUST NOT layer an earlier host timeout around it.
251
251
  - `contract-violation` events (C) are recorded by Lead via `okstra error-log append-observed --error-type contract-violation ...` after inspecting worker outputs.
252
252
  - Lead's responsibility regarding the sidecar is to dump it to the run-level error log via `okstra error-log append-from-worker` after each worker terminates; Lead does not write into the sidecar.
253
253
 
@@ -8,7 +8,7 @@ exploration"):
8
8
  file's body into the persisted executor prompt at dispatch time.
9
9
  The `Coding-conventions preflight` heading below is the literal string the CLI
10
10
  wrapper's "Executor preflight forwarding check" greps for in the persisted
11
- prompt (agents/workers/_cli-wrapper-template.md Prompt Composition).
11
+ prompt through `prepare_agent_invocation()` before `worker-dispatch`.
12
12
  -->
13
13
 
14
14
  # Coding-conventions preflight (BLOCKING — runs before the first `Edit` / `Write`, and binds the TDD loop)
@@ -14,7 +14,7 @@ Same delivery paths as the other executor gates (see _implementation-executor.md
14
14
  dispatch time (see `okstra_ctl.initial_prompt_materialization.materialize_initial_prompts()`).
15
15
  The `Pre-commit diff review sweep` heading below is the literal string the CLI
16
16
  wrapper's "Executor post-write gate forwarding check" greps for in the persisted
17
- prompt (agents/workers/_cli-wrapper-template.md Prompt Composition).
17
+ prompt through `prepare_agent_invocation()` before `worker-dispatch`.
18
18
  -->
19
19
 
20
20
  # Pre-commit diff review sweep (BLOCKING — before the executor's final commit)
@@ -34,6 +34,8 @@ Do not scan holistically and stop when it "looks fine". Work the matrix exhausti
34
34
  - a file that decides, mutates, or persists state → `clean-code.md` "Mutation and state boundaries": decide on the direct identifier rather than a status/flag proxy, capture before-state in one snapshot ahead of the mutating boundary, update only this work's owned fields on an existing row, re-read state before calling a zero-affected-rows write success or failure, put priority-between-inputs in a named domain function, and keep error messages to what was actually observed. Also check that no state union/enum was re-declared beside an authoritative one the domain or a dependency exports.
35
35
  - test file (`*.spec.*` / `*.test.*` / `test_*.py` / `*_test.go` …) → `clean-code.md` "Testing discipline": no self-mocking of the SUT, behavioral (outcome) assertions not interaction-only, no tautological delegation assertion, no effect claimed under its own mock, shared-fixture defaults left on the ordinary path, setup values that actually separate the scenarios, every new test helper/mock used by a test in this same diff, no positional mock-argument access (`rg 'mock\.calls'`), every branch this diff adds covered by a test that fails when the branch body is deleted, assertions on the last write to a record rather than an intermediate one, and test titles naming their unit plus the single condition each case isolates.
36
36
  - port / adapter / domain file, when the hexagonal overlay is loaded → `architectures/hexagonal.md`: no business logic in a port body, adapter methods are I/O only (no post-fetch filtering on domain state, no `findValid*`/`findActive*` names hiding a rule), domain objects declared under the domain boundary, no changed domain file importing an ORM / framework / adapter / service (read the import list — mechanical), and a service dependency you add or modify goes through a port rather than a concrete adapter (advisory only while the project has not declared `architecture.style = hexagonal` in `.okstra/project.json` — record it with the port sketch; blocking under that declaration, so fix it in place before the commit rather than record-and-pass, exactly as `_implementation-verifier.md` re-grades this same diff. Either way, the codebase already injecting concrete classes is the debt this pays down, not a reason to skip it).
37
+ - a service file, when the hexagonal overlay is loaded → `architectures/hexagonal.md` Rule H6: a decision this diff puts in a service — an `if`/`else` chain over domain fields, a business formula, a private method named for a domain concept — belongs in a domain function. Orchestration control flow, DTO mapping and calling a domain predicate are not violations.
38
+ - every changed source file → `clean-code.md` "Trace what this change can do wrong": walk the error, partial, concurrent and selection paths this diff creates to their end and fix wherever a wrong result comes out. Name the input that produces it — if you cannot name one, there is nothing to fix here and you move on rather than restructuring code that works.
37
39
  A file can hold several roles — apply every rule set that fits it.
38
40
  3. **Decide clean-or-finding for each cell.** Read the full file when a rule needs context (never judge a port/adapter/domain or a naming rule from the hunk alone).
39
41
  4. **Fix each finding in place** before the commit. When a readability finding is real, the fix is a named helper or named intermediate value — sketch the cleaner shape (a few lines) in your audit note, then apply it. When the fix is genuinely out of this stage's scope, record it as an `Out-of-plan` note instead of silently leaving it.
@@ -34,7 +34,7 @@ reaches it. Enforcement: the CLI wrapper refuses an Executor dispatch whose
34
34
  persisted prompt lacks the heading `Coding-conventions preflight`
35
35
  (`<SENTINEL_PREFIX>_PREFLIGHT_MISSING`) or either post-write heading
36
36
  (`<SENTINEL_PREFIX>_POSTWRITE_GATE_MISSING`) — see
37
- `agents/workers/_cli-wrapper-template.md` Prompt Composition.
37
+ `prepare_agent_invocation()` before `worker-dispatch`.
38
38
  -->
39
39
  - **Stage discipline (when a preceding stage is `done`):** its code is behavior-frozen — you may call, extend, or compose with it, never change what it already does. The rule body travels with this prompt the same way the gates do (`prompts/profiles/_stage-discipline.md`); only its `implementation` bullet binds you, the `implementation-planning` one binds the planner. Declaration-level — no wrapper sentinel.
40
40
  - **Non-interactive auto-execution (BLOCKING for `runner=cli-wrapper`).** A CLI-wrapper executor runs head-less — there is no human at the keyboard. Skills loaded during the run (tdd, coding-preflight, and others) contain "get user approval", "state your plan to the user and wait", or "ask before proceeding" gates written for interactive sessions; in this run those gates are **already satisfied** by the upstream `implementation-planning` approval (the plan this stage executes was human-approved). The executor MUST NOT stop to request approval, MUST NOT end its turn after only producing a plan, and MUST carry the stage through end-to-end — RED → GREEN → refactor → per-cycle commit → `### Stage Carry Evidence`. The ONLY skill step to skip is the interactive user-approval prompt itself; every other skill rule (TDD discipline, conventions, real-IO isolation) still binds. Stopping early for approval in a head-less run is the observed empty-exit failure (exit 0, no diff): treat it as `contract-violated`.
@@ -164,7 +164,9 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
164
164
  - **Interaction-only assertion:** a test whose only/primary assertion is `toHaveBeenCalled*` / `toHaveBeenCalledTimes` on an internal helper or a non-side-effecting collaborator, with no assertion on the returned value / resulting state / persisted row / emitted event.
165
165
  - **Tautological delegation assertion:** a test asserts the SUT result equals a direct call to the same pure helper/collaborator that the SUT delegates to, instead of asserting an independent literal value or observable state.
166
166
  - **Untruthful name:** a read-named function (`get*` / `find*` / `load*`) that writes/inserts/mutates; an adapter or repository name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*` / `findActive*`).
167
- - **Hexagonal (only when the overlay is loaded):** business logic inside a port body; an adapter method that is not pure I/O (post-fetch JS filtering on domain state, domain-rule evaluation); a domain object declared outside the `domain/` boundary; a changed file under the domain folder importing an ORM / DB layer, a framework or its DI decorators, or anything under adapters / infrastructure / services (read the import list this verdict is mechanical, not a judgment).
167
+ - **Wrong-result trace (every changed source file):** follow the paths this diff creates or alters to their end error, partial, concurrent, selection — and name the input or state that produces a wrong result (`clean-code.md` §"Trace what this change can do wrong"). This one is an obligation, not a pattern: the defects that reach production are usually ordinary code with no name on this list. The bar is also the noise filter a finding names the failing input; an alternative structure, a guard for a state no caller reaches, or a "consider extracting" is an improvement and verdicts `clean`. **The unit is the changed source file**: record `clean` or findings for each one, list every file you excluded with its reason (lockfiles, generated code, pure config), and close the section with `general: <N> files — all verdicted, <M> excluded`.
168
+ - **Business rules in an application service (only when the hexagonal overlay is loaded):** changed service code that decides a business outcome inline — an `if`/`else` chain over domain fields, a business formula in arithmetic, a private method named for a domain concept — instead of calling a domain function (`architectures/hexagonal.md` Rule H6). Orchestration control flow, DTO mapping, and calling a domain predicate are not violations. Blocking when the embedded rule is substantial: money, permissions, a state machine.
169
+ - **Hexagonal (only when the overlay is loaded):** business logic inside a port body; an adapter method that is not pure I/O (post-fetch JS filtering on domain state, domain-rule evaluation); a domain object declared outside the `domain/` boundary; a changed file under the domain folder importing an ORM / DB layer, a framework or its DI decorators, or anything under adapters / infrastructure / services (read the import list — this verdict is mechanical, not a judgment). **The routed pack's project-convention latitude does not reach this cell.** `architectures/hexagonal.md` lets a documented convention resolve conflicts in the project's favour elsewhere in that overlay; here the import list decides, and "matches existing convention" is not an answer to it (see that file's §"How far a project convention reaches"). A run that resolved exactly this finding as project convention returned `clean` and the team's PR review flagged the same import.
168
170
  - **gitignored file committed to the branch:** any path in the `git diff --name-only <base>...HEAD` enumeration that `.gitignore` excludes — enumerate them by piping that list through `git check-ignore --stdin --no-index`. A committed ignored file means the executor bulk-added (`git add .`/`-A`) or force-staged (`git add -f`) it, leaking build output, scratch files, or verification artifacts into the eventual PR. This explicitly includes `.okstra/` paths (and `.project-docs/` when the legacy symlink is present): `.okstra/**` is gitignored, so a committed okstra file (qa scripts, conformance results) is always this defect. Cite each path; recommend `git rm --cached <path>` to untrack it while keeping the file on disk. Conformance/qa evidence belongs in the carry sidecar / verifier result, never in git history.
169
171
  - **Real-IO test in source tree:** a changed/added test under the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*` — that opens a **real** DB connection / DSN, makes a real `fetch` / `axios` / `http` request, or otherwise hits real external IO without mocking the injected collaborator (a live handle, not a stub/spy). Real-IO tests MUST live under `<task_root>/qa/scripts/` per the executor's *Real-IO test isolation* rule — a live-IO test in source silently breaks the project's CI suite and violates the artifact-home rule. Cite the test file + the real-IO line; recommend moving it to `<task_root>/qa/scripts/` (or declaring it as a Tier 3 conformance script). Mock-only unit tests in source are NOT a hit.
170
172
  - **Proxy-based identity decision:** a move / ownership / re-parenting decision taken from a status field or flag while the source and destination identifiers were available and never compared. Cite the condition and the identifiers it should have compared, and show the opposite case the condition also reads true for.
@@ -11,9 +11,11 @@
11
11
  - antigravity — when added to the roster it joins the verifier set; when omitted only the default Claude+Codex verifiers participate. `--executor antigravity` requires `antigravity` in the roster: the direct CLI demands it explicitly in `--workers`, while the wizard adds it automatically when you pick antigravity as the executor.
12
12
  - **Executor binding (resolved at run-prep time, fixed for this run):**
13
13
  - Executor display name: `{{EXECUTOR_DISPLAY_NAME}}`
14
+ - Executor worker ID: `{{EXECUTOR_WORKER_ID}}`
14
15
  - Executor provider: `{{EXECUTOR_PROVIDER}}` (validated against the provider registry's `executor` capability; chosen via `--executor` or `OKSTRA_DEFAULT_EXECUTOR`, default `claude`)
15
- - Executor subagent for dispatch: `{{EXECUTOR_WORKER_AGENT}}`
16
- - Executor model: `{{EXECUTOR_MODEL_DISPLAY}}` (launch value: `{{EXECUTOR_MODEL_EXECUTION_VALUE}}`)
16
+ - Executor model: `{{EXECUTOR_MODEL_DISPLAY}}` (CLI launch value: `{{EXECUTOR_MODEL_EXECUTION_VALUE}}`; host-native launch value: `{{EXECUTOR_HOST_MODEL_VALUE}}`)
17
+ - Executor runner: `{{EXECUTOR_RUNNER}}`
18
+ - Executor dispatch mode: `{{EXECUTOR_DISPATCH_MODE}}`
17
19
  - Wherever this profile mentions the `Executor`, it refers to the role bound above. **Every** analysis provider in the resolved roster is also dispatched as a verifier — including the executor's own provider, which runs *separately* as a fresh session with no shared context so no verdict comes from the session that wrote the diff (`_implementation-verifier.md` owns this rule). Verifier dispatches remain strictly read-only.
18
20
  {{INCLUDE:_common-contract.md}}
19
21
  {{INCLUDE:_stage-discipline.md}}
@@ -14,6 +14,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
14
14
  numbered_interaction_port,
15
15
  )
16
16
  from okstra_ctl.domain.host import HostDescriptor
17
+ from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
17
18
  from okstra_ctl.registry.provider_registry import ProviderRegistry
18
19
 
19
20
 
@@ -42,6 +43,7 @@ def create_adapter(
42
43
  worker_dispatch_port=PENDING_HOST_PORT,
43
44
  usage_accounting_port=CliArtifactUsageAccountingPort(),
44
45
  provider_registry: ProviderRegistry | None = None,
46
+ host_model_port=None,
45
47
  ) -> CapabilityHostAdapter:
46
48
  return CapabilityHostAdapter(
47
49
  DESCRIPTOR,
@@ -57,4 +59,8 @@ def create_adapter(
57
59
  supported_functions=INTERACTION_FUNCTIONS,
58
60
  detector=no_automatic_claim,
59
61
  provider_registry=provider_registry,
62
+ host_model_port=host_model_port or NativeExecutionValueHostModelBindingPort(
63
+ DESCRIPTOR.id,
64
+ DESCRIPTOR.native_provider_id,
65
+ ),
60
66
  )
@@ -77,9 +77,9 @@ Render every numbered item as its option label followed by its description verba
77
77
  | `read_artifacts` | Read the manifest-provided paths through the current Antigravity host file interface. |
78
78
  | `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
79
79
  | `prompt_user` | Ask through the current host text/question interface and stop at approval gates until an explicit answer arrives. |
80
- | `dispatch_worker` | Dispatch every `runner=native-session` Antigravity assignment through the current host. Dispatch every `runner=cli-wrapper` assignment through its registered provider wrapper. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row — start every worker with `okstra team dispatch`, this host's native path included, so okstra owns the panes and the user can watch the work. |
80
+ | `dispatch_worker` | Verify each materialized invocation first. Dispatch `runner=native-session` through the current host with the returned `promptPath` and `hostModelValue`. Dispatch `runner=cli-wrapper` through `okstra worker-dispatch`, which consumes `modelExecutionValue`. **Not in a cmux run:** when `terminalBackend` is `cmux-pane`, the cmux adapter overrides this row. |
81
81
  | `await_workers` | Await native host workers through the host primitive and CLI workers through their status sidecars, then verify terminal state and Result Paths. |
82
- | `redispatch_worker` | Start a fresh native worker or CLI wrapper attempt according to the persisted assignment and record the supplied dispatch kind. |
82
+ | `redispatch_worker` | Materialize and verify a fresh invocation, then start a fresh native worker or deterministic `worker-dispatch` attempt according to the persisted runner. |
83
83
  | `shutdown_workers` | Perform host or process cleanup only for resources owned by this run. |
84
84
  | `record_lead_event` | Append the required structured event to the manifest-provided `leadEventsPath`; emit the matching user-facing `PROGRESS:` line. |
85
85
  | `collect_usage` | Collect host- or artifact-backed usage through the existing Okstra token-usage path; do not substitute another runtime's session log. |
@@ -92,6 +92,7 @@ Render every numbered item as its option label followed by its description verba
92
92
  - Do not infer the current host from an installed `agy` binary. The `antigravity` runtime must come from the active host skill or an explicit runtime flag.
93
93
  - Unsupported workers or unavailable models fail before dispatch; do not change the provider, model, or runner silently.
94
94
  - Reverify and critic retries use fresh attempts and persist the core-supplied `dispatchKind`.
95
+ - Native calls first run `okstra agent-prompt record-dispatch` with the project root, run manifest, verified metadata path, and `--enforcement-mode host-native-spec-link-gate`; after the Result Path exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and that path before accepting it. This links the accepted result to a verified specification but does not prove the host-delivered bytes. CLI calls are pre-verified and recorded by `worker-dispatch`.
95
96
  - Report-writer completion requires both the data Result Path and the worker-results audit path.
96
97
 
97
98
  ## Completion, cleanup, and resume
@@ -23,11 +23,13 @@ from okstra_ctl.domain.wizard.interaction import (
23
23
  WizardPrompt,
24
24
  )
25
25
  from okstra_ctl.ports import (
26
+ HostModelBindingPort,
26
27
  InteractionPort,
27
28
  LeadSessionPort,
28
29
  UsageAccountingPort,
29
30
  WorkerDispatchPort,
30
31
  )
32
+ from okstra_ctl.ports.host_model import FailClosedHostModelBindingPort
31
33
  from okstra_ctl.registry.provider_registry import (
32
34
  ProviderRegistry,
33
35
  default_provider_registry,
@@ -37,6 +39,7 @@ from okstra_ctl.registry.provider_registry import (
37
39
  @dataclass(frozen=True)
38
40
  class HostPorts:
39
41
  interaction: InteractionPort
42
+ host_model: HostModelBindingPort
40
43
  lead_session: LeadSessionPort
41
44
  worker_dispatch: WorkerDispatchPort
42
45
  usage_accounting: UsageAccountingPort
@@ -203,6 +206,7 @@ class CapabilityHostAdapter:
203
206
  readiness_probe: Callable[
204
207
  [HostSessionContext], tuple[Mapping[str, object], ...]
205
208
  ] | None = None,
209
+ host_model_port: HostModelBindingPort | None = None,
206
210
  ) -> None:
207
211
  self.descriptor = descriptor
208
212
  self._executable_finder = executable_finder
@@ -222,6 +226,7 @@ class CapabilityHostAdapter:
222
226
  )
223
227
  self._ports = HostPorts(
224
228
  interaction_port,
229
+ host_model_port or FailClosedHostModelBindingPort(descriptor.id),
225
230
  resolved_lead_session,
226
231
  resolved_worker_dispatch,
227
232
  usage_accounting_port,
@@ -256,6 +261,9 @@ class CapabilityHostAdapter:
256
261
  def interaction(self) -> InteractionPort:
257
262
  return self._ports.interaction
258
263
 
264
+ def host_model(self) -> HostModelBindingPort:
265
+ return self._ports.host_model
266
+
259
267
  def lead_session(self) -> LeadSessionPort:
260
268
  return self._ports.lead_session
261
269
 
@@ -15,6 +15,10 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
15
15
  numbered_interaction_port,
16
16
  )
17
17
  from okstra_ctl.domain.host import HostClaim, HostDescriptor, HostResolutionContext
18
+ from okstra_ctl.ports.host_model import (
19
+ HostModelBindingError,
20
+ HostModelBindingRequest,
21
+ )
18
22
  from okstra_ctl.registry.provider_registry import ProviderRegistry
19
23
 
20
24
 
@@ -35,6 +39,33 @@ DESCRIPTOR = HostDescriptor(
35
39
  )
36
40
 
37
41
 
42
+ class ClaudeFamilyHostModelBindingPort:
43
+ _FAMILIES = ("fable", "opus", "sonnet", "haiku")
44
+
45
+ def resolve(self, request: HostModelBindingRequest) -> str | None:
46
+ if request.runner == "cli-wrapper":
47
+ return None
48
+ if (
49
+ request.runner != "native-session"
50
+ or request.host_runtime != DESCRIPTOR.id
51
+ or request.provider != DESCRIPTOR.native_provider_id
52
+ ):
53
+ raise self._unsupported(request)
54
+ segments = request.model_execution_value.lower().split("-")
55
+ matches = [family for family in self._FAMILIES if family in segments]
56
+ if len(matches) != 1:
57
+ raise self._unsupported(request)
58
+ return matches[0]
59
+
60
+ @staticmethod
61
+ def _unsupported(request: HostModelBindingRequest) -> HostModelBindingError:
62
+ return HostModelBindingError(
63
+ "unsupported host model: "
64
+ f"host={request.host_runtime!r}, provider={request.provider!r}, "
65
+ f"model={request.model_execution_value!r}"
66
+ )
67
+
68
+
38
69
  def _detect_claude_skill_handoff(
39
70
  context: HostResolutionContext,
40
71
  ) -> HostClaim | None:
@@ -101,6 +132,7 @@ def create_adapter(
101
132
  worker_dispatch_port=PENDING_HOST_PORT,
102
133
  usage_accounting_port=ClaudeJsonlUsageAccountingPort(),
103
134
  provider_registry: ProviderRegistry | None = None,
135
+ host_model_port=None,
104
136
  ) -> CapabilityHostAdapter:
105
137
  return CapabilityHostAdapter(
106
138
  DESCRIPTOR,
@@ -117,4 +149,5 @@ def create_adapter(
117
149
  detector=_detect_claude_skill_handoff,
118
150
  provider_registry=provider_registry,
119
151
  readiness_probe=_workspace_trust_checks,
152
+ host_model_port=host_model_port or ClaudeFamilyHostModelBindingPort(),
120
153
  )
@@ -136,9 +136,9 @@ For a `host-text` mapping, render each numbered item as its option label followe
136
136
  | `read_artifacts` | Use the host file-read primitive and preserve the core contract's read order. |
137
137
  | `write_artifact` | Use the host file-write primitive only for paths authorized by the active lifecycle phase. |
138
138
  | `prompt_user` | Use the native question tool for approvals and clarifications; do not infer an answer from silence. |
139
- | `dispatch_worker` | Dispatch each assignment through `Agent(name: "<role>", run_in_background: true)` without `team_name`; use an in-process worker for `runner=native-session` and the assigned provider's wrapper worker for `runner=cli-wrapper`. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row — start every worker with `okstra team dispatch`, this host's native path included, so okstra owns the panes and the user can watch the work. |
139
+ | `dispatch_worker` | First verify the materialized invocation metadata. Dispatch `runner=native-session` through `Agent(name: "<role>", run_in_background: true)` without `team_name`, passing the verified final prompt and `hostModelValue`. Dispatch `runner=cli-wrapper` with the deterministic shell command `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; never wrap that process in another `Agent(...)` call. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row. |
140
140
  | `await_workers` | Arm one background shell poll for the pending Result Paths; the spawn acknowledgement is not completion. |
141
- | `redispatch_worker` | Dispatch a fresh `Agent(...)` session with the same prompt plus the core reverify/retry reason. |
141
+ | `redispatch_worker` | Materialize and verify a fresh invocation, then use a fresh native `Agent(...)` session or `okstra worker-dispatch` attempt according to the persisted runner. |
142
142
  | `shutdown_workers` | For each confirmed-complete worker selected for cleanup, send `SendMessage(to: <name>, message: { type: "shutdown_request" })` to idle the roster member **and** call `TaskStop(task_id: "<name>")` to stop its background task. Both are required; neither subsumes the other. |
143
143
  | `record_lead_event` | Emit the required `PROGRESS:` line as assistant text and persist core-required state/artifact updates. |
144
144
  | `collect_usage` | Run `okstra token-usage` against the team-state; it reads the run-scoped `~/.claude/projects` session JSONL evidence. |
@@ -147,27 +147,28 @@ For a `host-text` mapping, render each numbered item as its option label followe
147
147
 
148
148
  - The session owns one implicit team. `TeamCreate` and `TeamDelete` are absent on current Claude Code builds; never probe for them and never pass `team_name`.
149
149
  - Set `name` to the core-assigned functional role label so token attribution can match `agentName`.
150
- - Map a `runner=native-session` Claude assignment to `claude-worker`. Map a `runner=cli-wrapper` assignment to `<provider>-worker`; the registered providers currently resolve to `claude-worker`, `codex-worker`, `antigravity-worker`, `grok-worker`, or `kimi-worker`. The functional `report-writer` worker ID does not override its provider assignment. Never substitute `general-purpose` for a rostered worker.
151
- - For `runner=native-session`, map `modelExecutionValue` to the supported Claude family token and pass it as the `model` argument. CLI-wrapper roles apply their exact model in the provider wrapper and remain `inherit` at the Agent layer.
150
+ - Map a `runner=native-session` Claude assignment to the real host execution definition for its function (`claude-worker`, `report-writer-worker`, or `translator-worker`). A CLI-wrapper assignment has no Claude agent definition; `worker-dispatch` starts its registered provider process directly.
151
+ - For `runner=native-session`, pass the persisted `hostModelValue` as the `model` argument. For `runner=cli-wrapper`, `worker-dispatch` passes the persisted `modelExecutionValue` to the provider process. Never interchange the two fields.
152
+ - Immediately before every native host primitive, run `okstra agent-prompt record-dispatch` with the project root, run manifest, verified metadata path, and `--enforcement-mode host-native-spec-link-gate`. After the Result Path exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and that path before accepting or parsing it. CLI-wrapper calls are recorded by `worker-dispatch` itself.
152
153
  - A resumed lead can dispatch a fresh worker; resume is not a valid reason to omit a rostered role.
153
154
 
154
155
  ### Dispatch-time model enforcement
155
156
 
156
- - A native Claude worker definition declares `model: inherit`; the lead MUST override that default by passing the assigned family token (`fable`, `opus`, `sonnet`, or `haiku`) as the `Agent(...)` `model` argument.
157
- - Every CLI-wrapper agent remains `inherit` at the Agent layer because its exact `modelExecutionValue` is applied by `okstra-claude-exec.sh`, `okstra-codex-exec.sh`, `okstra-antigravity-exec.sh`, `okstra-grok-exec.sh`, or `okstra-kimi-exec.sh` according to the assignment provider.
157
+ - A native Claude execution definition declares `model: inherit`; the lead MUST override it with the verified assignment's `hostModelValue`.
158
+ - CLI-wrapper assignments never enter the Agent layer. `okstra worker-dispatch` validates the metadata and passes `modelExecutionValue` to the registered provider script.
158
159
  - Missing or unsupported family-token mapping is a pre-dispatch contract failure. Never inherit the lead model, choose a nearby alias, or switch provider silently.
159
160
  - Every analysis dispatch sets `name: "<workerId>-worker"`; convergence retries append `-reverify-r<N>`, implementation uses the functional `-executor` / `-verifier` suffix, and report writing uses `report-writer`. These values are retained as `agentName` in session JSONL for usage attribution.
160
161
  - Every CLI-worker prompt includes `**Pane role:** <functional-role>` so the entrypoint's optional fifth argument carries the dispatched role. That argument selects the dispatch's idle budget — `executor` and `verifier` run silent build+test suites and get 1500s, every other role 600s — and is recorded in the run's status sidecar. Omitting it defaults to `worker`, i.e. the short budget, which reaps a healthy build mid-suite.
161
- - The Agent SDK may supply transport metadata through the in-process worker definition, but the persisted semantic prompt body and primary analysis-packet input remain identical to the CLI-wrapper workers after permitted identity/path normalization.
162
+ - The host may supply transport metadata for native calls, but acceptance records only the verified invocation specification link. Record `enforcementMode=host-native-spec-link-gate`, `promptPath`, and `metadataPath`; do not claim the host-delivered bytes were observed.
162
163
  - A retry keeps the same Agent `name`. When logging a twice-failed CLI-wrapper attempt, reference both attempts' `bash_ids` and prompt-history paths.
163
164
  - An internally detected contract violation without a specific worker uses `--agent "claude-lead"` in the error-log event.
164
165
 
165
166
  ### Reverify, critic, and report-writer assignments
166
167
 
167
168
  - For convergence reverify, consume the persisted round plan exactly. This adapter may map and transport each returned batch, but it cannot change batch membership and does not classify findings or branch on task type, provider, or model identity.
168
- - Reverify dispatch uses a fresh one-shot `Agent(...)` call named `<workerId>-worker-reverify-r<N>`. Preserve the initial worker's definition and map an in-process Claude assignment's `modelExecutionValue` to its exact family token; CLI-wrapper assignments remain `inherit` at the Agent layer and apply the exact model in their wrapper.
169
+ - Reverify dispatch materializes `reverification-worker`, verifies its metadata, then uses a fresh one-shot native call named `<workerId>-worker-reverify-r<N>` or a fresh deterministic `worker-dispatch` attempt according to the persisted runner.
169
170
  - Critic dispatch uses `name: "<provider>-worker-critic"`, `dispatchKind: "critic"`, and the exact mapped model from `config.critic.modelExecutionValue`. If that value cannot be mapped, record `critic-skipped: model-unresolved` and do not dispatch.
170
- - Report-writer dispatch uses `name: "report-writer"`. A native Claude assignment maps `modelExecutionValue` to the supported family token; a CLI-wrapper assignment remains `inherit` at the Agent layer and applies the exact value in its provider wrapper. The prompt's `**Model:**` header must carry the same execution value.
171
+ - Report-writer dispatch uses `name: "report-writer"` only for a native Claude assignment and passes `hostModelValue`. A CLI assignment goes through `worker-dispatch` with `modelExecutionValue`.
171
172
  - Each variant persists its prompt path, Result Path, worker-results path, error paths, and `dispatchKind` before dispatch. Completion uses the shared background Result Path poll; an Agent acknowledgement never completes the variant.
172
173
 
173
174
  ## Completion, cleanup, and resume
@@ -180,12 +181,12 @@ For a `host-text` mapping, render each numbered item as its option label followe
180
181
  - On approved cleanup, reconcile the current live session roster before sending shutdown requests. Never target the lead session.
181
182
  - Collect usage before teardown. Resume through the recorded Claude session id and keep all run artifacts authoritative.
182
183
 
183
- ### CLI-wrapper polling
184
+ ### CLI process polling
184
185
 
185
- - Start the assignment's registered wrapper (`okstra-claude-exec.sh`, `okstra-codex-exec.sh`, `okstra-antigravity-exec.sh`, `okstra-grok-exec.sh`, or `okstra-kimi-exec.sh`) with `Bash(run_in_background: true)` and poll `BashOutput(bash_id)` back-to-back until terminal completion. Never add a foreground sleep.
186
+ - Start `okstra worker-dispatch` with `Bash(run_in_background: true)` and poll `BashOutput(bash_id)` back-to-back until terminal completion. The deterministic dispatcher starts the registered provider script after metadata verification. Never add a foreground sleep.
186
187
  - Return accumulated stdout on success. On a non-zero `exit_code`, record the real code and observed duration.
187
188
  - At the 1800-second cap, inspect the live log mtime once. Recent output grants one extension to 2100 seconds; otherwise call `KillShell(shell_id)`, record exit code 124, and return the wrapper timeout sentinel.
188
- - Keep the wrapper subagent alive throughout polling so its JSONL timestamp window covers the underlying CLI rollout.
189
+ - Keep the background process handle until the provider process reaches terminal state.
189
190
 
190
191
  ### Session accounting
191
192
 
@@ -14,6 +14,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
14
14
  numbered_interaction_port,
15
15
  )
16
16
  from okstra_ctl.domain.host import HostDescriptor
17
+ from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
17
18
  from okstra_ctl.registry.provider_registry import ProviderRegistry
18
19
 
19
20
 
@@ -42,6 +43,7 @@ def create_adapter(
42
43
  worker_dispatch_port=PENDING_HOST_PORT,
43
44
  usage_accounting_port=CliArtifactUsageAccountingPort(),
44
45
  provider_registry: ProviderRegistry | None = None,
46
+ host_model_port=None,
45
47
  ) -> CapabilityHostAdapter:
46
48
  return CapabilityHostAdapter(
47
49
  DESCRIPTOR,
@@ -57,4 +59,8 @@ def create_adapter(
57
59
  supported_functions=INTERACTION_FUNCTIONS,
58
60
  detector=no_automatic_claim,
59
61
  provider_registry=provider_registry,
62
+ host_model_port=host_model_port or NativeExecutionValueHostModelBindingPort(
63
+ DESCRIPTOR.id,
64
+ DESCRIPTOR.native_provider_id,
65
+ ),
60
66
  )
@@ -77,9 +77,9 @@ Render every numbered item as its option label followed by its description verba
77
77
  | `read_artifacts` | Read the manifest-provided paths through the current host's file interface. |
78
78
  | `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
79
79
  | `prompt_user` | Ask through the host text/question interface and stop at approval gates until an explicit answer arrives. |
80
- | `dispatch_worker` | Dispatch every `runner=native-session` assignment with the current Codex host's native worker/session primitive. Pass only `runner=cli-wrapper` assignments to `okstra codex-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; use `--dry-run` first when the core requires a dispatch preview. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row — start every worker with `okstra team dispatch`, this host's native path included, so okstra owns the panes and the user can watch the work. |
80
+ | `dispatch_worker` | Verify each materialized invocation first. Dispatch `runner=native-session` with the current Codex host's primitive, the returned `promptPath`, and `hostModelValue`. Pass `runner=cli-wrapper` assignments to `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; use `--dry-run` first when required. **Not in a cmux run:** when `terminalBackend` is `cmux-pane`, the cmux adapter overrides this row. |
81
81
  | `await_workers` | Await native host workers through the host primitive and CLI workers through synchronous dispatch, then verify team-state terminal records and Result Paths for both. |
82
- | `redispatch_worker` | Start a fresh native worker or `okstra codex-dispatch` attempt according to the persisted assignment's `runner`, and record the retry/reverify dispatch kind. |
82
+ | `redispatch_worker` | Materialize and verify a fresh invocation, then start a fresh native worker or `okstra worker-dispatch` attempt according to the persisted runner. |
83
83
  | `shutdown_workers` | Perform process cleanup when a wrapper remains live; otherwise this operation is a no-op recorded in state. |
84
84
  | `record_lead_event` | Append the required structured event to the manifest-provided `leadEventsPath`; emit the matching user-facing `PROGRESS:` line. |
85
85
  | `collect_usage` | Collect artifact/rollout-backed usage through the existing Okstra token-usage path; never read Claude session JSONL as a substitute. |
@@ -91,6 +91,7 @@ Render every numbered item as its option label followed by its description verba
91
91
  - The prepared run manifest and team-state are the dispatch authority. A `runner=native-session` assignment stays in the current Codex host; a `runner=cli-wrapper` assignment uses the registered provider wrapper. Unsupported explicitly requested workers fail; an adapter must not silently change the roster.
92
92
  - The report-writer follows its persisted provider, model, and runner assignment exactly. It has no Codex-only provider override or separate opt-in gate.
93
93
  - Reverify and critic retries invoke a fresh worker attempt and persist the core-supplied `dispatchKind` (`reverify-r<N>` or `critic`) in the dispatch record; never reuse a prior rollout as a new vote.
94
+ - Native calls use only `hostModelValue`. Immediately before the host primitive, run `okstra agent-prompt record-dispatch` with the project root, run manifest, verified metadata path, and `--enforcement-mode host-native-spec-link-gate`; after the Result Path exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and that path before accepting it. CLI calls use only `modelExecutionValue` through `worker-dispatch`, which records its own dispatch. The native linkage proves association with a verified specification, not observed prompt delivery.
94
95
  - Report-writer completion requires both the data.json Result Path and the worker-results audit path, even when the synchronous dispatch command exits successfully.
95
96
 
96
97
  ## Completion, cleanup, and resume
@@ -54,6 +54,7 @@ def create_adapter(
54
54
  "transcript or CLI usage artifact contract is registered."
55
55
  ),
56
56
  provider_registry: ProviderRegistry | None = None,
57
+ host_model_port=None,
57
58
  ) -> CapabilityHostAdapter:
58
59
  return CapabilityHostAdapter(
59
60
  DESCRIPTOR,
@@ -69,4 +70,5 @@ def create_adapter(
69
70
  supported_functions=INTERACTION_FUNCTIONS,
70
71
  detector=_detect_tmux,
71
72
  provider_registry=provider_registry,
73
+ host_model_port=host_model_port,
72
74
  )
@@ -77,7 +77,7 @@ Render every numbered item as its option label followed by its description verba
77
77
  | `read_artifacts` | Read the manifest-provided paths through the current host's file or shell interface. |
78
78
  | `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
79
79
  | `prompt_user` | Ask through the host text/question interface and require an explicit approval or clarification response. |
80
- | `dispatch_worker` | Run `okstra team dispatch --project-root <root> --run-manifest <path>`; use `--dry-run` first when the core requires a dispatch preview. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row — start every worker with `okstra team dispatch`, this host's native path included, so okstra owns the panes and the user can watch the work. |
80
+ | `dispatch_worker` | Verify each materialized invocation, then run deterministic `okstra worker-dispatch --project-root <root> --run-manifest <path>` for CLI assignments. Use `okstra team dispatch` only when the selected pane backend owns visible panes. |
81
81
  | `await_workers` | Run `okstra team await --project-root <root> --run-manifest <path>` through the host's asynchronous shell facility. |
82
82
  | `redispatch_worker` | Create the core-specified fresh jobs file and dispatch it with a new `dispatchKind`; never reuse a live worker conversation. |
83
83
  | `shutdown_workers` | Run `okstra team teardown --project-root <root> --run-manifest <path>` only after the user-approved cleanup gate. |
@@ -87,11 +87,11 @@ Render every numbered item as its option label followed by its description verba
87
87
  ## External dispatch details
88
88
 
89
89
  - For convergence reverify, consume the persisted round plan exactly. This adapter may map and transport each returned batch, but it cannot change batch membership and does not classify findings or branch on task type, provider, or model identity.
90
- - Do not invoke Claude Code team tools or `okstra codex-dispatch`.
90
+ - Do not invoke Claude Code team tools or provider-specific LLM transport agents. `okstra codex-dispatch` is a compatibility alias only; use `okstra worker-dispatch`.
91
91
  - Worker completion is valid only from `workerDispatches[]`, terminal status sidecars, and required Result Paths. Pane creation alone is not completion.
92
92
  - Reverify uses a fresh jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json`, sets `dispatchKind: "reverify-r<N>"`, and dispatches with `okstra team dispatch --project-root <root> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>`.
93
93
  - Report-writer uses a fresh one-job jobs file with `dispatchKind: "report-writer"` and the same schema, then dispatches through `okstra team dispatch --project-root <root> --run-manifest <path> --jobs-file <jobs-file>`.
94
- - Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `resultPath`, `workerResultPath`, and `completionPaths`. For reverify, set `role` to `worker-reverify-r<N>` — the role selects the dispatch's idle budget and is recorded in the run's status sidecar, so it must name the actual assignment. The report-writer completion paths include both data.json and the worker-results audit file.
94
+ - Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `promptMetadataPath`, `invocationId`, `assignmentRef`, `audience`, the five prompt digests, `resultPath`, `workerResultPath`, and `completionPaths`. `worker-dispatch` verifies these fields before launching the provider process. For reverify, set `role` to `worker-reverify-r<N>`. The report-writer completion paths include both data.json and the worker-results audit file.
95
95
  - After either dispatch, run `okstra team await --project-root <root> --run-manifest <path>` before evaluating terminal status or completion paths.
96
96
 
97
97
  ## Completion, cleanup, and resume
@@ -14,6 +14,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
14
14
  numbered_interaction_port,
15
15
  )
16
16
  from okstra_ctl.domain.host import HostDescriptor
17
+ from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
17
18
  from okstra_ctl.registry.provider_registry import ProviderRegistry
18
19
 
19
20
 
@@ -45,6 +46,7 @@ def create_adapter(
45
46
  "transcript or CLI usage artifact contract is registered."
46
47
  ),
47
48
  provider_registry: ProviderRegistry | None = None,
49
+ host_model_port=None,
48
50
  ) -> CapabilityHostAdapter:
49
51
  return CapabilityHostAdapter(
50
52
  DESCRIPTOR,
@@ -60,4 +62,8 @@ def create_adapter(
60
62
  supported_functions=INTERACTION_FUNCTIONS,
61
63
  detector=no_automatic_claim,
62
64
  provider_registry=provider_registry,
65
+ host_model_port=host_model_port or NativeExecutionValueHostModelBindingPort(
66
+ DESCRIPTOR.id,
67
+ DESCRIPTOR.native_provider_id,
68
+ ),
63
69
  )
@@ -76,7 +76,7 @@ Render every numbered item as its option label followed by its description verba
76
76
  | `read_artifacts` | Read the manifest-provided paths through the current Grok host file interface. |
77
77
  | `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
78
78
  | `prompt_user` | Ask through the current host text interface and wait for an explicit answer. |
79
- | `dispatch_worker` | Follow each persisted assignment's `runner` and use the common host dispatch boundary. **Not in a cmux run:** when the run manifest's `terminalBackend` is `cmux-pane`, `prompts/lead/adapters/cmux.md` overrides this row — start every worker with `okstra team dispatch`, this host's native path included, so okstra owns the panes and the user can watch the work. |
79
+ | `dispatch_worker` | Verify the materialized invocation. Use the host primitive with `promptPath` and `hostModelValue` for `native-session`; use deterministic `okstra worker-dispatch` with `modelExecutionValue` for `cli-wrapper`. **Not in a cmux run:** the cmux adapter overrides this row. |
80
80
  | `await_workers` | Await through the selected common dispatch backend, then verify terminal state and Result Paths. |
81
81
  | `redispatch_worker` | Start a fresh attempt from the persisted assignment and record the supplied dispatch kind. |
82
82
  | `shutdown_workers` | Clean up only host or process resources owned by this run. |
@@ -87,4 +87,5 @@ Render every numbered item as its option label followed by its description verba
87
87
 
88
88
  - Do not infer the current host from an installed `grok` executable. The runtime must come from an explicit request or current-session declaration.
89
89
  - Keep persisted provider, model, runner, and dispatch-kind assignments unchanged.
90
+ - Before a native call, run `okstra agent-prompt record-dispatch` with the project root, run manifest, verified metadata path, and `--enforcement-mode host-native-spec-link-gate`; after the Result Path exists, run `okstra agent-prompt link-result` with `--dispatch-id <invocationId>:attempt-1` and that path before accepting it. This is a verified specification link, not proof of delivered prompt bytes.
90
91
  - Resume with the persisted Grok session ID when one exists; otherwise resume from Okstra run artifacts.
@@ -14,6 +14,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
14
14
  numbered_interaction_port,
15
15
  )
16
16
  from okstra_ctl.domain.host import HostDescriptor
17
+ from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
17
18
  from okstra_ctl.registry.provider_registry import ProviderRegistry
18
19
 
19
20
 
@@ -45,6 +46,7 @@ def create_adapter(
45
46
  "transcript or CLI usage artifact contract is registered."
46
47
  ),
47
48
  provider_registry: ProviderRegistry | None = None,
49
+ host_model_port=None,
48
50
  ) -> CapabilityHostAdapter:
49
51
  return CapabilityHostAdapter(
50
52
  DESCRIPTOR,
@@ -60,4 +62,8 @@ def create_adapter(
60
62
  supported_functions=INTERACTION_FUNCTIONS,
61
63
  detector=no_automatic_claim,
62
64
  provider_registry=provider_registry,
65
+ host_model_port=host_model_port or NativeExecutionValueHostModelBindingPort(
66
+ DESCRIPTOR.id,
67
+ DESCRIPTOR.native_provider_id,
68
+ ),
63
69
  )