okstra 0.121.1 → 0.122.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 (37) hide show
  1. package/docs/architecture.md +4 -2
  2. package/docs/project-structure-overview.md +1 -1
  3. package/package.json +1 -1
  4. package/runtime/BUILD.json +2 -2
  5. package/runtime/agents/workers/claude-worker.md +1 -1
  6. package/runtime/prompts/launch.template.md +4 -4
  7. package/runtime/prompts/lead/adapters/claude-code.md +104 -0
  8. package/runtime/prompts/lead/adapters/codex.md +45 -0
  9. package/runtime/prompts/lead/adapters/external.md +46 -0
  10. package/runtime/prompts/lead/context-loader.md +5 -5
  11. package/runtime/prompts/lead/convergence.md +11 -28
  12. package/runtime/prompts/lead/okstra-lead-contract.md +42 -92
  13. package/runtime/prompts/lead/plan-body-verification.md +21 -5
  14. package/runtime/prompts/lead/report-writer.md +50 -51
  15. package/runtime/prompts/lead/team-contract.md +33 -106
  16. package/runtime/prompts/profiles/_common-contract.md +2 -2
  17. package/runtime/prompts/profiles/_implementation-executor.md +1 -1
  18. package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
  19. package/runtime/prompts/profiles/implementation-planning.md +3 -3
  20. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  21. package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
  22. package/runtime/python/okstra_ctl/render.py +76 -194
  23. package/runtime/python/okstra_ctl/report_views.py +22 -7
  24. package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
  25. package/runtime/schemas/final-report-v1.0.schema.json +3 -0
  26. package/runtime/skills/okstra-schedule/SKILL.md +60 -18
  27. package/runtime/templates/reports/final-report.template.md +4 -4
  28. package/runtime/templates/reports/i18n/en.json +1 -1
  29. package/runtime/templates/reports/i18n/ko.json +1 -1
  30. package/runtime/validators/validate-run.py +89 -0
  31. package/runtime/validators/validate-schedule.py +7 -3
  32. package/runtime/validators/validate_session_conformance.py +58 -33
  33. package/src/cli-registry.mjs +7 -0
  34. package/src/commands/inspect/stage-map.mjs +100 -0
  35. package/src/commands/lifecycle/install.mjs +3 -0
  36. package/src/commands/lifecycle/uninstall.mjs +8 -23
  37. package/src/lib/skill-catalog.mjs +8 -0
@@ -2,15 +2,17 @@
2
2
 
3
3
  ## File-author ownership (BLOCKING)
4
4
 
5
- The final-report **data.json** (JSON SSOT) at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` is authored by the `Report writer worker` subagent when that worker is in the run's roster. The user-facing **markdown** at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md` is then produced by `scripts/okstra-render-final-report.py` from the data.json — the worker invokes the renderer as part of its own turn so both files land on disk before it returns. Claude lead reviews both files but does NOT write them itself in that case. Lead-authored fallback is permitted only after a real Report writer worker dispatch attempt with a recorded non-`completed` terminal status (`error` / `timeout` / `not-run`) and a logged reason (`okstra-error-log.py`). **Except for `release-handoff`**, which has no worker roster — the Claude lead authors the data.json directly by design (see "Release-handoff section contract" below), and the fallback rules in this section do not apply.
5
+ The final-report data.json is authored by `Report writer worker` when that role is in the roster. The lead reviews both rendered artifacts but does not write them. Lead-authored fallback is legal only after a real `dispatch_worker` attempt records `error`, `timeout`, or `not-run` with a concrete reason. `release-handoff` remains the intentional single-lead exception.
6
+
7
+ The JSON SSOT path is `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`. The user-facing markdown at `runs/<task-type>/reports/final-report-<task-type>-<seq>.md` is produced by `scripts/okstra-render-final-report.py` from the data.json so both files land on disk before the worker returns.
6
8
 
7
9
  The data.json schema is `schemas/final-report-v1.0.schema.json`. The renderer + the run-validator both consume that schema, so a data.json that validates is guaranteed to render into a markdown that passes the contract checks.
8
10
 
9
11
  Two `frontmatter` approval fields are always emitted with their unset default — never pre-fill them: `frontmatter.approved` is emitted as `false`, and `frontmatter.implementationOption` is emitted as an empty string `""`. The user later flips `approved` to `true` (via `--approve` or manual edit) and fills `implementationOption` with the chosen Option Candidate name (via `--implementation-option <name>` or manual edit) to authorise and scope the next `implementation` run.
10
12
 
11
- If you are reading this contract **as the report-writer-worker subagent**, YOU are the one calling the `Write` tool against the data.json path AND invoking the renderer via `Bash`. Do not return either artifact inline — the files on disk are the canonical record.
13
+ If you are reading this contract **as the report-writer worker**, YOU are the one writing the data.json and invoking the renderer through the available execution interface. Do not return either artifact inline — the files on disk are the canonical record.
12
14
 
13
- If you are reading this contract **as Claude lead**, your job in Phase 6 is to (a) prepare the report-writer prompt, (b) dispatch the Report writer worker per the Phase 6 dispatch template in okstra-lead-contract.md, (c) review both files in Phase 7. Do not call `Write` against either path yourself when Report writer worker is in the roster.
15
+ If you are reading this contract **as the lead**, your job in Phase 6 is to prepare the report-writer prompt, dispatch the Report writer worker per the Phase 6 dispatch template in okstra-lead-contract.md, and review both files in Phase 7. Do not call `write_artifact` against either report path yourself when Report writer worker is in the roster.
14
16
 
15
17
  ## When to Use
16
18
 
@@ -20,21 +22,13 @@ If you are reading this contract **as Claude lead**, your job in Phase 6 is to (
20
22
 
21
23
  ## Phase 6 dispatch template (Report writer worker)
22
24
 
23
- Lead dispatches the report-writer worker via the Agent tool. Lead does NOT write the final-report file when this worker is in the roster.
24
-
25
- ```text
26
- Agent(
27
- description: "Author final report for <task-key>",
28
- prompt: "<report-writer prompt: see this contract + Required reading clause + Available MCP Servers section>",
29
- name: "report-writer",
30
- subagent_type: "report-writer-worker",
31
- run_in_background: true, # no team_name — implicit team; see team-contract Operating Rule 0
32
- model: "<family token of Report writer worker's modelExecutionValue>", # opus/sonnet/haiku — NOT hardcoded; see below
33
- mode: "auto"
34
- )
35
- ```
25
+ 1. Resolve the Report writer worker assignment and all required prompt/result/error paths from the manifests.
26
+ 2. Persist the exact prompt history with the required anchor headers and audience-specific reading list.
27
+ 3. Emit the Phase 6 checkpoint.
28
+ 4. Call `dispatch_worker(report_writer_assignment, prompt)` through the selected adapter.
29
+ 5. Call `await_workers([handle])` and verify both the data.json Result Path and worker-results audit path.
36
30
 
37
- The `model:` parameter is **derived from the Report writer worker's `modelExecutionValue`** in `task-manifest.json`, mapped to an Agent family token (`opus` / `sonnet` / `haiku`) per [team-contract](./team-contract.md) "Model Assignment Rules" #3–#4. Do NOT hardcode it — the report-writer-worker definition is `model: inherit`, so without this explicit parameter the worker silently runs on the lead's model instead of its assignment. The same `modelExecutionValue` feeds the prompt header in item 7 below, so the spawn model and the recorded `**Model:**` header always agree.
31
+ The assignment's `modelExecutionValue` feeds both adapter dispatch and the prompt header in item 7 below, so the execution model and recorded `**Model:**` header always agree. Missing or unsupported model resolution is a pre-dispatch contract failure; the common contract does not choose a runtime fallback.
38
32
 
39
33
  The prompt MUST include, in this order at the top:
40
34
 
@@ -58,36 +52,32 @@ The prompt MUST include, in this order at the top:
58
52
  before the dispatch is constructed. The worker copies this verbatim
59
53
  into `data.json.meta.reportLanguage`.
60
54
  12. For implementation-planning runs: a literal block listing the 8 required English section headings the validator scans for (`Option Candidates`, `Trade-off`, `Recommended Option`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `User Approval Request`). The writer must use these exact substrings as section headings (Korean translation in parentheses is allowed).
61
- 13. An explicit instruction: `You are the author of TWO files: (a) the final-report data.json at <Result Path>, (b) the worker-results audit file at <Worker Result Path>. After writing the data.json, invoke "okstra render-final-report <Result Path>" via Bash so the markdown sibling is rendered before you return. Do not return the report inline. The validator fails the run when (a)'s schema validation fails, when the rendered markdown is absent, or when (b) is missing.`
55
+ 13. An explicit instruction: `You are the author of TWO files: (a) the final-report data.json at <Result Path>, (b) the worker-results audit file at <Worker Result Path>. After writing the data.json, invoke "okstra render-final-report <Result Path>" through the available execution interface so the markdown sibling is rendered before you return. Do not return the report inline. The validator fails the run when (a)'s schema validation fails, when the rendered markdown is absent, or when (b) is missing.`
62
56
 
63
- **Completion detection after dispatch (BLOCKING).** The spawn ack is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection (self-scheduled polling)", with a one-entry pending set covering the data.json (Result Path) and the worker-results file (Worker Result Path). Do NOT end the turn with a prose "waiting for the report" statement.
57
+ **Completion detection after dispatch (BLOCKING).** A dispatch acknowledgement is NOT completion — detect completion via the SSOT protocol in [team-contract](./team-contract.md) "Worker-completion detection", with a one-entry pending set covering the data.json (Result Path) and the worker-results file (Worker Result Path). Do NOT end the turn with a prose "waiting for the report" statement.
64
58
 
65
59
  ### Resume-safe dispatch
66
60
 
67
- A resumed lead session can ALWAYS dispatch a fresh Report writer worker there is no team to revive; the session's implicit team is always present ([team-contract](./team-contract.md) Operating Rule 0):
68
-
69
- - Dispatch with `Agent(name: "report-writer", run_in_background: true)`; the worker's session is recoverable by `agentName: "report-writer"` in `okstra-token-usage.py`.
70
- - Do NOT skip dispatch because of any team-related concern. Record `teamCreate` in team-state and proceed.
61
+ A resumed lead can always call `redispatch_worker` for a fresh Report writer worker. Resume/session mechanics are adapter-owned and are never a reason to bypass the role.
71
62
 
72
63
  ### Lead-authored fallback (only if dispatch failed)
73
64
 
74
65
  Except for `release-handoff` (which is single-lead by design and never dispatches a Report writer worker — see "Release-handoff section contract" below), lead-authored fallback is permitted only if all of the following are true and recorded in team-state:
75
66
 
76
- 1. A Report writer worker dispatch was actually attempted (Agent call was issued).
67
+ 1. A Report writer worker dispatch was actually attempted through `dispatch_worker`.
77
68
  2. The attempt recorded a terminal status of `error`, `timeout`, or `not-run` with a concrete reason (tool error message, timeout duration, or external blocker).
78
69
  3. The reason is logged via `okstra error-log append-observed --error-type cli-failure ...` (or `tool-failure` if the failure was internal).
79
70
 
80
- Speculative reasons such as "session resume constraint", "team object no longer exists", or "lead can do it faster" are NOT valid.
81
-
82
- For `tmux-pane` backend runs, do not use the Agent template. Create a one-job jobs file for the `report-writer` with `dispatchKind: "report-writer"` and the same field set as reverify jobs files ([convergence](./convergence.md) "Re-verification Agent Dispatch", tmux-pane branch — the single source for the jobs-file fields). Then call `okstra team dispatch --project-root <dir> --run-manifest <path> --jobs-file <jobs-file>` followed by `okstra team await --project-root <dir> --run-manifest <path>`. Completion requires both the report data.json and the report-writer worker-results audit file.
71
+ Speculative reasons such as "session resume constraint", "runtime state is unavailable", or "lead can do it faster" are NOT valid.
83
72
 
84
73
 
85
74
  ## Phase 6 → Phase 7 execution sequence (BLOCKING order)
86
75
 
87
- The four steps below MUST execute in this exact order. Reordering them is the recurring root cause of reports shipping with `--` token cells (Phase 7 not run yet), Section 3 missing follow-up entries, or Section 4 rows never spawning.
76
+ Phase 6 first produces the final-report data.json at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json` and its rendered markdown sibling. Token Usage cells are `null` at this point, and Section 3 does not yet include auto-spawned follow-ups.
77
+
78
+ The four Phase 7 steps below MUST execute in this exact order. Reordering them is the recurring root cause of reports shipping with `--` token cells, Section 3 missing follow-up entries, or Section 4 rows never spawning.
88
79
 
89
- 1. **Phase 6 Report writer worker drafts the final-report data.json** at `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`, then invokes `scripts/okstra-render-final-report.py` to produce the sibling markdown. Token Usage cells in the data.json are `null` at this point (renderer emits `--` for nulls); Section 3 lists prioritized actions but does NOT yet include auto-spawned follow-ups (they don't exist yet).
90
- 2. **Phase 7 step 1 — Token-usage collector with `--substitute-data`** (BLOCKING). One invocation aggregates `leadUsage` / `workers[].usage` / `usageSummary` into team-state AND populates `tokenUsage` + `executionStatus[].totalTokens` etc. in the data.json AND re-invokes the renderer so the sibling markdown carries the real numbers. Skipping the flag ships a markdown full of `--` cells.
80
+ 1. **Collect usage.** Call `collect_usage` through the selected adapter with substitution enabled by the existing `okstra token-usage` workflow. One invocation aggregates `leadUsage` / `workers[].usage` / `usageSummary` into team-state, populates `tokenUsage` and the execution-status usage fields in data.json, and re-invokes the renderer so the markdown carries real numbers.
91
81
 
92
82
  ```bash
93
83
  okstra token-usage \
@@ -97,7 +87,7 @@ The four steps below MUST execute in this exact order. Reordering them is the re
97
87
  ```
98
88
 
99
89
  The data.json paths populated: `tokenUsage.lead.{totalTokens,billableTokens,costUsd}`, the `worker` / `grand` rows, `tokenUsage.cli.costUsd`, and each `executionStatus[].{totalTokens,billableTokens,costUsd,durationMs,cliTotalTokens,cliCostUsd}` for rows whose role matches a team-state worker. The data.json MUST already exist (Phase 6 output).
100
- 3. **Phase 7 step 1.5 Render report views** (BLOCKING, conditional output). Always invoke the renderer; it decides whether an html sibling is warranted:
90
+ 2. **Validate and render the report artifacts.** Always invoke the report-view renderer after the substituted data.json passes its schema/run checks; the renderer decides whether an html sibling is warranted:
101
91
 
102
92
  ```bash
103
93
  okstra render-views \
@@ -109,8 +99,8 @@ The four steps below MUST execute in this exact order. Reordering them is the re
109
99
  - implementation-planning 보고서에는 본문 끝에 **Plan Approval** 섹션(구현 옵션 `<select>` + 승인 체크박스)이 렌더된다 — §1 `Blocks: approval` 행이 미해소면 disabled. 승인을 체크하고 Export 하면 sidecar 본문에 `## APPROVAL` 블록이 실리고, implementation 시작 마법사의 approve-confirm 단계가 그것을 감지해 사용자 확인 후 기존 `--approve` / `--implementation-option` 경로로 적용한다.
110
100
  - When the report has **no** `C-*` clarification rows and is **not** a Plan Approval widget target, the html carries no interactive forms (it would only duplicate the MD), so the renderer prints `html: skipped (...)` and writes nothing. This is the expected state for such runs — `validators/validate-report-views.py` treats "no C-* rows + no approval target + no html" as a pass, not a missing artifact.
111
101
 
112
- Must run AFTER step 1 (so token placeholders are substituted in any rendered html) and BEFORE step 2 (so the html artifact, when generated, exists for the validator step that checks it).
113
- 4. **Phase 7 step 2 Follow-up task spawner** (BLOCKING when Section 4 is non-empty). Turns the report's `## 4. Follow-up Tasks (후속 작업)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
102
+ This step runs after usage collection so token placeholders are substituted in any rendered html and before routing persistence so the html artifact, when generated, exists for the validator step that checks it.
103
+ 3. **Complete routing and follow-up persistence.** Run the follow-up task spawner when Section 4 is non-empty. It turns the report's `## 4. Follow-up Tasks (후속 작업)` rows into `tasks/<task-group>/<new-task-id>/` stubs.
114
104
 
115
105
  ```bash
116
106
  okstra spawn-followups \
@@ -126,15 +116,14 @@ The four steps below MUST execute in this exact order. Reordering them is the re
126
116
  - Rows whose `origin` is `phase-continuation` are reported as `skipped (no new task dir)` and never spawn — they advance the same task-key via `/okstra-run` instead.
127
117
  - An invalid `origin`, `suggestedTaskType`, missing `title`, missing `reason`, or missing `newTaskId` exits `1`. (Schema validation in Phase 6 catches most of these before the spawner runs.)
128
118
  - **Canonical spawn rule (single source of truth):** the spawner runs when `task-type` ∈ {`implementation`, `final-verification`, `release-handoff`}, OR when `followUpTasks` is non-empty for any other task-type. For the listed task-types `followUpTasks` must be present (schema enforces the phase-continuation row for non-terminal task-types); an empty array is permitted only for `release-handoff`. Missing arrays are no-ops (exit `0`). All other references to this rule (including the Persistence Checklist) defer to this statement.
129
- 5. **Phase 7 step 3 — Update Section 3** after the spawner. The report-writer MUST append one row per newly spawned task-key with its entry command:
119
+ After the spawner, the report writer MUST append one row per newly spawned task-key with its entry command:
130
120
 
131
121
  ```
132
- - Follow-up: `<task-group>/<new-task-id>` — Claude Code 세션 안 `/okstra-run task-key=<task-group>/<new-task-id> task-type=<suggested>` / 별도 터미널 `scripts/okstra.sh --task-key <task-group>/<new-task-id> --task-type <suggested>`
122
+ - Follow-up: `<task-group>/<new-task-id>` — in-session `/okstra-run task-key=<task-group>/<new-task-id> task-type=<suggested>` / standalone `scripts/okstra.sh --task-key <task-group>/<new-task-id> --task-type <suggested>`
133
123
  ```
134
124
 
135
- The status file is written after step 3 completes.
136
-
137
- **Run-end teammate teardown follows this whole sequence.** Token-usage collection (step 1) reads the worker session jsonls, so the lead MUST NOT disband the team until every step above is done. Only then — after the combined pane/team disposition prompt and only when the user approves cleanup — does the lead dismiss the worker teammates via `SendMessage` shutdown_request per [okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped pane & teammate lifecycle" → "Run-end teammate teardown" (split-pane runs only; silent-skip when split-pane is off; if the user keeps panes/teammates, leave them intact and surface the manual Teams/FleetView cleanup path).
125
+ The status file is written after routing and follow-up persistence completes.
126
+ 4. **Execute the run-scoped cleanup gate.** Call `shutdown_workers` only after usage collection, all persistence work, and explicit user approval under [okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped worker-resource lifecycle". If the user keeps resources, leave the selected adapter's resources intact and surface its manual cleanup guidance.
138
127
 
139
128
  ## Final Report Structure
140
129
 
@@ -142,13 +131,15 @@ The final report follows the structure encoded in `schemas/final-report-v1.0.sch
142
131
 
143
132
  ### Report Header
144
133
 
134
+ Milestone 1 keeps the final-report schema unchanged. Read the exact permitted values for `header.reportOwner` and `header.reportAuthor` from the task bundle's `instruction-set/final-report-schema.json` excerpt of `schemas/final-report-v1.0.schema.json`, then write those schema v1 compatibility values according to the actual authorship path. Do not derive either header field from the selected runtime's lead role. Runtime identity remains visible in the execution-status row and team-state audit fields.
135
+
145
136
  ```markdown
146
137
  # <task-key> - Multi-Agent Cross Verification Final Report
147
138
  - Date: <ISO 8601 timestamp>
148
139
  - Task Key: <task-key>
149
140
  - Task Type: <task-type>
150
- - Report Owner: `Claude lead`
151
- - Report Author: `<Report writer worker if in roster, else Claude lead (release-handoff or recorded-fallback only)>`
141
+ - Report Owner: `<exact schema header.reportOwner compatibility value>`
142
+ - Report Author: `<exact schema header.reportAuthor compatibility value for the actual authorship path>`
152
143
  - Lead model: `<lead-model>`
153
144
  - Preparation Method: Final report authored by Report writer worker (or lead-authored fallback — record the documented dispatch failure reason here when applicable)
154
145
  ```
@@ -158,22 +149,19 @@ The final report follows the structure encoded in `schemas/final-report-v1.0.sch
158
149
  ```markdown
159
150
  | Agent | Role | Model | Status | 처리 토큰 | 환산 토큰 | 비용 (USD) | Duration | Summary of Key Findings |
160
151
  |-------|------|-------|--------|-----------|-----------|------------|----------|------------------------|
161
- | Claude Code | Claude lead | opus-4-6 | completed | 10,479,327 | 1,769,798 | $26.55 | 59m 12s | Final synthesis status |
162
- | Claude Code | Claude worker | sonnet | completed | 1,941,396 | 475,136 | $1.43 | 13m 33s | Key findings summary |
163
- | Codex | Codex worker | gpt-5.5 | completed | 2,274,011 (CLI: 5,261,833) | 586,223 | $8.79 (+ CLI $4.20) | 22m 06s | Key findings summary |
164
- | Antigravity | Antigravity worker | auto | completed | 3,107,795 | 746,623 | $11.20 | 22m 06s | Key findings summary |
165
- | Claude Code | Report writer | opus-4-6 | completed | 665,497 | 267,210 | $4.01 | 4m 20s | Report organization |
152
+ | <team-state.lead.agent> | <team-state.lead.role> | <model> | completed | <tokens> | <billable> | <cost> | <duration> | Final synthesis status |
153
+ | <team-state.workers[].agent> | <team-state.workers[].role> | <model> | <status> | <tokens> | <billable> | <cost> | <duration> | Key findings summary |
166
154
  ```
167
155
 
168
156
  Table Generation Rules:
169
- - The first row is always `Claude lead` with data from `leadUsage`; subsequent rows follow `recommendedWorkers` / `resultContract.requiredWorkerRoles` order.
170
- - Agent labels: Claude worker/Report writer "Claude Code", Codex worker "Codex", Antigravity worker "Antigravity".
157
+ - The first row derives its agent identifier and role from `team-state.lead.agent` / `team-state.lead.role` and its usage data from `leadUsage`; subsequent rows follow `recommendedWorkers` / `resultContract.requiredWorkerRoles` order.
158
+ - Every worker agent identifier and role derives from its persisted team-state entry; the common contract does not infer labels from provider names.
171
159
  - **처리 토큰** = `usage.totalTokens` (input + output + cache_creation + cache_read; the raw volume processed).
172
160
  - **환산 토큰** = `usage.billableEquivalentTokens` (cache reads weighted at 0.1×, cache_creation 1.25×, output 5×; useful as a single number for "how big was this session in cost terms").
173
161
  - **비용 (USD)** = `usage.estimatedCostUsd`. For Codex/Antigravity workers that actually invoked the CLI, append `(+ CLI $X.XX)` from `usage.cliEstimatedCostUsd`.
174
162
  - For Codex/Antigravity workers, append `(CLI: <cliTotalTokens>)` to the 처리 토큰 cell when `usage.cliTotalTokens` is set.
175
163
  - Status values are retrieved from team-state; format duration as `Xm Ys` from `usage.durationMs`.
176
- - Workers with status `not-run` or with `source: "unavailable"` show `--` for tokens/cost/duration; quote the `note` underneath the table if useful.
164
+ - Workers with status `not-run` or unavailable usage evidence show `--` for tokens/cost/duration; quote the `note` underneath the table if useful.
177
165
 
178
166
  ### Token Usage Summary Section
179
167
 
@@ -206,7 +194,7 @@ Token Summary Generation Rules:
206
194
  - **Grand total** (ko: `전체 합계`) row: `usageSummary.grandTotalTokens` / `usageSummary.grandBillableEquivalentTokens` / sum of `lead + claudeWorkers`.
207
195
  - **Codex/Antigravity CLI add-on** (ko: `Codex/Antigravity CLI 추가 비용`) row: `usageSummary.estimatedCostUsd.cliWorkers`. If 0, still show the row so the reader sees that no CLI work was billed under those providers (or that CLI fallback occurred).
208
196
  - Format tokens with comma separators (e.g., `32,500`); format USD with two decimals (e.g., `$1.43`).
209
- - If `lead` or any `worker.usage` has `source: "unavailable"`, show `--` for that row and append a one-line note (`reason: <note>`).
197
+ - If `lead` or any `worker.usage` records unavailable evidence, show `--` for that row and append a one-line note (`reason: <note>`).
210
198
  - If pricing for a model is unknown, the script omits `estimatedCostUsd` for that block — show `N/A` in that column and add a note like `pricing missing for model <model>`.
211
199
 
212
200
  ### Implementation-planning section heading contract (BLOCKING)
@@ -229,6 +217,17 @@ The Korean translation in parentheses is optional but the English keyword is man
229
217
 
230
218
  The final-report template `templates/reports/final-report.template.md` Section 5.5 already encodes this contract — copy that block verbatim and fill in.
231
219
 
220
+ ### Self-fix rewrite (plan-body self-fix 라운드)
221
+
222
+ lead 가 self-fix 라운드를 지시하면(대상 `P-*` 목록 + 각 결함 사유 전달), 지정된 planner-fixable 항목이 가리키는 **계획 섹션만** 교정한다 — 전체 draft 를 다시 쓰지 않는다:
223
+
224
+ - 경로 축약 → `<PROJECT_ROOT>` 기준 전체 상대경로로 교체(File Structure / Stepwise `files`).
225
+ - prose command → 작업 디렉터리·인자·task-type 이 포함된 실행 가능한 명령 시퀀스로 구체화.
226
+ - placeholder(`stage-<N>` 등) → 구체 파일명·재생성 계획으로 교체.
227
+ - requirement-coverage 매핑 오류 → 실재하는 option/stage/step 또는 XP 행으로 재매핑.
228
+
229
+ 교정 후 해당 `planItems[].selfFixNote` 에 `self-fixed in round <N>: <무엇을 고쳤는지>` 를 남기고, `planBodyVerification.selfFixRoundApplied = true` 로 표시한다. `needs-user-input` 항목은 절대 교정 대상이 아니다 — 그대로 clarification 으로 승격된다.
230
+
232
231
  ### Final-verification verdict token contract (BLOCKING)
233
232
 
234
233
  When the run's `task-type` is `final-verification`, the report's `## 7. Final Verdict` table MUST contain a `Verdict Token` row whose value is **exactly one of** the literal strings below. The `release-handoff` profile reads this row as its entry gate; any other value blocks the next phase.
@@ -247,13 +246,13 @@ The final-report template `templates/reports/final-report.template.md` Section 7
247
246
 
248
247
  When the run's `task-type` is `release-handoff`, the final report MUST include Section `## 5.6 Release Handoff Deliverables` with all eight sub-sections (`5.6.1` Source Verification Report, `5.6.2` Feature Branch & Working-Tree State, `5.6.3` User Selections, `5.6.4` Executed Commands, `5.6.5` Commit List, `5.6.6` Merge Conflict Probe, `5.6.7` Pull Request Outcome, `5.6.8` Routing Recommendation). Every entry is dictated by the lead's recorded git/gh command log and the user's verbatim answers to the H1/H2/H3 menu prompts. H1 choices are `local checkout`, `push + PR`, or `skip`; release-handoff records existing implementation commits and MUST NOT create new commits. If the user picked `skip` (H1) or `cancel` (H3), keep 5.6.3 populated but leave 5.6.4–5.6.6 explicitly empty per the template's empty-state lines.
249
248
 
250
- **Single-lead authorship (release-handoff only):** release-handoff has no worker roster (no `Report writer worker`, no `Claude worker` drafter). The Claude lead authors the final-report file directly — there is no `Report writer worker` dispatch to perform in Phase 6, no resume-safe dispatch concern, and no mandatory worker-results file for a report-writer role. The rest of this contract's dispatch / resume / fallback machinery applies ONLY when `Report writer worker` is in the roster (i.e. every task-type other than `release-handoff`).
249
+ **Single-lead authorship (release-handoff only):** release-handoff has no worker roster. The lead authors the final-report file directly — there is no `Report writer worker` dispatch to perform in Phase 6, no resume-safe dispatch concern, and no mandatory worker-results file for a report-writer role. The rest of this contract's dispatch / resume / fallback machinery applies ONLY when `Report writer worker` is in the roster (i.e. every task-type other than `release-handoff`).
251
250
 
252
251
  The final-report template `templates/reports/final-report.template.md` Section 5.6 already encodes this contract — copy that block verbatim and fill in. For non-`release-handoff` runs, omit Section 5.6 entirely.
253
252
 
254
253
  ### Mandatory worker-results file (BLOCKING)
255
254
 
256
- You (the report-writer-worker subagent) MUST also write a worker-results audit file at the path the lead provides as `**Worker Result Path:**`, defaulting to:
255
+ You (the report-writer worker) MUST also write a worker-results audit file at the path the lead provides as `**Worker Result Path:**`, defaulting to:
257
256
 
258
257
  ```
259
258
  runs/<task-type>/worker-results/report-writer-worker-<task-type>-<seq>.md
@@ -9,15 +9,15 @@
9
9
 
10
10
  ## Team Structure
11
11
 
12
- okstra tasks are always operated using the `Claude lead` + required worker team structure.
12
+ Okstra tasks use one lead plus the exact worker assignments selected in the prepared bundle. The lead runtime and the worker providers are independent axes.
13
13
 
14
14
  ### Role Definitions
15
15
 
16
16
  **All analysis workers (Claude / Codex / Antigravity) share an identical core responsibility.** Specialization is additive — it lives in optional Section 6 of the worker output, NOT in differentiated core questions. This is intentional: cross-verification only converges if all three workers are answering the same questions against the same brief. Disjoint per-worker scopes produce union-of-perspectives, not triangulation.
17
17
 
18
- | Role | Core responsibility | Specialization lens (Section 6 only) | subagent_type | Notes |
18
+ | Role | Core responsibility | Specialization lens (Section 6 only) | Assignment key | Notes |
19
19
  |------|------|------|---------------|------|
20
- | Claude lead | orchestration + convergence supervision + final-report review/approval | | -- | Does NOT author the final-report file when `Report writer worker` is in the roster |
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
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
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` |
@@ -31,9 +31,7 @@ okstra tasks are always operated using the `Claude lead` + required worker team
31
31
 
32
32
  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.
33
33
  2. If `modelExecutionValue` differs from `model`, use `modelExecutionValue` during execution.
34
- 3. **Spawn-time enforcement for in-process Claude subagents (BLOCKING).** `Claude worker` and `Report writer worker` are in-process Claude subagents whose agent definitions declare `model: inherit` (`agents/workers/claude-worker.md`, `agents/workers/report-writer-worker.md`). `inherit` follows the **lead's** runtime model, NOT the role's assignment so an opus assignment silently runs on a sonnet lead. To make the assignment binding (not merely declared), lead MUST pass an explicit `model:` parameter on every `Agent(...)` dispatch for these two roles, derived from that role's `modelExecutionValue`. The dispatch `model:` parameter overrides the `inherit` frontmatter; the frontmatter remains only as the fallback when no parameter is supplied. Omitting `model:` on a Claude-side dispatch is a contract violation that reproduces the assigned-vs-actual model deviation.
35
- 4. **`modelExecutionValue` → Agent `model:` family token.** The Agent tool's `model` parameter accepts family tokens only — `fable` / `opus` / `sonnet` / `haiku` (an exact version such as `claude-opus-4-7` is NOT a valid value). Map by prefix: a `modelExecutionValue` of `fable*` / `claude-fable*` → `"fable"`, `opus*` / `claude-opus*` → `"opus"`, `sonnet*` / `claude-sonnet*` → `"sonnet"`, `haiku*` / `claude-haiku*` → `"haiku"`. This enforces the assignment at **family granularity** (fable vs opus vs sonnet vs haiku); the exact version within a family is still inherited from the lead session and cannot be pinned via this parameter.
36
- 5. **Codex / Antigravity wrappers are out of scope for the Agent `model:` rule.** `Codex worker` / `Antigravity worker` subagents are Claude wrappers that shell out to an external CLI; the role's `modelExecutionValue` is already applied via the CLI's own `--model <modelExecutionValue>` argument (see `agents/workers/_cli-wrapper-template.md`). The Agent `model:` parameter for these wrappers would only set the wrapper's own orchestration model, not the external CLI's model — leave it at `inherit` and do NOT map it from `modelExecutionValue`.
34
+ 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.
37
35
 
38
36
  ### Dynamic Worker Role Determination
39
37
 
@@ -48,10 +46,10 @@ Only workers selected from `recommendedWorkers` in `task-manifest.json` and `res
48
46
 
49
47
  ## Operating Rules
50
48
 
51
- 0. **Teammate dispatch (BLOCKING).** Claude Code v2.1.178 removed the `TeamCreate` / `TeamDelete` tools and the `Agent(...)` `team_name` parameter. With `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` (seeded by `okstra install`) the session owns one implicit team from start; Lead dispatches every worker with `Agent(name: "<role>-worker", run_in_background: true)` and **NO `team_name`**, and teammates auto-join that implicit team. Do NOT search for, probe, or call `TeamCreate` — its absence is the normal state on every current build, never an "Agent Teams unavailable" signal, and never a reason to degrade the run. Before any dispatch, record the `teamName` audit label plus `teamCreate: { attempted: false, status: "implicit", splitPane: <true if $TMUX set, else false> }` (or the `status: "skipped", reason: "concurrent-run"` variant when the launch prompt's concurrent-run gate applies). Split-pane teammates appear when `$TMUX` is set (`teammateMode: auto`); without tmux they run in-process — both are normal. See [okstra-lead-contract.md Phase 3](./okstra-lead-contract.md) for the full sequence.
52
- 1. `Claude lead` is responsible for orchestration, convergence supervision, and final-report review/approval. It never overrides worker analysis results, and it never authors the final-report file when `Report writer worker` is in the roster.
49
+ 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.
50
+ 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
51
  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 — Lead can always dispatch a fresh subagent (the implicit team is always present).
52
+ 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.
55
53
  4. The assigned model for each role is maintained based on `resultContract.requiredWorkerRoles` in task-manifest.json and the lead model metadata.
56
54
  5. Required roles must not be replaced by unnamed generic parallel workers.
57
55
  6. Before dispatching any required worker, persist the exact worker prompt to the assigned current-run prompt history path under `runs/<task-type>/prompts/`.
@@ -101,8 +99,6 @@ Audience-scoped file enumeration (performance optimization — mandatory):
101
99
  | Report writer worker (Phase 6) | task-brief, analysis-profile, analysis-material, reference-expectations, clarification-response (if carry-in), **plus** the instruction-set-local `final-report-template.md` (phase-stripped) and `final-report-schema.json` (per-task-type excerpt) — NOT the full `templates/reports/...` / `schemas/...` sources |
102
100
  | Reverify dispatches | none — the lead provides only the items to reverify |
103
101
 
104
- Asymmetry note: `claude-worker` runs in-process and the Agent SDK auto-loads its agent definition; lead's dispatch prompt body for claude-worker can therefore be shorter than for codex/antigravity. The Worker Preamble pointer is still emitted for all three so the contract source is identical regardless of dispatch path.
105
-
106
102
  ## Terminal Statuses
107
103
 
108
104
  Terminal statuses that can be recorded for a worker:
@@ -114,58 +110,13 @@ Terminal statuses that can be recorded for a worker:
114
110
  | `error` | Execution error, reason recorded; prompt history file must exist |
115
111
  | `not-run` | Not executed, reason recorded |
116
112
 
117
- ## Worker-completion detection (self-scheduled polling)
118
-
119
- **SSOT.** This section is the single source of truth for how Lead detects worker completion across all phases and all worker kinds (Claude teammate, Codex / Antigravity wrappers). Other documents (`prompts/lead/okstra-lead-contract.md`, `report-writer`, `convergence`) reference this section by name; they MUST NOT restate the algorithm.
120
-
121
- Lead dispatches workers asynchronously: an `Agent` call with `run_in_background: true` returns `Spawned successfully` **immediately** — that ack is NOT a completion. Lead MUST NOT treat the spawn ack as completion, and MUST NOT end its turn with a prose "waiting for ..." statement (that path stalls the run — the Agent Teams idle-notification is experimental and can be dropped, leaving Lead parked until the user manually nudges it). Instead:
122
-
123
- For `tmux-pane` backend runs, Lead MUST use exactly one background wait command instead of raw result-file polling:
124
-
125
- ```bash
126
- okstra team await --project-root <dir> --run-manifest <path>
127
- ```
128
-
129
- This command checks both result files and wrapper status sidecars. Raw result-file-only polling is forbidden for `tmux-pane` because pane creation and file presence alone are not terminal worker completion.
130
-
131
- For the Claude Code `team` backend, use the self-scheduled polling protocol below.
132
-
133
-
134
-
135
- 1. Record the dispatched workers' Result Paths as the **pending set** (resolved to absolute from each launch prompt's `**Result Path:**` anchor header against `**Project Root:**`; the same paths recorded in run-manifest / team-state).
136
- 2. Arm a SINGLE self-wakeup: one `Bash(run_in_background: true)` poll covering ALL dispatched workers (not one background task per worker):
137
-
138
- ```bash
139
- deadline=$((SECONDS + <per-worker-deadline-seconds>))
140
- until [ -f "<result_A>" ] && [ -f "<result_B>" ]; do
141
- [ $SECONDS -ge $deadline ] && { echo "POLL_TIMEOUT"; exit 1; }
142
- sleep 5
143
- done
144
- echo "ALL_WORKERS_DONE"
145
- ```
113
+ ## Worker-completion detection
146
114
 
147
- The `sleep 5` inside this `until` loop is legal ONLY because the poll runs under `run_in_background: true`. A foreground `sleep` of 5s or longer is blocked by the harness anti-circumvention rule (see the Codex / Antigravity wrapper `BashOutput` polling contract above) — do NOT lift this loop into a foreground `Bash`.
148
- 3. End the turn. The harness auto-resumes Lead when the background poll exits — on completion (`ALL_WORKERS_DONE`) OR timeout (`POLL_TIMEOUT`) with no mailbox / idle-notification dependency and no user nudge.
149
- 4. On resume, for every path still in the pending set: verify the file exists AND passes the standardized worker-result header check (see "Worker Result Header Standard" below). Move each passing worker to the **done set**.
150
- 5. Termination:
151
- - pending set empty proceed to the next phase. The background task already self-terminated, so there is NO schedule to disarm this self-terminating property is why a background poll is preferred over a cron schedule.
152
- - the poll exited `POLL_TIMEOUT` for a worker past its deadline → record terminal status `timeout` for that worker, remove it from the pending set, then redispatch-once (per "Lead Redispatch Policy on Result-Missing" below) or proceed.
153
- - any error / abort path → no zombie schedule exists, because the background task is self-terminating.
154
- 6. **Per-worker soft timeout.** Use 2× the task-type expected per-worker duration in the table below as `<per-worker-deadline-seconds>`. The background poll's `deadline` IS the lead-side worker soft-timeout safety net.
155
-
156
- | Task type | Expected per-worker | Deadline (2×) |
157
- |---|---|---|
158
- | requirements-discovery | 10 min | 20 min |
159
- | error-analysis | 15 min | 30 min |
160
- | implementation-planning | 20 min | 40 min |
161
- | implementation | 20 min | 40 min |
162
- | final-verification | 10 min | 20 min |
163
-
164
- Relationship to the Codex / Antigravity wrapper polling contract: that contract (in the errors-sidecar section below) governs how a *wrapper subagent* waits on its own external CLI via `BashOutput`. This section governs how *Lead* waits on the worker subagents themselves. The two compose — Lead's background poll watches the result files; each wrapper independently watches its CLI — and neither imposes a timeout on the other (see "No external timeout on wrapper subagents").
165
-
166
- ### Claude-worker heartbeat staleness (lead-side liveness check)
167
-
168
- The claude-worker appends a `- PROGRESS: <stage> <ISO-UTC>` line to its audit sidecar (`runs/<task-type>/worker-results/claude-worker-audit-<task-type>-<seq>.md`) at least every 5 minutes (`agents/workers/claude-worker.md` "Heartbeat"). While the result file is still pending, if that sidecar is absent or its mtime is >5 minutes stale, treat the dispatch as `timeout` and redispatch once with a byte-identical prompt (one-retry budget shared with "Lead Redispatch Policy on Result-Missing" below); after a second silent hang, record terminal status `timeout` with the missing-sidecar reason in team-state. The heartbeat is an auxiliary liveness signal layered on top of the completion protocol above — the authoritative completion signal remains the result file — and a missing sidecar after the result file appears is itself a contract violation per the heartbeat rule.
115
+ 1. Build the pending set from the manifest-assigned completion paths.
116
+ 2. Call `await_workers(handles)` through the selected adapter.
117
+ 3. On every wake/return, read terminal dispatch records and verify every required completion path.
118
+ 4. A process exit, spawn acknowledgement, pane creation, or Result Path alone is insufficient when the backend contract requires additional status/audit artifacts.
119
+ 5. Apply the single shared retry budget through `redispatch_worker`; after the second failure, record the terminal status and proceed with reduced confidence.
169
120
 
170
121
  ## Lead Redispatch Policy on Result-Missing
171
122
 
@@ -179,12 +130,12 @@ After each worker subagent returns (regardless of role), Lead MUST verify the ca
179
130
 
180
131
  **One-retry policy:**
181
132
 
182
- 1. On the FIRST result-missing trigger for a given role within a single run, Lead MUST re-dispatch the same worker with the byte-identical prompt — same `**Result Path:**`, same `**Prompt History Path:**`, same model assignment, same Agent `name`. The redispatch counts as a second attempt against the existing role slot; do NOT create a new role-id, do NOT change the result file path, do NOT switch to a different model as a "workaround".
133
+ 1. On the FIRST result-missing trigger for a given role within a single run, Lead MUST call `redispatch_worker` with the byte-identical prompt — same `**Result Path:**`, same `**Prompt History Path:**`, same model assignment, and same adapter-native assignment identity. The redispatch counts as a second attempt against the existing role slot; do NOT create a new role-id, do NOT change the result file path, do NOT switch to a different model as a "workaround".
183
134
  2. If the SECOND attempt also fails the same check, Lead records the role's terminal status as `error` with `--message "result-missing after 1 retry"` and proceeds to Phase 5.5 / Phase 6 with the remaining workers' results. Lead MUST NOT retry a third time — convergence and the report writer are designed to operate on reduced-confidence single-or-two-analyser mode when one role is absent (`prompts/lead/okstra-lead-contract.md` "If only one worker result is usable: reduced-confidence synthesis").
184
135
  3. The retry counter is **per-run, per-role** and is NOT preserved across runs. A subsequent okstra run for the same task-key starts each role's counter fresh.
185
136
  4. Convergence reverify rounds (Phase 5.5) inherit the same one-retry budget — a reverify dispatch that triggers result-missing may be re-dispatched once.
186
137
 
187
- **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 attempts' bash_ids / prompt-history paths.
138
+ **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.
188
139
 
189
140
  **Diagnostic sidecar (advisory).** Both codex/antigravity wrappers also write a heartbeat sidecar at `<prompt-path>.status.json` recording `started_ts`, `ended_ts`, `exit_code`, `duration_ms`, and the canonical `log_path` (see `scripts/okstra-wrapper-status.py` for the schema). 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). 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.
190
141
 
@@ -310,7 +261,7 @@ Schema:
310
261
  ```
311
262
 
312
263
  Workers MUST omit `source` / `recordedAt` / `agent` / `agentRole` / `model` /
313
- `taskKey`. Claude lead fills those in when dumping the sidecar to the
264
+ `taskKey`. The lead fills those in when dumping the sidecar to the
314
265
  run-level errors log (`runs/<task-type>/logs/errors-<task-type>-<seq>.jsonl`)
315
266
  via `okstra error-log append-from-worker`.
316
267
 
@@ -326,13 +277,13 @@ omits either header, the worker MUST return `<WORKER>_ERRORS_PATH_MISSING`
326
277
  without proceeding.
327
278
 
328
279
  - `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.
329
- - **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-antigravity-exec.sh` accept four required positional arguments plus an optional fifth `<role>`: `<project-root> <model> <prompt-path> <worktree-path> [<role>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for antigravity it is appended to `--include-directories`. 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>` is folded into both the caller (worker) pane title `<cli>-<role>-<pid>` and the sibling trace-pane title `<cli>-<role>-<pid>-tail` (e.g. `codex-executor-93421` ↔ `codex-executor-93421-tail`). `<pid>` is the wrapper's own PID and disambiguates concurrent dispatches of the same role. The role value comes from the dispatch prompt's `**Pane role:**` line (injected by Lead per [okstra-lead-contract](./okstra-lead-contract.md) Phase 4 "Agent `name` on dispatch"); when the line is absent, pass the literal `worker` (the wrapper also defaults to `worker` if the argument is omitted).
330
- - **Background dispatch + polling contract (Codex / Antigravity wrappers).** Both wrapper subagents MUST dispatch `okstra-codex-exec.sh` / `okstra-antigravity-exec.sh` via `Bash(run_in_background: true)` and poll with `BashOutput(bash_id)` until the shell reports `status == "completed"`, capped at 30 minutes (1800s) of wall-clock elapsed time. `BashOutput` itself is the wait primitive — call it back-to-back; do NOT insert a standalone `sleep` between polls. The Claude Code harness blocks `sleep` calls of 5 seconds or longer as a circumvention vector and explicitly forbids chaining shorter sleeps inside until-loops to work around the block. Workers that hit the contract bug must NOT self-recover with `until ...; do sleep 2; done` wrappers — that path violates the harness anti-circumvention rule, even though it superficially "works". The current rule applies in **every phase** (analysis runs typically complete in 1–2 `BashOutput` calls, so there is no regression for short jobs). Recording responsibilities:
331
- - Successful completion: return the wrapper's accumulated stdout from the final `BashOutput`. No log entry.
332
- - Non-zero `exit_code` reported by `BashOutput`: record a `cli-failure` to the run-level error log with the real `exit_code` and observed `duration-ms`.
333
- - Polling cap reached: before `KillShell`, 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 this loop, extend the cap from 1800s 2100s (one-shot +5min) and continue polling. Otherwise (log stale, OR grace already applied), call `KillShell(shell_id)`, 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. The grace exists to absorb token-budget spikes where the CLI is genuinely still producing output past the 30-minute mark; it is a one-shot soft extension, NOT a loop.
334
- - Token-usage matching is unaffected: the wrapper subagent stays alive throughout polling, so the wrapper's jsonl timestamp window continues to cover the underlying CLI rollout's full duration (see §"Token-usage accounting" below).
335
- - **No external timeout on wrapper subagents.** The codex/antigravity wrapper subagent's polling loop (with optional mtime grace) is the SINGLE timeout authority for its dispatch. Lead MUST NOT impose a separate `Agent()` call timeout, an outer `Bash` wall-clock deadline, or any other mechanism that terminates the subagent before its own polling cap is reached. 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 wrapper's polling cap (30min + optional 5min grace) is 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, lower it in the wrapper subagent's polling contract (single source of truth), NOT by layering Lead-side timeouts.
280
+ - **Wrapper invocation arity.** Both `okstra-codex-exec.sh` and `okstra-antigravity-exec.sh` accept four required positional arguments plus an optional fifth `<role>`: `<project-root> <model> <prompt-path> <worktree-path> [<role>]`. The fourth (worktree) argument is **mandatory for implementation phase** and optional otherwise. For codex it becomes `--add-dir <worktree>` (sandbox write access); for antigravity it is appended to `--include-directories`. 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>` is folded into both the caller (worker) pane title `<cli>-<role>-<pid>` and the sibling trace-pane title `<cli>-<role>-<pid>-tail` (e.g. `codex-executor-93421` ↔ `codex-executor-93421-tail`). `<pid>` is the wrapper's own PID and disambiguates concurrent dispatches of the same role. The selected adapter maps the functional assignment identity to this value; when the value is absent, pass the literal `worker` (the wrapper also defaults to `worker` if the argument is omitted).
281
+ - **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:
282
+ - Successful completion: return the wrapper's accumulated stdout from the terminal await result. No log entry.
283
+ - Non-zero `exit_code`: record a `cli-failure` to the run-level error log with the real `exit_code` and observed `duration-ms`.
284
+ - 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.
285
+ - The selected adapter owns runtime-session accounting for the full wrapper window; core retains only the observed start/end event boundaries.
286
+ - **No external timeout on wrapper subagents.** The codex/antigravity wrapper subagent's polling loop (with optional mtime grace) is the SINGLE timeout authority for its dispatch. Lead MUST NOT impose a separate `dispatch_worker` timeout, an outer `Bash` wall-clock deadline, or any other mechanism that terminates the subagent before its own polling cap is reached. 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 wrapper's polling cap (30min + optional 5min grace) is 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, lower it in the wrapper subagent's polling contract (single source of truth), NOT by layering Lead-side timeouts.
336
287
  - `contract-violation` events (C) are recorded by Lead via `okstra error-log append-observed --error-type contract-violation ...` after inspecting worker outputs.
337
288
  - 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.
338
289
 
@@ -342,7 +293,7 @@ without proceeding.
342
293
  2. Re-verification workers follow a constrained response format (verdict + brief explanation).
343
294
  3. Workers cannot vote on their own findings (only verify other workers’ work).
344
295
  4. The `report writer worker` does not participate in re-verification voting. It is responsible only for generating the final report.
345
- 5. Division of labor: the Claude lead performs **finding-to-finding matching** (deciding which worker-A finding maps to which worker-B finding for cross-review) and mediates the round protocol; **workers cast the AGREE / DISAGREE / SUPPLEMENT votes** that determine consensus. The lead does NOT vote on substance and does NOT collapse worker disagreements by fiat — disagreements flow into the `contested` / `partial-consensus` classifications defined in `prompts/lead/convergence.md`.
296
+ 5. Division of labor: the lead performs **finding-to-finding matching** (deciding which worker-A finding maps to which worker-B finding for cross-review) and mediates the round protocol; **workers cast the AGREE / DISAGREE / SUPPLEMENT votes** that determine consensus. The lead does NOT vote on substance and does NOT collapse worker disagreements by fiat — disagreements flow into the `contested` / `partial-consensus` classifications defined in `prompts/lead/convergence.md`.
346
297
  6. Batch processing is performed with one spawn per worker per round (not one spawn per finding).
347
298
  7. These rules do not apply if Convergence is disabled.
348
299
 
@@ -382,22 +333,7 @@ The header is followed by the standard worker output contract sections (Findings
382
333
 
383
334
  ## Usage Tracking
384
335
 
385
- Token usage is collected from agent session transcripts after the run, NOT from any in-band Agent-tool response. Neither the Agent tool nor the running session exposes token counts to the lead in real time, so any "estimate" is unreliable. Use the script.
386
-
387
- ### How to Collect
388
-
389
- At the **start of Phase 7** (persistence), run the helper via the okstra CLI with the path to this run's `team-state.json`. Substitute `<runDirectoryPath>` with a literal absolute path (no shell variables, no `$(...)`) so the `Bash(okstra:*)` permission match holds:
390
-
391
- ```bash
392
- okstra token-usage /abs/path/to/run/state/team-state-<task-type>-<seq>.json --write --summary
393
- ```
394
-
395
- `okstra token-usage` is a thin Node-side wrapper around the python helper installed at `~/.okstra/bin/okstra-token-usage.py`. Calling the python script directly with `python3 "$HOME/..."` is forbidden — the `$HOME` expansion breaks the literal-token permission match and forces a confirmation prompt every call.
396
-
397
- The script reads:
398
- - `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl` for the lead and every Claude-side worker (Claude worker, Report writer worker, plus the Claude wrappers around Codex/Antigravity workers). Sessions are discovered by the recorded team-state `teamName`, lead is identified by `lead.sessionId`, and other workers are identified by `agentName` (e.g. `claude-worker`, `codex-worker`, `antigravity-worker`, `report-writer`). **For this `agentName` match to work, Lead MUST set the Agent `name` arg to `<workerId>-worker` on every dispatch** (see [agents okstra-lead-contract.md Phase 4 — "Agent `name` on dispatch"](./okstra-lead-contract.md)); a worker dispatched without `name` carries no `agentName`, so the collector cannot attribute its session and records it `unavailable` (now surfaced as a `usageSummary.unattributedTeamSessions` entry rather than dropped silently).
399
- - `~/.agent/sessions/Y/M/D/rollout-*.jsonl` for the underlying Codex CLI session (matched by `cwd` and timestamp window of the wrapper subagent). Last `event_msg.token_count.total_token_usage.total_tokens` is the session total.
400
- - `~/.antigravity/tmp/<project>/chats/session-*.json` for the underlying Antigravity CLI session. Sum of per-message `tokens.total`.
336
+ At the start of Phase 7, call `collect_usage(source)` through the selected adapter. `source` is `team-state.leadAdapter.sessionAccounting` plus the dispatch backend's recorded event source. Never read another runtime's session store as a fallback. Persist the resulting `leadUsage`, worker usage, and `usageSummary` in team-state before report substitution and cleanup.
401
337
 
402
338
  ### Resulting team-state shape
403
339
 
@@ -411,8 +347,8 @@ The script reads:
411
347
  "cacheReadTokens": 9842949,
412
348
  "toolUses": 47,
413
349
  "durationMs": 7253000,
414
- "source": "claude-jsonl",
415
- "sessionId": "<lead-session-id>",
350
+ "source": "<adapter-accounting-source>",
351
+ "sessionId": "<adapter-session-id>",
416
352
  "collectedAt": "<utc>"
417
353
  },
418
354
  "workers": [
@@ -420,11 +356,8 @@ The script reads:
420
356
  "workerId": "codex",
421
357
  "usage": {
422
358
  "totalTokens": 2274011,
423
- "source": "claude-jsonl",
424
- "sessionId": "<wrapper-session-id>",
425
- "cliSessionPaths": ["/Users/.../.agent/sessions/.../rollout-*.jsonl"],
426
- "cliTotalTokens": 5261833,
427
- "cliModel": "gpt-5.5"
359
+ "source": "<dispatch-event-source>",
360
+ "sessionId": "<worker-session-id>"
428
361
  }
429
362
  }
430
363
  ],
@@ -440,20 +373,14 @@ The script reads:
440
373
 
441
374
  ### Notes
442
375
 
443
- - `totalTokens` in Claude usage blocks is the sum of input + output + cache_creation + cache_read tokens (the cache figures dominate in long sessions).
444
- - For Codex/Antigravity workers, `usage.totalTokens` reflects the **Claude wrapper subagent** spend (the Claude tokens consumed by the codex-worker / antigravity-worker subagent itself). The optional `cliTotalTokens` is the underlying CLI's own tokens. They are NOT additive in any meaningful way (different providers, different prices) keep them separate.
445
- - If a worker has status `not-run`, the script records an "unavailable" block.
446
- - If the lead jsonl is missing (e.g. if `lead.sessionId` was never persisted), the script records an "unavailable" block with the searched path. Always populate `lead.sessionId` in team-state at Phase 3 — the okstra.sh launcher already passes it as `CLAUDE_SESSION_ID`.
447
- - Convergence re-verification agents are dispatched as fresh subagents under the same `teamName` and will be discovered automatically. If you want them split out, set a distinct `agentName` on dispatch and post-process.
448
-
449
- ### Lead Duration
450
-
451
- `durationMs` for the lead is computed from the first to last timestamp in the lead jsonl. If you need wall-clock from run-start to report-completion instead, override `leadUsage.durationMs` after running the script.
376
+ - If a worker has status `not-run`, `collect_usage` records an `unavailable` block.
377
+ - Adapter-native and underlying provider usage are separate accounting sources and must not be added together without an explicit pricing model.
378
+ - `durationMs` uses the selected adapter's documented session boundary.
452
379
 
453
380
  ## Team State Persistence
454
381
 
455
382
  Information to be recorded in the team-state JSON file:
456
- - `teamName` — record the audit label set in Phase 3 (`okstra-<task-key>` + stage suffix). Either `state.teamName` (root) or `state.team.teamName` (nested) is accepted by `scripts/okstra_token_usage/collect.py`. Be consistent within a single run. Without this value the Phase 7 collector falls back to `okstra-<task-id>` (short form), which does NOT match worker jsonls whose team needle carries the full multi-segment task key — every worker will then be recorded as `source: "unavailable"`.
383
+ - `teamName` — record the manifest-provided audit label set in Phase 3 (`okstra-<task-key>` + stage suffix) and preserve that exact value consistently within the run. The selected adapter owns any runtime-session matching derived from this audit field.
457
384
  - Current status of each worker role
458
385
  - Start/end times for each worker
459
386
  - Prompt history path for each worker
@@ -30,7 +30,7 @@ profile document.
30
30
  - This rule does NOT relax any phase-specific Forbidden actions list; safety rules in the per-profile document remain in force regardless of the user's authority.
31
31
  - Anti-escalation rule (shared):
32
32
  - treating "다음 단계 진행해" or equivalent user phrases as authorisation to start a *different* lifecycle phase is forbidden. The next phase begins only in a separate okstra run launched with the new `--task-type`. Per-profile documents may further restrict this within their own scope.
33
- - Run-scoped pane & teammate lifecycle (shared — run-start pane recording, per-batch cleanup, wrap-up pane disposition, run-end teammate teardown): these are lead-only operating duties and live in `prompts/lead/okstra-lead-contract.md` "Run-scoped pane & teammate lifecycle". Profiles do not restate them.
33
+ - Run-scoped worker-resource lifecycle (shared — run-start resource setup, per-batch cleanup, wrap-up resource disposition, run-end shutdown): these are lead-only operating duties and live in `prompts/lead/okstra-lead-contract.md` "Run-scoped worker-resource lifecycle". Profiles do not restate them.
34
34
  - Brief handoff contract (shared — applies whenever the run consumes a task brief produced by `okstra-brief-gen`):
35
35
  - the brief is a **pre-discovery artifact**: it converts a domain-reporter's words (non-expert *or* developer) into expert-consumable form so this and later phases can run with zero fill-in questions to the operator. The brief is **not** authoritative on solution decisions; it is authoritative on the reporter's intent.
36
36
  - **Reporter confirmation precondition (BLOCKING)**: the brief's frontmatter carries `reporter-confirmations: <complete | partial | pending | skipped>` set by `okstra-brief-gen` Step 6.5. Every phase that consumes the brief MUST read this field before doing analysis. The handling matrix is:
@@ -71,7 +71,7 @@ profile document.
71
71
  - each row's `Kind` column picks one of `{material, decision, data-point}`: `material` for files / snapshots / logs / screenshots the user must attach (the `User input` cell will hold a path or URL); `decision` for choices and yes/no confirmations only the user can make; `data-point` for a single number, ID, date, or short string the user can answer inline. A `decision` alternative must be a terminal choice the user can pick as-is; if acting on an alternative still requires the user to supply a concrete value (a path, string, number, or file), that value is its own `data-point` / `material` row — never phrase a data-entry action (e.g. "경로 구체화", "값 입력") as a selectable `decision` option, because the rendered `<select>` cannot capture the value the option demands. Items that mix "yes/no + file path if yes" are one row of `Kind=material` with the combined expectation written into `Expected form`.
72
72
  - each row's `Blocks` column picks one of `{approval, next-phase, none}`. `approval` is reserved for items that gate an approval action, especially the `implementation-planning` `approved:` frontmatter flip; outside `implementation-planning`, unresolved brief reporter-confirmation rows use `next-phase` instead. `next-phase` blocks the next run from starting cleanly. `none` is informational/audit-only.
73
73
  - write every entry in full, descriptive sentences that a non-developer can act on without further context. Avoid abbreviations and internal jargon. The `Statement` cell must state *what* is needed, *why* the answer / attachment changes the next step, and (for `material`) *where* the user can find it and *where* to place it. The `Expected form` cell must state the answer shape (예/아니오, 보기 중 하나, 숫자/날짜, 파일 경로, 짧은 서술 등); supply concrete option choices when applicable.
74
- - if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: <answer> — <rationale>; Alternatives: <options>`. When there is more than one alternative, enumerate them as `(a) (b) …` so each renders as its own selectable option; a single alternative may be written plainly. Do **not** append a pick-one answer-space summary such as `(A / B 중 택1)` or `(… 중 택N)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
74
+ - if a phase requires a recommended answer, alternatives, or an evidence-check note, encode it inside the existing 4-column schema: put evidence notes in `Statement` as `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>`, and put recommendations/options in `Expected form` as `Recommended: (a) <answer> — <rationale>; Alternatives: (b) <option> (c) <option>`. The recommended answer is always the first option and MUST carry the `(a)` label; alternatives continue the same letter sequence from `(b)` (a lone alternative is `(b) <option>`, never restart at `(a)`), so the full option set reads `(a) (b) (c) …` in order and renders each as its own selectable option. Do **not** append a pick-one answer-space summary such as `(A / B 중 택1)` or `(… 중 택N)` to `<options>` — the rendered `<select>` already enforces single choice, and that annotation leaks verbatim into an option label. Do not add `Recommended`, `Evidence`, `Alternatives`, or `evidence-checked` columns, and do not break the merged record-meta cell back into separate columns.
75
75
  - the same `final-report.md` file is the canonical artifact carried into the next run; the user appends answers inline before rerunning. The preferred turn-around is `scripts/okstra.sh --resume-clarification --task-key <project-id>:<task-group>:<task-id>` (opens the latest report in `$EDITOR`, then auto-reruns the same phase with `--clarification-response` carry-in). The lower-level form `--clarification-response <path>` remains available for scripted runs.
76
76
  - if a clarification response was carried in for this run, render the conditional `## 0. Clarification Response Carried In From Previous Run` section (the template's `RENDER_IF` guard activates it), walk every `C-*` row of the prior report's `## 1. Clarification Items` table, reconcile each one against new evidence, and update its `Status` to `resolved` or `obsolete` before issuing the next decision/verdict. When no carry-in path was provided, omit the `## 0.` heading entirely — the validator fails reports that emit an empty Section 0 stub (e.g. "No prior clarification response was provided for this run.").
77
77
  - Verdict Card (shared — applies to every final-report regardless of profile):
@@ -11,7 +11,7 @@ until Phase 5 ends, then drop from active context for Phase 6/7.
11
11
 
12
12
  ## Executor role binding (carried over from the thin core)
13
13
 
14
- - **Executor dispatch labelling.** Agent `name` = `<provider>-executor` (e.g. `codex-executor`); for a `codex` / `antigravity` Executor, Lead additionally injects `**Pane role:** executor` into the dispatched prompt body. Naming/mapping rules and the token-attribution guarantee are owned by `prompts/lead/okstra-lead-contract.md` Phase 4 "Agent `name` on dispatch".
14
+ - **Executor dispatch labelling.** The core functional role label is `<provider>-executor` (e.g. `codex-executor`). Provider, role, and model identity are owned by `prompts/lead/okstra-lead-contract.md` "Model assignments"; the selected runtime adapter owns provider-native dispatch-label mapping (including any `name` / `**Pane role:**` fields) and token-attribution wiring under its "Semantic operation mapping".
15
15
  - The `Executor` (bound in `implementation.md` thin core) is the **only worker permitted to use Edit / Write / state-mutating Bash commands** on project files. All other workers run read-only. When the executor provider is `codex` or `antigravity`, the actual file mutation happens inside the executor CLI's own auto-edit mode (e.g. `codex exec --sandbox workspace-write`, antigravity's equivalent) — not through Claude-side Edit/Write tools — but the safety rules in this sidecar still apply identically.
16
16
  - When the thin core's Task worktree block resolves status to `created` or `reused`, the Executor MUST run every Edit / Write / build / test / commit command with the worktree path as cwd. Treat it as `project_root` for the duration of this run. Do NOT mutate the caller's original checkout. Do NOT `cd` out of the worktree to reach files. If a file outside the worktree is genuinely needed, treat it as a planning gap: record it in `Out-of-plan edits` and continue.
17
17
  - **How to set cwd per Bash call**: the Claude Bash tool inherits its cwd from the lead session, which is NOT the worktree. To put cwd-sensitive toolchains (`cargo`, `npm`, `pnpm`, `bun`, `pytest`, `make`, `go`) into the worktree, prefix the command with `cd {{EXECUTOR_WORKTREE_PATH}} && ` inside the same Bash invocation — e.g. `cd {{EXECUTOR_WORKTREE_PATH}} && cargo test -p foo`. **Never wrap in `bash -lc "..."` or `bash -c "..."`** — the wrapper hides the leading `cd` token from Claude Code's permission auto-allow layer (causing prompts on every call) without any safety benefit. For tools that accept an explicit working-directory flag (`git -C <path>`, `cargo --manifest-path`, `pytest --rootdir`), prefer that form over the `cd && ` chain. Edit / Write / Read tool calls already use absolute paths and need no cwd handling. The codex / antigravity executor CLI wrappers (`okstra-codex-exec.sh -C`, `okstra-antigravity-exec.sh --include-directories`) already inject worktree cwd at the CLI layer, so this rule applies primarily to the Claude executor.